metal-orm 1.0.11 → 1.0.13

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.
Files changed (54) hide show
  1. package/README.md +21 -18
  2. package/dist/decorators/index.cjs +317 -34
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +1 -1
  5. package/dist/decorators/index.d.ts +1 -1
  6. package/dist/decorators/index.js +317 -34
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +1965 -267
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +273 -23
  11. package/dist/index.d.ts +273 -23
  12. package/dist/index.js +1947 -267
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-654m4qy8.d.cts → select-CCp1oz9p.d.cts} +254 -4
  15. package/dist/{select-654m4qy8.d.ts → select-CCp1oz9p.d.ts} +254 -4
  16. package/package.json +3 -2
  17. package/src/core/ast/query.ts +40 -22
  18. package/src/core/ddl/dialects/base-schema-dialect.ts +48 -0
  19. package/src/core/ddl/dialects/index.ts +5 -0
  20. package/src/core/ddl/dialects/mssql-schema-dialect.ts +97 -0
  21. package/src/core/ddl/dialects/mysql-schema-dialect.ts +109 -0
  22. package/src/core/ddl/dialects/postgres-schema-dialect.ts +99 -0
  23. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +103 -0
  24. package/src/core/ddl/introspect/mssql.ts +149 -0
  25. package/src/core/ddl/introspect/mysql.ts +99 -0
  26. package/src/core/ddl/introspect/postgres.ts +154 -0
  27. package/src/core/ddl/introspect/sqlite.ts +66 -0
  28. package/src/core/ddl/introspect/types.ts +19 -0
  29. package/src/core/ddl/introspect/utils.ts +27 -0
  30. package/src/core/ddl/schema-diff.ts +179 -0
  31. package/src/core/ddl/schema-generator.ts +229 -0
  32. package/src/core/ddl/schema-introspect.ts +32 -0
  33. package/src/core/ddl/schema-types.ts +39 -0
  34. package/src/core/dialect/abstract.ts +122 -37
  35. package/src/core/dialect/base/sql-dialect.ts +204 -0
  36. package/src/core/dialect/mssql/index.ts +125 -80
  37. package/src/core/dialect/mysql/index.ts +18 -112
  38. package/src/core/dialect/postgres/index.ts +29 -126
  39. package/src/core/dialect/sqlite/index.ts +28 -129
  40. package/src/index.ts +4 -0
  41. package/src/orm/execute.ts +25 -16
  42. package/src/orm/orm-context.ts +60 -55
  43. package/src/orm/query-logger.ts +38 -0
  44. package/src/orm/relations/belongs-to.ts +42 -26
  45. package/src/orm/relations/has-many.ts +41 -25
  46. package/src/orm/relations/many-to-many.ts +43 -27
  47. package/src/orm/unit-of-work.ts +60 -23
  48. package/src/query-builder/hydration-manager.ts +229 -25
  49. package/src/query-builder/query-ast-service.ts +27 -12
  50. package/src/query-builder/select-query-state.ts +24 -12
  51. package/src/query-builder/select.ts +58 -14
  52. package/src/schema/column.ts +206 -27
  53. package/src/schema/table.ts +89 -32
  54. package/src/schema/types.ts +8 -5
@@ -1,4 +1,4 @@
1
- import { w as TableHooks, y as ColumnType, C as ColumnDef, T as TableDef, K as CascadeMode, v as SelectQueryBuilder } from '../select-654m4qy8.cjs';
1
+ import { K as TableHooks, Q as ColumnType, C as ColumnDef, T as TableDef, a0 as CascadeMode, z as SelectQueryBuilder } from '../select-CCp1oz9p.cjs';
2
2
 
3
3
  interface EntityOptions {
4
4
  tableName?: string;
@@ -1,4 +1,4 @@
1
- import { w as TableHooks, y as ColumnType, C as ColumnDef, T as TableDef, K as CascadeMode, v as SelectQueryBuilder } from '../select-654m4qy8.js';
1
+ import { K as TableHooks, Q as ColumnType, C as ColumnDef, T as TableDef, a0 as CascadeMode, z as SelectQueryBuilder } from '../select-CCp1oz9p.js';
2
2
 
3
3
  interface EntityOptions {
4
4
  tableName?: string;
@@ -1,10 +1,23 @@
1
1
  // src/schema/table.ts
2
- var defineTable = (name, columns, relations = {}, hooks) => {
2
+ var defineTable = (name, columns, relations = {}, hooks, options = {}) => {
3
3
  const colsWithNames = Object.entries(columns).reduce((acc, [key, def]) => {
4
4
  acc[key] = { ...def, name: key, table: name };
5
5
  return acc;
6
6
  }, {});
7
- return { name, columns: colsWithNames, relations, hooks };
7
+ return {
8
+ name,
9
+ schema: options.schema,
10
+ columns: colsWithNames,
11
+ relations,
12
+ hooks,
13
+ primaryKey: options.primaryKey,
14
+ indexes: options.indexes,
15
+ checks: options.checks,
16
+ comment: options.comment,
17
+ engine: options.engine,
18
+ charset: options.charset,
19
+ collation: options.collation
20
+ };
8
21
  };
9
22
 
10
23
  // src/orm/entity-metadata.ts
@@ -454,6 +467,44 @@ var SelectQueryState = class _SelectQueryState {
454
467
  ctes: [...this.ast.ctes ?? [], cte]
455
468
  });
456
469
  }
470
+ /**
471
+ * Adds a set operation (UNION/INTERSECT/EXCEPT) to the query
472
+ * @param op - Set operation node to add
473
+ * @returns New SelectQueryState with set operation
474
+ */
475
+ withSetOperation(op) {
476
+ return this.clone({
477
+ ...this.ast,
478
+ setOps: [...this.ast.setOps ?? [], op]
479
+ });
480
+ }
481
+ };
482
+
483
+ // src/core/ast/join-node.ts
484
+ var createJoinNode = (kind, tableName, condition, relationName) => ({
485
+ type: "Join",
486
+ kind,
487
+ table: { type: "Table", name: tableName },
488
+ condition,
489
+ relationName
490
+ });
491
+
492
+ // src/core/sql/sql.ts
493
+ var JOIN_KINDS = {
494
+ /** INNER JOIN type */
495
+ INNER: "INNER",
496
+ /** LEFT JOIN type */
497
+ LEFT: "LEFT",
498
+ /** RIGHT JOIN type */
499
+ RIGHT: "RIGHT",
500
+ /** CROSS JOIN type */
501
+ CROSS: "CROSS"
502
+ };
503
+ var ORDER_DIRECTIONS = {
504
+ /** Ascending order */
505
+ ASC: "ASC",
506
+ /** Descending order */
507
+ DESC: "DESC"
457
508
  };
458
509
 
459
510
  // src/query-builder/hydration-manager.ts
@@ -505,8 +556,26 @@ var HydrationManager = class _HydrationManager {
505
556
  * @returns AST with hydration metadata
506
557
  */
507
558
  applyToAst(ast) {
559
+ if (ast.setOps && ast.setOps.length > 0) {
560
+ return ast;
561
+ }
508
562
  const plan = this.planner.getPlan();
509
563
  if (!plan) return ast;
564
+ const needsPaginationGuard = this.requiresParentPagination(ast, plan);
565
+ const rewritten = needsPaginationGuard ? this.wrapForParentPagination(ast, plan) : ast;
566
+ return this.attachHydrationMeta(rewritten, plan);
567
+ }
568
+ /**
569
+ * Gets the current hydration plan
570
+ * @returns Hydration plan or undefined if none exists
571
+ */
572
+ getPlan() {
573
+ return this.planner.getPlan();
574
+ }
575
+ /**
576
+ * Attaches hydration metadata to a query AST node.
577
+ */
578
+ attachHydrationMeta(ast, plan) {
510
579
  return {
511
580
  ...ast,
512
581
  meta: {
@@ -516,11 +585,154 @@ var HydrationManager = class _HydrationManager {
516
585
  };
517
586
  }
518
587
  /**
519
- * Gets the current hydration plan
520
- * @returns Hydration plan or undefined if none exists
588
+ * Determines whether the query needs pagination rewriting to keep LIMIT/OFFSET
589
+ * applied to parent rows when eager-loading multiplicative relations.
521
590
  */
522
- getPlan() {
523
- return this.planner.getPlan();
591
+ requiresParentPagination(ast, plan) {
592
+ const hasPagination = ast.limit !== void 0 || ast.offset !== void 0;
593
+ return hasPagination && this.hasMultiplyingRelations(plan);
594
+ }
595
+ hasMultiplyingRelations(plan) {
596
+ return plan.relations.some(
597
+ (rel) => rel.type === RelationKinds.HasMany || rel.type === RelationKinds.BelongsToMany
598
+ );
599
+ }
600
+ /**
601
+ * Rewrites the query using CTEs so LIMIT/OFFSET target distinct parent rows
602
+ * instead of the joined result set.
603
+ *
604
+ * The strategy:
605
+ * - Hoist the original query (minus limit/offset) into a base CTE.
606
+ * - Select distinct parent ids from that base CTE with the original ordering and pagination.
607
+ * - Join the base CTE against the paged ids to retrieve the joined rows for just that page.
608
+ */
609
+ wrapForParentPagination(ast, plan) {
610
+ const projectionNames = this.getProjectionNames(ast.columns);
611
+ if (!projectionNames) {
612
+ return ast;
613
+ }
614
+ const projectionAliases = this.buildProjectionAliasMap(ast.columns);
615
+ const projectionSet = new Set(projectionNames);
616
+ const rootPkAlias = projectionAliases.get(`${plan.rootTable}.${plan.rootPrimaryKey}`) ?? plan.rootPrimaryKey;
617
+ const baseCteName = this.nextCteName(ast.ctes, "__metal_pagination_base");
618
+ const baseQuery = {
619
+ ...ast,
620
+ ctes: void 0,
621
+ limit: void 0,
622
+ offset: void 0,
623
+ orderBy: void 0,
624
+ meta: void 0
625
+ };
626
+ const baseCte = {
627
+ type: "CommonTableExpression",
628
+ name: baseCteName,
629
+ query: baseQuery,
630
+ recursive: false
631
+ };
632
+ const orderBy = this.mapOrderBy(ast.orderBy, plan, projectionAliases, baseCteName, projectionSet);
633
+ if (orderBy === null) {
634
+ return ast;
635
+ }
636
+ const pageCteName = this.nextCteName([...ast.ctes ?? [], baseCte], "__metal_pagination_page");
637
+ const pagingColumns = this.buildPagingColumns(rootPkAlias, orderBy, baseCteName);
638
+ const pageCte = {
639
+ type: "CommonTableExpression",
640
+ name: pageCteName,
641
+ query: {
642
+ type: "SelectQuery",
643
+ from: { type: "Table", name: baseCteName },
644
+ columns: pagingColumns,
645
+ joins: [],
646
+ distinct: [{ type: "Column", table: baseCteName, name: rootPkAlias }],
647
+ orderBy,
648
+ limit: ast.limit,
649
+ offset: ast.offset
650
+ },
651
+ recursive: false
652
+ };
653
+ const joinCondition = eq(
654
+ { type: "Column", table: baseCteName, name: rootPkAlias },
655
+ { type: "Column", table: pageCteName, name: rootPkAlias }
656
+ );
657
+ const outerColumns = projectionNames.map((name) => ({
658
+ type: "Column",
659
+ table: baseCteName,
660
+ name,
661
+ alias: name
662
+ }));
663
+ return {
664
+ type: "SelectQuery",
665
+ from: { type: "Table", name: baseCteName },
666
+ columns: outerColumns,
667
+ joins: [createJoinNode(JOIN_KINDS.INNER, pageCteName, joinCondition)],
668
+ orderBy,
669
+ ctes: [...ast.ctes ?? [], baseCte, pageCte]
670
+ };
671
+ }
672
+ nextCteName(existing, baseName) {
673
+ const names = new Set((existing ?? []).map((cte) => cte.name));
674
+ let candidate = baseName;
675
+ let suffix = 1;
676
+ while (names.has(candidate)) {
677
+ suffix += 1;
678
+ candidate = `${baseName}_${suffix}`;
679
+ }
680
+ return candidate;
681
+ }
682
+ getProjectionNames(columns) {
683
+ const names = [];
684
+ for (const col of columns) {
685
+ const alias = col.alias ?? col.name;
686
+ if (!alias) return void 0;
687
+ names.push(alias);
688
+ }
689
+ return names;
690
+ }
691
+ buildProjectionAliasMap(columns) {
692
+ const map = /* @__PURE__ */ new Map();
693
+ for (const col of columns) {
694
+ if (col.type !== "Column") continue;
695
+ const node = col;
696
+ const key = `${node.table}.${node.name}`;
697
+ map.set(key, node.alias ?? node.name);
698
+ }
699
+ return map;
700
+ }
701
+ mapOrderBy(orderBy, plan, projectionAliases, baseAlias, availableColumns) {
702
+ if (!orderBy || orderBy.length === 0) {
703
+ return void 0;
704
+ }
705
+ const mapped = [];
706
+ for (const ob of orderBy) {
707
+ if (ob.column.table !== plan.rootTable) {
708
+ return null;
709
+ }
710
+ const alias = projectionAliases.get(`${ob.column.table}.${ob.column.name}`) ?? ob.column.name;
711
+ if (!availableColumns.has(alias)) {
712
+ return null;
713
+ }
714
+ mapped.push({
715
+ type: "OrderBy",
716
+ column: { type: "Column", table: baseAlias, name: alias },
717
+ direction: ob.direction
718
+ });
719
+ }
720
+ return mapped;
721
+ }
722
+ buildPagingColumns(primaryKey, orderBy, tableAlias) {
723
+ const columns = [{ type: "Column", table: tableAlias, name: primaryKey, alias: primaryKey }];
724
+ if (!orderBy) return columns;
725
+ for (const ob of orderBy) {
726
+ if (!columns.some((col) => col.name === ob.column.name)) {
727
+ columns.push({
728
+ type: "Column",
729
+ table: tableAlias,
730
+ name: ob.column.name,
731
+ alias: ob.column.name
732
+ });
733
+ }
734
+ }
735
+ return columns;
524
736
  }
525
737
  };
526
738
 
@@ -774,6 +986,20 @@ var QueryAstService = class {
774
986
  };
775
987
  return this.state.withCte(cte);
776
988
  }
989
+ /**
990
+ * Adds a set operation (UNION/UNION ALL/INTERSECT/EXCEPT) to the query
991
+ * @param operator - Set operator
992
+ * @param query - Right-hand side query
993
+ * @returns Updated query state with set operation
994
+ */
995
+ withSetOperation(operator, query) {
996
+ const op = {
997
+ type: "SetOperation",
998
+ operator,
999
+ query
1000
+ };
1001
+ return this.state.withSetOperation(op);
1002
+ }
777
1003
  /**
778
1004
  * Selects a subquery as a column
779
1005
  * @param alias - Alias for the subquery
@@ -926,15 +1152,6 @@ var RelationProjectionHelper = class {
926
1152
  }
927
1153
  };
928
1154
 
929
- // src/core/ast/join-node.ts
930
- var createJoinNode = (kind, tableName, condition, relationName) => ({
931
- type: "Join",
932
- kind,
933
- table: { type: "Table", name: tableName },
934
- condition,
935
- relationName
936
- });
937
-
938
1155
  // src/query-builder/relation-conditions.ts
939
1156
  var assertNever = (value) => {
940
1157
  throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
@@ -990,24 +1207,6 @@ var buildRelationCorrelation = (root, relation) => {
990
1207
  return baseRelationCondition(root, relation);
991
1208
  };
992
1209
 
993
- // src/core/sql/sql.ts
994
- var JOIN_KINDS = {
995
- /** INNER JOIN type */
996
- INNER: "INNER",
997
- /** LEFT JOIN type */
998
- LEFT: "LEFT",
999
- /** RIGHT JOIN type */
1000
- RIGHT: "RIGHT",
1001
- /** CROSS JOIN type */
1002
- CROSS: "CROSS"
1003
- };
1004
- var ORDER_DIRECTIONS = {
1005
- /** Ascending order */
1006
- ASC: "ASC",
1007
- /** Descending order */
1008
- DESC: "DESC"
1009
- };
1010
-
1011
1210
  // src/query-builder/relation-service.ts
1012
1211
  var RelationService = class {
1013
1212
  /**
@@ -1453,6 +1652,16 @@ var hasEntityMeta = (entity) => {
1453
1652
 
1454
1653
  // src/orm/relations/has-many.ts
1455
1654
  var toKey2 = (value) => value === null || value === void 0 ? "" : String(value);
1655
+ var hideInternal = (obj, keys) => {
1656
+ for (const key of keys) {
1657
+ Object.defineProperty(obj, key, {
1658
+ value: obj[key],
1659
+ writable: false,
1660
+ configurable: false,
1661
+ enumerable: false
1662
+ });
1663
+ }
1664
+ };
1456
1665
  var DefaultHasManyCollection = class {
1457
1666
  constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
1458
1667
  this.ctx = ctx;
@@ -1468,6 +1677,7 @@ var DefaultHasManyCollection = class {
1468
1677
  this.items = [];
1469
1678
  this.added = /* @__PURE__ */ new Set();
1470
1679
  this.removed = /* @__PURE__ */ new Set();
1680
+ hideInternal(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
1471
1681
  this.hydrateFromCache();
1472
1682
  }
1473
1683
  async load() {
@@ -1543,10 +1753,23 @@ var DefaultHasManyCollection = class {
1543
1753
  this.items = rows.map((row) => this.createEntity(row));
1544
1754
  this.loaded = true;
1545
1755
  }
1756
+ toJSON() {
1757
+ return this.items;
1758
+ }
1546
1759
  };
1547
1760
 
1548
1761
  // src/orm/relations/belongs-to.ts
1549
1762
  var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
1763
+ var hideInternal2 = (obj, keys) => {
1764
+ for (const key of keys) {
1765
+ Object.defineProperty(obj, key, {
1766
+ value: obj[key],
1767
+ writable: false,
1768
+ configurable: false,
1769
+ enumerable: false
1770
+ });
1771
+ }
1772
+ };
1550
1773
  var DefaultBelongsToReference = class {
1551
1774
  constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, targetKey) {
1552
1775
  this.ctx = ctx;
@@ -1560,6 +1783,7 @@ var DefaultBelongsToReference = class {
1560
1783
  this.targetKey = targetKey;
1561
1784
  this.loaded = false;
1562
1785
  this.current = null;
1786
+ hideInternal2(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
1563
1787
  this.populateFromHydrationCache();
1564
1788
  }
1565
1789
  async load() {
@@ -1620,10 +1844,23 @@ var DefaultBelongsToReference = class {
1620
1844
  this.current = this.createEntity(row);
1621
1845
  this.loaded = true;
1622
1846
  }
1847
+ toJSON() {
1848
+ return this.current;
1849
+ }
1623
1850
  };
1624
1851
 
1625
1852
  // src/orm/relations/many-to-many.ts
1626
1853
  var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
1854
+ var hideInternal3 = (obj, keys) => {
1855
+ for (const key of keys) {
1856
+ Object.defineProperty(obj, key, {
1857
+ value: obj[key],
1858
+ writable: false,
1859
+ configurable: false,
1860
+ enumerable: false
1861
+ });
1862
+ }
1863
+ };
1627
1864
  var DefaultManyToManyCollection = class {
1628
1865
  constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
1629
1866
  this.ctx = ctx;
@@ -1637,6 +1874,7 @@ var DefaultManyToManyCollection = class {
1637
1874
  this.localKey = localKey;
1638
1875
  this.loaded = false;
1639
1876
  this.items = [];
1877
+ hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
1640
1878
  this.hydrateFromCache();
1641
1879
  }
1642
1880
  async load() {
@@ -1742,6 +1980,9 @@ var DefaultManyToManyCollection = class {
1742
1980
  });
1743
1981
  this.loaded = true;
1744
1982
  }
1983
+ toJSON() {
1984
+ return this.items;
1985
+ }
1745
1986
  };
1746
1987
 
1747
1988
  // src/orm/lazy-batch.ts
@@ -2102,9 +2343,15 @@ var flattenResults = (results) => {
2102
2343
  return rows;
2103
2344
  };
2104
2345
  async function executeHydrated(ctx, qb) {
2105
- const compiled = ctx.dialect.compileSelect(qb.getAST());
2346
+ const ast = qb.getAST();
2347
+ const compiled = ctx.dialect.compileSelect(ast);
2106
2348
  const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
2107
2349
  const rows = flattenResults(executed);
2350
+ if (ast.setOps && ast.setOps.length > 0) {
2351
+ return rows.map(
2352
+ (row) => createEntityProxy(ctx, qb.getTable(), row, qb.getLazyRelations())
2353
+ );
2354
+ }
2108
2355
  const hydrated = hydrateRows(rows, qb.getHydrationPlan());
2109
2356
  return hydrated.map(
2110
2357
  (row) => createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
@@ -2151,6 +2398,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
2151
2398
  const joinNode = createJoinNode(kind, table.name, condition);
2152
2399
  return this.applyAst(context, (service) => service.withJoin(joinNode));
2153
2400
  }
2401
+ applySetOperation(operator, query) {
2402
+ const subAst = this.resolveQueryNode(query);
2403
+ return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
2404
+ }
2154
2405
  /**
2155
2406
  * Selects specific columns for the query
2156
2407
  * @param columns - Record of column definitions, function nodes, case expressions, or window functions
@@ -2339,6 +2590,38 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
2339
2590
  const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
2340
2591
  return this.clone(nextContext);
2341
2592
  }
2593
+ /**
2594
+ * Combines this query with another using UNION
2595
+ * @param query - Query to union with
2596
+ * @returns New query builder instance with the set operation
2597
+ */
2598
+ union(query) {
2599
+ return this.clone(this.applySetOperation("UNION", query));
2600
+ }
2601
+ /**
2602
+ * Combines this query with another using UNION ALL
2603
+ * @param query - Query to union with
2604
+ * @returns New query builder instance with the set operation
2605
+ */
2606
+ unionAll(query) {
2607
+ return this.clone(this.applySetOperation("UNION ALL", query));
2608
+ }
2609
+ /**
2610
+ * Combines this query with another using INTERSECT
2611
+ * @param query - Query to intersect with
2612
+ * @returns New query builder instance with the set operation
2613
+ */
2614
+ intersect(query) {
2615
+ return this.clone(this.applySetOperation("INTERSECT", query));
2616
+ }
2617
+ /**
2618
+ * Combines this query with another using EXCEPT
2619
+ * @param query - Query to subtract
2620
+ * @returns New query builder instance with the set operation
2621
+ */
2622
+ except(query) {
2623
+ return this.clone(this.applySetOperation("EXCEPT", query));
2624
+ }
2342
2625
  /**
2343
2626
  * Adds a WHERE EXISTS condition to the query
2344
2627
  * @param subquery - Subquery to check for existence