metal-orm 1.1.11 → 1.1.16
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 +758 -769
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +100 -96
- package/scripts/generate-entities/schema.mjs +122 -122
- package/scripts/generate-entities/tree-detection.mjs +92 -92
- package/scripts/inflection/pt-br.mjs +468 -468
- package/scripts/run-eslint.mjs +33 -34
- package/scripts/show-sql-seed.sql +102 -0
- package/scripts/show-sql.mjs +321 -325
- package/src/codegen/naming-strategy.ts +54 -54
- package/src/codegen/typescript.ts +482 -482
- package/src/core/ast/adapters.ts +1 -1
- package/src/core/ast/builders.ts +123 -123
- package/src/core/ast/expression-builders.ts +745 -745
- package/src/core/ast/expression-nodes.ts +304 -304
- package/src/core/ast/expression-visitor.ts +212 -212
- package/src/core/ast/expression.ts +20 -20
- package/src/core/ast/helpers.ts +14 -14
- package/src/core/ast/join.ts +18 -18
- package/src/core/ast/query.ts +200 -200
- package/src/core/ddl/dialects/base-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +1 -1
- package/src/core/ddl/dialects/render-reference.test.ts +70 -70
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +1 -1
- package/src/core/ddl/introspect/catalogs/index.ts +5 -5
- package/src/core/ddl/introspect/catalogs/postgres.ts +146 -146
- package/src/core/ddl/introspect/context.ts +15 -15
- package/src/core/ddl/introspect/functions/postgres.ts +35 -35
- package/src/core/ddl/introspect/registry.ts +40 -40
- package/src/core/ddl/introspect/run-select.ts +35 -35
- package/src/core/ddl/introspect/utils.ts +56 -56
- package/src/core/ddl/naming-strategy.ts +16 -16
- package/src/core/ddl/schema-dialect.ts +56 -56
- package/src/core/ddl/schema-diff.ts +234 -234
- package/src/core/ddl/schema-generator.ts +1 -1
- package/src/core/ddl/schema-introspect.ts +25 -25
- package/src/core/ddl/sql-writing.ts +171 -171
- package/src/core/dialect/abstract.ts +541 -541
- package/src/core/dialect/base/cte-compiler.ts +33 -33
- package/src/core/dialect/base/function-table-formatter.ts +103 -103
- package/src/core/dialect/base/groupby-compiler.ts +21 -21
- package/src/core/dialect/base/join-compiler.ts +25 -25
- package/src/core/dialect/base/orderby-compiler.ts +35 -35
- package/src/core/dialect/base/pagination-strategy.ts +32 -32
- package/src/core/dialect/base/returning-strategy.ts +57 -57
- package/src/core/dialect/base/sql-dialect.ts +294 -294
- package/src/core/dialect/dialect-factory.ts +91 -91
- package/src/core/dialect/mssql/functions.ts +120 -120
- package/src/core/dialect/mssql/index.ts +317 -317
- package/src/core/dialect/mysql/functions.ts +103 -103
- package/src/core/dialect/mysql/index.ts +105 -105
- package/src/core/dialect/postgres/functions.ts +108 -108
- package/src/core/dialect/postgres/index.ts +91 -91
- package/src/core/dialect/sqlite/functions.ts +129 -129
- package/src/core/dialect/sqlite/index.ts +67 -67
- package/src/core/driver/database-driver.ts +19 -19
- package/src/core/driver/mssql-driver.ts +23 -23
- package/src/core/driver/mysql-driver.ts +23 -23
- package/src/core/driver/postgres-driver.ts +23 -23
- package/src/core/driver/sqlite-driver.ts +23 -23
- package/src/core/execution/db-executor.ts +92 -92
- package/src/core/execution/executors/better-sqlite3-executor.ts +90 -90
- package/src/core/execution/executors/mssql-executor.ts +184 -184
- package/src/core/execution/executors/mysql-executor.ts +88 -88
- package/src/core/execution/executors/postgres-executor.ts +26 -26
- package/src/core/execution/executors/sqlite-executor.ts +42 -42
- package/src/core/execution/pooling/pool-types.ts +30 -30
- package/src/core/execution/pooling/pool.ts +275 -275
- package/src/core/functions/array.ts +35 -35
- package/src/core/functions/control-flow.ts +79 -79
- package/src/core/functions/datetime.ts +237 -237
- package/src/core/functions/json.ts +70 -70
- package/src/core/functions/numeric.ts +297 -297
- package/src/core/functions/text.ts +334 -334
- package/src/core/functions/types.ts +33 -33
- package/src/core/hydration/types.ts +51 -51
- package/src/core/sql/sql-operator-config.ts +39 -39
- package/src/core/sql/sql.ts +152 -152
- package/src/decorators/column-decorator.ts +87 -87
- package/src/decorators/decorator-metadata.ts +143 -143
- package/src/decorators/entity.ts +93 -93
- package/src/decorators/index.ts +39 -39
- package/src/decorators/relations.ts +231 -231
- package/src/decorators/transformers/built-in/string-transformers.ts +231 -231
- package/src/decorators/transformers/transformer-decorators.ts +134 -134
- package/src/decorators/transformers/transformer-executor.ts +171 -171
- package/src/decorators/transformers/transformer-metadata.ts +73 -73
- package/src/decorators/validators/built-in/br-cep-validator.ts +55 -55
- package/src/decorators/validators/built-in/br-cnpj-validator.ts +105 -105
- package/src/decorators/validators/built-in/br-cpf-validator.ts +103 -103
- package/src/decorators/validators/country-validator-registry.ts +64 -64
- package/src/decorators/validators/country-validators-decorators.ts +203 -203
- package/src/decorators/validators/country-validators.ts +105 -105
- package/src/decorators/validators/validator-metadata.ts +59 -59
- package/src/dto/dto-types.ts +226 -226
- package/src/dto/filter-types.ts +141 -141
- package/src/dto/index.ts +95 -95
- package/src/dto/openapi/generators/base.ts +29 -29
- package/src/dto/openapi/generators/column.ts +37 -37
- package/src/dto/openapi/generators/dto.ts +91 -91
- package/src/dto/openapi/generators/filter.ts +75 -75
- package/src/dto/openapi/generators/nested-dto.ts +618 -618
- package/src/dto/openapi/generators/pagination.ts +111 -111
- package/src/dto/openapi/generators/relation-filter.ts +228 -228
- package/src/dto/openapi/index.ts +12 -12
- package/src/dto/openapi/types.ts +101 -101
- package/src/dto/openapi/utilities.ts +95 -95
- package/src/dto/pagination-utils.ts +150 -150
- package/src/dto/transform.ts +197 -197
- package/src/index.ts +86 -86
- package/src/orm/als.ts +58 -58
- package/src/orm/domain-event-bus.ts +119 -119
- package/src/orm/entity-context.ts +86 -86
- package/src/orm/entity-materializer.ts +159 -159
- package/src/orm/entity-meta.ts +97 -97
- package/src/orm/entity-registry.ts +39 -39
- package/src/orm/entity-relation-cache.ts +39 -39
- package/src/orm/execute.ts +194 -194
- package/src/orm/execution-context.ts +18 -18
- package/src/orm/hydration-context.ts +26 -26
- package/src/orm/hydration.ts +112 -112
- package/src/orm/identity-map.ts +61 -61
- package/src/orm/interceptor-pipeline.ts +16 -16
- package/src/orm/lazy-batch/belongs-to-many.ts +119 -119
- package/src/orm/lazy-batch/belongs-to.ts +108 -108
- package/src/orm/lazy-batch/has-many.ts +69 -69
- package/src/orm/lazy-batch/has-one.ts +68 -68
- package/src/orm/lazy-batch/shared.ts +125 -125
- package/src/orm/orm-session.ts +620 -620
- package/src/orm/orm.ts +34 -34
- package/src/orm/pooled-executor-factory.ts +121 -121
- package/src/orm/query-logger.ts +40 -40
- package/src/orm/relation-change-processor.ts +420 -420
- package/src/orm/relations/belongs-to.ts +143 -143
- package/src/orm/relations/has-many.ts +204 -204
- package/src/orm/relations/has-one.ts +174 -174
- package/src/orm/relations/many-to-many.ts +288 -288
- package/src/orm/relations/morph-many.ts +156 -156
- package/src/orm/relations/morph-one.ts +151 -151
- package/src/orm/relations/morph-to.ts +162 -162
- package/src/orm/runtime-types.ts +92 -92
- package/src/orm/save-graph.ts +561 -561
- package/src/orm/transaction-runner.ts +24 -24
- package/src/orm/unit-of-work.ts +426 -426
- package/src/query/index.ts +69 -69
- package/src/query/target.ts +46 -46
- package/src/query-builder/column-selector.ts +81 -81
- package/src/query-builder/delete.ts +141 -141
- package/src/query-builder/insert-query-state.ts +149 -149
- package/src/query-builder/insert.ts +117 -117
- package/src/query-builder/query-ast-service.ts +287 -287
- package/src/query-builder/query-resolution.ts +78 -78
- package/src/query-builder/raw-column-parser.ts +38 -38
- package/src/query-builder/relation-filter-utils.ts +153 -153
- package/src/query-builder/relation-join-planner.ts +10 -10
- package/src/query-builder/relation-manager.ts +87 -87
- package/src/query-builder/relation-projection-helper.ts +102 -102
- package/src/query-builder/relation-types.ts +9 -9
- package/src/query-builder/relation-utils.ts +15 -15
- package/src/query-builder/select/cte-facet.ts +40 -40
- package/src/query-builder/select/from-facet.ts +80 -80
- package/src/query-builder/select/join-facet.ts +62 -62
- package/src/query-builder/select/predicate-facet.ts +104 -104
- package/src/query-builder/select/projection-facet.ts +70 -70
- package/src/query-builder/select/relation-facet.ts +81 -81
- package/src/query-builder/select/setop-facet.ts +36 -36
- package/src/query-builder/select-helpers.ts +1 -1
- package/src/query-builder/select-query-builder-deps.ts +130 -130
- package/src/query-builder/select-query-state.ts +207 -207
- package/src/query-builder/update.ts +151 -151
- package/src/schema/column-types.ts +322 -322
- package/src/schema/table-guards.ts +37 -37
- package/src/schema/table.ts +281 -281
- package/src/tree/index.ts +13 -13
- package/src/tree/nested-set-strategy.ts +545 -545
- package/src/tree/tree-decorator.ts +397 -397
- package/src/tree/tree-manager.ts +754 -754
- package/src/tree/tree-query.ts +427 -427
- package/src/tree/tree-types.ts +299 -299
package/src/orm/hydration.ts
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
import { HydrationPlan, HydrationRelationPlan } from '../core/hydration/types.js';
|
|
2
|
-
import { RelationKinds } from '../schema/relation.js';
|
|
3
|
-
import { isRelationAlias, makeRelationAlias } from '../query-builder/relation-alias.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Hydrates query results according to a hydration plan
|
|
7
|
-
* @param rows - Raw database rows
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Hydrates query results according to a hydration plan
|
|
11
|
-
* @param rows - Raw database rows
|
|
12
|
-
* @param plan - Hydration plan
|
|
13
|
-
* @returns Hydrated result objects with nested relations
|
|
14
|
-
*/
|
|
15
|
-
export const hydrateRows = (rows: Record<string, unknown>[], plan?: HydrationPlan): Record<string, unknown>[] => {
|
|
16
|
-
if (!plan || !rows.length) return rows;
|
|
17
|
-
|
|
18
|
-
const rootMap = new Map<unknown, Record<string, unknown>>();
|
|
19
|
-
const relationIndex = new Map<unknown, Record<string, Set<unknown>>>();
|
|
20
|
-
|
|
21
|
-
const getOrCreateParent = (row: Record<string, unknown>) => {
|
|
22
|
-
const rootId = row[plan.rootPrimaryKey];
|
|
23
|
-
if (rootId === undefined) return undefined;
|
|
24
|
-
|
|
25
|
-
if (!rootMap.has(rootId)) {
|
|
26
|
-
rootMap.set(rootId, createBaseRow(row, plan));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return rootMap.get(rootId);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const getRelationSeenSet = (rootId: unknown, relationName: string): Set<unknown> => {
|
|
33
|
-
let byRelation = relationIndex.get(rootId);
|
|
34
|
-
if (!byRelation) {
|
|
35
|
-
byRelation = {};
|
|
36
|
-
relationIndex.set(rootId, byRelation);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let seen = byRelation[relationName];
|
|
40
|
-
if (!seen) {
|
|
41
|
-
seen = new Set<unknown>();
|
|
42
|
-
byRelation[relationName] = seen;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return seen;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const hasRelations = plan.relations.length > 0;
|
|
49
|
-
|
|
50
|
-
for (const row of rows) {
|
|
51
|
-
const rootId = row[plan.rootPrimaryKey];
|
|
52
|
-
|
|
53
|
-
if (rootId === undefined || rootId === null) {
|
|
54
|
-
if (!hasRelations) {
|
|
55
|
-
rootMap.set(Symbol(), createBaseRow(row, plan));
|
|
56
|
-
}
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const parent = getOrCreateParent(row);
|
|
61
|
-
if (!parent) continue;
|
|
62
|
-
|
|
63
|
-
for (const rel of plan.relations) {
|
|
64
|
-
const childPkKey = makeRelationAlias(rel.aliasPrefix, rel.targetPrimaryKey);
|
|
65
|
-
const childPk = row[childPkKey];
|
|
66
|
-
if (childPk === null || childPk === undefined) continue;
|
|
67
|
-
|
|
68
|
-
const seen = getRelationSeenSet(rootId, rel.name);
|
|
69
|
-
if (seen.has(childPk)) continue;
|
|
70
|
-
seen.add(childPk);
|
|
71
|
-
|
|
72
|
-
if (rel.type === RelationKinds.HasOne) {
|
|
73
|
-
if (!parent[rel.name]) {
|
|
74
|
-
parent[rel.name] = buildChild(row, rel);
|
|
75
|
-
}
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const bucket = parent[rel.name] as unknown[];
|
|
80
|
-
bucket.push(buildChild(row, rel));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return Array.from(rootMap.values());
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const createBaseRow = (row: Record<string, unknown>, plan: HydrationPlan): Record<string, unknown> => {
|
|
88
|
-
const base: Record<string, unknown> = {};
|
|
89
|
-
const baseKeys = plan.rootColumns.length
|
|
90
|
-
? plan.rootColumns
|
|
91
|
-
: Object.keys(row).filter(k => !isRelationAlias(k));
|
|
92
|
-
|
|
93
|
-
for (const key of baseKeys) {
|
|
94
|
-
base[key] = row[key];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
for (const rel of plan.relations) {
|
|
98
|
-
base[rel.name] = rel.type === RelationKinds.HasOne ? null : [];
|
|
99
|
-
}
|
|
100
|
-
return base;
|
|
101
|
-
};
|
|
102
|
-
|
|
1
|
+
import { HydrationPlan, HydrationRelationPlan } from '../core/hydration/types.js';
|
|
2
|
+
import { RelationKinds } from '../schema/relation.js';
|
|
3
|
+
import { isRelationAlias, makeRelationAlias } from '../query-builder/relation-alias.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hydrates query results according to a hydration plan
|
|
7
|
+
* @param rows - Raw database rows
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hydrates query results according to a hydration plan
|
|
11
|
+
* @param rows - Raw database rows
|
|
12
|
+
* @param plan - Hydration plan
|
|
13
|
+
* @returns Hydrated result objects with nested relations
|
|
14
|
+
*/
|
|
15
|
+
export const hydrateRows = (rows: Record<string, unknown>[], plan?: HydrationPlan): Record<string, unknown>[] => {
|
|
16
|
+
if (!plan || !rows.length) return rows;
|
|
17
|
+
|
|
18
|
+
const rootMap = new Map<unknown, Record<string, unknown>>();
|
|
19
|
+
const relationIndex = new Map<unknown, Record<string, Set<unknown>>>();
|
|
20
|
+
|
|
21
|
+
const getOrCreateParent = (row: Record<string, unknown>) => {
|
|
22
|
+
const rootId = row[plan.rootPrimaryKey];
|
|
23
|
+
if (rootId === undefined) return undefined;
|
|
24
|
+
|
|
25
|
+
if (!rootMap.has(rootId)) {
|
|
26
|
+
rootMap.set(rootId, createBaseRow(row, plan));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return rootMap.get(rootId);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getRelationSeenSet = (rootId: unknown, relationName: string): Set<unknown> => {
|
|
33
|
+
let byRelation = relationIndex.get(rootId);
|
|
34
|
+
if (!byRelation) {
|
|
35
|
+
byRelation = {};
|
|
36
|
+
relationIndex.set(rootId, byRelation);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let seen = byRelation[relationName];
|
|
40
|
+
if (!seen) {
|
|
41
|
+
seen = new Set<unknown>();
|
|
42
|
+
byRelation[relationName] = seen;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return seen;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const hasRelations = plan.relations.length > 0;
|
|
49
|
+
|
|
50
|
+
for (const row of rows) {
|
|
51
|
+
const rootId = row[plan.rootPrimaryKey];
|
|
52
|
+
|
|
53
|
+
if (rootId === undefined || rootId === null) {
|
|
54
|
+
if (!hasRelations) {
|
|
55
|
+
rootMap.set(Symbol(), createBaseRow(row, plan));
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const parent = getOrCreateParent(row);
|
|
61
|
+
if (!parent) continue;
|
|
62
|
+
|
|
63
|
+
for (const rel of plan.relations) {
|
|
64
|
+
const childPkKey = makeRelationAlias(rel.aliasPrefix, rel.targetPrimaryKey);
|
|
65
|
+
const childPk = row[childPkKey];
|
|
66
|
+
if (childPk === null || childPk === undefined) continue;
|
|
67
|
+
|
|
68
|
+
const seen = getRelationSeenSet(rootId, rel.name);
|
|
69
|
+
if (seen.has(childPk)) continue;
|
|
70
|
+
seen.add(childPk);
|
|
71
|
+
|
|
72
|
+
if (rel.type === RelationKinds.HasOne) {
|
|
73
|
+
if (!parent[rel.name]) {
|
|
74
|
+
parent[rel.name] = buildChild(row, rel);
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const bucket = parent[rel.name] as unknown[];
|
|
80
|
+
bucket.push(buildChild(row, rel));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return Array.from(rootMap.values());
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const createBaseRow = (row: Record<string, unknown>, plan: HydrationPlan): Record<string, unknown> => {
|
|
88
|
+
const base: Record<string, unknown> = {};
|
|
89
|
+
const baseKeys = plan.rootColumns.length
|
|
90
|
+
? plan.rootColumns
|
|
91
|
+
: Object.keys(row).filter(k => !isRelationAlias(k));
|
|
92
|
+
|
|
93
|
+
for (const key of baseKeys) {
|
|
94
|
+
base[key] = row[key];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const rel of plan.relations) {
|
|
98
|
+
base[rel.name] = rel.type === RelationKinds.HasOne ? null : [];
|
|
99
|
+
}
|
|
100
|
+
return base;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
103
|
const buildChild = (row: Record<string, unknown>, rel: HydrationRelationPlan): Record<string, unknown> => {
|
|
104
104
|
const child: Record<string, unknown> = {};
|
|
105
105
|
for (const col of rel.columns) {
|
|
@@ -127,13 +127,13 @@ const mergePivotIntoChild = (child: Record<string, unknown>, pivot: Record<strin
|
|
|
127
127
|
|
|
128
128
|
const buildPivot = (row: Record<string, unknown>, rel: HydrationRelationPlan): Record<string, unknown> | undefined => {
|
|
129
129
|
if (!rel.pivot) return undefined;
|
|
130
|
-
|
|
131
|
-
const pivot: Record<string, unknown> = {};
|
|
132
|
-
for (const col of rel.pivot.columns) {
|
|
133
|
-
const key = makeRelationAlias(rel.pivot.aliasPrefix, col);
|
|
134
|
-
pivot[col] = row[key];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const hasValue = Object.values(pivot).some(v => v !== null && v !== undefined);
|
|
138
|
-
return hasValue ? pivot : undefined;
|
|
139
|
-
};
|
|
130
|
+
|
|
131
|
+
const pivot: Record<string, unknown> = {};
|
|
132
|
+
for (const col of rel.pivot.columns) {
|
|
133
|
+
const key = makeRelationAlias(rel.pivot.aliasPrefix, col);
|
|
134
|
+
pivot[col] = row[key];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const hasValue = Object.values(pivot).some(v => v !== null && v !== undefined);
|
|
138
|
+
return hasValue ? pivot : undefined;
|
|
139
|
+
};
|
package/src/orm/identity-map.ts
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
import type { TableDef } from '../schema/table.js';
|
|
2
|
-
import type { TrackedEntity } from './runtime-types.js';
|
|
3
|
-
import type { PrimaryKey } from './entity-context.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Simple identity map for tracking entities within a session.
|
|
7
|
-
* Ensures that the same database record is represented by a single entity instance.
|
|
8
|
-
*/
|
|
9
|
-
export class IdentityMap {
|
|
10
|
-
private readonly buckets = new Map<string, Map<string, TrackedEntity>>();
|
|
11
|
-
|
|
12
|
-
get bucketsMap(): Map<string, Map<string, TrackedEntity>> {
|
|
13
|
-
return this.buckets;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Retrieves an entity from the identity map if it exists.
|
|
18
|
-
* @param table The table definition of the entity.
|
|
19
|
-
* @param pk The primary key value.
|
|
20
|
-
* @returns The entity instance if found, undefined otherwise.
|
|
21
|
-
*/
|
|
22
|
-
getEntity(table: TableDef, pk: PrimaryKey): object | undefined {
|
|
23
|
-
const bucket = this.buckets.get(table.name);
|
|
24
|
-
return bucket?.get(this.toIdentityKey(pk))?.entity;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Registers a tracked entity in the identity map.
|
|
29
|
-
* @param tracked The tracked entity metadata and instance.
|
|
30
|
-
*/
|
|
31
|
-
register(tracked: TrackedEntity): void {
|
|
32
|
-
if (tracked.pk == null) return;
|
|
33
|
-
const bucket = this.buckets.get(tracked.table.name) ?? new Map<string, TrackedEntity>();
|
|
34
|
-
bucket.set(this.toIdentityKey(tracked.pk), tracked);
|
|
35
|
-
this.buckets.set(tracked.table.name, bucket);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
remove(tracked: TrackedEntity): void {
|
|
39
|
-
if (tracked.pk == null) return;
|
|
40
|
-
const bucket = this.buckets.get(tracked.table.name);
|
|
41
|
-
bucket?.delete(this.toIdentityKey(tracked.pk));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Returns all tracked entities for a specific table.
|
|
46
|
-
* @param table The table definition.
|
|
47
|
-
* @returns Array of tracked entities.
|
|
48
|
-
*/
|
|
49
|
-
getEntitiesForTable(table: TableDef): TrackedEntity[] {
|
|
50
|
-
const bucket = this.buckets.get(table.name);
|
|
51
|
-
return bucket ? Array.from(bucket.values()) : [];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
clear(): void {
|
|
55
|
-
this.buckets.clear();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private toIdentityKey(pk: PrimaryKey): string {
|
|
59
|
-
return String(pk);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
1
|
+
import type { TableDef } from '../schema/table.js';
|
|
2
|
+
import type { TrackedEntity } from './runtime-types.js';
|
|
3
|
+
import type { PrimaryKey } from './entity-context.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Simple identity map for tracking entities within a session.
|
|
7
|
+
* Ensures that the same database record is represented by a single entity instance.
|
|
8
|
+
*/
|
|
9
|
+
export class IdentityMap {
|
|
10
|
+
private readonly buckets = new Map<string, Map<string, TrackedEntity>>();
|
|
11
|
+
|
|
12
|
+
get bucketsMap(): Map<string, Map<string, TrackedEntity>> {
|
|
13
|
+
return this.buckets;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Retrieves an entity from the identity map if it exists.
|
|
18
|
+
* @param table The table definition of the entity.
|
|
19
|
+
* @param pk The primary key value.
|
|
20
|
+
* @returns The entity instance if found, undefined otherwise.
|
|
21
|
+
*/
|
|
22
|
+
getEntity(table: TableDef, pk: PrimaryKey): object | undefined {
|
|
23
|
+
const bucket = this.buckets.get(table.name);
|
|
24
|
+
return bucket?.get(this.toIdentityKey(pk))?.entity;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Registers a tracked entity in the identity map.
|
|
29
|
+
* @param tracked The tracked entity metadata and instance.
|
|
30
|
+
*/
|
|
31
|
+
register(tracked: TrackedEntity): void {
|
|
32
|
+
if (tracked.pk == null) return;
|
|
33
|
+
const bucket = this.buckets.get(tracked.table.name) ?? new Map<string, TrackedEntity>();
|
|
34
|
+
bucket.set(this.toIdentityKey(tracked.pk), tracked);
|
|
35
|
+
this.buckets.set(tracked.table.name, bucket);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
remove(tracked: TrackedEntity): void {
|
|
39
|
+
if (tracked.pk == null) return;
|
|
40
|
+
const bucket = this.buckets.get(tracked.table.name);
|
|
41
|
+
bucket?.delete(this.toIdentityKey(tracked.pk));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns all tracked entities for a specific table.
|
|
46
|
+
* @param table The table definition.
|
|
47
|
+
* @returns Array of tracked entities.
|
|
48
|
+
*/
|
|
49
|
+
getEntitiesForTable(table: TableDef): TrackedEntity[] {
|
|
50
|
+
const bucket = this.buckets.get(table.name);
|
|
51
|
+
return bucket ? Array.from(bucket.values()) : [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
clear(): void {
|
|
55
|
+
this.buckets.clear();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private toIdentityKey(pk: PrimaryKey): string {
|
|
59
|
+
return String(pk);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import type { DbExecutor, ExecutionPayload } from '../core/execution/db-executor.js';
|
|
2
2
|
import { toExecutionPayload } from '../core/execution/db-executor.js';
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
export interface QueryContext {
|
|
5
|
-
sql: string;
|
|
6
|
-
params: unknown[];
|
|
7
|
-
// maybe metadata like entity type, operation type, etc.
|
|
5
|
+
sql: string;
|
|
6
|
+
params: unknown[];
|
|
7
|
+
// maybe metadata like entity type, operation type, etc.
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export type QueryInterceptor = (
|
|
11
11
|
ctx: QueryContext,
|
|
12
12
|
next: () => Promise<ExecutionPayload>
|
|
13
13
|
) => Promise<ExecutionPayload>;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Pipeline for query interceptors.
|
|
17
|
-
* Interceptors can wrap query execution to add logging, tracing, caching, etc.
|
|
18
|
-
*/
|
|
19
|
-
export class InterceptorPipeline {
|
|
20
|
-
private interceptors: QueryInterceptor[] = [];
|
|
21
|
-
|
|
22
|
-
use(interceptor: QueryInterceptor) {
|
|
23
|
-
this.interceptors.push(interceptor);
|
|
24
|
-
}
|
|
25
|
-
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Pipeline for query interceptors.
|
|
17
|
+
* Interceptors can wrap query execution to add logging, tracing, caching, etc.
|
|
18
|
+
*/
|
|
19
|
+
export class InterceptorPipeline {
|
|
20
|
+
private interceptors: QueryInterceptor[] = [];
|
|
21
|
+
|
|
22
|
+
use(interceptor: QueryInterceptor) {
|
|
23
|
+
this.interceptors.push(interceptor);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
26
|
async run(ctx: QueryContext, executor: DbExecutor): Promise<ExecutionPayload> {
|
|
27
27
|
let i = 0;
|
|
28
28
|
const dispatch = async (): Promise<ExecutionPayload> => {
|
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
import { TableDef } from '../../schema/table.js';
|
|
2
|
-
import { BelongsToManyRelation } from '../../schema/relation.js';
|
|
3
|
-
import { findPrimaryKey } from '../../query-builder/hydration-planner.js';
|
|
4
|
-
import { RelationIncludeOptions } from '../../query-builder/relation-types.js';
|
|
5
|
-
import { buildDefaultPivotColumns } from '../../query-builder/relation-utils.js';
|
|
6
|
-
import { EntityContext } from '../entity-context.js';
|
|
7
|
-
import {
|
|
8
|
-
buildColumnSelection,
|
|
9
|
-
collectKeysFromRoots,
|
|
10
|
-
fetchRowsForKeys,
|
|
11
|
-
filterRow,
|
|
12
|
-
groupRowsByUnique,
|
|
13
|
-
hasColumns,
|
|
14
|
-
toKey,
|
|
15
|
-
Rows
|
|
16
|
-
} from './shared.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Loads related entities for a belongs-to-many relation in batch, including pivot data.
|
|
20
|
-
* @param ctx - The entity context.
|
|
21
|
-
* @param rootTable - The root table of the relation.
|
|
22
|
-
* @param _relationName - The name of the relation (unused).
|
|
23
|
-
* @param relation - The belongs-to-many relation definition.
|
|
24
|
-
* @returns A promise resolving to a map of root keys to arrays of related rows with pivot data.
|
|
25
|
-
*/
|
|
26
|
-
export const loadBelongsToManyRelation = async (
|
|
27
|
-
ctx: EntityContext,
|
|
28
|
-
rootTable: TableDef,
|
|
29
|
-
relationName: string,
|
|
30
|
-
relation: BelongsToManyRelation,
|
|
31
|
-
options?: RelationIncludeOptions
|
|
32
|
-
): Promise<Map<string, Rows>> => {
|
|
33
|
-
const rootKey = relation.localKey || findPrimaryKey(rootTable);
|
|
34
|
-
const roots = ctx.getEntitiesForTable(rootTable);
|
|
35
|
-
const rootIds = collectKeysFromRoots(roots, rootKey);
|
|
36
|
-
|
|
37
|
-
if (!rootIds.size) {
|
|
38
|
-
return new Map();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const pivotColumn = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
|
|
42
|
-
if (!pivotColumn) return new Map();
|
|
43
|
-
|
|
44
|
-
const pivotColumnsRequested = hasColumns(options?.pivot?.columns) ? [...options!.pivot!.columns] : undefined;
|
|
45
|
-
const useIncludeDefaults = options !== undefined;
|
|
46
|
-
let pivotSelectedColumns: string[];
|
|
47
|
-
if (pivotColumnsRequested) {
|
|
48
|
-
pivotSelectedColumns = [...pivotColumnsRequested];
|
|
49
|
-
} else if (useIncludeDefaults) {
|
|
50
|
-
const pivotPk = relation.pivotPrimaryKey || findPrimaryKey(relation.pivotTable);
|
|
51
|
-
pivotSelectedColumns = relation.defaultPivotColumns ?? buildDefaultPivotColumns(relation, pivotPk);
|
|
52
|
-
} else {
|
|
53
|
-
pivotSelectedColumns = Object.keys(relation.pivotTable.columns);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const pivotQueryColumns = new Set(pivotSelectedColumns);
|
|
57
|
-
pivotQueryColumns.add(relation.pivotForeignKeyToRoot);
|
|
58
|
-
pivotQueryColumns.add(relation.pivotForeignKeyToTarget);
|
|
59
|
-
|
|
60
|
-
const pivotSelection = buildColumnSelection(
|
|
61
|
-
relation.pivotTable,
|
|
62
|
-
Array.from(pivotQueryColumns),
|
|
63
|
-
column => `Column '${column}' not found on pivot table '${relation.pivotTable.name}'`
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const pivotRows = await fetchRowsForKeys(ctx, relation.pivotTable, pivotColumn, rootIds, pivotSelection);
|
|
67
|
-
const rootLookup = new Map<string, { targetId: unknown; pivot: Record<string, unknown> }[]>();
|
|
68
|
-
const targetIds = new Set<unknown>();
|
|
69
|
-
const pivotVisibleColumns = new Set(pivotSelectedColumns);
|
|
70
|
-
|
|
71
|
-
for (const pivot of pivotRows) {
|
|
72
|
-
const rootValue = pivot[relation.pivotForeignKeyToRoot];
|
|
73
|
-
const targetValue = pivot[relation.pivotForeignKeyToTarget];
|
|
74
|
-
if (rootValue === null || rootValue === undefined || targetValue === null || targetValue === undefined) {
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
const bucket = rootLookup.get(toKey(rootValue)) ?? [];
|
|
78
|
-
bucket.push({
|
|
79
|
-
targetId: targetValue,
|
|
80
|
-
pivot: pivotVisibleColumns.size ? filterRow(pivot, pivotVisibleColumns) : {}
|
|
81
|
-
});
|
|
82
|
-
rootLookup.set(toKey(rootValue), bucket);
|
|
83
|
-
targetIds.add(targetValue);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!targetIds.size) {
|
|
87
|
-
return new Map();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
91
|
-
const targetPkColumn = relation.target.columns[targetKey];
|
|
92
|
-
if (!targetPkColumn) return new Map();
|
|
93
|
-
|
|
94
|
-
const targetRequestedColumns = hasColumns(options?.columns) ? [...options!.columns] : undefined;
|
|
95
|
-
const targetSelectedColumns = targetRequestedColumns
|
|
96
|
-
? [...targetRequestedColumns]
|
|
97
|
-
: Object.keys(relation.target.columns);
|
|
98
|
-
if (!targetSelectedColumns.includes(targetKey)) {
|
|
99
|
-
targetSelectedColumns.push(targetKey);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const targetSelection = buildColumnSelection(
|
|
103
|
-
relation.target,
|
|
104
|
-
targetSelectedColumns,
|
|
105
|
-
column => `Column '${column}' not found on relation '${relationName}'`
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
const targetRows = await fetchRowsForKeys(
|
|
109
|
-
ctx,
|
|
110
|
-
relation.target,
|
|
111
|
-
targetPkColumn,
|
|
112
|
-
targetIds,
|
|
113
|
-
targetSelection,
|
|
114
|
-
options?.filter
|
|
115
|
-
);
|
|
116
|
-
const targetMap = groupRowsByUnique(targetRows, targetKey);
|
|
117
|
-
const targetVisibleColumns = new Set(targetSelectedColumns);
|
|
118
|
-
const result = new Map<string, Rows>();
|
|
119
|
-
|
|
1
|
+
import { TableDef } from '../../schema/table.js';
|
|
2
|
+
import { BelongsToManyRelation } from '../../schema/relation.js';
|
|
3
|
+
import { findPrimaryKey } from '../../query-builder/hydration-planner.js';
|
|
4
|
+
import { RelationIncludeOptions } from '../../query-builder/relation-types.js';
|
|
5
|
+
import { buildDefaultPivotColumns } from '../../query-builder/relation-utils.js';
|
|
6
|
+
import { EntityContext } from '../entity-context.js';
|
|
7
|
+
import {
|
|
8
|
+
buildColumnSelection,
|
|
9
|
+
collectKeysFromRoots,
|
|
10
|
+
fetchRowsForKeys,
|
|
11
|
+
filterRow,
|
|
12
|
+
groupRowsByUnique,
|
|
13
|
+
hasColumns,
|
|
14
|
+
toKey,
|
|
15
|
+
Rows
|
|
16
|
+
} from './shared.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Loads related entities for a belongs-to-many relation in batch, including pivot data.
|
|
20
|
+
* @param ctx - The entity context.
|
|
21
|
+
* @param rootTable - The root table of the relation.
|
|
22
|
+
* @param _relationName - The name of the relation (unused).
|
|
23
|
+
* @param relation - The belongs-to-many relation definition.
|
|
24
|
+
* @returns A promise resolving to a map of root keys to arrays of related rows with pivot data.
|
|
25
|
+
*/
|
|
26
|
+
export const loadBelongsToManyRelation = async (
|
|
27
|
+
ctx: EntityContext,
|
|
28
|
+
rootTable: TableDef,
|
|
29
|
+
relationName: string,
|
|
30
|
+
relation: BelongsToManyRelation,
|
|
31
|
+
options?: RelationIncludeOptions
|
|
32
|
+
): Promise<Map<string, Rows>> => {
|
|
33
|
+
const rootKey = relation.localKey || findPrimaryKey(rootTable);
|
|
34
|
+
const roots = ctx.getEntitiesForTable(rootTable);
|
|
35
|
+
const rootIds = collectKeysFromRoots(roots, rootKey);
|
|
36
|
+
|
|
37
|
+
if (!rootIds.size) {
|
|
38
|
+
return new Map();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const pivotColumn = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
|
|
42
|
+
if (!pivotColumn) return new Map();
|
|
43
|
+
|
|
44
|
+
const pivotColumnsRequested = hasColumns(options?.pivot?.columns) ? [...options!.pivot!.columns] : undefined;
|
|
45
|
+
const useIncludeDefaults = options !== undefined;
|
|
46
|
+
let pivotSelectedColumns: string[];
|
|
47
|
+
if (pivotColumnsRequested) {
|
|
48
|
+
pivotSelectedColumns = [...pivotColumnsRequested];
|
|
49
|
+
} else if (useIncludeDefaults) {
|
|
50
|
+
const pivotPk = relation.pivotPrimaryKey || findPrimaryKey(relation.pivotTable);
|
|
51
|
+
pivotSelectedColumns = relation.defaultPivotColumns ?? buildDefaultPivotColumns(relation, pivotPk);
|
|
52
|
+
} else {
|
|
53
|
+
pivotSelectedColumns = Object.keys(relation.pivotTable.columns);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const pivotQueryColumns = new Set(pivotSelectedColumns);
|
|
57
|
+
pivotQueryColumns.add(relation.pivotForeignKeyToRoot);
|
|
58
|
+
pivotQueryColumns.add(relation.pivotForeignKeyToTarget);
|
|
59
|
+
|
|
60
|
+
const pivotSelection = buildColumnSelection(
|
|
61
|
+
relation.pivotTable,
|
|
62
|
+
Array.from(pivotQueryColumns),
|
|
63
|
+
column => `Column '${column}' not found on pivot table '${relation.pivotTable.name}'`
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const pivotRows = await fetchRowsForKeys(ctx, relation.pivotTable, pivotColumn, rootIds, pivotSelection);
|
|
67
|
+
const rootLookup = new Map<string, { targetId: unknown; pivot: Record<string, unknown> }[]>();
|
|
68
|
+
const targetIds = new Set<unknown>();
|
|
69
|
+
const pivotVisibleColumns = new Set(pivotSelectedColumns);
|
|
70
|
+
|
|
71
|
+
for (const pivot of pivotRows) {
|
|
72
|
+
const rootValue = pivot[relation.pivotForeignKeyToRoot];
|
|
73
|
+
const targetValue = pivot[relation.pivotForeignKeyToTarget];
|
|
74
|
+
if (rootValue === null || rootValue === undefined || targetValue === null || targetValue === undefined) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const bucket = rootLookup.get(toKey(rootValue)) ?? [];
|
|
78
|
+
bucket.push({
|
|
79
|
+
targetId: targetValue,
|
|
80
|
+
pivot: pivotVisibleColumns.size ? filterRow(pivot, pivotVisibleColumns) : {}
|
|
81
|
+
});
|
|
82
|
+
rootLookup.set(toKey(rootValue), bucket);
|
|
83
|
+
targetIds.add(targetValue);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!targetIds.size) {
|
|
87
|
+
return new Map();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
91
|
+
const targetPkColumn = relation.target.columns[targetKey];
|
|
92
|
+
if (!targetPkColumn) return new Map();
|
|
93
|
+
|
|
94
|
+
const targetRequestedColumns = hasColumns(options?.columns) ? [...options!.columns] : undefined;
|
|
95
|
+
const targetSelectedColumns = targetRequestedColumns
|
|
96
|
+
? [...targetRequestedColumns]
|
|
97
|
+
: Object.keys(relation.target.columns);
|
|
98
|
+
if (!targetSelectedColumns.includes(targetKey)) {
|
|
99
|
+
targetSelectedColumns.push(targetKey);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const targetSelection = buildColumnSelection(
|
|
103
|
+
relation.target,
|
|
104
|
+
targetSelectedColumns,
|
|
105
|
+
column => `Column '${column}' not found on relation '${relationName}'`
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const targetRows = await fetchRowsForKeys(
|
|
109
|
+
ctx,
|
|
110
|
+
relation.target,
|
|
111
|
+
targetPkColumn,
|
|
112
|
+
targetIds,
|
|
113
|
+
targetSelection,
|
|
114
|
+
options?.filter
|
|
115
|
+
);
|
|
116
|
+
const targetMap = groupRowsByUnique(targetRows, targetKey);
|
|
117
|
+
const targetVisibleColumns = new Set(targetSelectedColumns);
|
|
118
|
+
const result = new Map<string, Rows>();
|
|
119
|
+
|
|
120
120
|
for (const [rootId, entries] of rootLookup.entries()) {
|
|
121
121
|
const bucket: Rows = [];
|
|
122
122
|
for (const entry of entries) {
|