All files / blong-gogo/src/adapter/schema schemaTableSync.ts

0% Statements 0/112
0% Branches 0/1
0% Functions 0/1
0% Lines 0/112

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113                                                                                                                                                                                                                                 
import {library} from '@feasibleone/blong';
import type {TObject} from 'typebox';

interface IColumnSchema {
    type?: string;
    format?: string;
    maxLength?: number;
    default?: unknown;
}

export default library(
    ({config}) => {
        function addColumn(
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            table: any,
            columnName: string,
            prop: IColumnSchema,
            isNullable: boolean,
        ): void {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            let column: any;

            if (columnName.endsWith('Id') && prop.type === 'integer') {
                column = table.increments(columnName);
                return;
            }

            switch (prop.type) {
                case 'string':
                    if (prop.format === 'date-time' || prop.format === 'datetime')
                        column = table.dateTime(columnName);
                    else if (prop.format === 'date') column = table.date(columnName);
                    else if (prop.format === 'time') column = table.time(columnName);
                    else if (prop.format === 'uuid') column = table.uuid(columnName);
                    else if (prop.maxLength != null && prop.maxLength > 255)
                        column = table.text(columnName);
                    else column = table.string(columnName, prop.maxLength ?? 255);
                    break;
                case 'number':
                    column = table.double(columnName);
                    break;
                case 'integer':
                    column = table.integer(columnName);
                    break;
                case 'boolean':
                    column = table.boolean(columnName);
                    break;
                case 'array':
                case 'object':
                    column = table.json(columnName);
                    break;
                default:
                    column = table.text(columnName);
                    break;
            }

            if (isNullable) column.nullable();
            else column.notNullable();
            if (prop.default !== undefined) column.defaultTo(prop.default);
        }

        return async function schemaTableSync(
            tableName: string,
            schema: TObject,
            options: {dropColumns?: boolean} = {},
        ): Promise<{created: boolean; added: string[]; dropped: string[]}> {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const knex = (config as Record<string, any>)?.context?.queryBuilder;
            if (!knex) throw new Error('Knex queryBuilder not available in adapter context');

            const required = new Set(schema.required ?? []);
            const schemaProps = Object.keys(schema.properties);
            const exists = await knex.schema.hasTable(tableName);

            if (!exists) {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                await knex.schema.createTable(tableName, (table: any) => {
                    for (const [name, prop] of Object.entries(schema.properties)) {
                        addColumn(table, name, prop as IColumnSchema, !required.has(name));
                    }
                });
                return {created: true, added: schemaProps, dropped: []};
            }

            const columnInfo = await knex(tableName).columnInfo();
            const existingColumns = new Set(Object.keys(columnInfo));
            const added: string[] = [];
            const dropped: string[] = [];

            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            await knex.schema.alterTable(tableName, (table: any) => {
                for (const [name, prop] of Object.entries(schema.properties)) {
                    if (!existingColumns.has(name)) {
                        addColumn(table, name, prop as IColumnSchema, true);
                        added.push(name);
                    }
                }

                if (options.dropColumns) {
                    for (const col of existingColumns) {
                        if (!schemaProps.includes(col)) {
                            table.dropColumn(col);
                            dropped.push(col);
                        }
                    }
                }
            });

            return {created: false, added, dropped};
        };
    },
);