orchid-orm 1.9.2 → 1.9.3

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.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { columnTypes, getColumnTypes, addQueryOn, VirtualColumn, pushQueryValue, isQueryReturnsAll, getQueryAs, toSqlCacheKey, NotFoundError, relationQueryKey, Adapter, Db, anyShape, getClonedQueryData, columnsShapeToCode, primaryKeyToCode, indexToCode, constraintToCode, columnIndexesToCode, columnForeignKeysToCode, columnCheckToCode, identityToCode } from 'pqb';
1
+ import { columnTypes, getColumnTypes, addQueryOn, VirtualColumn, pushQueryValue, isQueryReturnsAll, getQueryAs, toSqlCacheKey, NotFoundError, relationQueryKey, Adapter, Db, anyShape, getClonedQueryData, columnsShapeToCode, ColumnType, primaryKeyToCode, indexToCode, constraintToCode, columnIndexesToCode, columnForeignKeysToCode, columnCheckToCode, identityToCode } from 'pqb';
2
2
  export { OrchidOrmError, OrchidOrmInternalError, columnTypes } from 'pqb';
3
3
  import { getCallerFilePath, snakeCaseKey, toSnakeCase, emptyObject, pathToLog, toCamelCase, toPascalCase, getImportPath, singleQuote, codeToString, quoteObjectKey, addCode, columnDefaultArgumentToCode, deepCompare } from 'orchid-core';
4
4
  import { AsyncLocalStorage } from 'node:async_hooks';
@@ -6,6 +6,7 @@ import * as path from 'path';
6
6
  import path__default from 'path';
7
7
  import fs from 'fs/promises';
8
8
  import typescript from 'typescript';
9
+ import { plural } from 'pluralize';
9
10
 
10
11
  const createBaseTable = ({
11
12
  columnTypes: columnTypes$1,
@@ -30,26 +31,35 @@ const create = (columnTypes, filePath, snakeCase) => {
30
31
  const base = (_a = class {
31
32
  constructor() {
32
33
  this.snakeCase = snakeCase;
33
- this.setColumns = (fn) => {
34
- columnTypes[snakeCaseKey] = this.snakeCase;
35
- const shape = getColumnTypes(columnTypes, fn);
36
- if (this.snakeCase) {
37
- for (const key in shape) {
38
- const column = shape[key];
39
- if (column.data.name)
40
- continue;
41
- const snakeName = toSnakeCase(key);
42
- if (snakeName !== key) {
43
- column.data.name = snakeName;
44
- }
34
+ this.columnTypes = columnTypes;
35
+ }
36
+ setColumns(fn) {
37
+ if (!this.filePath) {
38
+ const filePath2 = getCallerFilePath();
39
+ if (!filePath2) {
40
+ throw new Error(
41
+ `Failed to determine file path for table ${this.constructor.name}. Please set \`filePath\` property manually`
42
+ );
43
+ }
44
+ this.filePath = filePath2;
45
+ }
46
+ columnTypes[snakeCaseKey] = this.snakeCase;
47
+ const shape = getColumnTypes(columnTypes, fn);
48
+ if (this.snakeCase) {
49
+ for (const key in shape) {
50
+ const column = shape[key];
51
+ if (column.data.name)
52
+ continue;
53
+ const snakeName = toSnakeCase(key);
54
+ if (snakeName !== key) {
55
+ column.data.name = snakeName;
45
56
  }
46
57
  }
47
- return {
48
- shape,
49
- type: void 0
50
- };
58
+ }
59
+ return {
60
+ shape,
61
+ type: void 0
51
62
  };
52
- this.columnTypes = columnTypes;
53
63
  }
54
64
  belongsTo(fn, options) {
55
65
  return {
@@ -1386,6 +1396,8 @@ const orchidORM = (_a, tables) => {
1386
1396
  );
1387
1397
  dbTable.definedAs = key;
1388
1398
  dbTable.db = result;
1399
+ dbTable.filePath = table.filePath;
1400
+ dbTable.name = table.constructor.name;
1389
1401
  result[key] = dbTable;
1390
1402
  }
1391
1403
  applyRelations(qb, tableInstances, result);
@@ -1759,6 +1771,48 @@ const getTablesListObject = (importName, statements) => {
1759
1771
  return;
1760
1772
  };
1761
1773
 
1774
+ const handleForeignKey = async ({
1775
+ getTable,
1776
+ relations,
1777
+ tableName,
1778
+ columns,
1779
+ foreignTableName,
1780
+ foreignColumns,
1781
+ skipBelongsTo
1782
+ }) => {
1783
+ var _a, _b;
1784
+ const table = await getTable(tableName);
1785
+ if (!table)
1786
+ return;
1787
+ const foreignTable = await getTable(foreignTableName);
1788
+ if (!foreignTable)
1789
+ return;
1790
+ if (!skipBelongsTo) {
1791
+ (_a = relations[tableName]) != null ? _a : relations[tableName] = {
1792
+ path: table.path,
1793
+ relations: []
1794
+ };
1795
+ relations[tableName].relations.push({
1796
+ kind: "belongsTo",
1797
+ columns,
1798
+ className: foreignTable.name,
1799
+ path: foreignTable.path,
1800
+ foreignColumns
1801
+ });
1802
+ }
1803
+ (_b = relations[foreignTableName]) != null ? _b : relations[foreignTableName] = {
1804
+ path: foreignTable.path,
1805
+ relations: []
1806
+ };
1807
+ relations[foreignTableName].relations.push({
1808
+ kind: "hasMany",
1809
+ columns: foreignColumns,
1810
+ className: table.name,
1811
+ path: table.path,
1812
+ foreignColumns: columns
1813
+ });
1814
+ };
1815
+
1762
1816
  var __getOwnPropSymbols$4 = Object.getOwnPropertySymbols;
1763
1817
  var __hasOwnProp$4 = Object.prototype.hasOwnProperty;
1764
1818
  var __propIsEnum$4 = Object.prototype.propertyIsEnumerable;
@@ -1777,13 +1831,29 @@ var __objRest$2 = (source, exclude) => {
1777
1831
  const createTable = async (_a) => {
1778
1832
  var _b = _a, {
1779
1833
  ast,
1780
- logger
1834
+ logger,
1835
+ getTable,
1836
+ relations,
1837
+ tables
1781
1838
  } = _b, params = __objRest$2(_b, [
1782
1839
  "ast",
1783
- "logger"
1840
+ "logger",
1841
+ "getTable",
1842
+ "relations",
1843
+ "tables"
1784
1844
  ]);
1785
- const tablePath = params.tablePath(toCamelCase(ast.name));
1845
+ const key = toCamelCase(ast.name);
1846
+ const tablePath = params.tablePath(key);
1786
1847
  const baseTablePath = getImportPath(tablePath, params.baseTable.filePath);
1848
+ const className = `${toPascalCase(ast.name)}Table`;
1849
+ tables[ast.name] = {
1850
+ key,
1851
+ name: className,
1852
+ path: tablePath
1853
+ };
1854
+ const imports = {
1855
+ [baseTablePath]: params.baseTable.name
1856
+ };
1787
1857
  const props = [];
1788
1858
  if (ast.schema) {
1789
1859
  props.push(`schema = ${singleQuote(ast.schema)};`);
@@ -1797,10 +1867,23 @@ const createTable = async (_a) => {
1797
1867
  columnsShapeToCode(ast.shape, ast, "t"),
1798
1868
  "}));"
1799
1869
  );
1870
+ const relCode = await getRelations(
1871
+ ast,
1872
+ getTable,
1873
+ tablePath,
1874
+ imports,
1875
+ relations,
1876
+ ast.name
1877
+ );
1878
+ if (relCode) {
1879
+ props.push("", ...relCode);
1880
+ }
1800
1881
  const code = [
1801
- `import { ${params.baseTable.name} } from '${baseTablePath}';
1802
- `,
1803
- `export class ${toPascalCase(ast.name)}Table extends ${params.baseTable.name} {`,
1882
+ ...Object.entries(imports).map(
1883
+ ([from, name]) => `import { ${name} } from '${from}';`
1884
+ ),
1885
+ "",
1886
+ `export class ${className} extends ${params.baseTable.name} {`,
1804
1887
  props,
1805
1888
  "}\n"
1806
1889
  ];
@@ -1814,6 +1897,62 @@ const createTable = async (_a) => {
1814
1897
  }
1815
1898
  }
1816
1899
  };
1900
+ const getRelations = async (ast, getTable, tablePath, imports, relations, tableName) => {
1901
+ const refs = [];
1902
+ for (const key in ast.shape) {
1903
+ const item = ast.shape[key];
1904
+ if (!(item instanceof ColumnType) || !item.data.foreignKeys)
1905
+ continue;
1906
+ for (const fkey of item.data.foreignKeys) {
1907
+ if ("table" in fkey) {
1908
+ refs.push({
1909
+ table: fkey.table,
1910
+ columns: [key],
1911
+ foreignColumns: fkey.columns
1912
+ });
1913
+ }
1914
+ }
1915
+ }
1916
+ if (ast.constraints) {
1917
+ for (const { references: ref } of ast.constraints) {
1918
+ if (ref && typeof ref.fnOrTable === "string") {
1919
+ refs.push({
1920
+ table: ref.fnOrTable,
1921
+ columns: ref.columns,
1922
+ foreignColumns: ref.foreignColumns
1923
+ });
1924
+ }
1925
+ }
1926
+ }
1927
+ if (!refs.length)
1928
+ return;
1929
+ const code = [];
1930
+ for (const ref of refs) {
1931
+ const { columns, foreignColumns } = ref;
1932
+ if (columns.length > 1 || foreignColumns.length > 1)
1933
+ continue;
1934
+ const info = await getTable(ref.table);
1935
+ if (!info)
1936
+ continue;
1937
+ const path2 = getImportPath(tablePath, info.path);
1938
+ imports[path2] = info.name;
1939
+ code.push(
1940
+ `${info.key}: this.belongsTo(() => ${info.name}, {`,
1941
+ [`primaryKey: '${foreignColumns[0]}',`, `foreignKey: '${columns[0]}',`],
1942
+ "}),"
1943
+ );
1944
+ await handleForeignKey({
1945
+ getTable,
1946
+ relations,
1947
+ tableName,
1948
+ columns: ref.columns,
1949
+ foreignTableName: ref.table,
1950
+ foreignColumns: ref.foreignColumns,
1951
+ skipBelongsTo: true
1952
+ });
1953
+ }
1954
+ return code.length ? ["relations = {", code, "};"] : void 0;
1955
+ };
1817
1956
 
1818
1957
  var __defProp$2 = Object.defineProperty;
1819
1958
  var __getOwnPropSymbols$3 = Object.getOwnPropertySymbols;
@@ -2408,6 +2547,18 @@ const updateTableFile = async (params) => {
2408
2547
  await changeTable(__spreadProps$1(__spreadValues$1({}, params), { ast }));
2409
2548
  } else if (ast.type === "renameTable") {
2410
2549
  await renameTable(__spreadProps$1(__spreadValues$1({}, params), { ast }));
2550
+ } else if (ast.type === "constraint" && ast.references) {
2551
+ const ref = ast.references;
2552
+ if (typeof ref.fnOrTable === "string") {
2553
+ await handleForeignKey({
2554
+ getTable: params.getTable,
2555
+ relations: params.relations,
2556
+ tableName: ast.tableName,
2557
+ columns: ref.columns,
2558
+ foreignTableName: ref.fnOrTable,
2559
+ foreignColumns: ref.foreignColumns
2560
+ });
2561
+ }
2411
2562
  }
2412
2563
  };
2413
2564
 
@@ -2434,6 +2585,131 @@ export const ${baseTable.name} = createBaseTable();
2434
2585
  });
2435
2586
  };
2436
2587
 
2588
+ const updateRelations = async ({
2589
+ relations,
2590
+ logger
2591
+ }) => {
2592
+ await Promise.all(
2593
+ Object.entries(relations).map(
2594
+ ([tableName, item]) => updateRelationItem(tableName, item, logger)
2595
+ )
2596
+ );
2597
+ };
2598
+ const updateRelationItem = async (tableName, item, logger) => {
2599
+ var _a;
2600
+ const content = await fs.readFile(item.path, "utf-8").catch(() => void 0);
2601
+ if (!content)
2602
+ return;
2603
+ const changes = new FileChanges(content);
2604
+ const statements = ts.getStatements(content);
2605
+ const dirName = path__default.dirname(item.path);
2606
+ const imports = {};
2607
+ for (const relation of item.relations) {
2608
+ if (!imports[relation.path]) {
2609
+ imports[relation.path] = relation.className;
2610
+ }
2611
+ }
2612
+ let importsEnd = 0;
2613
+ for (const node of ts.import.iterate(statements)) {
2614
+ const source = ts.import.getSource(node);
2615
+ const full = path__default.join(dirName, source + ".ts");
2616
+ if (imports[full]) {
2617
+ delete imports[full];
2618
+ }
2619
+ importsEnd = node.end;
2620
+ }
2621
+ const addImports = Object.entries(imports).map(
2622
+ ([path2, name]) => `import { ${name} } from '${getImportPath(item.path, path2)}';`
2623
+ ).join("\n");
2624
+ if (addImports) {
2625
+ changes.add(importsEnd, `
2626
+ ${addImports}`);
2627
+ }
2628
+ const targetClass = findClassByTableName(statements, tableName);
2629
+ if (targetClass) {
2630
+ const relationsMember = findRelationsMember(targetClass.members);
2631
+ if (relationsMember) {
2632
+ const { initializer } = relationsMember;
2633
+ const takenKeys = {};
2634
+ if (ts.is.objectLiteral(initializer)) {
2635
+ const props = initializer.properties;
2636
+ for (const prop of props) {
2637
+ const name = (_a = prop.name) == null ? void 0 : _a.getText();
2638
+ if (name)
2639
+ takenKeys[name] = true;
2640
+ }
2641
+ const addRelations = [];
2642
+ for (const rel of item.relations) {
2643
+ if (!checkRelation(rel))
2644
+ continue;
2645
+ const name = makeRelationName(rel);
2646
+ if (takenKeys[name])
2647
+ continue;
2648
+ addRelations.push(relationToCode(rel));
2649
+ }
2650
+ if (addRelations.length) {
2651
+ const pos = props.end;
2652
+ changes.add(pos, addRelations.join(""));
2653
+ }
2654
+ }
2655
+ } else {
2656
+ changes.add(
2657
+ targetClass.end - 1,
2658
+ `
2659
+ relations = {${item.relations.filter(checkRelation).map((rel) => relationToCode(rel)).join("")}
2660
+ };
2661
+ `
2662
+ );
2663
+ }
2664
+ }
2665
+ await fs.writeFile(item.path, changes.apply());
2666
+ logger == null ? void 0 : logger.log(`Updated ${pathToLog(item.path)}`);
2667
+ };
2668
+ const findClassByTableName = (statements, tableName) => {
2669
+ for (const node of ts.class.iterate(statements)) {
2670
+ for (const member of node.members) {
2671
+ const name = ts.prop.getName(member);
2672
+ if (name !== "table")
2673
+ continue;
2674
+ const { initializer: value } = member;
2675
+ if (!value || !ts.is.stringLiteral(value))
2676
+ continue;
2677
+ if (value.text === tableName) {
2678
+ return node;
2679
+ }
2680
+ }
2681
+ }
2682
+ return;
2683
+ };
2684
+ const findRelationsMember = (members) => {
2685
+ for (const member of members) {
2686
+ const name = ts.prop.getName(member);
2687
+ if (name === "relations")
2688
+ return member;
2689
+ }
2690
+ return;
2691
+ };
2692
+ const checkRelation = (rel) => {
2693
+ return rel.columns.length === 1 && rel.foreignColumns.length === 1;
2694
+ };
2695
+ const makeRelationName = (rel) => {
2696
+ return plural(
2697
+ rel.className[0].toLowerCase() + rel.className.slice(1).replace(/Table$/, "")
2698
+ );
2699
+ };
2700
+ const relationToCode = (rel, name = makeRelationName(rel)) => {
2701
+ const code = [`
2702
+ ${name}: this.${rel.kind}(() => ${rel.className}, {`];
2703
+ const pk = rel[rel.kind === "hasMany" ? "columns" : "foreignColumns"][0];
2704
+ const fk = rel[rel.kind === "hasMany" ? "foreignColumns" : "columns"][0];
2705
+ code.push(
2706
+ ` primaryKey: '${pk}',`,
2707
+ ` foreignKey: '${fk}',`,
2708
+ " }),"
2709
+ );
2710
+ return code.join("\n");
2711
+ };
2712
+
2437
2713
  var __defProp = Object.defineProperty;
2438
2714
  var __defProps = Object.defineProperties;
2439
2715
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
@@ -2455,37 +2731,68 @@ var __spreadValues = (a, b) => {
2455
2731
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
2456
2732
  class AppCodeUpdaterError extends Error {
2457
2733
  }
2734
+ const makeGetTable = (path2, ormExportedAs, tables) => {
2735
+ let orm;
2736
+ return async (tableName) => {
2737
+ if (tables[tableName])
2738
+ return tables[tableName];
2739
+ if (!orm) {
2740
+ orm = (await import(path2))[ormExportedAs];
2741
+ if (!orm) {
2742
+ throw new Error(`ORM is not exported as ${ormExportedAs} from ${path2}`);
2743
+ }
2744
+ }
2745
+ for (const key in orm) {
2746
+ const table = orm[key];
2747
+ if (table && typeof table === "object" && table instanceof Db && table.table === tableName) {
2748
+ const tableInfo = {
2749
+ key,
2750
+ name: table.name,
2751
+ path: table.filePath
2752
+ };
2753
+ return tables[tableName] = tableInfo;
2754
+ }
2755
+ }
2756
+ return;
2757
+ };
2758
+ };
2458
2759
  const appCodeUpdater = ({
2459
2760
  tablePath,
2460
- mainFilePath
2461
- }) => {
2462
- return async ({
2761
+ ormPath,
2762
+ ormExportedAs = "db"
2763
+ }) => ({
2764
+ async process({
2463
2765
  ast,
2464
2766
  options,
2465
2767
  basePath,
2466
2768
  cache: cacheObject,
2467
2769
  logger,
2468
2770
  baseTable
2469
- }) => {
2771
+ }) {
2772
+ var _a, _b;
2470
2773
  const params = {
2471
2774
  tablePath(name) {
2472
2775
  const file = tablePath(name);
2473
2776
  return resolvePath(basePath, file);
2474
2777
  },
2475
- mainFilePath: resolvePath(basePath, mainFilePath),
2778
+ ormPath: resolvePath(basePath, ormPath),
2779
+ ormExportedAs,
2476
2780
  logger
2477
2781
  };
2782
+ const cache = cacheObject;
2783
+ (_a = cache.relations) != null ? _a : cache.relations = {};
2784
+ (_b = cache.tables) != null ? _b : cache.tables = {};
2785
+ const getTable = makeGetTable(params.ormPath, ormExportedAs, cache.tables);
2478
2786
  const promises = [
2479
- updateMainFile(
2480
- params.mainFilePath,
2481
- params.tablePath,
2787
+ updateMainFile(params.ormPath, params.tablePath, ast, options, logger),
2788
+ updateTableFile(__spreadProps(__spreadValues({}, params), {
2482
2789
  ast,
2483
- options,
2484
- logger
2485
- ),
2486
- updateTableFile(__spreadProps(__spreadValues({}, params), { ast, baseTable }))
2790
+ baseTable,
2791
+ getTable,
2792
+ relations: cache.relations,
2793
+ tables: cache.tables
2794
+ }))
2487
2795
  ];
2488
- const cache = cacheObject;
2489
2796
  if (!cache.createdBaseTable) {
2490
2797
  promises.push(
2491
2798
  createBaseTableFile({ logger: params.logger, baseTable }).then(() => {
@@ -2494,8 +2801,17 @@ const appCodeUpdater = ({
2494
2801
  );
2495
2802
  }
2496
2803
  await Promise.all(promises);
2497
- };
2498
- };
2804
+ },
2805
+ async afterAll({ cache, logger }) {
2806
+ const { relations } = cache;
2807
+ if (!relations)
2808
+ return;
2809
+ await updateRelations({
2810
+ relations,
2811
+ logger
2812
+ });
2813
+ }
2814
+ });
2499
2815
  const resolvePath = (basePath, filePath) => path.isAbsolute(filePath) ? filePath : path.resolve(basePath, filePath);
2500
2816
 
2501
2817
  export { AppCodeUpdaterError, appCodeUpdater, createBaseTable, createRepo, orchidORM };