orchid-orm 1.77.0 → 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.
@@ -3,7 +3,6 @@ import { ArrayColumn, DomainColumn, EnumColumn, RawSql, UnknownColumn, VirtualCo
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) => ({
@@ -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;
@@ -3601,13 +3602,11 @@ const addTrailingSemicolon = (code) => {
3601
3602
  function importsToCode(imports) {
3602
3603
  return Object.entries(imports).map(([from, name]) => `import { ${name} } from '${from}';`).join("\n");
3603
3604
  }
3604
- const { createSourceFile, ScriptTarget, SyntaxKind } = typescript;
3605
3605
  const appCodeGenUpdateDbFile = async (dbPath, tables, extensions, domains, currentSchema) => {
3606
3606
  const content = await fs.readFile(dbPath, "utf-8");
3607
- const statements = getTsStatements(content);
3608
- const importName = getOrchidOrmImportName(statements);
3609
- if (!importName) throw new Error(`Main file does not contain import of orchid-orm`);
3610
- 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);
3611
3610
  const changes = [];
3612
3611
  let replacedConfig;
3613
3612
  if (extensions.length || domains.length) {
@@ -3623,32 +3622,18 @@ const appCodeGenUpdateDbFile = async (dbPath, tables, extensions, domains, curre
3623
3622
  }, ast.baseType.data.name ?? "")},`).join("\n ")}\n },`;
3624
3623
  replacedConfig = code + "\n}";
3625
3624
  }
3626
- const tablesChanges = makeTablesListChanges(content, statements, tablesList, tables, dbPath);
3625
+ const tablesChanges = makeTablesListChanges(content, importInfo.lastImportEnd, tablesList, tables, dbPath);
3627
3626
  if (tablesChanges) addChange(content, changes, tablesChanges.imports.pos, tablesChanges.imports.text);
3628
3627
  if (replacedConfig) replaceContent(content, changes, config.pos, config.end, replacedConfig);
3629
3628
  if (tablesChanges) addChange(content, changes, tablesChanges.tablesList.pos, tablesChanges.tablesList.text);
3630
3629
  return applyChanges(content, changes);
3631
3630
  };
3632
- const getTsStatements = (content) => {
3633
- return createSourceFile("file.ts", content, ScriptTarget.Latest, true).statements;
3634
- };
3635
- const getOrchidOrmImportName = (statements) => {
3636
- for (const node of statements) {
3637
- if (node.kind !== SyntaxKind.ImportDeclaration) continue;
3638
- const imp = node;
3639
- if (imp.moduleSpecifier.getText().slice(1, -1) !== "orchid-orm") continue;
3640
- if (!imp.importClause) continue;
3641
- const elements = imp.importClause.namedBindings?.elements;
3642
- if (!elements);
3643
- 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();
3644
- }
3645
- };
3646
- const makeTablesListChanges = (content, statements, object, tables, dbPath) => {
3631
+ const makeTablesListChanges = (content, importPos, object, tables, dbPath) => {
3647
3632
  const spaces = getTablesListSpaces(content, object);
3648
3633
  let imports = "";
3649
3634
  let tablesList = "";
3650
- const prependComma = object.properties.length && !object.properties.hasTrailingComma;
3651
- 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");
3652
3637
  const tablesArr = Object.values(tables);
3653
3638
  for (let i = 0; i < tablesArr.length; i++) {
3654
3639
  const { path, className, key } = tablesArr[i];
@@ -3658,15 +3643,13 @@ const makeTablesListChanges = (content, statements, object, tables, dbPath) => {
3658
3643
  if (i === tablesArr.length - 1 && !tablesListNewLine) tablesList += `\n${spaces}`;
3659
3644
  }
3660
3645
  if (!imports.length) return;
3661
- let importPos = 0;
3662
- for (const node of statements) if (node.kind === SyntaxKind.ImportDeclaration) importPos = node.end;
3663
3646
  return {
3664
3647
  imports: {
3665
3648
  pos: importPos,
3666
3649
  text: imports
3667
3650
  },
3668
3651
  tablesList: {
3669
- pos: object.properties.end,
3652
+ pos: object.propertiesEnd,
3670
3653
  text: tablesList
3671
3654
  }
3672
3655
  };
@@ -3675,24 +3658,165 @@ const getTablesListSpaces = (content, object) => {
3675
3658
  const lines = content.slice(0, object.end).split("\n");
3676
3659
  return lines[lines.length - 1].match(/^\s+/)?.[0] || "";
3677
3660
  };
3678
- const getOrchidOrmArgs = (importName, statements) => {
3679
- for (const v of statements) {
3680
- if (v.kind !== SyntaxKind.VariableStatement) continue;
3681
- for (const node of v.declarationList.declarations) {
3682
- const call = node.initializer;
3683
- if (call?.kind !== SyntaxKind.CallExpression) continue;
3684
- if (call.expression.getText() !== importName) continue;
3685
- if (call.arguments.length !== 2) throw new Error("Invalid number of arguments when initializing orchid orm");
3686
- const object = call.arguments[1];
3687
- if (object?.kind !== SyntaxKind.ObjectLiteralExpression) throw new Error("Second argument of orchidORM must be an object literal");
3688
- return {
3689
- config: call.arguments[0],
3690
- tablesList: object
3691
- };
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";
3695
+ }
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 };
3692
3713
  }
3714
+ index = found + importName.length;
3693
3715
  }
3694
3716
  throw new Error("List of tables is not found in main file");
3695
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
+ };
3696
3820
  const addChange = (content, changes, at, text, end = at) => {
3697
3821
  if (changes.length === 0) changes.push([0, at], text, [end, content.length]);
3698
3822
  else {
@@ -3720,7 +3844,8 @@ const pull = async (adapters, config) => {
3720
3844
  const [adapter] = adapters;
3721
3845
  const adapterSchema = adapter.getSchema();
3722
3846
  const currentSchema = (typeof adapterSchema === "function" ? adapterSchema() : adapterSchema) ?? "public";
3723
- const asts = await structureToAst(makeStructureToAstCtx(config, currentSchema), adapter, config);
3847
+ const ctx = makeStructureToAstCtx(config, currentSchema);
3848
+ const asts = await structureToAst(ctx, adapter, config);
3724
3849
  const tableInfos = getTableInfos(asts, config);
3725
3850
  const exclusiveWriteOptions = { flag: "wx" };
3726
3851
  const pendingFileWrites = [];
@@ -3746,9 +3871,7 @@ const pull = async (adapters, config) => {
3746
3871
  version: ast.version
3747
3872
  });
3748
3873
  break;
3749
- case "domain":
3750
- domains.push(ast);
3751
- break;
3874
+ case "domain": domains.push(ast);
3752
3875
  }
3753
3876
  if (!firstTable && !extensions.length && !domains.length) {
3754
3877
  await adapter.close();