metal-orm 1.0.12 → 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.
- package/README.md +17 -15
- package/dist/decorators/index.cjs +302 -32
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +1 -1
- package/dist/decorators/index.d.ts +1 -1
- package/dist/decorators/index.js +302 -32
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +583 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +583 -130
- package/dist/index.js.map +1 -1
- package/dist/{select-BKlr2ivY.d.cts → select-CCp1oz9p.d.cts} +114 -1
- package/dist/{select-BKlr2ivY.d.ts → select-CCp1oz9p.d.ts} +114 -1
- package/package.json +3 -2
- package/src/core/ast/query.ts +40 -22
- package/src/core/dialect/abstract.ts +122 -37
- package/src/core/dialect/base/sql-dialect.ts +51 -8
- package/src/core/dialect/mssql/index.ts +125 -80
- package/src/core/dialect/postgres/index.ts +5 -6
- package/src/core/dialect/sqlite/index.ts +5 -6
- package/src/orm/execute.ts +25 -16
- package/src/orm/orm-context.ts +60 -55
- package/src/orm/query-logger.ts +38 -0
- package/src/orm/relations/belongs-to.ts +42 -26
- package/src/orm/relations/has-many.ts +41 -25
- package/src/orm/relations/many-to-many.ts +43 -27
- package/src/orm/unit-of-work.ts +60 -23
- package/src/query-builder/hydration-manager.ts +229 -25
- package/src/query-builder/query-ast-service.ts +27 -12
- package/src/query-builder/select-query-state.ts +24 -12
- package/src/query-builder/select.ts +58 -14
package/README.md
CHANGED
|
@@ -62,15 +62,16 @@ Full docs live in the `docs/` folder:
|
|
|
62
62
|
### Level 1 – Query builder & hydration
|
|
63
63
|
|
|
64
64
|
- **Declarative schema definition** with `defineTable`, `col.*`, and typed relations.
|
|
65
|
-
- **Fluent query builder** over a real SQL AST
|
|
66
|
-
(`SelectQueryBuilder`, `InsertQueryBuilder`, `UpdateQueryBuilder`, `DeleteQueryBuilder`).
|
|
67
|
-
- **Advanced SQL**: CTEs, aggregates, window functions, subqueries, JSON, CASE, EXISTS.
|
|
68
|
-
- **
|
|
69
|
-
- **
|
|
70
|
-
- **
|
|
71
|
-
- **
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
- **Fluent query builder** over a real SQL AST
|
|
66
|
+
(`SelectQueryBuilder`, `InsertQueryBuilder`, `UpdateQueryBuilder`, `DeleteQueryBuilder`).
|
|
67
|
+
- **Advanced SQL**: CTEs, aggregates, window functions, subqueries, JSON, CASE, EXISTS.
|
|
68
|
+
- **Set operations**: `union`, `unionAll`, `intersect`, `except` across all dialects (ORDER/LIMIT apply to the combined result; hydration is disabled for compound queries so rows are returned as-is without collapsing duplicates).
|
|
69
|
+
- **Expression builders**: `eq`, `and`, `or`, `between`, `inList`, `exists`, `jsonPath`, `caseWhen`, window functions like `rowNumber`, `rank`, `lag`, `lead`, etc., all backed by typed AST nodes.
|
|
70
|
+
- **Relation-aware hydration**: turn flat rows into nested objects (`user.posts`, `user.roles`, etc.) using a hydration plan derived from the AST metadata.
|
|
71
|
+
- **Multi-dialect**: compile once, run on MySQL/MariaDB, PostgreSQL, SQLite, or SQL Server via pluggable dialects.
|
|
72
|
+
- **DML**: type-safe INSERT / UPDATE / DELETE with `RETURNING` where supported.
|
|
73
|
+
|
|
74
|
+
Level 1 is ideal when you:
|
|
74
75
|
|
|
75
76
|
- Already have a domain model and just want a serious SQL builder.
|
|
76
77
|
- Want deterministic SQL (no magical query generation).
|
|
@@ -82,12 +83,13 @@ On top of the query builder, MetalORM ships a focused runtime:
|
|
|
82
83
|
|
|
83
84
|
- **Entities inferred from your `TableDef`s** (no separate mapping file).
|
|
84
85
|
- **Lazy, batched relations**: `user.posts.load()`, `user.roles.syncByIds([...])`, etc.
|
|
85
|
-
- **Identity map**: the same row becomes the same entity instance within a context (see the [Identity map pattern](https://en.wikipedia.org/wiki/Identity_map_pattern)).
|
|
86
|
-
- **Unit of Work (`OrmContext`)** tracking New/Dirty/Removed entities and relation changes, inspired by the classic [Unit of Work pattern](https://en.wikipedia.org/wiki/Unit_of_work).
|
|
87
|
-
- **Graph persistence**: mutate a whole object graph and flush once with `ctx.saveChanges()`.
|
|
88
|
-
- **Relation change processor** that knows how to deal with has-many and many-to-many pivot tables.
|
|
89
|
-
- **Interceptors**: `beforeFlush` / `afterFlush` hooks for cross-cutting concerns (auditing, multi-tenant filters, soft delete filters, etc.).
|
|
90
|
-
- **Domain events**: `addDomainEvent` and a DomainEventBus integrated into `saveChanges()`, aligned with domain events from [Domain-driven design](https://en.wikipedia.org/wiki/Domain-driven_design).
|
|
86
|
+
- **Identity map**: the same row becomes the same entity instance within a context (see the [Identity map pattern](https://en.wikipedia.org/wiki/Identity_map_pattern)).
|
|
87
|
+
- **Unit of Work (`OrmContext`)** tracking New/Dirty/Removed entities and relation changes, inspired by the classic [Unit of Work pattern](https://en.wikipedia.org/wiki/Unit_of_work).
|
|
88
|
+
- **Graph persistence**: mutate a whole object graph and flush once with `ctx.saveChanges()`.
|
|
89
|
+
- **Relation change processor** that knows how to deal with has-many and many-to-many pivot tables.
|
|
90
|
+
- **Interceptors**: `beforeFlush` / `afterFlush` hooks for cross-cutting concerns (auditing, multi-tenant filters, soft delete filters, etc.).
|
|
91
|
+
- **Domain events**: `addDomainEvent` and a DomainEventBus integrated into `saveChanges()`, aligned with domain events from [Domain-driven design](https://en.wikipedia.org/wiki/Domain-driven_design).
|
|
92
|
+
- **JSON-safe entities**: relation wrappers hide internal references and implement `toJSON`, so `JSON.stringify` of hydrated entities works without circular reference errors.
|
|
91
93
|
|
|
92
94
|
Use this layer where:
|
|
93
95
|
|
|
@@ -500,6 +500,44 @@ var SelectQueryState = class _SelectQueryState {
|
|
|
500
500
|
ctes: [...this.ast.ctes ?? [], cte]
|
|
501
501
|
});
|
|
502
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Adds a set operation (UNION/INTERSECT/EXCEPT) to the query
|
|
505
|
+
* @param op - Set operation node to add
|
|
506
|
+
* @returns New SelectQueryState with set operation
|
|
507
|
+
*/
|
|
508
|
+
withSetOperation(op) {
|
|
509
|
+
return this.clone({
|
|
510
|
+
...this.ast,
|
|
511
|
+
setOps: [...this.ast.setOps ?? [], op]
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
// src/core/ast/join-node.ts
|
|
517
|
+
var createJoinNode = (kind, tableName, condition, relationName) => ({
|
|
518
|
+
type: "Join",
|
|
519
|
+
kind,
|
|
520
|
+
table: { type: "Table", name: tableName },
|
|
521
|
+
condition,
|
|
522
|
+
relationName
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// src/core/sql/sql.ts
|
|
526
|
+
var JOIN_KINDS = {
|
|
527
|
+
/** INNER JOIN type */
|
|
528
|
+
INNER: "INNER",
|
|
529
|
+
/** LEFT JOIN type */
|
|
530
|
+
LEFT: "LEFT",
|
|
531
|
+
/** RIGHT JOIN type */
|
|
532
|
+
RIGHT: "RIGHT",
|
|
533
|
+
/** CROSS JOIN type */
|
|
534
|
+
CROSS: "CROSS"
|
|
535
|
+
};
|
|
536
|
+
var ORDER_DIRECTIONS = {
|
|
537
|
+
/** Ascending order */
|
|
538
|
+
ASC: "ASC",
|
|
539
|
+
/** Descending order */
|
|
540
|
+
DESC: "DESC"
|
|
503
541
|
};
|
|
504
542
|
|
|
505
543
|
// src/query-builder/hydration-manager.ts
|
|
@@ -551,8 +589,26 @@ var HydrationManager = class _HydrationManager {
|
|
|
551
589
|
* @returns AST with hydration metadata
|
|
552
590
|
*/
|
|
553
591
|
applyToAst(ast) {
|
|
592
|
+
if (ast.setOps && ast.setOps.length > 0) {
|
|
593
|
+
return ast;
|
|
594
|
+
}
|
|
554
595
|
const plan = this.planner.getPlan();
|
|
555
596
|
if (!plan) return ast;
|
|
597
|
+
const needsPaginationGuard = this.requiresParentPagination(ast, plan);
|
|
598
|
+
const rewritten = needsPaginationGuard ? this.wrapForParentPagination(ast, plan) : ast;
|
|
599
|
+
return this.attachHydrationMeta(rewritten, plan);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Gets the current hydration plan
|
|
603
|
+
* @returns Hydration plan or undefined if none exists
|
|
604
|
+
*/
|
|
605
|
+
getPlan() {
|
|
606
|
+
return this.planner.getPlan();
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Attaches hydration metadata to a query AST node.
|
|
610
|
+
*/
|
|
611
|
+
attachHydrationMeta(ast, plan) {
|
|
556
612
|
return {
|
|
557
613
|
...ast,
|
|
558
614
|
meta: {
|
|
@@ -562,11 +618,154 @@ var HydrationManager = class _HydrationManager {
|
|
|
562
618
|
};
|
|
563
619
|
}
|
|
564
620
|
/**
|
|
565
|
-
*
|
|
566
|
-
*
|
|
621
|
+
* Determines whether the query needs pagination rewriting to keep LIMIT/OFFSET
|
|
622
|
+
* applied to parent rows when eager-loading multiplicative relations.
|
|
567
623
|
*/
|
|
568
|
-
|
|
569
|
-
|
|
624
|
+
requiresParentPagination(ast, plan) {
|
|
625
|
+
const hasPagination = ast.limit !== void 0 || ast.offset !== void 0;
|
|
626
|
+
return hasPagination && this.hasMultiplyingRelations(plan);
|
|
627
|
+
}
|
|
628
|
+
hasMultiplyingRelations(plan) {
|
|
629
|
+
return plan.relations.some(
|
|
630
|
+
(rel) => rel.type === RelationKinds.HasMany || rel.type === RelationKinds.BelongsToMany
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Rewrites the query using CTEs so LIMIT/OFFSET target distinct parent rows
|
|
635
|
+
* instead of the joined result set.
|
|
636
|
+
*
|
|
637
|
+
* The strategy:
|
|
638
|
+
* - Hoist the original query (minus limit/offset) into a base CTE.
|
|
639
|
+
* - Select distinct parent ids from that base CTE with the original ordering and pagination.
|
|
640
|
+
* - Join the base CTE against the paged ids to retrieve the joined rows for just that page.
|
|
641
|
+
*/
|
|
642
|
+
wrapForParentPagination(ast, plan) {
|
|
643
|
+
const projectionNames = this.getProjectionNames(ast.columns);
|
|
644
|
+
if (!projectionNames) {
|
|
645
|
+
return ast;
|
|
646
|
+
}
|
|
647
|
+
const projectionAliases = this.buildProjectionAliasMap(ast.columns);
|
|
648
|
+
const projectionSet = new Set(projectionNames);
|
|
649
|
+
const rootPkAlias = projectionAliases.get(`${plan.rootTable}.${plan.rootPrimaryKey}`) ?? plan.rootPrimaryKey;
|
|
650
|
+
const baseCteName = this.nextCteName(ast.ctes, "__metal_pagination_base");
|
|
651
|
+
const baseQuery = {
|
|
652
|
+
...ast,
|
|
653
|
+
ctes: void 0,
|
|
654
|
+
limit: void 0,
|
|
655
|
+
offset: void 0,
|
|
656
|
+
orderBy: void 0,
|
|
657
|
+
meta: void 0
|
|
658
|
+
};
|
|
659
|
+
const baseCte = {
|
|
660
|
+
type: "CommonTableExpression",
|
|
661
|
+
name: baseCteName,
|
|
662
|
+
query: baseQuery,
|
|
663
|
+
recursive: false
|
|
664
|
+
};
|
|
665
|
+
const orderBy = this.mapOrderBy(ast.orderBy, plan, projectionAliases, baseCteName, projectionSet);
|
|
666
|
+
if (orderBy === null) {
|
|
667
|
+
return ast;
|
|
668
|
+
}
|
|
669
|
+
const pageCteName = this.nextCteName([...ast.ctes ?? [], baseCte], "__metal_pagination_page");
|
|
670
|
+
const pagingColumns = this.buildPagingColumns(rootPkAlias, orderBy, baseCteName);
|
|
671
|
+
const pageCte = {
|
|
672
|
+
type: "CommonTableExpression",
|
|
673
|
+
name: pageCteName,
|
|
674
|
+
query: {
|
|
675
|
+
type: "SelectQuery",
|
|
676
|
+
from: { type: "Table", name: baseCteName },
|
|
677
|
+
columns: pagingColumns,
|
|
678
|
+
joins: [],
|
|
679
|
+
distinct: [{ type: "Column", table: baseCteName, name: rootPkAlias }],
|
|
680
|
+
orderBy,
|
|
681
|
+
limit: ast.limit,
|
|
682
|
+
offset: ast.offset
|
|
683
|
+
},
|
|
684
|
+
recursive: false
|
|
685
|
+
};
|
|
686
|
+
const joinCondition = eq(
|
|
687
|
+
{ type: "Column", table: baseCteName, name: rootPkAlias },
|
|
688
|
+
{ type: "Column", table: pageCteName, name: rootPkAlias }
|
|
689
|
+
);
|
|
690
|
+
const outerColumns = projectionNames.map((name) => ({
|
|
691
|
+
type: "Column",
|
|
692
|
+
table: baseCteName,
|
|
693
|
+
name,
|
|
694
|
+
alias: name
|
|
695
|
+
}));
|
|
696
|
+
return {
|
|
697
|
+
type: "SelectQuery",
|
|
698
|
+
from: { type: "Table", name: baseCteName },
|
|
699
|
+
columns: outerColumns,
|
|
700
|
+
joins: [createJoinNode(JOIN_KINDS.INNER, pageCteName, joinCondition)],
|
|
701
|
+
orderBy,
|
|
702
|
+
ctes: [...ast.ctes ?? [], baseCte, pageCte]
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
nextCteName(existing, baseName) {
|
|
706
|
+
const names = new Set((existing ?? []).map((cte) => cte.name));
|
|
707
|
+
let candidate = baseName;
|
|
708
|
+
let suffix = 1;
|
|
709
|
+
while (names.has(candidate)) {
|
|
710
|
+
suffix += 1;
|
|
711
|
+
candidate = `${baseName}_${suffix}`;
|
|
712
|
+
}
|
|
713
|
+
return candidate;
|
|
714
|
+
}
|
|
715
|
+
getProjectionNames(columns) {
|
|
716
|
+
const names = [];
|
|
717
|
+
for (const col of columns) {
|
|
718
|
+
const alias = col.alias ?? col.name;
|
|
719
|
+
if (!alias) return void 0;
|
|
720
|
+
names.push(alias);
|
|
721
|
+
}
|
|
722
|
+
return names;
|
|
723
|
+
}
|
|
724
|
+
buildProjectionAliasMap(columns) {
|
|
725
|
+
const map = /* @__PURE__ */ new Map();
|
|
726
|
+
for (const col of columns) {
|
|
727
|
+
if (col.type !== "Column") continue;
|
|
728
|
+
const node = col;
|
|
729
|
+
const key = `${node.table}.${node.name}`;
|
|
730
|
+
map.set(key, node.alias ?? node.name);
|
|
731
|
+
}
|
|
732
|
+
return map;
|
|
733
|
+
}
|
|
734
|
+
mapOrderBy(orderBy, plan, projectionAliases, baseAlias, availableColumns) {
|
|
735
|
+
if (!orderBy || orderBy.length === 0) {
|
|
736
|
+
return void 0;
|
|
737
|
+
}
|
|
738
|
+
const mapped = [];
|
|
739
|
+
for (const ob of orderBy) {
|
|
740
|
+
if (ob.column.table !== plan.rootTable) {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
const alias = projectionAliases.get(`${ob.column.table}.${ob.column.name}`) ?? ob.column.name;
|
|
744
|
+
if (!availableColumns.has(alias)) {
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
mapped.push({
|
|
748
|
+
type: "OrderBy",
|
|
749
|
+
column: { type: "Column", table: baseAlias, name: alias },
|
|
750
|
+
direction: ob.direction
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
return mapped;
|
|
754
|
+
}
|
|
755
|
+
buildPagingColumns(primaryKey, orderBy, tableAlias) {
|
|
756
|
+
const columns = [{ type: "Column", table: tableAlias, name: primaryKey, alias: primaryKey }];
|
|
757
|
+
if (!orderBy) return columns;
|
|
758
|
+
for (const ob of orderBy) {
|
|
759
|
+
if (!columns.some((col) => col.name === ob.column.name)) {
|
|
760
|
+
columns.push({
|
|
761
|
+
type: "Column",
|
|
762
|
+
table: tableAlias,
|
|
763
|
+
name: ob.column.name,
|
|
764
|
+
alias: ob.column.name
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return columns;
|
|
570
769
|
}
|
|
571
770
|
};
|
|
572
771
|
|
|
@@ -820,6 +1019,20 @@ var QueryAstService = class {
|
|
|
820
1019
|
};
|
|
821
1020
|
return this.state.withCte(cte);
|
|
822
1021
|
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Adds a set operation (UNION/UNION ALL/INTERSECT/EXCEPT) to the query
|
|
1024
|
+
* @param operator - Set operator
|
|
1025
|
+
* @param query - Right-hand side query
|
|
1026
|
+
* @returns Updated query state with set operation
|
|
1027
|
+
*/
|
|
1028
|
+
withSetOperation(operator, query) {
|
|
1029
|
+
const op = {
|
|
1030
|
+
type: "SetOperation",
|
|
1031
|
+
operator,
|
|
1032
|
+
query
|
|
1033
|
+
};
|
|
1034
|
+
return this.state.withSetOperation(op);
|
|
1035
|
+
}
|
|
823
1036
|
/**
|
|
824
1037
|
* Selects a subquery as a column
|
|
825
1038
|
* @param alias - Alias for the subquery
|
|
@@ -972,15 +1185,6 @@ var RelationProjectionHelper = class {
|
|
|
972
1185
|
}
|
|
973
1186
|
};
|
|
974
1187
|
|
|
975
|
-
// src/core/ast/join-node.ts
|
|
976
|
-
var createJoinNode = (kind, tableName, condition, relationName) => ({
|
|
977
|
-
type: "Join",
|
|
978
|
-
kind,
|
|
979
|
-
table: { type: "Table", name: tableName },
|
|
980
|
-
condition,
|
|
981
|
-
relationName
|
|
982
|
-
});
|
|
983
|
-
|
|
984
1188
|
// src/query-builder/relation-conditions.ts
|
|
985
1189
|
var assertNever = (value) => {
|
|
986
1190
|
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
@@ -1036,24 +1240,6 @@ var buildRelationCorrelation = (root, relation) => {
|
|
|
1036
1240
|
return baseRelationCondition(root, relation);
|
|
1037
1241
|
};
|
|
1038
1242
|
|
|
1039
|
-
// src/core/sql/sql.ts
|
|
1040
|
-
var JOIN_KINDS = {
|
|
1041
|
-
/** INNER JOIN type */
|
|
1042
|
-
INNER: "INNER",
|
|
1043
|
-
/** LEFT JOIN type */
|
|
1044
|
-
LEFT: "LEFT",
|
|
1045
|
-
/** RIGHT JOIN type */
|
|
1046
|
-
RIGHT: "RIGHT",
|
|
1047
|
-
/** CROSS JOIN type */
|
|
1048
|
-
CROSS: "CROSS"
|
|
1049
|
-
};
|
|
1050
|
-
var ORDER_DIRECTIONS = {
|
|
1051
|
-
/** Ascending order */
|
|
1052
|
-
ASC: "ASC",
|
|
1053
|
-
/** Descending order */
|
|
1054
|
-
DESC: "DESC"
|
|
1055
|
-
};
|
|
1056
|
-
|
|
1057
1243
|
// src/query-builder/relation-service.ts
|
|
1058
1244
|
var RelationService = class {
|
|
1059
1245
|
/**
|
|
@@ -1499,6 +1685,16 @@ var hasEntityMeta = (entity) => {
|
|
|
1499
1685
|
|
|
1500
1686
|
// src/orm/relations/has-many.ts
|
|
1501
1687
|
var toKey2 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1688
|
+
var hideInternal = (obj, keys) => {
|
|
1689
|
+
for (const key of keys) {
|
|
1690
|
+
Object.defineProperty(obj, key, {
|
|
1691
|
+
value: obj[key],
|
|
1692
|
+
writable: false,
|
|
1693
|
+
configurable: false,
|
|
1694
|
+
enumerable: false
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1502
1698
|
var DefaultHasManyCollection = class {
|
|
1503
1699
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
1504
1700
|
this.ctx = ctx;
|
|
@@ -1514,6 +1710,7 @@ var DefaultHasManyCollection = class {
|
|
|
1514
1710
|
this.items = [];
|
|
1515
1711
|
this.added = /* @__PURE__ */ new Set();
|
|
1516
1712
|
this.removed = /* @__PURE__ */ new Set();
|
|
1713
|
+
hideInternal(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
|
|
1517
1714
|
this.hydrateFromCache();
|
|
1518
1715
|
}
|
|
1519
1716
|
async load() {
|
|
@@ -1589,10 +1786,23 @@ var DefaultHasManyCollection = class {
|
|
|
1589
1786
|
this.items = rows.map((row) => this.createEntity(row));
|
|
1590
1787
|
this.loaded = true;
|
|
1591
1788
|
}
|
|
1789
|
+
toJSON() {
|
|
1790
|
+
return this.items;
|
|
1791
|
+
}
|
|
1592
1792
|
};
|
|
1593
1793
|
|
|
1594
1794
|
// src/orm/relations/belongs-to.ts
|
|
1595
1795
|
var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1796
|
+
var hideInternal2 = (obj, keys) => {
|
|
1797
|
+
for (const key of keys) {
|
|
1798
|
+
Object.defineProperty(obj, key, {
|
|
1799
|
+
value: obj[key],
|
|
1800
|
+
writable: false,
|
|
1801
|
+
configurable: false,
|
|
1802
|
+
enumerable: false
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1596
1806
|
var DefaultBelongsToReference = class {
|
|
1597
1807
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, targetKey) {
|
|
1598
1808
|
this.ctx = ctx;
|
|
@@ -1606,6 +1816,7 @@ var DefaultBelongsToReference = class {
|
|
|
1606
1816
|
this.targetKey = targetKey;
|
|
1607
1817
|
this.loaded = false;
|
|
1608
1818
|
this.current = null;
|
|
1819
|
+
hideInternal2(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
|
|
1609
1820
|
this.populateFromHydrationCache();
|
|
1610
1821
|
}
|
|
1611
1822
|
async load() {
|
|
@@ -1666,10 +1877,23 @@ var DefaultBelongsToReference = class {
|
|
|
1666
1877
|
this.current = this.createEntity(row);
|
|
1667
1878
|
this.loaded = true;
|
|
1668
1879
|
}
|
|
1880
|
+
toJSON() {
|
|
1881
|
+
return this.current;
|
|
1882
|
+
}
|
|
1669
1883
|
};
|
|
1670
1884
|
|
|
1671
1885
|
// src/orm/relations/many-to-many.ts
|
|
1672
1886
|
var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1887
|
+
var hideInternal3 = (obj, keys) => {
|
|
1888
|
+
for (const key of keys) {
|
|
1889
|
+
Object.defineProperty(obj, key, {
|
|
1890
|
+
value: obj[key],
|
|
1891
|
+
writable: false,
|
|
1892
|
+
configurable: false,
|
|
1893
|
+
enumerable: false
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1673
1897
|
var DefaultManyToManyCollection = class {
|
|
1674
1898
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
1675
1899
|
this.ctx = ctx;
|
|
@@ -1683,6 +1907,7 @@ var DefaultManyToManyCollection = class {
|
|
|
1683
1907
|
this.localKey = localKey;
|
|
1684
1908
|
this.loaded = false;
|
|
1685
1909
|
this.items = [];
|
|
1910
|
+
hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
|
|
1686
1911
|
this.hydrateFromCache();
|
|
1687
1912
|
}
|
|
1688
1913
|
async load() {
|
|
@@ -1788,6 +2013,9 @@ var DefaultManyToManyCollection = class {
|
|
|
1788
2013
|
});
|
|
1789
2014
|
this.loaded = true;
|
|
1790
2015
|
}
|
|
2016
|
+
toJSON() {
|
|
2017
|
+
return this.items;
|
|
2018
|
+
}
|
|
1791
2019
|
};
|
|
1792
2020
|
|
|
1793
2021
|
// src/orm/lazy-batch.ts
|
|
@@ -2148,9 +2376,15 @@ var flattenResults = (results) => {
|
|
|
2148
2376
|
return rows;
|
|
2149
2377
|
};
|
|
2150
2378
|
async function executeHydrated(ctx, qb) {
|
|
2151
|
-
const
|
|
2379
|
+
const ast = qb.getAST();
|
|
2380
|
+
const compiled = ctx.dialect.compileSelect(ast);
|
|
2152
2381
|
const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
2153
2382
|
const rows = flattenResults(executed);
|
|
2383
|
+
if (ast.setOps && ast.setOps.length > 0) {
|
|
2384
|
+
return rows.map(
|
|
2385
|
+
(row) => createEntityProxy(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
2386
|
+
);
|
|
2387
|
+
}
|
|
2154
2388
|
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
2155
2389
|
return hydrated.map(
|
|
2156
2390
|
(row) => createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
@@ -2197,6 +2431,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2197
2431
|
const joinNode = createJoinNode(kind, table.name, condition);
|
|
2198
2432
|
return this.applyAst(context, (service) => service.withJoin(joinNode));
|
|
2199
2433
|
}
|
|
2434
|
+
applySetOperation(operator, query) {
|
|
2435
|
+
const subAst = this.resolveQueryNode(query);
|
|
2436
|
+
return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
|
|
2437
|
+
}
|
|
2200
2438
|
/**
|
|
2201
2439
|
* Selects specific columns for the query
|
|
2202
2440
|
* @param columns - Record of column definitions, function nodes, case expressions, or window functions
|
|
@@ -2385,6 +2623,38 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2385
2623
|
const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
|
|
2386
2624
|
return this.clone(nextContext);
|
|
2387
2625
|
}
|
|
2626
|
+
/**
|
|
2627
|
+
* Combines this query with another using UNION
|
|
2628
|
+
* @param query - Query to union with
|
|
2629
|
+
* @returns New query builder instance with the set operation
|
|
2630
|
+
*/
|
|
2631
|
+
union(query) {
|
|
2632
|
+
return this.clone(this.applySetOperation("UNION", query));
|
|
2633
|
+
}
|
|
2634
|
+
/**
|
|
2635
|
+
* Combines this query with another using UNION ALL
|
|
2636
|
+
* @param query - Query to union with
|
|
2637
|
+
* @returns New query builder instance with the set operation
|
|
2638
|
+
*/
|
|
2639
|
+
unionAll(query) {
|
|
2640
|
+
return this.clone(this.applySetOperation("UNION ALL", query));
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Combines this query with another using INTERSECT
|
|
2644
|
+
* @param query - Query to intersect with
|
|
2645
|
+
* @returns New query builder instance with the set operation
|
|
2646
|
+
*/
|
|
2647
|
+
intersect(query) {
|
|
2648
|
+
return this.clone(this.applySetOperation("INTERSECT", query));
|
|
2649
|
+
}
|
|
2650
|
+
/**
|
|
2651
|
+
* Combines this query with another using EXCEPT
|
|
2652
|
+
* @param query - Query to subtract
|
|
2653
|
+
* @returns New query builder instance with the set operation
|
|
2654
|
+
*/
|
|
2655
|
+
except(query) {
|
|
2656
|
+
return this.clone(this.applySetOperation("EXCEPT", query));
|
|
2657
|
+
}
|
|
2388
2658
|
/**
|
|
2389
2659
|
* Adds a WHERE EXISTS condition to the query
|
|
2390
2660
|
* @param subquery - Subquery to check for existence
|