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.js CHANGED
@@ -6,6 +6,7 @@ var node_async_hooks = require('node:async_hooks');
6
6
  var path = require('path');
7
7
  var fs = require('fs/promises');
8
8
  var typescript = require('typescript');
9
+ var pluralize = require('pluralize');
9
10
 
10
11
  function _interopNamespaceDefault(e) {
11
12
  var n = Object.create(null);
@@ -49,26 +50,35 @@ const create = (columnTypes, filePath, snakeCase) => {
49
50
  const base = (_a = class {
50
51
  constructor() {
51
52
  this.snakeCase = snakeCase;
52
- this.setColumns = (fn) => {
53
- columnTypes[orchidCore.snakeCaseKey] = this.snakeCase;
54
- const shape = pqb.getColumnTypes(columnTypes, fn);
55
- if (this.snakeCase) {
56
- for (const key in shape) {
57
- const column = shape[key];
58
- if (column.data.name)
59
- continue;
60
- const snakeName = orchidCore.toSnakeCase(key);
61
- if (snakeName !== key) {
62
- column.data.name = snakeName;
63
- }
53
+ this.columnTypes = columnTypes;
54
+ }
55
+ setColumns(fn) {
56
+ if (!this.filePath) {
57
+ const filePath2 = orchidCore.getCallerFilePath();
58
+ if (!filePath2) {
59
+ throw new Error(
60
+ `Failed to determine file path for table ${this.constructor.name}. Please set \`filePath\` property manually`
61
+ );
62
+ }
63
+ this.filePath = filePath2;
64
+ }
65
+ columnTypes[orchidCore.snakeCaseKey] = this.snakeCase;
66
+ const shape = pqb.getColumnTypes(columnTypes, fn);
67
+ if (this.snakeCase) {
68
+ for (const key in shape) {
69
+ const column = shape[key];
70
+ if (column.data.name)
71
+ continue;
72
+ const snakeName = orchidCore.toSnakeCase(key);
73
+ if (snakeName !== key) {
74
+ column.data.name = snakeName;
64
75
  }
65
76
  }
66
- return {
67
- shape,
68
- type: void 0
69
- };
77
+ }
78
+ return {
79
+ shape,
80
+ type: void 0
70
81
  };
71
- this.columnTypes = columnTypes;
72
82
  }
73
83
  belongsTo(fn, options) {
74
84
  return {
@@ -1405,6 +1415,8 @@ const orchidORM = (_a, tables) => {
1405
1415
  );
1406
1416
  dbTable.definedAs = key;
1407
1417
  dbTable.db = result;
1418
+ dbTable.filePath = table.filePath;
1419
+ dbTable.name = table.constructor.name;
1408
1420
  result[key] = dbTable;
1409
1421
  }
1410
1422
  applyRelations(qb, tableInstances, result);
@@ -1778,6 +1790,48 @@ const getTablesListObject = (importName, statements) => {
1778
1790
  return;
1779
1791
  };
1780
1792
 
1793
+ const handleForeignKey = async ({
1794
+ getTable,
1795
+ relations,
1796
+ tableName,
1797
+ columns,
1798
+ foreignTableName,
1799
+ foreignColumns,
1800
+ skipBelongsTo
1801
+ }) => {
1802
+ var _a, _b;
1803
+ const table = await getTable(tableName);
1804
+ if (!table)
1805
+ return;
1806
+ const foreignTable = await getTable(foreignTableName);
1807
+ if (!foreignTable)
1808
+ return;
1809
+ if (!skipBelongsTo) {
1810
+ (_a = relations[tableName]) != null ? _a : relations[tableName] = {
1811
+ path: table.path,
1812
+ relations: []
1813
+ };
1814
+ relations[tableName].relations.push({
1815
+ kind: "belongsTo",
1816
+ columns,
1817
+ className: foreignTable.name,
1818
+ path: foreignTable.path,
1819
+ foreignColumns
1820
+ });
1821
+ }
1822
+ (_b = relations[foreignTableName]) != null ? _b : relations[foreignTableName] = {
1823
+ path: foreignTable.path,
1824
+ relations: []
1825
+ };
1826
+ relations[foreignTableName].relations.push({
1827
+ kind: "hasMany",
1828
+ columns: foreignColumns,
1829
+ className: table.name,
1830
+ path: table.path,
1831
+ foreignColumns: columns
1832
+ });
1833
+ };
1834
+
1781
1835
  var __getOwnPropSymbols$4 = Object.getOwnPropertySymbols;
1782
1836
  var __hasOwnProp$4 = Object.prototype.hasOwnProperty;
1783
1837
  var __propIsEnum$4 = Object.prototype.propertyIsEnumerable;
@@ -1796,13 +1850,29 @@ var __objRest$2 = (source, exclude) => {
1796
1850
  const createTable = async (_a) => {
1797
1851
  var _b = _a, {
1798
1852
  ast,
1799
- logger
1853
+ logger,
1854
+ getTable,
1855
+ relations,
1856
+ tables
1800
1857
  } = _b, params = __objRest$2(_b, [
1801
1858
  "ast",
1802
- "logger"
1859
+ "logger",
1860
+ "getTable",
1861
+ "relations",
1862
+ "tables"
1803
1863
  ]);
1804
- const tablePath = params.tablePath(orchidCore.toCamelCase(ast.name));
1864
+ const key = orchidCore.toCamelCase(ast.name);
1865
+ const tablePath = params.tablePath(key);
1805
1866
  const baseTablePath = orchidCore.getImportPath(tablePath, params.baseTable.filePath);
1867
+ const className = `${orchidCore.toPascalCase(ast.name)}Table`;
1868
+ tables[ast.name] = {
1869
+ key,
1870
+ name: className,
1871
+ path: tablePath
1872
+ };
1873
+ const imports = {
1874
+ [baseTablePath]: params.baseTable.name
1875
+ };
1806
1876
  const props = [];
1807
1877
  if (ast.schema) {
1808
1878
  props.push(`schema = ${orchidCore.singleQuote(ast.schema)};`);
@@ -1816,10 +1886,23 @@ const createTable = async (_a) => {
1816
1886
  pqb.columnsShapeToCode(ast.shape, ast, "t"),
1817
1887
  "}));"
1818
1888
  );
1889
+ const relCode = await getRelations(
1890
+ ast,
1891
+ getTable,
1892
+ tablePath,
1893
+ imports,
1894
+ relations,
1895
+ ast.name
1896
+ );
1897
+ if (relCode) {
1898
+ props.push("", ...relCode);
1899
+ }
1819
1900
  const code = [
1820
- `import { ${params.baseTable.name} } from '${baseTablePath}';
1821
- `,
1822
- `export class ${orchidCore.toPascalCase(ast.name)}Table extends ${params.baseTable.name} {`,
1901
+ ...Object.entries(imports).map(
1902
+ ([from, name]) => `import { ${name} } from '${from}';`
1903
+ ),
1904
+ "",
1905
+ `export class ${className} extends ${params.baseTable.name} {`,
1823
1906
  props,
1824
1907
  "}\n"
1825
1908
  ];
@@ -1833,6 +1916,62 @@ const createTable = async (_a) => {
1833
1916
  }
1834
1917
  }
1835
1918
  };
1919
+ const getRelations = async (ast, getTable, tablePath, imports, relations, tableName) => {
1920
+ const refs = [];
1921
+ for (const key in ast.shape) {
1922
+ const item = ast.shape[key];
1923
+ if (!(item instanceof pqb.ColumnType) || !item.data.foreignKeys)
1924
+ continue;
1925
+ for (const fkey of item.data.foreignKeys) {
1926
+ if ("table" in fkey) {
1927
+ refs.push({
1928
+ table: fkey.table,
1929
+ columns: [key],
1930
+ foreignColumns: fkey.columns
1931
+ });
1932
+ }
1933
+ }
1934
+ }
1935
+ if (ast.constraints) {
1936
+ for (const { references: ref } of ast.constraints) {
1937
+ if (ref && typeof ref.fnOrTable === "string") {
1938
+ refs.push({
1939
+ table: ref.fnOrTable,
1940
+ columns: ref.columns,
1941
+ foreignColumns: ref.foreignColumns
1942
+ });
1943
+ }
1944
+ }
1945
+ }
1946
+ if (!refs.length)
1947
+ return;
1948
+ const code = [];
1949
+ for (const ref of refs) {
1950
+ const { columns, foreignColumns } = ref;
1951
+ if (columns.length > 1 || foreignColumns.length > 1)
1952
+ continue;
1953
+ const info = await getTable(ref.table);
1954
+ if (!info)
1955
+ continue;
1956
+ const path2 = orchidCore.getImportPath(tablePath, info.path);
1957
+ imports[path2] = info.name;
1958
+ code.push(
1959
+ `${info.key}: this.belongsTo(() => ${info.name}, {`,
1960
+ [`primaryKey: '${foreignColumns[0]}',`, `foreignKey: '${columns[0]}',`],
1961
+ "}),"
1962
+ );
1963
+ await handleForeignKey({
1964
+ getTable,
1965
+ relations,
1966
+ tableName,
1967
+ columns: ref.columns,
1968
+ foreignTableName: ref.table,
1969
+ foreignColumns: ref.foreignColumns,
1970
+ skipBelongsTo: true
1971
+ });
1972
+ }
1973
+ return code.length ? ["relations = {", code, "};"] : void 0;
1974
+ };
1836
1975
 
1837
1976
  var __defProp$2 = Object.defineProperty;
1838
1977
  var __getOwnPropSymbols$3 = Object.getOwnPropertySymbols;
@@ -2427,6 +2566,18 @@ const updateTableFile = async (params) => {
2427
2566
  await changeTable(__spreadProps$1(__spreadValues$1({}, params), { ast }));
2428
2567
  } else if (ast.type === "renameTable") {
2429
2568
  await renameTable(__spreadProps$1(__spreadValues$1({}, params), { ast }));
2569
+ } else if (ast.type === "constraint" && ast.references) {
2570
+ const ref = ast.references;
2571
+ if (typeof ref.fnOrTable === "string") {
2572
+ await handleForeignKey({
2573
+ getTable: params.getTable,
2574
+ relations: params.relations,
2575
+ tableName: ast.tableName,
2576
+ columns: ref.columns,
2577
+ foreignTableName: ref.fnOrTable,
2578
+ foreignColumns: ref.foreignColumns
2579
+ });
2580
+ }
2430
2581
  }
2431
2582
  };
2432
2583
 
@@ -2453,6 +2604,131 @@ export const ${baseTable.name} = createBaseTable();
2453
2604
  });
2454
2605
  };
2455
2606
 
2607
+ const updateRelations = async ({
2608
+ relations,
2609
+ logger
2610
+ }) => {
2611
+ await Promise.all(
2612
+ Object.entries(relations).map(
2613
+ ([tableName, item]) => updateRelationItem(tableName, item, logger)
2614
+ )
2615
+ );
2616
+ };
2617
+ const updateRelationItem = async (tableName, item, logger) => {
2618
+ var _a;
2619
+ const content = await fs.readFile(item.path, "utf-8").catch(() => void 0);
2620
+ if (!content)
2621
+ return;
2622
+ const changes = new FileChanges(content);
2623
+ const statements = ts.getStatements(content);
2624
+ const dirName = path.dirname(item.path);
2625
+ const imports = {};
2626
+ for (const relation of item.relations) {
2627
+ if (!imports[relation.path]) {
2628
+ imports[relation.path] = relation.className;
2629
+ }
2630
+ }
2631
+ let importsEnd = 0;
2632
+ for (const node of ts.import.iterate(statements)) {
2633
+ const source = ts.import.getSource(node);
2634
+ const full = path.join(dirName, source + ".ts");
2635
+ if (imports[full]) {
2636
+ delete imports[full];
2637
+ }
2638
+ importsEnd = node.end;
2639
+ }
2640
+ const addImports = Object.entries(imports).map(
2641
+ ([path2, name]) => `import { ${name} } from '${orchidCore.getImportPath(item.path, path2)}';`
2642
+ ).join("\n");
2643
+ if (addImports) {
2644
+ changes.add(importsEnd, `
2645
+ ${addImports}`);
2646
+ }
2647
+ const targetClass = findClassByTableName(statements, tableName);
2648
+ if (targetClass) {
2649
+ const relationsMember = findRelationsMember(targetClass.members);
2650
+ if (relationsMember) {
2651
+ const { initializer } = relationsMember;
2652
+ const takenKeys = {};
2653
+ if (ts.is.objectLiteral(initializer)) {
2654
+ const props = initializer.properties;
2655
+ for (const prop of props) {
2656
+ const name = (_a = prop.name) == null ? void 0 : _a.getText();
2657
+ if (name)
2658
+ takenKeys[name] = true;
2659
+ }
2660
+ const addRelations = [];
2661
+ for (const rel of item.relations) {
2662
+ if (!checkRelation(rel))
2663
+ continue;
2664
+ const name = makeRelationName(rel);
2665
+ if (takenKeys[name])
2666
+ continue;
2667
+ addRelations.push(relationToCode(rel));
2668
+ }
2669
+ if (addRelations.length) {
2670
+ const pos = props.end;
2671
+ changes.add(pos, addRelations.join(""));
2672
+ }
2673
+ }
2674
+ } else {
2675
+ changes.add(
2676
+ targetClass.end - 1,
2677
+ `
2678
+ relations = {${item.relations.filter(checkRelation).map((rel) => relationToCode(rel)).join("")}
2679
+ };
2680
+ `
2681
+ );
2682
+ }
2683
+ }
2684
+ await fs.writeFile(item.path, changes.apply());
2685
+ logger == null ? void 0 : logger.log(`Updated ${orchidCore.pathToLog(item.path)}`);
2686
+ };
2687
+ const findClassByTableName = (statements, tableName) => {
2688
+ for (const node of ts.class.iterate(statements)) {
2689
+ for (const member of node.members) {
2690
+ const name = ts.prop.getName(member);
2691
+ if (name !== "table")
2692
+ continue;
2693
+ const { initializer: value } = member;
2694
+ if (!value || !ts.is.stringLiteral(value))
2695
+ continue;
2696
+ if (value.text === tableName) {
2697
+ return node;
2698
+ }
2699
+ }
2700
+ }
2701
+ return;
2702
+ };
2703
+ const findRelationsMember = (members) => {
2704
+ for (const member of members) {
2705
+ const name = ts.prop.getName(member);
2706
+ if (name === "relations")
2707
+ return member;
2708
+ }
2709
+ return;
2710
+ };
2711
+ const checkRelation = (rel) => {
2712
+ return rel.columns.length === 1 && rel.foreignColumns.length === 1;
2713
+ };
2714
+ const makeRelationName = (rel) => {
2715
+ return pluralize.plural(
2716
+ rel.className[0].toLowerCase() + rel.className.slice(1).replace(/Table$/, "")
2717
+ );
2718
+ };
2719
+ const relationToCode = (rel, name = makeRelationName(rel)) => {
2720
+ const code = [`
2721
+ ${name}: this.${rel.kind}(() => ${rel.className}, {`];
2722
+ const pk = rel[rel.kind === "hasMany" ? "columns" : "foreignColumns"][0];
2723
+ const fk = rel[rel.kind === "hasMany" ? "foreignColumns" : "columns"][0];
2724
+ code.push(
2725
+ ` primaryKey: '${pk}',`,
2726
+ ` foreignKey: '${fk}',`,
2727
+ " }),"
2728
+ );
2729
+ return code.join("\n");
2730
+ };
2731
+
2456
2732
  var __defProp = Object.defineProperty;
2457
2733
  var __defProps = Object.defineProperties;
2458
2734
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
@@ -2474,37 +2750,68 @@ var __spreadValues = (a, b) => {
2474
2750
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
2475
2751
  class AppCodeUpdaterError extends Error {
2476
2752
  }
2753
+ const makeGetTable = (path2, ormExportedAs, tables) => {
2754
+ let orm;
2755
+ return async (tableName) => {
2756
+ if (tables[tableName])
2757
+ return tables[tableName];
2758
+ if (!orm) {
2759
+ orm = (await import(path2))[ormExportedAs];
2760
+ if (!orm) {
2761
+ throw new Error(`ORM is not exported as ${ormExportedAs} from ${path2}`);
2762
+ }
2763
+ }
2764
+ for (const key in orm) {
2765
+ const table = orm[key];
2766
+ if (table && typeof table === "object" && table instanceof pqb.Db && table.table === tableName) {
2767
+ const tableInfo = {
2768
+ key,
2769
+ name: table.name,
2770
+ path: table.filePath
2771
+ };
2772
+ return tables[tableName] = tableInfo;
2773
+ }
2774
+ }
2775
+ return;
2776
+ };
2777
+ };
2477
2778
  const appCodeUpdater = ({
2478
2779
  tablePath,
2479
- mainFilePath
2480
- }) => {
2481
- return async ({
2780
+ ormPath,
2781
+ ormExportedAs = "db"
2782
+ }) => ({
2783
+ async process({
2482
2784
  ast,
2483
2785
  options,
2484
2786
  basePath,
2485
2787
  cache: cacheObject,
2486
2788
  logger,
2487
2789
  baseTable
2488
- }) => {
2790
+ }) {
2791
+ var _a, _b;
2489
2792
  const params = {
2490
2793
  tablePath(name) {
2491
2794
  const file = tablePath(name);
2492
2795
  return resolvePath(basePath, file);
2493
2796
  },
2494
- mainFilePath: resolvePath(basePath, mainFilePath),
2797
+ ormPath: resolvePath(basePath, ormPath),
2798
+ ormExportedAs,
2495
2799
  logger
2496
2800
  };
2801
+ const cache = cacheObject;
2802
+ (_a = cache.relations) != null ? _a : cache.relations = {};
2803
+ (_b = cache.tables) != null ? _b : cache.tables = {};
2804
+ const getTable = makeGetTable(params.ormPath, ormExportedAs, cache.tables);
2497
2805
  const promises = [
2498
- updateMainFile(
2499
- params.mainFilePath,
2500
- params.tablePath,
2806
+ updateMainFile(params.ormPath, params.tablePath, ast, options, logger),
2807
+ updateTableFile(__spreadProps(__spreadValues({}, params), {
2501
2808
  ast,
2502
- options,
2503
- logger
2504
- ),
2505
- updateTableFile(__spreadProps(__spreadValues({}, params), { ast, baseTable }))
2809
+ baseTable,
2810
+ getTable,
2811
+ relations: cache.relations,
2812
+ tables: cache.tables
2813
+ }))
2506
2814
  ];
2507
- const cache = cacheObject;
2508
2815
  if (!cache.createdBaseTable) {
2509
2816
  promises.push(
2510
2817
  createBaseTableFile({ logger: params.logger, baseTable }).then(() => {
@@ -2513,8 +2820,17 @@ const appCodeUpdater = ({
2513
2820
  );
2514
2821
  }
2515
2822
  await Promise.all(promises);
2516
- };
2517
- };
2823
+ },
2824
+ async afterAll({ cache, logger }) {
2825
+ const { relations } = cache;
2826
+ if (!relations)
2827
+ return;
2828
+ await updateRelations({
2829
+ relations,
2830
+ logger
2831
+ });
2832
+ }
2833
+ });
2518
2834
  const resolvePath = (basePath, filePath) => path__namespace.isAbsolute(filePath) ? filePath : path__namespace.resolve(basePath, filePath);
2519
2835
 
2520
2836
  Object.defineProperty(exports, 'OrchidOrmError', {