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.
- package/README.md +21 -18
- package/dist/decorators/index.cjs +317 -34
- 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 +317 -34
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +1965 -267
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +273 -23
- package/dist/index.d.ts +273 -23
- package/dist/index.js +1947 -267
- package/dist/index.js.map +1 -1
- package/dist/{select-654m4qy8.d.cts → select-CCp1oz9p.d.cts} +254 -4
- package/dist/{select-654m4qy8.d.ts → select-CCp1oz9p.d.ts} +254 -4
- package/package.json +3 -2
- package/src/core/ast/query.ts +40 -22
- package/src/core/ddl/dialects/base-schema-dialect.ts +48 -0
- package/src/core/ddl/dialects/index.ts +5 -0
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +97 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +109 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +99 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +103 -0
- package/src/core/ddl/introspect/mssql.ts +149 -0
- package/src/core/ddl/introspect/mysql.ts +99 -0
- package/src/core/ddl/introspect/postgres.ts +154 -0
- package/src/core/ddl/introspect/sqlite.ts +66 -0
- package/src/core/ddl/introspect/types.ts +19 -0
- package/src/core/ddl/introspect/utils.ts +27 -0
- package/src/core/ddl/schema-diff.ts +179 -0
- package/src/core/ddl/schema-generator.ts +229 -0
- package/src/core/ddl/schema-introspect.ts +32 -0
- package/src/core/ddl/schema-types.ts +39 -0
- package/src/core/dialect/abstract.ts +122 -37
- package/src/core/dialect/base/sql-dialect.ts +204 -0
- package/src/core/dialect/mssql/index.ts +125 -80
- package/src/core/dialect/mysql/index.ts +18 -112
- package/src/core/dialect/postgres/index.ts +29 -126
- package/src/core/dialect/sqlite/index.ts +28 -129
- package/src/index.ts +4 -0
- 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/src/schema/column.ts +206 -27
- package/src/schema/table.ts +89 -32
- package/src/schema/types.ts +8 -5
package/README.md
CHANGED
|
@@ -49,9 +49,10 @@ Full docs live in the `docs/` folder:
|
|
|
49
49
|
- [DML Operations](https://github.com/celsowm/metal-orm/blob/main/docs/dml-operations.md)
|
|
50
50
|
- [Hydration & Entities](https://github.com/celsowm/metal-orm/blob/main/docs/hydration.md)
|
|
51
51
|
- [Runtime & Unit of Work](https://github.com/celsowm/metal-orm/blob/main/docs/runtime.md)
|
|
52
|
-
- [Advanced Features](https://github.com/celsowm/metal-orm/blob/main/docs/advanced-features.md)
|
|
53
|
-
- [Multi-Dialect Support](https://github.com/celsowm/metal-orm/blob/main/docs/multi-dialect-support.md)
|
|
54
|
-
- [
|
|
52
|
+
- [Advanced Features](https://github.com/celsowm/metal-orm/blob/main/docs/advanced-features.md)
|
|
53
|
+
- [Multi-Dialect Support](https://github.com/celsowm/metal-orm/blob/main/docs/multi-dialect-support.md)
|
|
54
|
+
- [Schema Generation (DDL)](https://github.com/celsowm/metal-orm/blob/main/docs/schema-generation.md)
|
|
55
|
+
- [API Reference](https://github.com/celsowm/metal-orm/blob/main/docs/api-reference.md)
|
|
55
56
|
|
|
56
57
|
---
|
|
57
58
|
|
|
@@ -61,15 +62,16 @@ Full docs live in the `docs/` folder:
|
|
|
61
62
|
### Level 1 – Query builder & hydration
|
|
62
63
|
|
|
63
64
|
- **Declarative schema definition** with `defineTable`, `col.*`, and typed relations.
|
|
64
|
-
- **Fluent query builder** over a real SQL AST
|
|
65
|
-
(`SelectQueryBuilder`, `InsertQueryBuilder`, `UpdateQueryBuilder`, `DeleteQueryBuilder`).
|
|
66
|
-
- **Advanced SQL**: CTEs, aggregates, window functions, subqueries, JSON, CASE, EXISTS.
|
|
67
|
-
- **
|
|
68
|
-
- **
|
|
69
|
-
- **
|
|
70
|
-
- **
|
|
71
|
-
|
|
72
|
-
|
|
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:
|
|
73
75
|
|
|
74
76
|
- Already have a domain model and just want a serious SQL builder.
|
|
75
77
|
- Want deterministic SQL (no magical query generation).
|
|
@@ -81,12 +83,13 @@ On top of the query builder, MetalORM ships a focused runtime:
|
|
|
81
83
|
|
|
82
84
|
- **Entities inferred from your `TableDef`s** (no separate mapping file).
|
|
83
85
|
- **Lazy, batched relations**: `user.posts.load()`, `user.roles.syncByIds([...])`, etc.
|
|
84
|
-
- **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)).
|
|
85
|
-
- **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).
|
|
86
|
-
- **Graph persistence**: mutate a whole object graph and flush once with `ctx.saveChanges()`.
|
|
87
|
-
- **Relation change processor** that knows how to deal with has-many and many-to-many pivot tables.
|
|
88
|
-
- **Interceptors**: `beforeFlush` / `afterFlush` hooks for cross-cutting concerns (auditing, multi-tenant filters, soft delete filters, etc.).
|
|
89
|
-
- **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.
|
|
90
93
|
|
|
91
94
|
Use this layer where:
|
|
92
95
|
|
|
@@ -32,12 +32,25 @@ __export(decorators_exports, {
|
|
|
32
32
|
module.exports = __toCommonJS(decorators_exports);
|
|
33
33
|
|
|
34
34
|
// src/schema/table.ts
|
|
35
|
-
var defineTable = (name, columns, relations = {}, hooks) => {
|
|
35
|
+
var defineTable = (name, columns, relations = {}, hooks, options = {}) => {
|
|
36
36
|
const colsWithNames = Object.entries(columns).reduce((acc, [key, def]) => {
|
|
37
37
|
acc[key] = { ...def, name: key, table: name };
|
|
38
38
|
return acc;
|
|
39
39
|
}, {});
|
|
40
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
name,
|
|
42
|
+
schema: options.schema,
|
|
43
|
+
columns: colsWithNames,
|
|
44
|
+
relations,
|
|
45
|
+
hooks,
|
|
46
|
+
primaryKey: options.primaryKey,
|
|
47
|
+
indexes: options.indexes,
|
|
48
|
+
checks: options.checks,
|
|
49
|
+
comment: options.comment,
|
|
50
|
+
engine: options.engine,
|
|
51
|
+
charset: options.charset,
|
|
52
|
+
collation: options.collation
|
|
53
|
+
};
|
|
41
54
|
};
|
|
42
55
|
|
|
43
56
|
// src/orm/entity-metadata.ts
|
|
@@ -487,6 +500,44 @@ var SelectQueryState = class _SelectQueryState {
|
|
|
487
500
|
ctes: [...this.ast.ctes ?? [], cte]
|
|
488
501
|
});
|
|
489
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"
|
|
490
541
|
};
|
|
491
542
|
|
|
492
543
|
// src/query-builder/hydration-manager.ts
|
|
@@ -538,8 +589,26 @@ var HydrationManager = class _HydrationManager {
|
|
|
538
589
|
* @returns AST with hydration metadata
|
|
539
590
|
*/
|
|
540
591
|
applyToAst(ast) {
|
|
592
|
+
if (ast.setOps && ast.setOps.length > 0) {
|
|
593
|
+
return ast;
|
|
594
|
+
}
|
|
541
595
|
const plan = this.planner.getPlan();
|
|
542
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) {
|
|
543
612
|
return {
|
|
544
613
|
...ast,
|
|
545
614
|
meta: {
|
|
@@ -549,11 +618,154 @@ var HydrationManager = class _HydrationManager {
|
|
|
549
618
|
};
|
|
550
619
|
}
|
|
551
620
|
/**
|
|
552
|
-
*
|
|
553
|
-
*
|
|
621
|
+
* Determines whether the query needs pagination rewriting to keep LIMIT/OFFSET
|
|
622
|
+
* applied to parent rows when eager-loading multiplicative relations.
|
|
554
623
|
*/
|
|
555
|
-
|
|
556
|
-
|
|
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;
|
|
557
769
|
}
|
|
558
770
|
};
|
|
559
771
|
|
|
@@ -807,6 +1019,20 @@ var QueryAstService = class {
|
|
|
807
1019
|
};
|
|
808
1020
|
return this.state.withCte(cte);
|
|
809
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
|
+
}
|
|
810
1036
|
/**
|
|
811
1037
|
* Selects a subquery as a column
|
|
812
1038
|
* @param alias - Alias for the subquery
|
|
@@ -959,15 +1185,6 @@ var RelationProjectionHelper = class {
|
|
|
959
1185
|
}
|
|
960
1186
|
};
|
|
961
1187
|
|
|
962
|
-
// src/core/ast/join-node.ts
|
|
963
|
-
var createJoinNode = (kind, tableName, condition, relationName) => ({
|
|
964
|
-
type: "Join",
|
|
965
|
-
kind,
|
|
966
|
-
table: { type: "Table", name: tableName },
|
|
967
|
-
condition,
|
|
968
|
-
relationName
|
|
969
|
-
});
|
|
970
|
-
|
|
971
1188
|
// src/query-builder/relation-conditions.ts
|
|
972
1189
|
var assertNever = (value) => {
|
|
973
1190
|
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
@@ -1023,24 +1240,6 @@ var buildRelationCorrelation = (root, relation) => {
|
|
|
1023
1240
|
return baseRelationCondition(root, relation);
|
|
1024
1241
|
};
|
|
1025
1242
|
|
|
1026
|
-
// src/core/sql/sql.ts
|
|
1027
|
-
var JOIN_KINDS = {
|
|
1028
|
-
/** INNER JOIN type */
|
|
1029
|
-
INNER: "INNER",
|
|
1030
|
-
/** LEFT JOIN type */
|
|
1031
|
-
LEFT: "LEFT",
|
|
1032
|
-
/** RIGHT JOIN type */
|
|
1033
|
-
RIGHT: "RIGHT",
|
|
1034
|
-
/** CROSS JOIN type */
|
|
1035
|
-
CROSS: "CROSS"
|
|
1036
|
-
};
|
|
1037
|
-
var ORDER_DIRECTIONS = {
|
|
1038
|
-
/** Ascending order */
|
|
1039
|
-
ASC: "ASC",
|
|
1040
|
-
/** Descending order */
|
|
1041
|
-
DESC: "DESC"
|
|
1042
|
-
};
|
|
1043
|
-
|
|
1044
1243
|
// src/query-builder/relation-service.ts
|
|
1045
1244
|
var RelationService = class {
|
|
1046
1245
|
/**
|
|
@@ -1486,6 +1685,16 @@ var hasEntityMeta = (entity) => {
|
|
|
1486
1685
|
|
|
1487
1686
|
// src/orm/relations/has-many.ts
|
|
1488
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
|
+
};
|
|
1489
1698
|
var DefaultHasManyCollection = class {
|
|
1490
1699
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
1491
1700
|
this.ctx = ctx;
|
|
@@ -1501,6 +1710,7 @@ var DefaultHasManyCollection = class {
|
|
|
1501
1710
|
this.items = [];
|
|
1502
1711
|
this.added = /* @__PURE__ */ new Set();
|
|
1503
1712
|
this.removed = /* @__PURE__ */ new Set();
|
|
1713
|
+
hideInternal(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
|
|
1504
1714
|
this.hydrateFromCache();
|
|
1505
1715
|
}
|
|
1506
1716
|
async load() {
|
|
@@ -1576,10 +1786,23 @@ var DefaultHasManyCollection = class {
|
|
|
1576
1786
|
this.items = rows.map((row) => this.createEntity(row));
|
|
1577
1787
|
this.loaded = true;
|
|
1578
1788
|
}
|
|
1789
|
+
toJSON() {
|
|
1790
|
+
return this.items;
|
|
1791
|
+
}
|
|
1579
1792
|
};
|
|
1580
1793
|
|
|
1581
1794
|
// src/orm/relations/belongs-to.ts
|
|
1582
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
|
+
};
|
|
1583
1806
|
var DefaultBelongsToReference = class {
|
|
1584
1807
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, targetKey) {
|
|
1585
1808
|
this.ctx = ctx;
|
|
@@ -1593,6 +1816,7 @@ var DefaultBelongsToReference = class {
|
|
|
1593
1816
|
this.targetKey = targetKey;
|
|
1594
1817
|
this.loaded = false;
|
|
1595
1818
|
this.current = null;
|
|
1819
|
+
hideInternal2(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
|
|
1596
1820
|
this.populateFromHydrationCache();
|
|
1597
1821
|
}
|
|
1598
1822
|
async load() {
|
|
@@ -1653,10 +1877,23 @@ var DefaultBelongsToReference = class {
|
|
|
1653
1877
|
this.current = this.createEntity(row);
|
|
1654
1878
|
this.loaded = true;
|
|
1655
1879
|
}
|
|
1880
|
+
toJSON() {
|
|
1881
|
+
return this.current;
|
|
1882
|
+
}
|
|
1656
1883
|
};
|
|
1657
1884
|
|
|
1658
1885
|
// src/orm/relations/many-to-many.ts
|
|
1659
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
|
+
};
|
|
1660
1897
|
var DefaultManyToManyCollection = class {
|
|
1661
1898
|
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
1662
1899
|
this.ctx = ctx;
|
|
@@ -1670,6 +1907,7 @@ var DefaultManyToManyCollection = class {
|
|
|
1670
1907
|
this.localKey = localKey;
|
|
1671
1908
|
this.loaded = false;
|
|
1672
1909
|
this.items = [];
|
|
1910
|
+
hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
|
|
1673
1911
|
this.hydrateFromCache();
|
|
1674
1912
|
}
|
|
1675
1913
|
async load() {
|
|
@@ -1775,6 +2013,9 @@ var DefaultManyToManyCollection = class {
|
|
|
1775
2013
|
});
|
|
1776
2014
|
this.loaded = true;
|
|
1777
2015
|
}
|
|
2016
|
+
toJSON() {
|
|
2017
|
+
return this.items;
|
|
2018
|
+
}
|
|
1778
2019
|
};
|
|
1779
2020
|
|
|
1780
2021
|
// src/orm/lazy-batch.ts
|
|
@@ -2135,9 +2376,15 @@ var flattenResults = (results) => {
|
|
|
2135
2376
|
return rows;
|
|
2136
2377
|
};
|
|
2137
2378
|
async function executeHydrated(ctx, qb) {
|
|
2138
|
-
const
|
|
2379
|
+
const ast = qb.getAST();
|
|
2380
|
+
const compiled = ctx.dialect.compileSelect(ast);
|
|
2139
2381
|
const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
2140
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
|
+
}
|
|
2141
2388
|
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
2142
2389
|
return hydrated.map(
|
|
2143
2390
|
(row) => createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
@@ -2184,6 +2431,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2184
2431
|
const joinNode = createJoinNode(kind, table.name, condition);
|
|
2185
2432
|
return this.applyAst(context, (service) => service.withJoin(joinNode));
|
|
2186
2433
|
}
|
|
2434
|
+
applySetOperation(operator, query) {
|
|
2435
|
+
const subAst = this.resolveQueryNode(query);
|
|
2436
|
+
return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
|
|
2437
|
+
}
|
|
2187
2438
|
/**
|
|
2188
2439
|
* Selects specific columns for the query
|
|
2189
2440
|
* @param columns - Record of column definitions, function nodes, case expressions, or window functions
|
|
@@ -2372,6 +2623,38 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
2372
2623
|
const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
|
|
2373
2624
|
return this.clone(nextContext);
|
|
2374
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
|
+
}
|
|
2375
2658
|
/**
|
|
2376
2659
|
* Adds a WHERE EXISTS condition to the query
|
|
2377
2660
|
* @param subquery - Subquery to check for existence
|