orchid-orm 1.76.5 → 1.77.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,8 @@
1
- import { astToMigration, concatSchemaAndName, createMigrationInterface, dbColumnToAst, encodeColumnDefault, getConstraintName, getDbStructureTableData, getDbTableColumnsChecks, getDbVersion, getExcludeName, getIndexName, getMigrationsSchemaAndTable, getSchemaAndTableFromName, instantiateDbColumn, introspectDbSchema, makeDomainsMap, makeFileVersion, makeStructureToAstCtx, migrate, migrateAndClose, promptSelect, rakeDbCommands, saveMigratedVersion, structureToAst, tableToAst, writeMigrationFile } from "rake-db";
2
- import { ArrayColumn, DomainColumn, EnumColumn, RawSql, UnknownColumn, VirtualColumn, addCode, codeToString, colors, columnsShapeToCode, deepCompare, emptyArray, emptyObject, exhaustive, getColumnBaseType, getDriverErrorCode, getFreeSetAlias, getImportPath, getQuerySchema, getSupportedDefaultPrivileges, pathToLog, pluralize, pushTableDataCode, queryToSql, quoteObjectKey, raw, rawSqlToSql, singleQuote, sqlToRawSql, toArray, toCamelCase, toPascalCase, toSnakeCase } from "pqb/internal";
1
+ import { astToMigration, concatSchemaAndName, createMigrationInterface, dbColumnToAst, encodeColumnDefault, getConstraintName, getDbStructureTableData, getDbTableColumnsChecks, getDbVersion, getExcludeName, getIndexName, getMigrationsSchemaAndTable, getSchemaAndTableFromName, getTableFactoryConfig, instantiateDbColumn, introspectDbSchema, makeDomainsMap, makeFileVersion, makeStructureToAstCtx, migrate, migrateAndClose, promptSelect, rakeDbCommands, saveMigratedVersion, structureToAst, tableToAst, writeMigrationFile } from "rake-db";
2
+ import { ArrayColumn, DomainColumn, EnumColumn, RawSql, UnknownColumn, VirtualColumn, addCode, codeToString, colors, columnsShapeToCode, constraintToCode, deepCompare, emptyArray, emptyObject, excludeToCode, exhaustive, getColumnBaseType, getDriverErrorCode, getForeignKeyTableInstance, getFreeSetAlias, getImportPath, getQuerySchema, getSupportedDefaultPrivileges, indexToCode, pathToLog, pluralize, primaryKeyInnerToCode, queryToSql, quoteObjectKey, raw, rawSqlToSql, singleQuote, sqlToRawSql, toArray, toCamelCase, toPascalCase, toSnakeCase } from "pqb/internal";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import fs from "node:fs/promises";
6
- import typescript from "typescript";
7
6
  export * from "rake-db";
8
7
  const viewDataToSql = (viewData, viewName) => {
9
8
  return sqlToRawSql(viewDataToQuerySql(viewData, viewName));
@@ -328,8 +327,9 @@ const compareColumns = async (adapter, domainsMap, ast, currentSchema, compareSq
328
327
  if (!typeCasts.get(dbBaseType)?.has(codeBaseType)) {
329
328
  if (!(dbColumn instanceof EnumColumn) || !(codeColumn instanceof EnumColumn) || !deepCompare(dbColumn.options, codeColumn.options)) {
330
329
  if (verifying) throw new AbortSignal();
330
+ const tableName = concatSchemaAndName(changeTableData.changeTableAst);
331
331
  if (await promptSelect({
332
- message: `Cannot cast type of ${concatSchemaAndName(changeTableData.changeTableAst)}'s column ${key} from ${dbType} to ${codeType}`,
332
+ message: `Cannot cast type of ${tableName}'s column ${key} from ${dbType} to ${codeType}`,
333
333
  options: [`${colors.yellowBold(`-/+`)} recreate the column, existing data will be ${colors.red("lost")}`, `write migration manually`]
334
334
  })) throw new AbortSignal();
335
335
  dbColumn.data.name = codeColumn.data.name;
@@ -409,7 +409,7 @@ JOIN pg_type AS t ON t.oid = casttarget`);
409
409
  for (const [source, target] of rows) {
410
410
  const set = directTypeCasts.get(source);
411
411
  if (set) set.add(target);
412
- else directTypeCasts.set(source, new Set([target]));
412
+ else directTypeCasts.set(source, /* @__PURE__ */ new Set([target]));
413
413
  }
414
414
  typeCasts = /* @__PURE__ */ new Map();
415
415
  for (const [type, directSet] of directTypeCasts.entries()) {
@@ -540,7 +540,8 @@ const makeComparableDomain = (currentSchema, schemaName, name, column) => {
540
540
  inner = inner.data.item;
541
541
  arrayDims++;
542
542
  }
543
- const [typeSchema = "pg_catalog", type] = getSchemaAndTableFromName(currentSchema, getColumnDbType(inner, currentSchema));
543
+ const fullType = getColumnDbType(inner, currentSchema);
544
+ const [typeSchema = "pg_catalog", type] = getSchemaAndTableFromName(currentSchema, fullType);
544
545
  return {
545
546
  schemaName,
546
547
  name,
@@ -779,7 +780,7 @@ const processPrimaryKey = (config, changeTableData) => {
779
780
  };
780
781
  const changePrimaryKey = (config, columnsPrimaryKey, { codeTable, dbTableData: { primaryKey: dbPrimaryKey }, changeTableAst: { shape, add, drop }, changingColumns }) => {
781
782
  const tablePrimaryKey = codeTable.internal.tableData.primaryKey;
782
- const primaryKey = [...new Set([...columnsPrimaryKey, ...(config.snakeCase ? tablePrimaryKey?.columns.map((key) => ({
783
+ const primaryKey = [.../* @__PURE__ */ new Set([...columnsPrimaryKey, ...(config.snakeCase ? tablePrimaryKey?.columns.map((key) => ({
783
784
  key,
784
785
  name: toSnakeCase(key)
785
786
  })) : tablePrimaryKey?.columns.map((key) => ({
@@ -1279,7 +1280,7 @@ const collectCodeFkeys = (config, { codeTable, changeTableAst: { shape } }, curr
1279
1280
  };
1280
1281
  const fnOrTableToString = (fnOrTable) => {
1281
1282
  if (typeof fnOrTable !== "string") {
1282
- const { schema, table, nameInDb } = new (fnOrTable())();
1283
+ const { schema, table, nameInDb } = getForeignKeyTableInstance(fnOrTable());
1283
1284
  const name = nameInDb || table;
1284
1285
  if (!name) throw new Error("Referenced table is missing table property");
1285
1286
  fnOrTable = concatSchemaAndName({
@@ -2187,7 +2188,7 @@ const processDefaultPrivileges = (ast, dbStructure, { internal: { roles } }) =>
2187
2188
  const codeObj = codePrivilege.objectConfigs.find((o) => o.object === objectType);
2188
2189
  const dbPrivs = dbObj?.privilegeConfigs ?? [];
2189
2190
  const codePrivs = codeObj?.privilegeConfigs ?? [];
2190
- const collapsedDbPrivs = collapsePrivilegesToAll({
2191
+ const collapsedDbPrivilege = {
2191
2192
  owner: dbPrivilege.owner,
2192
2193
  grantee: dbPrivilege.grantee,
2193
2194
  schema: dbPrivilege.schema,
@@ -2195,7 +2196,8 @@ const processDefaultPrivileges = (ast, dbStructure, { internal: { roles } }) =>
2195
2196
  object: objectType,
2196
2197
  privilegeConfigs: dbPrivs
2197
2198
  }]
2198
- }, objectTypeToAllPrivileges).objectConfigs[0]?.privilegeConfigs ?? [];
2199
+ };
2200
+ const collapsedDbPrivs = collapsePrivilegesToAll(collapsedDbPrivilege, objectTypeToAllPrivileges).objectConfigs[0]?.privilegeConfigs ?? [];
2199
2201
  const toGrant = codePrivs.filter((cp) => !collapsedDbPrivs.some((dp) => privilegeConfigsMatch(cp, dp, objectType, objectTypeToAllPrivileges)));
2200
2202
  const toRevoke = dbPrivs.filter((dp) => !codePrivs.some((cp) => privilegeConfigsMatch(cp, dp, objectType, objectTypeToAllPrivileges)));
2201
2203
  if (toGrant.length) {
@@ -2619,9 +2621,7 @@ const dbViewOptionsToAst = (view) => {
2619
2621
  case "security_barrier":
2620
2622
  options.securityBarrier = value === "true";
2621
2623
  break;
2622
- case "security_invoker":
2623
- options.securityInvoker = value === "true";
2624
- break;
2624
+ case "security_invoker": options.securityInvoker = value === "true";
2625
2625
  }
2626
2626
  }
2627
2627
  return options;
@@ -3046,10 +3046,11 @@ const report = (ast, config, currentSchema) => {
3046
3046
  break;
3047
3047
  }
3048
3048
  case "policy": {
3049
- const summary = formatPolicySummary(a, dbItemName({
3049
+ const table = dbItemName({
3050
3050
  schema: a.schema,
3051
3051
  name: a.table
3052
- }, currentSchema));
3052
+ }, currentSchema);
3053
+ const summary = formatPolicySummary(a, table);
3053
3054
  const message = a.action === "create" ? `${green("+ create policy")} ${a.name}: ${summary}` : `${red("- drop policy")} ${a.name}: ${summary}`;
3054
3055
  code.push(message);
3055
3056
  break;
@@ -3136,7 +3137,7 @@ const formatPolicyChangeDefinition = (policy) => {
3136
3137
  var AbortSignal = class extends Error {};
3137
3138
  const generate = async (adapters, config, args, afterPull) => {
3138
3139
  let { dbPath } = config;
3139
- if (!dbPath || !config.baseTable) throw invalidConfig(config);
3140
+ if (!dbPath || !getTableFactoryConfig(config)) throw invalidConfig(config);
3140
3141
  if (!adapters.length) throw new Error(`Database options must not be empty`);
3141
3142
  if (!dbPath.endsWith(".ts")) dbPath += ".ts";
3142
3143
  let migrationName = args[0] ?? "generated";
@@ -3206,7 +3207,7 @@ const generate = async (adapters, config, args, afterPull) => {
3206
3207
  if (up) for (const adapter of adapters) await migrateAndClose(adapter, config);
3207
3208
  else if (!afterPull) await closeAdapters(adapters);
3208
3209
  };
3209
- const invalidConfig = (config) => /* @__PURE__ */ new Error(`\`${config.dbPath ? "baseTable" : "dbPath"}\` setting must be set in the migrations config for the generator to work`);
3210
+ const invalidConfig = (config) => /* @__PURE__ */ new Error(`\`${config.dbPath ? "baseTable` or `defineTable" : "dbPath"}\` setting must be set in the migrations config for the generator to work`);
3210
3211
  const getDbFromConfig = async (config, dbPath) => {
3211
3212
  const db = (await config.import(pathToFileURL(path.resolve(config.basePath, dbPath)).toString()))[config.dbExportedAs ?? "db"];
3212
3213
  if (!db?.$qb) throw new Error(`Unable to import OrchidORM instance as ${config.dbExportedAs ?? "db"} from ${config.dbPath}`);
@@ -3484,95 +3485,128 @@ const processHasAndBelongsToManyColumn = (column, habtmTables, codeItems) => {
3484
3485
  const closeAdapters = (adapters) => {
3485
3486
  return Promise.all(adapters.map((x) => x.close()));
3486
3487
  };
3487
- const getTableInfosAndFKeys = (asts, config) => {
3488
+ const getTableInfos = (asts, config) => {
3488
3489
  const generateTableTo = config.generateTableTo ?? ((name) => `./tables/${name}.table.ts`);
3489
3490
  const tableInfos = {};
3490
- const fkeys = {};
3491
3491
  for (const ast of asts) if (ast.type === "table") {
3492
3492
  const tableKey = toCamelCase(ast.name);
3493
3493
  const dbTableName = ast.schema ? `${ast.schema}.${ast.name}` : ast.name;
3494
3494
  let tablePath = path.resolve(config.basePath, generateTableTo(tableKey));
3495
3495
  if (!tablePath.endsWith(".ts")) tablePath += ".ts";
3496
3496
  const name = toPascalCase(ast.name);
3497
- const info = {
3497
+ tableInfos[dbTableName] = {
3498
3498
  dbTableName,
3499
3499
  key: tableKey,
3500
3500
  path: tablePath,
3501
3501
  name,
3502
3502
  className: `${name}Table`
3503
3503
  };
3504
- tableInfos[dbTableName] = info;
3505
- if (ast.constraints) for (const { references } of ast.constraints) {
3506
- if (!references) continue;
3507
- (fkeys[references.fnOrTable] ??= []).push({
3508
- table: info,
3509
- references
3510
- });
3511
- }
3512
3504
  }
3513
- return {
3514
- tableInfos,
3515
- fkeys
3516
- };
3505
+ return tableInfos;
3517
3506
  };
3518
- const appCodeGenTable = (tableInfos, fkeys, ast, baseTablePath, baseTableExportedAs, currentSchema) => {
3507
+ const appCodeGenTable = (tableInfos, ast, tableFactorySource, currentSchema) => {
3519
3508
  const tableInfo = tableInfos[ast.schema ? `${ast.schema}.${ast.name}` : ast.name];
3520
- const imports = {
3521
- "orchid-orm": "Selectable, Insertable, Updatable",
3522
- [getImportPath(tableInfo.path, baseTablePath)]: baseTableExportedAs
3509
+ const content = appCodeGenDefineTable(tableInfo, ast, tableFactorySource, currentSchema);
3510
+ return {
3511
+ ...tableInfo,
3512
+ content
3523
3513
  };
3524
- const props = [];
3525
- if (ast.schema) props.push(`schema = ${singleQuote(ast.schema)};`);
3526
- props.push(`readonly table = ${singleQuote(ast.name)};`);
3527
- if (ast.comment) props.push(`comment = ${singleQuote(ast.comment)};`);
3528
- if (ast.noPrimaryKey === "ignore") props.push("noPrimaryKey = true;");
3514
+ };
3515
+ const appCodeGenDefineTable = (tableInfo, ast, tableFactorySource, currentSchema) => {
3516
+ const tableOptions = getDefineTableOptions(tableInfo, ast);
3529
3517
  const hasTableData = Boolean(ast.primaryKey || ast.indexes?.length || ast.excludes?.length || ast.constraints?.length);
3530
- const shapeCode = columnsShapeToCode({
3518
+ const ctx = {
3531
3519
  t: "t",
3520
+ sql: "sql",
3532
3521
  table: ast.name,
3533
3522
  currentSchema
3534
- }, ast.shape);
3535
- props.push(`columns = this.setColumns(${hasTableData ? "\n " : ""}(t) => ({`, hasTableData ? [shapeCode] : shapeCode, hasTableData ? " })," : "}));");
3536
- if (hasTableData) props.push(pushTableDataCode([], ast), ");");
3537
- const relations = [];
3538
- const belongsTo = fkeys[ast.schema ? `${ast.schema}.${ast.name}` : ast.name];
3539
- if (belongsTo) for (const { table, references } of belongsTo) {
3540
- imports[getImportPath(tableInfo.path, table.path)] = table.className;
3541
- relations.push(`${table.key}: this.belongsTo(() => ${table.className}, {`, [`columns: [${references.foreignColumns.map(singleQuote).join(", ")}],`, `references: [${references.columns.map(singleQuote).join(", ")}],`], "}),");
3542
- }
3543
- if (ast.constraints) for (const { references } of ast.constraints) {
3544
- if (!references) continue;
3545
- const table = tableInfos[references.fnOrTable];
3546
- imports[getImportPath(tableInfo.path, table.path)] = table.className;
3547
- relations.push(`${table.key}: this.hasMany(() => ${table.className}, {`, [`columns: [${references.columns.map(singleQuote).join(", ")}],`, `references: [${references.foreignColumns.map(singleQuote).join(", ")}],`], "}),");
3548
- }
3549
- if (relations.length) props.push("", "relations = {", relations, "};");
3550
- const importsCode = importsToCode(imports);
3551
- const { name, className } = tableInfo;
3552
- const code = [
3553
- `export type ${name} = Selectable<${className}>;
3554
- export type ${name}New = Insertable<${className}>;
3555
- export type ${name}Update = Updatable<${className}>;
3556
-
3557
- export class ${className} extends ${baseTableExportedAs} {`,
3558
- props,
3559
- "}\n"
3560
- ];
3561
- return {
3562
- ...tableInfo,
3563
- content: importsCode + "\n\n" + codeToString(code, "", " ")
3564
3523
  };
3524
+ const shapeCode = columnsShapeToCode(ctx, ast.shape);
3525
+ const { name, className } = tableInfo;
3526
+ const code = [`export type ${name} = Selectable<typeof ${className}>;
3527
+ export type ${name}New = Insertable<typeof ${className}>;
3528
+ export type ${name}Update = Updatable<typeof ${className}>;
3529
+ `];
3530
+ if (tableOptions.length) code.push(`export const ${className} = ${tableFactorySource.exportedAs}(`, [
3531
+ `${singleQuote(tableInfo.key)},`,
3532
+ ...tableOptions,
3533
+ "(t) => ({",
3534
+ shapeCode,
3535
+ "}),"
3536
+ ], `)${hasTableData ? "" : ";"}`);
3537
+ else if (hasTableData && getTableDataItemsCount(ast) === 1) code.push(`export const ${className} = ${tableFactorySource.exportedAs}(`, [
3538
+ `${singleQuote(tableInfo.key)},`,
3539
+ "(t) => ({",
3540
+ shapeCode,
3541
+ "}),"
3542
+ ], ")");
3543
+ else code.push(`export const ${className} = ${tableFactorySource.exportedAs}(${singleQuote(tableInfo.key)}, (t) => ({`, shapeCode, `}))${hasTableData ? "" : ";"}`);
3544
+ if (hasTableData) {
3545
+ let tableDataCode = getTableDataChainCode(ast, ctx);
3546
+ if (ast.constraints?.some(({ check }) => check)) tableDataCode = addTrailingSemicolon(tableDataCode);
3547
+ code.push(tableDataCode, ";");
3548
+ }
3549
+ return importsToCode({
3550
+ "orchid-orm": "Selectable, Insertable, Updatable",
3551
+ [getImportPath(tableInfo.path, tableFactorySource.path)]: ctx.isSqlUsed ? `${tableFactorySource.exportedAs}, sql` : tableFactorySource.exportedAs
3552
+ }) + "\n\n" + formatColumnSqlChains(codeToString(code, "", " ")) + "\n";
3553
+ };
3554
+ const getDefineTableOptions = (tableInfo, ast) => {
3555
+ const options = [];
3556
+ if (ast.schema) options.push(`schema: ${singleQuote(ast.schema)},`);
3557
+ if (tableInfo.key !== ast.name) options.push(`nameInDb: ${singleQuote(ast.name)},`);
3558
+ if (ast.comment) options.push(`comment: ${singleQuote(ast.comment)},`);
3559
+ if (ast.noPrimaryKey === "ignore") options.push("noPrimaryKey: true,");
3560
+ return options.length ? [
3561
+ "{",
3562
+ options,
3563
+ "},"
3564
+ ] : [];
3565
+ };
3566
+ const getTableDataChainCode = (ast, ctx) => {
3567
+ const code = [];
3568
+ if (ast.primaryKey) code.push(primaryKeyInnerToCode(ast.primaryKey, ""));
3569
+ if (ast.indexes) for (const index of ast.indexes) code.push(...removeTrailingComma(indexToCode(index, "")));
3570
+ if (ast.excludes) for (const exclude of ast.excludes) code.push(...removeTrailingComma(excludeToCode(exclude, "")));
3571
+ if (ast.constraints) for (const constraint of ast.constraints) code.push(...removeTrailingComma(constraintToCode(constraint, "", true, "", ctx)));
3572
+ return code;
3573
+ };
3574
+ const getTableDataItemsCount = (ast) => {
3575
+ return (ast.primaryKey ? 1 : 0) + (ast.indexes?.length ?? 0) + (ast.excludes?.length ?? 0) + (ast.constraints?.length ?? 0);
3576
+ };
3577
+ const removeTrailingComma = (code) => {
3578
+ if (typeof code === "string") return removeComma(code);
3579
+ const copy = [...code];
3580
+ const lastIndex = copy.length - 1;
3581
+ const last = copy[lastIndex];
3582
+ if (last !== void 0) copy[lastIndex] = removeTrailingComma(last);
3583
+ return copy;
3584
+ };
3585
+ const removeComma = (code) => code.endsWith(",") ? code.slice(0, -1) : code;
3586
+ const formatColumnSqlChains = (code) => {
3587
+ return code.replace(/^(\s+\S+: )t((?:\.\w+\([^{}\n]*\))+)\.index\(\{\n((?:\s+[^\n]*\n)*?)(\s+)\}\)(\.check\(sql[^\n]*)$/gm, (line, prefix, chain, options, _closeIndent, check) => {
3588
+ const methodIndent = `${" ".repeat(line.match(/^\s*/)?.[0].length ?? 0)} `;
3589
+ const optionIndent = `${methodIndent} `;
3590
+ const optionLines = options.split("\n").filter(Boolean).map((line) => `${optionIndent}${line.trimStart()}`).join("\n");
3591
+ return `${prefix}t${chain.replace(/\./g, `\n${methodIndent}.`)}\n${methodIndent}.index({\n${optionLines}\n${methodIndent}})\n${methodIndent}${check}`;
3592
+ });
3593
+ };
3594
+ const addTrailingSemicolon = (code) => {
3595
+ if (typeof code === "string") return `${code};`;
3596
+ const copy = [...code];
3597
+ const lastIndex = copy.length - 1;
3598
+ const last = copy[lastIndex];
3599
+ if (last !== void 0) copy[lastIndex] = addTrailingSemicolon(last);
3600
+ return copy;
3565
3601
  };
3566
3602
  function importsToCode(imports) {
3567
3603
  return Object.entries(imports).map(([from, name]) => `import { ${name} } from '${from}';`).join("\n");
3568
3604
  }
3569
- const { createSourceFile, ScriptTarget, SyntaxKind } = typescript;
3570
3605
  const appCodeGenUpdateDbFile = async (dbPath, tables, extensions, domains, currentSchema) => {
3571
3606
  const content = await fs.readFile(dbPath, "utf-8");
3572
- const statements = getTsStatements(content);
3573
- const importName = getOrchidOrmImportName(statements);
3574
- if (!importName) throw new Error(`Main file does not contain import of orchid-orm`);
3575
- const { config, tablesList } = getOrchidOrmArgs(importName, statements);
3607
+ const importInfo = getOrchidOrmImport(content);
3608
+ if (!importInfo) throw new Error(`Main file does not contain import of orchid-orm`);
3609
+ const { config, tablesList } = getOrchidOrmArgs(content, importInfo.importName);
3576
3610
  const changes = [];
3577
3611
  let replacedConfig;
3578
3612
  if (extensions.length || domains.length) {
@@ -3588,32 +3622,18 @@ const appCodeGenUpdateDbFile = async (dbPath, tables, extensions, domains, curre
3588
3622
  }, ast.baseType.data.name ?? "")},`).join("\n ")}\n },`;
3589
3623
  replacedConfig = code + "\n}";
3590
3624
  }
3591
- const tablesChanges = makeTablesListChanges(content, statements, tablesList, tables, dbPath);
3625
+ const tablesChanges = makeTablesListChanges(content, importInfo.lastImportEnd, tablesList, tables, dbPath);
3592
3626
  if (tablesChanges) addChange(content, changes, tablesChanges.imports.pos, tablesChanges.imports.text);
3593
3627
  if (replacedConfig) replaceContent(content, changes, config.pos, config.end, replacedConfig);
3594
3628
  if (tablesChanges) addChange(content, changes, tablesChanges.tablesList.pos, tablesChanges.tablesList.text);
3595
3629
  return applyChanges(content, changes);
3596
3630
  };
3597
- const getTsStatements = (content) => {
3598
- return createSourceFile("file.ts", content, ScriptTarget.Latest, true).statements;
3599
- };
3600
- const getOrchidOrmImportName = (statements) => {
3601
- for (const node of statements) {
3602
- if (node.kind !== SyntaxKind.ImportDeclaration) continue;
3603
- const imp = node;
3604
- if (imp.moduleSpecifier.getText().slice(1, -1) !== "orchid-orm") continue;
3605
- if (!imp.importClause) continue;
3606
- const elements = imp.importClause.namedBindings?.elements;
3607
- if (!elements);
3608
- for (const element of elements) if (element.name.escapedText === "orchidORM" || element.propertyName && "escapedText" in element.propertyName && element.propertyName.escapedText === "orchidORM") return element.name.escapedText.toString();
3609
- }
3610
- };
3611
- const makeTablesListChanges = (content, statements, object, tables, dbPath) => {
3631
+ const makeTablesListChanges = (content, importPos, object, tables, dbPath) => {
3612
3632
  const spaces = getTablesListSpaces(content, object);
3613
3633
  let imports = "";
3614
3634
  let tablesList = "";
3615
- const prependComma = object.properties.length && !object.properties.hasTrailingComma;
3616
- const tablesListNewLine = content.slice(object.properties.end, object.end).includes("\n");
3635
+ const prependComma = object.hasProperties && !object.hasTrailingComma;
3636
+ const tablesListNewLine = content.slice(object.propertiesEnd, object.end).includes("\n");
3617
3637
  const tablesArr = Object.values(tables);
3618
3638
  for (let i = 0; i < tablesArr.length; i++) {
3619
3639
  const { path, className, key } = tablesArr[i];
@@ -3623,15 +3643,13 @@ const makeTablesListChanges = (content, statements, object, tables, dbPath) => {
3623
3643
  if (i === tablesArr.length - 1 && !tablesListNewLine) tablesList += `\n${spaces}`;
3624
3644
  }
3625
3645
  if (!imports.length) return;
3626
- let importPos = 0;
3627
- for (const node of statements) if (node.kind === SyntaxKind.ImportDeclaration) importPos = node.end;
3628
3646
  return {
3629
3647
  imports: {
3630
3648
  pos: importPos,
3631
3649
  text: imports
3632
3650
  },
3633
3651
  tablesList: {
3634
- pos: object.properties.end,
3652
+ pos: object.propertiesEnd,
3635
3653
  text: tablesList
3636
3654
  }
3637
3655
  };
@@ -3640,24 +3658,165 @@ const getTablesListSpaces = (content, object) => {
3640
3658
  const lines = content.slice(0, object.end).split("\n");
3641
3659
  return lines[lines.length - 1].match(/^\s+/)?.[0] || "";
3642
3660
  };
3643
- const getOrchidOrmArgs = (importName, statements) => {
3644
- for (const v of statements) {
3645
- if (v.kind !== SyntaxKind.VariableStatement) continue;
3646
- for (const node of v.declarationList.declarations) {
3647
- const call = node.initializer;
3648
- if (call?.kind !== SyntaxKind.CallExpression) continue;
3649
- if (call.expression.getText() !== importName) continue;
3650
- if (call.arguments.length !== 2) throw new Error("Invalid number of arguments when initializing orchid orm");
3651
- const object = call.arguments[1];
3652
- if (object?.kind !== SyntaxKind.ObjectLiteralExpression) throw new Error("Second argument of orchidORM must be an object literal");
3653
- return {
3654
- config: call.arguments[0],
3655
- tablesList: object
3656
- };
3661
+ const getOrchidOrmArgs = (content, importName) => {
3662
+ const call = findCall(content, importName);
3663
+ if (!call) throw new Error("List of tables is not found in main file");
3664
+ const closeParen = findClosingBracket(content, call.openParen);
3665
+ if (closeParen === -1) throw new Error("List of tables is not found in main file");
3666
+ const firstComma = findTopLevelComma(content, call.openParen + 1, closeParen);
3667
+ if (firstComma === -1) throw new Error("Invalid number of arguments when initializing orchid orm");
3668
+ if (findTopLevelComma(content, firstComma + 1, closeParen) !== -1) throw new Error("Invalid number of arguments when initializing orchid orm");
3669
+ const config = makeTrimmedRange(content, call.openParen + 1, firstComma);
3670
+ const tablesList = getObjectRange(content, firstComma + 1, closeParen);
3671
+ if (!tablesList) throw new Error("Second argument of orchidORM must be an object literal");
3672
+ return {
3673
+ config,
3674
+ tablesList
3675
+ };
3676
+ };
3677
+ const getOrchidOrmImport = (content) => {
3678
+ let lastImportEnd = 0;
3679
+ let importName;
3680
+ const importRegex = /(^|\n)\s*import\s+([\s\S]*?)\s+from\s+['"]([^'"]+)['"]\s*;?/g;
3681
+ let match;
3682
+ while (match = importRegex.exec(content)) {
3683
+ lastImportEnd = importRegex.lastIndex;
3684
+ if (match[3] !== "orchid-orm") continue;
3685
+ const named = match[2].match(/\{([\s\S]*)\}/);
3686
+ if (!named) continue;
3687
+ for (const part of named[1].split(",")) {
3688
+ const item = part.trim();
3689
+ const alias = item.match(/^orchidORM\s+as\s+([A-Za-z_$][\w$]*)$/);
3690
+ if (alias) {
3691
+ importName = alias[1];
3692
+ continue;
3693
+ }
3694
+ if (item === "orchidORM") importName = "orchidORM";
3657
3695
  }
3658
3696
  }
3697
+ if (!importName) return;
3698
+ return {
3699
+ importName,
3700
+ lastImportEnd
3701
+ };
3702
+ };
3703
+ const findCall = (content, importName) => {
3704
+ let index = 0;
3705
+ while (index < content.length) {
3706
+ const found = content.indexOf(importName, index);
3707
+ if (found === -1) return;
3708
+ const before = content[found - 1];
3709
+ const after = content[found + importName.length];
3710
+ if ((!before || !isIdentifierChar(before)) && (!after || !isIdentifierChar(after))) {
3711
+ const openParen = skipWhitespace(content, found + importName.length);
3712
+ if (content[openParen] === "(") return { openParen };
3713
+ }
3714
+ index = found + importName.length;
3715
+ }
3659
3716
  throw new Error("List of tables is not found in main file");
3660
3717
  };
3718
+ const getObjectRange = (content, from, to) => {
3719
+ const pos = skipWhitespace(content, from);
3720
+ if (content[pos] !== "{") return;
3721
+ const end = findClosingBracket(content, pos);
3722
+ if (end === -1 || skipWhitespace(content, end + 1) < to) return;
3723
+ const lastBodyChar = findLastNonWhitespace(content, pos + 1, end);
3724
+ const hasProperties = lastBodyChar !== -1;
3725
+ const hasTrailingComma = hasProperties && content[lastBodyChar] === ",";
3726
+ const propertiesEnd = hasProperties ? lastBodyChar + 1 : pos + 1;
3727
+ return {
3728
+ pos,
3729
+ end: end + 1,
3730
+ propertiesEnd,
3731
+ hasProperties,
3732
+ hasTrailingComma
3733
+ };
3734
+ };
3735
+ const makeTrimmedRange = (content, from, to) => {
3736
+ return {
3737
+ pos: skipWhitespace(content, from),
3738
+ end: trimWhitespaceEnd(content, from, to)
3739
+ };
3740
+ };
3741
+ const findTopLevelComma = (content, from, to) => {
3742
+ let depth = 0;
3743
+ for (let i = from; i < to; i++) {
3744
+ const skipped = skipCodeFragment(content, i);
3745
+ if (skipped !== i) {
3746
+ i = skipped - 1;
3747
+ continue;
3748
+ }
3749
+ const char = content[i];
3750
+ if (char === "(" || char === "{" || char === "[") depth++;
3751
+ else if (char === ")" || char === "}" || char === "]") depth--;
3752
+ else if (char === "," && depth === 0) return i;
3753
+ }
3754
+ return -1;
3755
+ };
3756
+ const findClosingBracket = (content, open) => {
3757
+ const close = getClosingBracket(content[open]);
3758
+ if (!close) return -1;
3759
+ let depth = 1;
3760
+ for (let i = open + 1; i < content.length; i++) {
3761
+ const skipped = skipCodeFragment(content, i);
3762
+ if (skipped !== i) {
3763
+ i = skipped - 1;
3764
+ continue;
3765
+ }
3766
+ const char = content[i];
3767
+ if (char === content[open]) depth++;
3768
+ else if (char === close) {
3769
+ depth--;
3770
+ if (depth === 0) return i;
3771
+ }
3772
+ }
3773
+ return -1;
3774
+ };
3775
+ const getClosingBracket = (char) => {
3776
+ if (char === "(") return ")";
3777
+ if (char === "{") return "}";
3778
+ if (char === "[") return "]";
3779
+ };
3780
+ const skipCodeFragment = (content, i) => {
3781
+ const char = content[i];
3782
+ if (char === "'" || char === "\"" || char === "`") return skipQuoted(content, i, char);
3783
+ if (char === "/" && content[i + 1] === "/") {
3784
+ const nextLine = content.indexOf("\n", i + 2);
3785
+ return nextLine === -1 ? content.length : nextLine + 1;
3786
+ }
3787
+ if (char === "/" && content[i + 1] === "*") {
3788
+ const end = content.indexOf("*/", i + 2);
3789
+ return end === -1 ? content.length : end + 2;
3790
+ }
3791
+ return i;
3792
+ };
3793
+ const skipQuoted = (content, i, quote) => {
3794
+ for (let j = i + 1; j < content.length; j++) {
3795
+ if (content[j] === "\\") {
3796
+ j++;
3797
+ continue;
3798
+ }
3799
+ if (content[j] === quote) return j + 1;
3800
+ }
3801
+ return content.length;
3802
+ };
3803
+ const skipWhitespace = (content, from) => {
3804
+ let i = from;
3805
+ while (/\s/.test(content[i])) i++;
3806
+ return i;
3807
+ };
3808
+ const trimWhitespaceEnd = (content, from, to) => {
3809
+ let i = to;
3810
+ while (i > from && /\s/.test(content[i - 1])) i--;
3811
+ return i;
3812
+ };
3813
+ const findLastNonWhitespace = (content, from, to) => {
3814
+ for (let i = to - 1; i >= from; i--) if (!/\s/.test(content[i])) return i;
3815
+ return -1;
3816
+ };
3817
+ const isIdentifierChar = (char) => {
3818
+ return /[A-Za-z0-9_$]/.test(char);
3819
+ };
3661
3820
  const addChange = (content, changes, at, text, end = at) => {
3662
3821
  if (changes.length === 0) changes.push([0, at], text, [end, content.length]);
3663
3822
  else {
@@ -3673,14 +3832,21 @@ const applyChanges = (content, changes) => {
3673
3832
  return changes.length ? changes.map((item) => typeof item === "string" ? item : content.slice(item[0], item[1])).join("") : content;
3674
3833
  };
3675
3834
  const pull = async (adapters, config) => {
3676
- if (!config.dbPath || !config.baseTable) throw new Error(`\`${config.dbPath ? "baseTable" : "dbPath"}\` setting must be set in the migrations config for pull command`);
3677
- const baseTablePath = config.baseTable.getFilePath();
3678
- const baseTableExportedAs = config.baseTable.exportAs;
3835
+ const { defineTable } = config;
3836
+ if (!config.dbPath || !defineTable) throw new Error(`\`${config.dbPath ? "defineTable" : "dbPath"}\` setting must be set in the migrations config for pull command`);
3837
+ if (!defineTable.types) throw new Error("defineTable is missing types");
3838
+ if (!defineTable.exportAs) throw new Error("defineTable is missing exportAs");
3839
+ if (typeof defineTable.getFilePath !== "function") throw new Error("defineTable is missing getFilePath");
3840
+ const tableFactorySource = {
3841
+ path: defineTable.getFilePath(),
3842
+ exportedAs: defineTable.exportAs
3843
+ };
3679
3844
  const [adapter] = adapters;
3680
3845
  const adapterSchema = adapter.getSchema();
3681
3846
  const currentSchema = (typeof adapterSchema === "function" ? adapterSchema() : adapterSchema) ?? "public";
3682
- const asts = await structureToAst(makeStructureToAstCtx(config, currentSchema), adapter, config);
3683
- const { tableInfos, fkeys } = getTableInfosAndFKeys(asts, config);
3847
+ const ctx = makeStructureToAstCtx(config, currentSchema);
3848
+ const asts = await structureToAst(ctx, adapter, config);
3849
+ const tableInfos = getTableInfos(asts, config);
3684
3850
  const exclusiveWriteOptions = { flag: "wx" };
3685
3851
  const pendingFileWrites = [];
3686
3852
  const tables = {};
@@ -3689,7 +3855,7 @@ const pull = async (adapters, config) => {
3689
3855
  let firstTable;
3690
3856
  for (const ast of asts) switch (ast.type) {
3691
3857
  case "table": {
3692
- const table = appCodeGenTable(tableInfos, fkeys, ast, baseTablePath, baseTableExportedAs, currentSchema);
3858
+ const table = appCodeGenTable(tableInfos, ast, tableFactorySource, currentSchema);
3693
3859
  tables[table.key] = table;
3694
3860
  if (!firstTable) firstTable = table;
3695
3861
  pendingFileWrites.push([
@@ -3705,9 +3871,7 @@ const pull = async (adapters, config) => {
3705
3871
  version: ast.version
3706
3872
  });
3707
3873
  break;
3708
- case "domain":
3709
- domains.push(ast);
3710
- break;
3874
+ case "domain": domains.push(ast);
3711
3875
  }
3712
3876
  if (!firstTable && !extensions.length && !domains.length) {
3713
3877
  await adapter.close();