metal-orm 1.0.7 → 1.0.9
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 +133 -121
- package/dist/decorators/index.cjs +2564 -0
- package/dist/decorators/index.cjs.map +1 -0
- package/dist/decorators/index.d.cts +53 -0
- package/dist/decorators/index.d.ts +53 -0
- package/dist/decorators/index.js +2530 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/index.cjs +4227 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +701 -0
- package/dist/index.d.ts +701 -0
- package/dist/index.js +4131 -0
- package/dist/index.js.map +1 -0
- package/dist/select-654m4qy8.d.cts +1522 -0
- package/dist/select-654m4qy8.d.ts +1522 -0
- package/package.json +27 -20
- package/src/codegen/typescript.ts +405 -393
- package/src/core/ast/aggregate-functions.ts +30 -0
- package/src/core/ast/builders.ts +43 -0
- package/src/core/ast/expression-builders.ts +310 -0
- package/src/core/ast/expression-nodes.ts +211 -0
- package/src/core/ast/expression-visitor.ts +99 -0
- package/src/core/ast/expression.ts +5 -0
- package/src/{utils → core/ast}/join-node.ts +20 -20
- package/src/{ast → core/ast}/join.ts +18 -18
- package/src/{ast → core/ast}/query.ts +113 -113
- package/src/core/ast/window-functions.ts +140 -0
- package/src/{dialect → core/dialect}/abstract.ts +94 -94
- package/src/{dialect → core/dialect}/mssql/index.ts +31 -31
- package/src/{dialect → core/dialect}/mysql/index.ts +31 -31
- package/src/{dialect → core/dialect}/postgres/index.ts +45 -45
- package/src/{dialect → core/dialect}/sqlite/index.ts +45 -45
- package/src/{constants → core/sql}/sql-operator-config.ts +39 -39
- package/src/decorators/bootstrap.ts +126 -0
- package/src/decorators/column.ts +78 -0
- package/src/decorators/entity.ts +36 -0
- package/src/decorators/index.ts +4 -0
- package/src/decorators/relations.ts +107 -0
- package/src/global.d.ts +1 -0
- package/src/index.ts +22 -22
- package/src/orm/db-executor.ts +11 -0
- package/src/orm/domain-event-bus.ts +52 -0
- package/src/{runtime → orm}/entity-meta.ts +52 -52
- package/src/orm/entity-metadata.ts +140 -0
- package/src/{runtime → orm}/entity.ts +252 -252
- package/src/{runtime → orm}/execute.ts +36 -36
- package/src/{runtime → orm}/hydration.ts +103 -103
- package/src/orm/identity-map.ts +37 -0
- package/src/{runtime → orm}/lazy-batch.ts +205 -205
- package/src/orm/orm-context.ts +154 -0
- package/src/orm/relation-change-processor.ts +140 -0
- package/src/{runtime → orm}/relations/belongs-to.ts +92 -92
- package/src/{runtime → orm}/relations/has-many.ts +111 -111
- package/src/{runtime → orm}/relations/many-to-many.ts +149 -149
- package/src/orm/runtime-types.ts +39 -0
- package/src/orm/transaction-runner.ts +17 -0
- package/src/orm/unit-of-work.ts +232 -0
- package/src/{builder/operations → query-builder}/column-selector.ts +78 -78
- package/src/{builder → query-builder}/delete-query-state.ts +38 -42
- package/src/{builder → query-builder}/delete.ts +46 -57
- package/src/{builder → query-builder}/hydration-manager.ts +87 -87
- package/src/{builder → query-builder}/hydration-planner.ts +182 -182
- package/src/{builder → query-builder}/insert-query-state.ts +51 -62
- package/src/{builder → query-builder}/insert.ts +48 -59
- package/src/{builder → query-builder}/query-ast-service.ts +208 -226
- package/src/{utils → query-builder}/raw-column-parser.ts +32 -32
- package/src/{builder → query-builder}/relation-conditions.ts +112 -112
- package/src/{builder/operations → query-builder}/relation-manager.ts +82 -82
- package/src/{builder → query-builder}/relation-projection-helper.ts +101 -101
- package/src/{builder → query-builder}/relation-service.ts +284 -284
- package/src/{builder → query-builder}/relation-types.ts +21 -21
- package/src/{builder → query-builder}/relation-utils.ts +12 -12
- package/src/{builder → query-builder}/select-query-builder-deps.ts +112 -94
- package/src/{builder → query-builder}/select-query-state.ts +179 -179
- package/src/{builder → query-builder}/select.ts +78 -69
- package/src/{builder → query-builder}/update-query-state.ts +55 -59
- package/src/{builder → query-builder}/update.ts +50 -61
- package/src/schema/column.ts +25 -25
- package/src/schema/relation.ts +116 -116
- package/src/schema/table.ts +34 -34
- package/src/schema/types.ts +76 -76
- package/.github/workflows/publish-metal-orm.yml +0 -38
- package/ROADMAP.md +0 -125
- package/docs/CHANGES.md +0 -104
- package/docs/advanced-features.md +0 -176
- package/docs/api-reference.md +0 -31
- package/docs/dml-operations.md +0 -156
- package/docs/getting-started.md +0 -171
- package/docs/hydration.md +0 -115
- package/docs/index.md +0 -36
- package/docs/multi-dialect-support.md +0 -59
- package/docs/query-builder.md +0 -135
- package/docs/runtime.md +0 -105
- package/docs/schema-definition.md +0 -112
- package/metadata.json +0 -5
- package/playground/api/playground-api.ts +0 -94
- package/playground/index.html +0 -15
- package/playground/src/App.css +0 -1
- package/playground/src/App.tsx +0 -114
- package/playground/src/components/CodeDisplay.tsx +0 -43
- package/playground/src/components/QueryExecutor.tsx +0 -189
- package/playground/src/components/ResultsTable.tsx +0 -67
- package/playground/src/components/ResultsTabs.tsx +0 -105
- package/playground/src/components/ScenarioList.tsx +0 -56
- package/playground/src/components/logo.svg +0 -45
- package/playground/src/data/scenarios.ts +0 -2
- package/playground/src/main.tsx +0 -9
- package/playground/src/services/PlaygroundApiService.ts +0 -60
- package/postcss.config.cjs +0 -5
- package/sql_sql-ansi-cheatsheet-2025.md +0 -264
- package/src/ast/expression.ts +0 -658
- package/src/builder/operations/cte-manager.ts +0 -34
- package/src/builder/operations/filter-manager.ts +0 -68
- package/src/builder/operations/join-manager.ts +0 -36
- package/src/builder/operations/pagination-manager.ts +0 -36
- package/src/playground/features/playground/api/types.ts +0 -16
- package/src/playground/features/playground/clients/MockClient.ts +0 -17
- package/src/playground/features/playground/clients/SqliteClient.ts +0 -57
- package/src/playground/features/playground/common/IDatabaseClient.ts +0 -10
- package/src/playground/features/playground/data/scenarios/aggregation.ts +0 -36
- package/src/playground/features/playground/data/scenarios/basics.ts +0 -25
- package/src/playground/features/playground/data/scenarios/edge_cases.ts +0 -57
- package/src/playground/features/playground/data/scenarios/filtering.ts +0 -94
- package/src/playground/features/playground/data/scenarios/hydration.ts +0 -27
- package/src/playground/features/playground/data/scenarios/index.ts +0 -29
- package/src/playground/features/playground/data/scenarios/ordering.ts +0 -25
- package/src/playground/features/playground/data/scenarios/pagination.ts +0 -16
- package/src/playground/features/playground/data/scenarios/relationships.ts +0 -75
- package/src/playground/features/playground/data/scenarios/types.ts +0 -70
- package/src/playground/features/playground/data/schema.ts +0 -91
- package/src/playground/features/playground/data/seed.ts +0 -104
- package/src/playground/features/playground/services/QueryExecutionService.ts +0 -121
- package/src/runtime/orm-context.ts +0 -539
- package/tests/belongs-to-many.test.ts +0 -57
- package/tests/between.test.ts +0 -43
- package/tests/case-expression.test.ts +0 -58
- package/tests/complex-exists.test.ts +0 -230
- package/tests/cte.test.ts +0 -118
- package/tests/dml.test.ts +0 -206
- package/tests/exists.test.ts +0 -127
- package/tests/like.test.ts +0 -33
- package/tests/orm-runtime.test.ts +0 -254
- package/tests/postgres.test.ts +0 -30
- package/tests/right-join.test.ts +0 -89
- package/tests/subquery-having.test.ts +0 -193
- package/tests/window-function.test.ts +0 -151
- package/tsconfig.json +0 -30
- package/tsup.config.ts +0 -10
- package/vite.config.ts +0 -22
- package/vitest.config.ts +0 -14
- /package/src/{constants → core/sql}/sql.ts +0 -0
- /package/src/{runtime → orm}/als.ts +0 -0
- /package/src/{utils → query-builder}/relation-alias.ts +0 -0
|
@@ -0,0 +1,2564 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/decorators/index.ts
|
|
20
|
+
var decorators_exports = {};
|
|
21
|
+
__export(decorators_exports, {
|
|
22
|
+
BelongsTo: () => BelongsTo,
|
|
23
|
+
BelongsToMany: () => BelongsToMany,
|
|
24
|
+
Column: () => Column,
|
|
25
|
+
Entity: () => Entity,
|
|
26
|
+
HasMany: () => HasMany,
|
|
27
|
+
PrimaryKey: () => PrimaryKey,
|
|
28
|
+
bootstrapEntities: () => bootstrapEntities,
|
|
29
|
+
getTableDefFromEntity: () => getTableDefFromEntity,
|
|
30
|
+
selectFromEntity: () => selectFromEntity
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(decorators_exports);
|
|
33
|
+
|
|
34
|
+
// src/schema/table.ts
|
|
35
|
+
var defineTable = (name, columns, relations = {}, hooks) => {
|
|
36
|
+
const colsWithNames = Object.entries(columns).reduce((acc, [key, def]) => {
|
|
37
|
+
acc[key] = { ...def, name: key, table: name };
|
|
38
|
+
return acc;
|
|
39
|
+
}, {});
|
|
40
|
+
return { name, columns: colsWithNames, relations, hooks };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/orm/entity-metadata.ts
|
|
44
|
+
var metadataMap = /* @__PURE__ */ new Map();
|
|
45
|
+
var ensureEntityMetadata = (target) => {
|
|
46
|
+
let meta = metadataMap.get(target);
|
|
47
|
+
if (!meta) {
|
|
48
|
+
meta = {
|
|
49
|
+
target,
|
|
50
|
+
tableName: target.name || "unknown",
|
|
51
|
+
columns: {},
|
|
52
|
+
relations: {}
|
|
53
|
+
};
|
|
54
|
+
metadataMap.set(target, meta);
|
|
55
|
+
}
|
|
56
|
+
return meta;
|
|
57
|
+
};
|
|
58
|
+
var getEntityMetadata = (target) => {
|
|
59
|
+
return metadataMap.get(target);
|
|
60
|
+
};
|
|
61
|
+
var getAllEntityMetadata = () => {
|
|
62
|
+
return Array.from(metadataMap.values());
|
|
63
|
+
};
|
|
64
|
+
var addColumnMetadata = (target, propertyKey, column) => {
|
|
65
|
+
const meta = ensureEntityMetadata(target);
|
|
66
|
+
meta.columns[propertyKey] = { ...column };
|
|
67
|
+
};
|
|
68
|
+
var addRelationMetadata = (target, propertyKey, relation) => {
|
|
69
|
+
const meta = ensureEntityMetadata(target);
|
|
70
|
+
meta.relations[propertyKey] = relation;
|
|
71
|
+
};
|
|
72
|
+
var setEntityTableName = (target, tableName, hooks) => {
|
|
73
|
+
const meta = ensureEntityMetadata(target);
|
|
74
|
+
if (tableName && tableName.length > 0) {
|
|
75
|
+
meta.tableName = tableName;
|
|
76
|
+
}
|
|
77
|
+
if (hooks) {
|
|
78
|
+
meta.hooks = hooks;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var buildTableDef = (meta) => {
|
|
82
|
+
if (meta.table) {
|
|
83
|
+
return meta.table;
|
|
84
|
+
}
|
|
85
|
+
const columns = Object.entries(meta.columns).reduce((acc, [key, def]) => {
|
|
86
|
+
acc[key] = {
|
|
87
|
+
...def,
|
|
88
|
+
name: key,
|
|
89
|
+
table: meta.tableName
|
|
90
|
+
};
|
|
91
|
+
return acc;
|
|
92
|
+
}, {});
|
|
93
|
+
const table = defineTable(meta.tableName, columns, {}, meta.hooks);
|
|
94
|
+
meta.table = table;
|
|
95
|
+
return table;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/decorators/entity.ts
|
|
99
|
+
var toSnakeCase = (value) => {
|
|
100
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]+/gi, "_").replace(/__+/g, "_").replace(/^_|_$/g, "").toLowerCase();
|
|
101
|
+
};
|
|
102
|
+
var deriveTableNameFromConstructor = (ctor) => {
|
|
103
|
+
const fallback = "unknown";
|
|
104
|
+
const rawName = ctor.name || fallback;
|
|
105
|
+
const strippedName = rawName.replace(/Entity$/i, "");
|
|
106
|
+
const normalized = toSnakeCase(strippedName || rawName);
|
|
107
|
+
if (!normalized) {
|
|
108
|
+
return fallback;
|
|
109
|
+
}
|
|
110
|
+
return normalized.endsWith("s") ? normalized : `${normalized}s`;
|
|
111
|
+
};
|
|
112
|
+
function Entity(options = {}) {
|
|
113
|
+
const decorator = (value) => {
|
|
114
|
+
const tableName = options.tableName ?? deriveTableNameFromConstructor(value);
|
|
115
|
+
setEntityTableName(value, tableName, options.hooks);
|
|
116
|
+
return value;
|
|
117
|
+
};
|
|
118
|
+
return decorator;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/decorators/column.ts
|
|
122
|
+
var normalizeColumnInput = (input) => {
|
|
123
|
+
const column = {
|
|
124
|
+
type: input.type ?? input.type,
|
|
125
|
+
args: input.args ?? input.args,
|
|
126
|
+
notNull: input.notNull ?? input.notNull,
|
|
127
|
+
primary: input.primary ?? input.primary
|
|
128
|
+
};
|
|
129
|
+
if (!column.type) {
|
|
130
|
+
throw new Error("Column decorator requires a column type");
|
|
131
|
+
}
|
|
132
|
+
return column;
|
|
133
|
+
};
|
|
134
|
+
var normalizePropertyName = (name) => {
|
|
135
|
+
if (typeof name === "symbol") {
|
|
136
|
+
return name.description ?? name.toString();
|
|
137
|
+
}
|
|
138
|
+
return name;
|
|
139
|
+
};
|
|
140
|
+
var resolveConstructor = (target) => {
|
|
141
|
+
if (typeof target === "function") {
|
|
142
|
+
return target;
|
|
143
|
+
}
|
|
144
|
+
if (target && typeof target.constructor === "function") {
|
|
145
|
+
return target.constructor;
|
|
146
|
+
}
|
|
147
|
+
return void 0;
|
|
148
|
+
};
|
|
149
|
+
var registerColumn = (ctor, propertyName, column) => {
|
|
150
|
+
const meta = ensureEntityMetadata(ctor);
|
|
151
|
+
if (meta.columns[propertyName]) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
addColumnMetadata(ctor, propertyName, column);
|
|
155
|
+
};
|
|
156
|
+
function Column(definition) {
|
|
157
|
+
const normalized = normalizeColumnInput(definition);
|
|
158
|
+
const decorator = (target, propertyKey) => {
|
|
159
|
+
const propertyName = normalizePropertyName(propertyKey);
|
|
160
|
+
const ctor = resolveConstructor(target);
|
|
161
|
+
if (!ctor) {
|
|
162
|
+
throw new Error("Unable to resolve constructor when registering column metadata");
|
|
163
|
+
}
|
|
164
|
+
registerColumn(ctor, propertyName, { ...normalized });
|
|
165
|
+
};
|
|
166
|
+
return decorator;
|
|
167
|
+
}
|
|
168
|
+
function PrimaryKey(definition) {
|
|
169
|
+
const normalized = normalizeColumnInput(definition);
|
|
170
|
+
normalized.primary = true;
|
|
171
|
+
return Column(normalized);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/schema/relation.ts
|
|
175
|
+
var RelationKinds = {
|
|
176
|
+
/** One-to-many relationship */
|
|
177
|
+
HasMany: "HAS_MANY",
|
|
178
|
+
/** Many-to-one relationship */
|
|
179
|
+
BelongsTo: "BELONGS_TO",
|
|
180
|
+
/** Many-to-many relationship with pivot metadata */
|
|
181
|
+
BelongsToMany: "BELONGS_TO_MANY"
|
|
182
|
+
};
|
|
183
|
+
var hasMany = (target, foreignKey, localKey, cascade) => ({
|
|
184
|
+
type: RelationKinds.HasMany,
|
|
185
|
+
target,
|
|
186
|
+
foreignKey,
|
|
187
|
+
localKey,
|
|
188
|
+
cascade
|
|
189
|
+
});
|
|
190
|
+
var belongsTo = (target, foreignKey, localKey, cascade) => ({
|
|
191
|
+
type: RelationKinds.BelongsTo,
|
|
192
|
+
target,
|
|
193
|
+
foreignKey,
|
|
194
|
+
localKey,
|
|
195
|
+
cascade
|
|
196
|
+
});
|
|
197
|
+
var belongsToMany = (target, pivotTable, options) => ({
|
|
198
|
+
type: RelationKinds.BelongsToMany,
|
|
199
|
+
target,
|
|
200
|
+
pivotTable,
|
|
201
|
+
pivotForeignKeyToRoot: options.pivotForeignKeyToRoot,
|
|
202
|
+
pivotForeignKeyToTarget: options.pivotForeignKeyToTarget,
|
|
203
|
+
localKey: options.localKey,
|
|
204
|
+
targetKey: options.targetKey,
|
|
205
|
+
pivotPrimaryKey: options.pivotPrimaryKey,
|
|
206
|
+
defaultPivotColumns: options.defaultPivotColumns,
|
|
207
|
+
cascade: options.cascade
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// src/decorators/relations.ts
|
|
211
|
+
var normalizePropertyName2 = (name) => {
|
|
212
|
+
if (typeof name === "symbol") {
|
|
213
|
+
return name.description ?? name.toString();
|
|
214
|
+
}
|
|
215
|
+
return name;
|
|
216
|
+
};
|
|
217
|
+
var resolveConstructor2 = (instanceOrCtor) => {
|
|
218
|
+
if (typeof instanceOrCtor === "function") {
|
|
219
|
+
return instanceOrCtor;
|
|
220
|
+
}
|
|
221
|
+
if (instanceOrCtor && typeof instanceOrCtor.constructor === "function") {
|
|
222
|
+
return instanceOrCtor.constructor;
|
|
223
|
+
}
|
|
224
|
+
return void 0;
|
|
225
|
+
};
|
|
226
|
+
var registerRelation = (ctor, propertyName, metadata) => {
|
|
227
|
+
addRelationMetadata(ctor, propertyName, metadata);
|
|
228
|
+
};
|
|
229
|
+
var createFieldDecorator = (metadataFactory) => {
|
|
230
|
+
const decorator = (target, propertyKey) => {
|
|
231
|
+
const propertyName = normalizePropertyName2(propertyKey);
|
|
232
|
+
const ctor = resolveConstructor2(target);
|
|
233
|
+
if (!ctor) {
|
|
234
|
+
throw new Error("Unable to resolve constructor when registering relation metadata");
|
|
235
|
+
}
|
|
236
|
+
registerRelation(ctor, propertyName, metadataFactory(propertyName));
|
|
237
|
+
};
|
|
238
|
+
return decorator;
|
|
239
|
+
};
|
|
240
|
+
function HasMany(options) {
|
|
241
|
+
return createFieldDecorator((propertyName) => ({
|
|
242
|
+
kind: RelationKinds.HasMany,
|
|
243
|
+
propertyKey: propertyName,
|
|
244
|
+
target: options.target,
|
|
245
|
+
foreignKey: options.foreignKey,
|
|
246
|
+
localKey: options.localKey,
|
|
247
|
+
cascade: options.cascade
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
function BelongsTo(options) {
|
|
251
|
+
return createFieldDecorator((propertyName) => ({
|
|
252
|
+
kind: RelationKinds.BelongsTo,
|
|
253
|
+
propertyKey: propertyName,
|
|
254
|
+
target: options.target,
|
|
255
|
+
foreignKey: options.foreignKey,
|
|
256
|
+
localKey: options.localKey,
|
|
257
|
+
cascade: options.cascade
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
function BelongsToMany(options) {
|
|
261
|
+
return createFieldDecorator((propertyName) => ({
|
|
262
|
+
kind: RelationKinds.BelongsToMany,
|
|
263
|
+
propertyKey: propertyName,
|
|
264
|
+
target: options.target,
|
|
265
|
+
pivotTable: options.pivotTable,
|
|
266
|
+
pivotForeignKeyToRoot: options.pivotForeignKeyToRoot,
|
|
267
|
+
pivotForeignKeyToTarget: options.pivotForeignKeyToTarget,
|
|
268
|
+
localKey: options.localKey,
|
|
269
|
+
targetKey: options.targetKey,
|
|
270
|
+
pivotPrimaryKey: options.pivotPrimaryKey,
|
|
271
|
+
defaultPivotColumns: options.defaultPivotColumns,
|
|
272
|
+
cascade: options.cascade
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/core/ast/expression-nodes.ts
|
|
277
|
+
var operandTypes = /* @__PURE__ */ new Set([
|
|
278
|
+
"Column",
|
|
279
|
+
"Literal",
|
|
280
|
+
"Function",
|
|
281
|
+
"JsonPath",
|
|
282
|
+
"ScalarSubquery",
|
|
283
|
+
"CaseExpression",
|
|
284
|
+
"WindowFunction"
|
|
285
|
+
]);
|
|
286
|
+
var isOperandNode = (node) => node && operandTypes.has(node.type);
|
|
287
|
+
var isFunctionNode = (node) => node?.type === "Function";
|
|
288
|
+
var isCaseExpressionNode = (node) => node?.type === "CaseExpression";
|
|
289
|
+
var isWindowFunctionNode = (node) => node?.type === "WindowFunction";
|
|
290
|
+
var isExpressionSelectionNode = (node) => isFunctionNode(node) || isCaseExpressionNode(node) || isWindowFunctionNode(node);
|
|
291
|
+
|
|
292
|
+
// src/core/ast/expression-builders.ts
|
|
293
|
+
var toNode = (col) => {
|
|
294
|
+
if (isOperandNode(col)) return col;
|
|
295
|
+
const def = col;
|
|
296
|
+
return { type: "Column", table: def.table || "unknown", name: def.name };
|
|
297
|
+
};
|
|
298
|
+
var toLiteralNode = (value) => ({
|
|
299
|
+
type: "Literal",
|
|
300
|
+
value
|
|
301
|
+
});
|
|
302
|
+
var toOperand = (val) => {
|
|
303
|
+
if (val === null) return { type: "Literal", value: null };
|
|
304
|
+
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
|
|
305
|
+
return { type: "Literal", value: val };
|
|
306
|
+
}
|
|
307
|
+
return toNode(val);
|
|
308
|
+
};
|
|
309
|
+
var columnOperand = (col) => toNode(col);
|
|
310
|
+
var createBinaryExpression = (operator, left, right, escape) => {
|
|
311
|
+
const node = {
|
|
312
|
+
type: "BinaryExpression",
|
|
313
|
+
left: toNode(left),
|
|
314
|
+
operator,
|
|
315
|
+
right: toOperand(right)
|
|
316
|
+
};
|
|
317
|
+
if (escape !== void 0) {
|
|
318
|
+
node.escape = toLiteralNode(escape);
|
|
319
|
+
}
|
|
320
|
+
return node;
|
|
321
|
+
};
|
|
322
|
+
var eq = (left, right) => createBinaryExpression("=", left, right);
|
|
323
|
+
var and = (...operands) => ({
|
|
324
|
+
type: "LogicalExpression",
|
|
325
|
+
operator: "AND",
|
|
326
|
+
operands
|
|
327
|
+
});
|
|
328
|
+
var createInExpression = (operator, left, values) => ({
|
|
329
|
+
type: "InExpression",
|
|
330
|
+
left: toNode(left),
|
|
331
|
+
operator,
|
|
332
|
+
right: values.map((v) => toOperand(v))
|
|
333
|
+
});
|
|
334
|
+
var inList = (left, values) => createInExpression("IN", left, values);
|
|
335
|
+
var exists = (subquery) => ({
|
|
336
|
+
type: "ExistsExpression",
|
|
337
|
+
operator: "EXISTS",
|
|
338
|
+
subquery
|
|
339
|
+
});
|
|
340
|
+
var notExists = (subquery) => ({
|
|
341
|
+
type: "ExistsExpression",
|
|
342
|
+
operator: "NOT EXISTS",
|
|
343
|
+
subquery
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// src/core/ast/aggregate-functions.ts
|
|
347
|
+
var buildAggregate = (name) => (col) => ({
|
|
348
|
+
type: "Function",
|
|
349
|
+
name,
|
|
350
|
+
args: [columnOperand(col)]
|
|
351
|
+
});
|
|
352
|
+
var count = buildAggregate("COUNT");
|
|
353
|
+
var sum = buildAggregate("SUM");
|
|
354
|
+
var avg = buildAggregate("AVG");
|
|
355
|
+
|
|
356
|
+
// src/query-builder/select-query-state.ts
|
|
357
|
+
var SelectQueryState = class _SelectQueryState {
|
|
358
|
+
/**
|
|
359
|
+
* Creates a new SelectQueryState instance
|
|
360
|
+
* @param table - Table definition
|
|
361
|
+
* @param ast - Optional existing AST
|
|
362
|
+
*/
|
|
363
|
+
constructor(table, ast) {
|
|
364
|
+
this.table = table;
|
|
365
|
+
this.ast = ast ?? {
|
|
366
|
+
type: "SelectQuery",
|
|
367
|
+
from: { type: "Table", name: table.name },
|
|
368
|
+
columns: [],
|
|
369
|
+
joins: []
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Creates a new SelectQueryState with updated AST
|
|
374
|
+
* @param nextAst - Updated AST
|
|
375
|
+
* @returns New SelectQueryState instance
|
|
376
|
+
*/
|
|
377
|
+
clone(nextAst) {
|
|
378
|
+
return new _SelectQueryState(this.table, nextAst);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Adds columns to the query
|
|
382
|
+
* @param newCols - Columns to add
|
|
383
|
+
* @returns New SelectQueryState with added columns
|
|
384
|
+
*/
|
|
385
|
+
withColumns(newCols) {
|
|
386
|
+
return this.clone({
|
|
387
|
+
...this.ast,
|
|
388
|
+
columns: [...this.ast.columns ?? [], ...newCols]
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Adds a join to the query
|
|
393
|
+
* @param join - Join node to add
|
|
394
|
+
* @returns New SelectQueryState with added join
|
|
395
|
+
*/
|
|
396
|
+
withJoin(join) {
|
|
397
|
+
return this.clone({
|
|
398
|
+
...this.ast,
|
|
399
|
+
joins: [...this.ast.joins ?? [], join]
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Adds a WHERE clause to the query
|
|
404
|
+
* @param predicate - WHERE predicate expression
|
|
405
|
+
* @returns New SelectQueryState with WHERE clause
|
|
406
|
+
*/
|
|
407
|
+
withWhere(predicate) {
|
|
408
|
+
return this.clone({
|
|
409
|
+
...this.ast,
|
|
410
|
+
where: predicate
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Adds a HAVING clause to the query
|
|
415
|
+
* @param predicate - HAVING predicate expression
|
|
416
|
+
* @returns New SelectQueryState with HAVING clause
|
|
417
|
+
*/
|
|
418
|
+
withHaving(predicate) {
|
|
419
|
+
return this.clone({
|
|
420
|
+
...this.ast,
|
|
421
|
+
having: predicate
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Adds GROUP BY columns to the query
|
|
426
|
+
* @param columns - Columns to group by
|
|
427
|
+
* @returns New SelectQueryState with GROUP BY clause
|
|
428
|
+
*/
|
|
429
|
+
withGroupBy(columns) {
|
|
430
|
+
return this.clone({
|
|
431
|
+
...this.ast,
|
|
432
|
+
groupBy: [...this.ast.groupBy ?? [], ...columns]
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Adds ORDER BY clauses to the query
|
|
437
|
+
* @param orderBy - ORDER BY nodes
|
|
438
|
+
* @returns New SelectQueryState with ORDER BY clause
|
|
439
|
+
*/
|
|
440
|
+
withOrderBy(orderBy) {
|
|
441
|
+
return this.clone({
|
|
442
|
+
...this.ast,
|
|
443
|
+
orderBy: [...this.ast.orderBy ?? [], ...orderBy]
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Adds DISTINCT columns to the query
|
|
448
|
+
* @param columns - Columns to make distinct
|
|
449
|
+
* @returns New SelectQueryState with DISTINCT clause
|
|
450
|
+
*/
|
|
451
|
+
withDistinct(columns) {
|
|
452
|
+
return this.clone({
|
|
453
|
+
...this.ast,
|
|
454
|
+
distinct: [...this.ast.distinct ?? [], ...columns]
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Adds a LIMIT clause to the query
|
|
459
|
+
* @param limit - Maximum number of rows to return
|
|
460
|
+
* @returns New SelectQueryState with LIMIT clause
|
|
461
|
+
*/
|
|
462
|
+
withLimit(limit) {
|
|
463
|
+
return this.clone({
|
|
464
|
+
...this.ast,
|
|
465
|
+
limit
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Adds an OFFSET clause to the query
|
|
470
|
+
* @param offset - Number of rows to skip
|
|
471
|
+
* @returns New SelectQueryState with OFFSET clause
|
|
472
|
+
*/
|
|
473
|
+
withOffset(offset) {
|
|
474
|
+
return this.clone({
|
|
475
|
+
...this.ast,
|
|
476
|
+
offset
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Adds a Common Table Expression (CTE) to the query
|
|
481
|
+
* @param cte - CTE node to add
|
|
482
|
+
* @returns New SelectQueryState with CTE
|
|
483
|
+
*/
|
|
484
|
+
withCte(cte) {
|
|
485
|
+
return this.clone({
|
|
486
|
+
...this.ast,
|
|
487
|
+
ctes: [...this.ast.ctes ?? [], cte]
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
// src/query-builder/hydration-manager.ts
|
|
493
|
+
var HydrationManager = class _HydrationManager {
|
|
494
|
+
/**
|
|
495
|
+
* Creates a new HydrationManager instance
|
|
496
|
+
* @param table - Table definition
|
|
497
|
+
* @param planner - Hydration planner
|
|
498
|
+
*/
|
|
499
|
+
constructor(table, planner) {
|
|
500
|
+
this.table = table;
|
|
501
|
+
this.planner = planner;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Creates a new HydrationManager with updated planner
|
|
505
|
+
* @param nextPlanner - Updated hydration planner
|
|
506
|
+
* @returns New HydrationManager instance
|
|
507
|
+
*/
|
|
508
|
+
clone(nextPlanner) {
|
|
509
|
+
return new _HydrationManager(this.table, nextPlanner);
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Handles column selection for hydration planning
|
|
513
|
+
* @param state - Current query state
|
|
514
|
+
* @param newColumns - Newly selected columns
|
|
515
|
+
* @returns Updated HydrationManager with captured columns
|
|
516
|
+
*/
|
|
517
|
+
onColumnsSelected(state, newColumns) {
|
|
518
|
+
const updated = this.planner.captureRootColumns(newColumns);
|
|
519
|
+
return this.clone(updated);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Handles relation inclusion for hydration planning
|
|
523
|
+
* @param state - Current query state
|
|
524
|
+
* @param relation - Relation definition
|
|
525
|
+
* @param relationName - Name of the relation
|
|
526
|
+
* @param aliasPrefix - Alias prefix for the relation
|
|
527
|
+
* @param targetColumns - Target columns to include
|
|
528
|
+
* @returns Updated HydrationManager with included relation
|
|
529
|
+
*/
|
|
530
|
+
onRelationIncluded(state, relation, relationName, aliasPrefix, targetColumns, pivot) {
|
|
531
|
+
const withRoots = this.planner.captureRootColumns(state.ast.columns);
|
|
532
|
+
const next = withRoots.includeRelation(relation, relationName, aliasPrefix, targetColumns, pivot);
|
|
533
|
+
return this.clone(next);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Applies hydration plan to the AST
|
|
537
|
+
* @param ast - Query AST to modify
|
|
538
|
+
* @returns AST with hydration metadata
|
|
539
|
+
*/
|
|
540
|
+
applyToAst(ast) {
|
|
541
|
+
const plan = this.planner.getPlan();
|
|
542
|
+
if (!plan) return ast;
|
|
543
|
+
return {
|
|
544
|
+
...ast,
|
|
545
|
+
meta: {
|
|
546
|
+
...ast.meta || {},
|
|
547
|
+
hydration: plan
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Gets the current hydration plan
|
|
553
|
+
* @returns Hydration plan or undefined if none exists
|
|
554
|
+
*/
|
|
555
|
+
getPlan() {
|
|
556
|
+
return this.planner.getPlan();
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
// src/query-builder/relation-alias.ts
|
|
561
|
+
var RELATION_SEPARATOR = "__";
|
|
562
|
+
var makeRelationAlias = (relationName, columnName) => `${relationName}${RELATION_SEPARATOR}${columnName}`;
|
|
563
|
+
var isRelationAlias = (alias) => !!alias && alias.includes(RELATION_SEPARATOR);
|
|
564
|
+
|
|
565
|
+
// src/query-builder/relation-utils.ts
|
|
566
|
+
var buildDefaultPivotColumns = (rel, pivotPk) => {
|
|
567
|
+
const excluded = /* @__PURE__ */ new Set([pivotPk, rel.pivotForeignKeyToRoot, rel.pivotForeignKeyToTarget]);
|
|
568
|
+
return Object.keys(rel.pivotTable.columns).filter((col) => !excluded.has(col));
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
// src/query-builder/hydration-planner.ts
|
|
572
|
+
var findPrimaryKey = (table) => {
|
|
573
|
+
const pk = Object.values(table.columns).find((c) => c.primary);
|
|
574
|
+
return pk?.name || "id";
|
|
575
|
+
};
|
|
576
|
+
var HydrationPlanner = class _HydrationPlanner {
|
|
577
|
+
/**
|
|
578
|
+
* Creates a new HydrationPlanner instance
|
|
579
|
+
* @param table - Table definition
|
|
580
|
+
* @param plan - Optional existing hydration plan
|
|
581
|
+
*/
|
|
582
|
+
constructor(table, plan) {
|
|
583
|
+
this.table = table;
|
|
584
|
+
this.plan = plan;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Captures root table columns for hydration planning
|
|
588
|
+
* @param columns - Columns to capture
|
|
589
|
+
* @returns Updated HydrationPlanner with captured columns
|
|
590
|
+
*/
|
|
591
|
+
captureRootColumns(columns) {
|
|
592
|
+
const currentPlan = this.getPlanOrDefault();
|
|
593
|
+
const rootCols = new Set(currentPlan.rootColumns);
|
|
594
|
+
let changed = false;
|
|
595
|
+
columns.forEach((node) => {
|
|
596
|
+
if (node.type !== "Column") return;
|
|
597
|
+
if (node.table !== this.table.name) return;
|
|
598
|
+
const alias = node.alias || node.name;
|
|
599
|
+
if (isRelationAlias(alias)) return;
|
|
600
|
+
if (!rootCols.has(alias)) {
|
|
601
|
+
rootCols.add(alias);
|
|
602
|
+
changed = true;
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
if (!changed) return this;
|
|
606
|
+
return new _HydrationPlanner(this.table, {
|
|
607
|
+
...currentPlan,
|
|
608
|
+
rootColumns: Array.from(rootCols)
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Includes a relation in the hydration plan
|
|
613
|
+
* @param rel - Relation definition
|
|
614
|
+
* @param relationName - Name of the relation
|
|
615
|
+
* @param aliasPrefix - Alias prefix for relation columns
|
|
616
|
+
* @param columns - Columns to include from the relation
|
|
617
|
+
* @returns Updated HydrationPlanner with included relation
|
|
618
|
+
*/
|
|
619
|
+
includeRelation(rel, relationName, aliasPrefix, columns, pivot) {
|
|
620
|
+
const currentPlan = this.getPlanOrDefault();
|
|
621
|
+
const relations = currentPlan.relations.filter((r) => r.name !== relationName);
|
|
622
|
+
relations.push(this.buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot));
|
|
623
|
+
return new _HydrationPlanner(this.table, {
|
|
624
|
+
...currentPlan,
|
|
625
|
+
relations
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Gets the current hydration plan
|
|
630
|
+
* @returns Current hydration plan or undefined
|
|
631
|
+
*/
|
|
632
|
+
getPlan() {
|
|
633
|
+
return this.plan;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Gets the current hydration plan or creates a default one
|
|
637
|
+
* @returns Current hydration plan or default plan
|
|
638
|
+
*/
|
|
639
|
+
getPlanOrDefault() {
|
|
640
|
+
return this.plan ?? buildDefaultHydrationPlan(this.table);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Builds a relation plan for hydration
|
|
644
|
+
* @param rel - Relation definition
|
|
645
|
+
* @param relationName - Name of the relation
|
|
646
|
+
* @param aliasPrefix - Alias prefix for relation columns
|
|
647
|
+
* @param columns - Columns to include from the relation
|
|
648
|
+
* @returns Hydration relation plan
|
|
649
|
+
*/
|
|
650
|
+
buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot) {
|
|
651
|
+
switch (rel.type) {
|
|
652
|
+
case RelationKinds.HasMany: {
|
|
653
|
+
const localKey = rel.localKey || findPrimaryKey(this.table);
|
|
654
|
+
return {
|
|
655
|
+
name: relationName,
|
|
656
|
+
aliasPrefix,
|
|
657
|
+
type: rel.type,
|
|
658
|
+
targetTable: rel.target.name,
|
|
659
|
+
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
660
|
+
foreignKey: rel.foreignKey,
|
|
661
|
+
localKey,
|
|
662
|
+
columns
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
case RelationKinds.BelongsTo: {
|
|
666
|
+
const localKey = rel.localKey || findPrimaryKey(rel.target);
|
|
667
|
+
return {
|
|
668
|
+
name: relationName,
|
|
669
|
+
aliasPrefix,
|
|
670
|
+
type: rel.type,
|
|
671
|
+
targetTable: rel.target.name,
|
|
672
|
+
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
673
|
+
foreignKey: rel.foreignKey,
|
|
674
|
+
localKey,
|
|
675
|
+
columns
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
case RelationKinds.BelongsToMany: {
|
|
679
|
+
const many = rel;
|
|
680
|
+
const localKey = many.localKey || findPrimaryKey(this.table);
|
|
681
|
+
const targetPk = many.targetKey || findPrimaryKey(many.target);
|
|
682
|
+
const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
|
|
683
|
+
const pivotAliasPrefix = pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
|
|
684
|
+
const pivotColumns = pivot?.columns ?? many.defaultPivotColumns ?? buildDefaultPivotColumns(many, pivotPk);
|
|
685
|
+
return {
|
|
686
|
+
name: relationName,
|
|
687
|
+
aliasPrefix,
|
|
688
|
+
type: rel.type,
|
|
689
|
+
targetTable: many.target.name,
|
|
690
|
+
targetPrimaryKey: targetPk,
|
|
691
|
+
foreignKey: many.pivotForeignKeyToRoot,
|
|
692
|
+
localKey,
|
|
693
|
+
columns,
|
|
694
|
+
pivot: {
|
|
695
|
+
table: many.pivotTable.name,
|
|
696
|
+
primaryKey: pivotPk,
|
|
697
|
+
aliasPrefix: pivotAliasPrefix,
|
|
698
|
+
columns: pivotColumns
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
var buildDefaultHydrationPlan = (table) => ({
|
|
706
|
+
rootTable: table.name,
|
|
707
|
+
rootPrimaryKey: findPrimaryKey(table),
|
|
708
|
+
rootColumns: [],
|
|
709
|
+
relations: []
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
// src/core/ast/builders.ts
|
|
713
|
+
var buildColumnNode = (table, column) => {
|
|
714
|
+
if (column.type === "Column") {
|
|
715
|
+
return column;
|
|
716
|
+
}
|
|
717
|
+
const def = column;
|
|
718
|
+
return {
|
|
719
|
+
type: "Column",
|
|
720
|
+
table: def.table || table.name,
|
|
721
|
+
name: def.name
|
|
722
|
+
};
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
// src/query-builder/raw-column-parser.ts
|
|
726
|
+
var parseRawColumn = (col, tableName, ctes) => {
|
|
727
|
+
if (col.includes("(")) {
|
|
728
|
+
const [fn, rest] = col.split("(");
|
|
729
|
+
const colName = rest.replace(")", "");
|
|
730
|
+
const [table, name] = colName.includes(".") ? colName.split(".") : [tableName, colName];
|
|
731
|
+
return { type: "Column", table, name, alias: col };
|
|
732
|
+
}
|
|
733
|
+
if (col.includes(".")) {
|
|
734
|
+
const [potentialCteName, columnName] = col.split(".");
|
|
735
|
+
const hasCte = ctes?.some((cte) => cte.name === potentialCteName);
|
|
736
|
+
if (hasCte) {
|
|
737
|
+
return { type: "Column", table: tableName, name: col };
|
|
738
|
+
}
|
|
739
|
+
return { type: "Column", table: potentialCteName, name: columnName };
|
|
740
|
+
}
|
|
741
|
+
return { type: "Column", table: tableName, name: col };
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
// src/query-builder/query-ast-service.ts
|
|
745
|
+
var QueryAstService = class {
|
|
746
|
+
/**
|
|
747
|
+
* Creates a new QueryAstService instance
|
|
748
|
+
* @param table - Table definition
|
|
749
|
+
* @param state - Current query state
|
|
750
|
+
*/
|
|
751
|
+
constructor(table, state) {
|
|
752
|
+
this.table = table;
|
|
753
|
+
this.state = state;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Selects columns for the query
|
|
757
|
+
* @param columns - Columns to select (key: alias, value: column definition or expression)
|
|
758
|
+
* @returns Column selection result with updated state and added columns
|
|
759
|
+
*/
|
|
760
|
+
select(columns) {
|
|
761
|
+
const existingAliases = new Set(
|
|
762
|
+
this.state.ast.columns.map((c) => c.alias || c.name)
|
|
763
|
+
);
|
|
764
|
+
const newCols = Object.entries(columns).reduce((acc, [alias, val]) => {
|
|
765
|
+
if (existingAliases.has(alias)) return acc;
|
|
766
|
+
if (isExpressionSelectionNode(val)) {
|
|
767
|
+
acc.push({ ...val, alias });
|
|
768
|
+
return acc;
|
|
769
|
+
}
|
|
770
|
+
const colDef = val;
|
|
771
|
+
acc.push({
|
|
772
|
+
type: "Column",
|
|
773
|
+
table: colDef.table || this.table.name,
|
|
774
|
+
name: colDef.name,
|
|
775
|
+
alias
|
|
776
|
+
});
|
|
777
|
+
return acc;
|
|
778
|
+
}, []);
|
|
779
|
+
const nextState = this.state.withColumns(newCols);
|
|
780
|
+
return { state: nextState, addedColumns: newCols };
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Selects raw column expressions (best-effort parser for simple references/functions)
|
|
784
|
+
* @param cols - Raw column expressions
|
|
785
|
+
* @returns Column selection result with updated state and added columns
|
|
786
|
+
*/
|
|
787
|
+
selectRaw(cols) {
|
|
788
|
+
const newCols = cols.map((col) => parseRawColumn(col, this.table.name, this.state.ast.ctes));
|
|
789
|
+
const nextState = this.state.withColumns(newCols);
|
|
790
|
+
return { state: nextState, addedColumns: newCols };
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Adds a Common Table Expression (CTE) to the query
|
|
794
|
+
* @param name - Name of the CTE
|
|
795
|
+
* @param query - Query for the CTE
|
|
796
|
+
* @param columns - Optional column names for the CTE
|
|
797
|
+
* @param recursive - Whether the CTE is recursive
|
|
798
|
+
* @returns Updated query state with CTE
|
|
799
|
+
*/
|
|
800
|
+
withCte(name, query, columns, recursive = false) {
|
|
801
|
+
const cte = {
|
|
802
|
+
type: "CommonTableExpression",
|
|
803
|
+
name,
|
|
804
|
+
query,
|
|
805
|
+
columns,
|
|
806
|
+
recursive
|
|
807
|
+
};
|
|
808
|
+
return this.state.withCte(cte);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Selects a subquery as a column
|
|
812
|
+
* @param alias - Alias for the subquery
|
|
813
|
+
* @param query - Subquery to select
|
|
814
|
+
* @returns Updated query state with subquery selection
|
|
815
|
+
*/
|
|
816
|
+
selectSubquery(alias, query) {
|
|
817
|
+
const node = { type: "ScalarSubquery", query, alias };
|
|
818
|
+
return this.state.withColumns([node]);
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Adds a JOIN clause to the query
|
|
822
|
+
* @param join - Join node to add
|
|
823
|
+
* @returns Updated query state with JOIN
|
|
824
|
+
*/
|
|
825
|
+
withJoin(join) {
|
|
826
|
+
return this.state.withJoin(join);
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Adds a WHERE clause to the query
|
|
830
|
+
* @param expr - Expression for the WHERE clause
|
|
831
|
+
* @returns Updated query state with WHERE clause
|
|
832
|
+
*/
|
|
833
|
+
withWhere(expr) {
|
|
834
|
+
const combined = this.combineExpressions(this.state.ast.where, expr);
|
|
835
|
+
return this.state.withWhere(combined);
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Adds a GROUP BY clause to the query
|
|
839
|
+
* @param col - Column to group by
|
|
840
|
+
* @returns Updated query state with GROUP BY clause
|
|
841
|
+
*/
|
|
842
|
+
withGroupBy(col) {
|
|
843
|
+
const node = buildColumnNode(this.table, col);
|
|
844
|
+
return this.state.withGroupBy([node]);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Adds a HAVING clause to the query
|
|
848
|
+
* @param expr - Expression for the HAVING clause
|
|
849
|
+
* @returns Updated query state with HAVING clause
|
|
850
|
+
*/
|
|
851
|
+
withHaving(expr) {
|
|
852
|
+
const combined = this.combineExpressions(this.state.ast.having, expr);
|
|
853
|
+
return this.state.withHaving(combined);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Adds an ORDER BY clause to the query
|
|
857
|
+
* @param col - Column to order by
|
|
858
|
+
* @param direction - Order direction (ASC/DESC)
|
|
859
|
+
* @returns Updated query state with ORDER BY clause
|
|
860
|
+
*/
|
|
861
|
+
withOrderBy(col, direction) {
|
|
862
|
+
const node = buildColumnNode(this.table, col);
|
|
863
|
+
return this.state.withOrderBy([{ type: "OrderBy", column: node, direction }]);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Adds a DISTINCT clause to the query
|
|
867
|
+
* @param cols - Columns to make distinct
|
|
868
|
+
* @returns Updated query state with DISTINCT clause
|
|
869
|
+
*/
|
|
870
|
+
withDistinct(cols) {
|
|
871
|
+
return this.state.withDistinct(cols);
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Adds a LIMIT clause to the query
|
|
875
|
+
* @param limit - Maximum number of rows to return
|
|
876
|
+
* @returns Updated query state with LIMIT clause
|
|
877
|
+
*/
|
|
878
|
+
withLimit(limit) {
|
|
879
|
+
return this.state.withLimit(limit);
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Adds an OFFSET clause to the query
|
|
883
|
+
* @param offset - Number of rows to skip
|
|
884
|
+
* @returns Updated query state with OFFSET clause
|
|
885
|
+
*/
|
|
886
|
+
withOffset(offset) {
|
|
887
|
+
return this.state.withOffset(offset);
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Combines expressions with AND operator
|
|
891
|
+
* @param existing - Existing expression
|
|
892
|
+
* @param next - New expression to combine
|
|
893
|
+
* @returns Combined expression
|
|
894
|
+
*/
|
|
895
|
+
combineExpressions(existing, next) {
|
|
896
|
+
return existing ? and(existing, next) : next;
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
// src/query-builder/relation-projection-helper.ts
|
|
901
|
+
var RelationProjectionHelper = class {
|
|
902
|
+
/**
|
|
903
|
+
* Creates a new RelationProjectionHelper instance
|
|
904
|
+
* @param table - Table definition
|
|
905
|
+
* @param selectColumns - Callback for selecting columns
|
|
906
|
+
*/
|
|
907
|
+
constructor(table, selectColumns) {
|
|
908
|
+
this.table = table;
|
|
909
|
+
this.selectColumns = selectColumns;
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Ensures base projection is included in the query
|
|
913
|
+
* @param state - Current query state
|
|
914
|
+
* @param hydration - Hydration manager
|
|
915
|
+
* @returns Relation result with updated state and hydration
|
|
916
|
+
*/
|
|
917
|
+
ensureBaseProjection(state, hydration) {
|
|
918
|
+
const primaryKey = findPrimaryKey(this.table);
|
|
919
|
+
if (!this.hasBaseProjection(state)) {
|
|
920
|
+
return this.selectColumns(state, hydration, this.getBaseColumns());
|
|
921
|
+
}
|
|
922
|
+
if (primaryKey && !this.hasPrimarySelected(state, primaryKey) && this.table.columns[primaryKey]) {
|
|
923
|
+
return this.selectColumns(state, hydration, {
|
|
924
|
+
[primaryKey]: this.table.columns[primaryKey]
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
return { state, hydration };
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Checks if base projection exists in the query
|
|
931
|
+
* @param state - Current query state
|
|
932
|
+
* @returns True if base projection exists
|
|
933
|
+
*/
|
|
934
|
+
hasBaseProjection(state) {
|
|
935
|
+
return state.ast.columns.some((col) => !isRelationAlias(col.alias));
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Checks if primary key is selected in the query
|
|
939
|
+
* @param state - Current query state
|
|
940
|
+
* @param primaryKey - Primary key name
|
|
941
|
+
* @returns True if primary key is selected
|
|
942
|
+
*/
|
|
943
|
+
hasPrimarySelected(state, primaryKey) {
|
|
944
|
+
return state.ast.columns.some((col) => {
|
|
945
|
+
const alias = col.alias;
|
|
946
|
+
const name = alias || col.name;
|
|
947
|
+
return !isRelationAlias(alias) && name === primaryKey;
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Gets all base columns for the table
|
|
952
|
+
* @returns Record of all table columns
|
|
953
|
+
*/
|
|
954
|
+
getBaseColumns() {
|
|
955
|
+
return Object.keys(this.table.columns).reduce((acc, key) => {
|
|
956
|
+
acc[key] = this.table.columns[key];
|
|
957
|
+
return acc;
|
|
958
|
+
}, {});
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
|
|
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
|
+
// src/query-builder/relation-conditions.ts
|
|
972
|
+
var assertNever = (value) => {
|
|
973
|
+
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
974
|
+
};
|
|
975
|
+
var baseRelationCondition = (root, relation) => {
|
|
976
|
+
const defaultLocalKey = relation.type === RelationKinds.HasMany ? findPrimaryKey(root) : findPrimaryKey(relation.target);
|
|
977
|
+
const localKey = relation.localKey || defaultLocalKey;
|
|
978
|
+
switch (relation.type) {
|
|
979
|
+
case RelationKinds.HasMany:
|
|
980
|
+
return eq(
|
|
981
|
+
{ type: "Column", table: relation.target.name, name: relation.foreignKey },
|
|
982
|
+
{ type: "Column", table: root.name, name: localKey }
|
|
983
|
+
);
|
|
984
|
+
case RelationKinds.BelongsTo:
|
|
985
|
+
return eq(
|
|
986
|
+
{ type: "Column", table: relation.target.name, name: localKey },
|
|
987
|
+
{ type: "Column", table: root.name, name: relation.foreignKey }
|
|
988
|
+
);
|
|
989
|
+
case RelationKinds.BelongsToMany:
|
|
990
|
+
throw new Error("BelongsToMany relations do not support the standard join condition builder");
|
|
991
|
+
default:
|
|
992
|
+
return assertNever(relation);
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra) => {
|
|
996
|
+
const rootKey = relation.localKey || findPrimaryKey(root);
|
|
997
|
+
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
998
|
+
const pivotCondition = eq(
|
|
999
|
+
{ type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
|
|
1000
|
+
{ type: "Column", table: root.name, name: rootKey }
|
|
1001
|
+
);
|
|
1002
|
+
const pivotJoin = createJoinNode(joinKind, relation.pivotTable.name, pivotCondition);
|
|
1003
|
+
let targetCondition = eq(
|
|
1004
|
+
{ type: "Column", table: relation.target.name, name: targetKey },
|
|
1005
|
+
{ type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToTarget }
|
|
1006
|
+
);
|
|
1007
|
+
if (extra) {
|
|
1008
|
+
targetCondition = and(targetCondition, extra);
|
|
1009
|
+
}
|
|
1010
|
+
const targetJoin = createJoinNode(
|
|
1011
|
+
joinKind,
|
|
1012
|
+
relation.target.name,
|
|
1013
|
+
targetCondition,
|
|
1014
|
+
relationName
|
|
1015
|
+
);
|
|
1016
|
+
return [pivotJoin, targetJoin];
|
|
1017
|
+
};
|
|
1018
|
+
var buildRelationJoinCondition = (root, relation, extra) => {
|
|
1019
|
+
const base = baseRelationCondition(root, relation);
|
|
1020
|
+
return extra ? and(base, extra) : base;
|
|
1021
|
+
};
|
|
1022
|
+
var buildRelationCorrelation = (root, relation) => {
|
|
1023
|
+
return baseRelationCondition(root, relation);
|
|
1024
|
+
};
|
|
1025
|
+
|
|
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
|
+
// src/query-builder/relation-service.ts
|
|
1045
|
+
var RelationService = class {
|
|
1046
|
+
/**
|
|
1047
|
+
* Creates a new RelationService instance
|
|
1048
|
+
* @param table - Table definition
|
|
1049
|
+
* @param state - Current query state
|
|
1050
|
+
* @param hydration - Hydration manager
|
|
1051
|
+
*/
|
|
1052
|
+
constructor(table, state, hydration, createQueryAstService) {
|
|
1053
|
+
this.table = table;
|
|
1054
|
+
this.state = state;
|
|
1055
|
+
this.hydration = hydration;
|
|
1056
|
+
this.createQueryAstService = createQueryAstService;
|
|
1057
|
+
this.projectionHelper = new RelationProjectionHelper(
|
|
1058
|
+
table,
|
|
1059
|
+
(state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Joins a relation to the query
|
|
1064
|
+
* @param relationName - Name of the relation to join
|
|
1065
|
+
* @param joinKind - Type of join to use
|
|
1066
|
+
* @param extraCondition - Additional join condition
|
|
1067
|
+
* @returns Relation result with updated state and hydration
|
|
1068
|
+
*/
|
|
1069
|
+
joinRelation(relationName, joinKind, extraCondition) {
|
|
1070
|
+
const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition);
|
|
1071
|
+
return { state: nextState, hydration: this.hydration };
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Matches records based on a relation with an optional predicate
|
|
1075
|
+
* @param relationName - Name of the relation to match
|
|
1076
|
+
* @param predicate - Optional predicate expression
|
|
1077
|
+
* @returns Relation result with updated state and hydration
|
|
1078
|
+
*/
|
|
1079
|
+
match(relationName, predicate) {
|
|
1080
|
+
const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
|
|
1081
|
+
const pk = findPrimaryKey(this.table);
|
|
1082
|
+
const distinctCols = [{ type: "Column", table: this.table.name, name: pk }];
|
|
1083
|
+
const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
|
|
1084
|
+
const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
|
|
1085
|
+
return { state: nextState, hydration: joined.hydration };
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Includes a relation in the query result
|
|
1089
|
+
* @param relationName - Name of the relation to include
|
|
1090
|
+
* @param options - Options for relation inclusion
|
|
1091
|
+
* @returns Relation result with updated state and hydration
|
|
1092
|
+
*/
|
|
1093
|
+
include(relationName, options) {
|
|
1094
|
+
let state = this.state;
|
|
1095
|
+
let hydration = this.hydration;
|
|
1096
|
+
const relation = this.getRelation(relationName);
|
|
1097
|
+
const aliasPrefix = options?.aliasPrefix ?? relationName;
|
|
1098
|
+
const alreadyJoined = state.ast.joins.some((j) => j.relationName === relationName);
|
|
1099
|
+
if (!alreadyJoined) {
|
|
1100
|
+
const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
|
|
1101
|
+
state = joined.state;
|
|
1102
|
+
}
|
|
1103
|
+
const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
|
|
1104
|
+
state = projectionResult.state;
|
|
1105
|
+
hydration = projectionResult.hydration;
|
|
1106
|
+
const targetColumns = options?.columns?.length ? options.columns : Object.keys(relation.target.columns);
|
|
1107
|
+
const buildTypedSelection = (columns, prefix, keys, missingMsg) => {
|
|
1108
|
+
return keys.reduce((acc, key) => {
|
|
1109
|
+
const def = columns[key];
|
|
1110
|
+
if (!def) {
|
|
1111
|
+
throw new Error(missingMsg(key));
|
|
1112
|
+
}
|
|
1113
|
+
acc[makeRelationAlias(prefix, key)] = def;
|
|
1114
|
+
return acc;
|
|
1115
|
+
}, {});
|
|
1116
|
+
};
|
|
1117
|
+
const targetSelection = buildTypedSelection(
|
|
1118
|
+
relation.target.columns,
|
|
1119
|
+
aliasPrefix,
|
|
1120
|
+
targetColumns,
|
|
1121
|
+
(key) => `Column '${key}' not found on relation '${relationName}'`
|
|
1122
|
+
);
|
|
1123
|
+
if (relation.type !== RelationKinds.BelongsToMany) {
|
|
1124
|
+
const relationSelectionResult2 = this.selectColumns(state, hydration, targetSelection);
|
|
1125
|
+
state = relationSelectionResult2.state;
|
|
1126
|
+
hydration = relationSelectionResult2.hydration;
|
|
1127
|
+
hydration = hydration.onRelationIncluded(
|
|
1128
|
+
state,
|
|
1129
|
+
relation,
|
|
1130
|
+
relationName,
|
|
1131
|
+
aliasPrefix,
|
|
1132
|
+
targetColumns
|
|
1133
|
+
);
|
|
1134
|
+
return { state, hydration };
|
|
1135
|
+
}
|
|
1136
|
+
const many = relation;
|
|
1137
|
+
const pivotAliasPrefix = options?.pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
|
|
1138
|
+
const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
|
|
1139
|
+
const pivotColumns = options?.pivot?.columns ?? many.defaultPivotColumns ?? buildDefaultPivotColumns(many, pivotPk);
|
|
1140
|
+
const pivotSelection = buildTypedSelection(
|
|
1141
|
+
many.pivotTable.columns,
|
|
1142
|
+
pivotAliasPrefix,
|
|
1143
|
+
pivotColumns,
|
|
1144
|
+
(key) => `Column '${key}' not found on pivot table '${many.pivotTable.name}'`
|
|
1145
|
+
);
|
|
1146
|
+
const combinedSelection = {
|
|
1147
|
+
...targetSelection,
|
|
1148
|
+
...pivotSelection
|
|
1149
|
+
};
|
|
1150
|
+
const relationSelectionResult = this.selectColumns(state, hydration, combinedSelection);
|
|
1151
|
+
state = relationSelectionResult.state;
|
|
1152
|
+
hydration = relationSelectionResult.hydration;
|
|
1153
|
+
hydration = hydration.onRelationIncluded(
|
|
1154
|
+
state,
|
|
1155
|
+
relation,
|
|
1156
|
+
relationName,
|
|
1157
|
+
aliasPrefix,
|
|
1158
|
+
targetColumns,
|
|
1159
|
+
{ aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
|
|
1160
|
+
);
|
|
1161
|
+
return { state, hydration };
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Applies relation correlation to a query AST
|
|
1165
|
+
* @param relationName - Name of the relation
|
|
1166
|
+
* @param ast - Query AST to modify
|
|
1167
|
+
* @returns Modified query AST with relation correlation
|
|
1168
|
+
*/
|
|
1169
|
+
applyRelationCorrelation(relationName, ast) {
|
|
1170
|
+
const relation = this.getRelation(relationName);
|
|
1171
|
+
const correlation = buildRelationCorrelation(this.table, relation);
|
|
1172
|
+
const whereInSubquery = ast.where ? and(correlation, ast.where) : correlation;
|
|
1173
|
+
return {
|
|
1174
|
+
...ast,
|
|
1175
|
+
where: whereInSubquery
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Creates a join node for a relation
|
|
1180
|
+
* @param state - Current query state
|
|
1181
|
+
* @param relationName - Name of the relation
|
|
1182
|
+
* @param joinKind - Type of join to use
|
|
1183
|
+
* @param extraCondition - Additional join condition
|
|
1184
|
+
* @returns Updated query state with join
|
|
1185
|
+
*/
|
|
1186
|
+
withJoin(state, relationName, joinKind, extraCondition) {
|
|
1187
|
+
const relation = this.getRelation(relationName);
|
|
1188
|
+
if (relation.type === RelationKinds.BelongsToMany) {
|
|
1189
|
+
const joins = buildBelongsToManyJoins(
|
|
1190
|
+
this.table,
|
|
1191
|
+
relationName,
|
|
1192
|
+
relation,
|
|
1193
|
+
joinKind,
|
|
1194
|
+
extraCondition
|
|
1195
|
+
);
|
|
1196
|
+
return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
|
|
1197
|
+
}
|
|
1198
|
+
const condition = buildRelationJoinCondition(this.table, relation, extraCondition);
|
|
1199
|
+
const joinNode = createJoinNode(joinKind, relation.target.name, condition, relationName);
|
|
1200
|
+
return this.astService(state).withJoin(joinNode);
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Selects columns for a relation
|
|
1204
|
+
* @param state - Current query state
|
|
1205
|
+
* @param hydration - Hydration manager
|
|
1206
|
+
* @param columns - Columns to select
|
|
1207
|
+
* @returns Relation result with updated state and hydration
|
|
1208
|
+
*/
|
|
1209
|
+
selectColumns(state, hydration, columns) {
|
|
1210
|
+
const { state: nextState, addedColumns } = this.astService(state).select(columns);
|
|
1211
|
+
return {
|
|
1212
|
+
state: nextState,
|
|
1213
|
+
hydration: hydration.onColumnsSelected(nextState, addedColumns)
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Gets a relation definition by name
|
|
1218
|
+
* @param relationName - Name of the relation
|
|
1219
|
+
* @returns Relation definition
|
|
1220
|
+
* @throws Error if relation is not found
|
|
1221
|
+
*/
|
|
1222
|
+
getRelation(relationName) {
|
|
1223
|
+
const relation = this.table.relations[relationName];
|
|
1224
|
+
if (!relation) {
|
|
1225
|
+
throw new Error(`Relation '${relationName}' not found on table '${this.table.name}'`);
|
|
1226
|
+
}
|
|
1227
|
+
return relation;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Creates a QueryAstService instance
|
|
1231
|
+
* @param state - Current query state
|
|
1232
|
+
* @returns QueryAstService instance
|
|
1233
|
+
*/
|
|
1234
|
+
astService(state = this.state) {
|
|
1235
|
+
return this.createQueryAstService(this.table, state);
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
|
|
1239
|
+
// src/query-builder/select-query-builder-deps.ts
|
|
1240
|
+
var defaultCreateQueryAstService = (table, state) => new QueryAstService(table, state);
|
|
1241
|
+
var defaultCreateHydrationPlanner = (table) => new HydrationPlanner(table);
|
|
1242
|
+
var defaultCreateHydration = (table, plannerFactory) => new HydrationManager(table, plannerFactory(table));
|
|
1243
|
+
var resolveSelectQueryBuilderDependencies = (overrides = {}) => {
|
|
1244
|
+
const createQueryAstService = overrides.createQueryAstService ?? defaultCreateQueryAstService;
|
|
1245
|
+
const createHydrationPlanner = overrides.createHydrationPlanner ?? defaultCreateHydrationPlanner;
|
|
1246
|
+
const createHydration = overrides.createHydration ?? ((table) => defaultCreateHydration(table, createHydrationPlanner));
|
|
1247
|
+
const createRelationService = overrides.createRelationService ?? ((table, state, hydration) => new RelationService(table, state, hydration, createQueryAstService));
|
|
1248
|
+
return {
|
|
1249
|
+
createState: overrides.createState ?? ((table) => new SelectQueryState(table)),
|
|
1250
|
+
createHydration,
|
|
1251
|
+
createHydrationPlanner,
|
|
1252
|
+
createQueryAstService,
|
|
1253
|
+
createRelationService
|
|
1254
|
+
};
|
|
1255
|
+
};
|
|
1256
|
+
var defaultSelectQueryBuilderDependencies = resolveSelectQueryBuilderDependencies();
|
|
1257
|
+
|
|
1258
|
+
// src/query-builder/column-selector.ts
|
|
1259
|
+
var ColumnSelector = class {
|
|
1260
|
+
/**
|
|
1261
|
+
* Creates a new ColumnSelector instance
|
|
1262
|
+
* @param env - Query builder environment
|
|
1263
|
+
*/
|
|
1264
|
+
constructor(env) {
|
|
1265
|
+
this.env = env;
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Selects columns for the query
|
|
1269
|
+
* @param context - Current query context
|
|
1270
|
+
* @param columns - Columns to select
|
|
1271
|
+
* @returns Updated query context with selected columns
|
|
1272
|
+
*/
|
|
1273
|
+
select(context, columns) {
|
|
1274
|
+
const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
|
|
1275
|
+
const { state: nextState, addedColumns } = astService.select(columns);
|
|
1276
|
+
return {
|
|
1277
|
+
state: nextState,
|
|
1278
|
+
hydration: context.hydration.onColumnsSelected(nextState, addedColumns)
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Selects raw column expressions
|
|
1283
|
+
* @param context - Current query context
|
|
1284
|
+
* @param columns - Raw column expressions
|
|
1285
|
+
* @returns Updated query context with raw column selections
|
|
1286
|
+
*/
|
|
1287
|
+
selectRaw(context, columns) {
|
|
1288
|
+
const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
|
|
1289
|
+
const nextState = astService.selectRaw(columns).state;
|
|
1290
|
+
return { state: nextState, hydration: context.hydration };
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Selects a subquery as a column
|
|
1294
|
+
* @param context - Current query context
|
|
1295
|
+
* @param alias - Alias for the subquery
|
|
1296
|
+
* @param query - Subquery to select
|
|
1297
|
+
* @returns Updated query context with subquery selection
|
|
1298
|
+
*/
|
|
1299
|
+
selectSubquery(context, alias, query) {
|
|
1300
|
+
const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
|
|
1301
|
+
const nextState = astService.selectSubquery(alias, query);
|
|
1302
|
+
return { state: nextState, hydration: context.hydration };
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Adds DISTINCT clause to the query
|
|
1306
|
+
* @param context - Current query context
|
|
1307
|
+
* @param columns - Columns to make distinct
|
|
1308
|
+
* @returns Updated query context with DISTINCT clause
|
|
1309
|
+
*/
|
|
1310
|
+
distinct(context, columns) {
|
|
1311
|
+
const nodes = columns.map((col) => buildColumnNode(this.env.table, col));
|
|
1312
|
+
const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
|
|
1313
|
+
const nextState = astService.withDistinct(nodes);
|
|
1314
|
+
return { state: nextState, hydration: context.hydration };
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
// src/query-builder/relation-manager.ts
|
|
1319
|
+
var RelationManager = class {
|
|
1320
|
+
/**
|
|
1321
|
+
* Creates a new RelationManager instance
|
|
1322
|
+
* @param env - Query builder environment
|
|
1323
|
+
*/
|
|
1324
|
+
constructor(env) {
|
|
1325
|
+
this.env = env;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Matches records based on a relation with an optional predicate
|
|
1329
|
+
* @param context - Current query context
|
|
1330
|
+
* @param relationName - Name of the relation to match
|
|
1331
|
+
* @param predicate - Optional predicate expression
|
|
1332
|
+
* @returns Updated query context with relation match
|
|
1333
|
+
*/
|
|
1334
|
+
match(context, relationName, predicate) {
|
|
1335
|
+
const result = this.createService(context).match(relationName, predicate);
|
|
1336
|
+
return { state: result.state, hydration: result.hydration };
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Joins a relation to the query
|
|
1340
|
+
* @param context - Current query context
|
|
1341
|
+
* @param relationName - Name of the relation to join
|
|
1342
|
+
* @param joinKind - Type of join to use
|
|
1343
|
+
* @param extraCondition - Additional join condition
|
|
1344
|
+
* @returns Updated query context with relation join
|
|
1345
|
+
*/
|
|
1346
|
+
joinRelation(context, relationName, joinKind, extraCondition) {
|
|
1347
|
+
const result = this.createService(context).joinRelation(relationName, joinKind, extraCondition);
|
|
1348
|
+
return { state: result.state, hydration: result.hydration };
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Includes a relation in the query result
|
|
1352
|
+
* @param context - Current query context
|
|
1353
|
+
* @param relationName - Name of the relation to include
|
|
1354
|
+
* @param options - Options for relation inclusion
|
|
1355
|
+
* @returns Updated query context with included relation
|
|
1356
|
+
*/
|
|
1357
|
+
include(context, relationName, options) {
|
|
1358
|
+
const result = this.createService(context).include(relationName, options);
|
|
1359
|
+
return { state: result.state, hydration: result.hydration };
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Applies relation correlation to a query AST
|
|
1363
|
+
* @param context - Current query context
|
|
1364
|
+
* @param relationName - Name of the relation
|
|
1365
|
+
* @param ast - Query AST to modify
|
|
1366
|
+
* @returns Modified query AST with relation correlation
|
|
1367
|
+
*/
|
|
1368
|
+
applyRelationCorrelation(context, relationName, ast) {
|
|
1369
|
+
return this.createService(context).applyRelationCorrelation(relationName, ast);
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Creates a relation service instance
|
|
1373
|
+
* @param context - Current query context
|
|
1374
|
+
* @returns Relation service instance
|
|
1375
|
+
*/
|
|
1376
|
+
createService(context) {
|
|
1377
|
+
return this.env.deps.createRelationService(this.env.table, context.state, context.hydration);
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
|
|
1381
|
+
// src/orm/hydration.ts
|
|
1382
|
+
var hydrateRows = (rows, plan) => {
|
|
1383
|
+
if (!plan || !rows.length) return rows;
|
|
1384
|
+
const rootMap = /* @__PURE__ */ new Map();
|
|
1385
|
+
const relationIndex = /* @__PURE__ */ new Map();
|
|
1386
|
+
const getOrCreateParent = (row) => {
|
|
1387
|
+
const rootId = row[plan.rootPrimaryKey];
|
|
1388
|
+
if (rootId === void 0) return void 0;
|
|
1389
|
+
if (!rootMap.has(rootId)) {
|
|
1390
|
+
rootMap.set(rootId, createBaseRow(row, plan));
|
|
1391
|
+
}
|
|
1392
|
+
return rootMap.get(rootId);
|
|
1393
|
+
};
|
|
1394
|
+
const getRelationSeenSet = (rootId, relationName) => {
|
|
1395
|
+
let byRelation = relationIndex.get(rootId);
|
|
1396
|
+
if (!byRelation) {
|
|
1397
|
+
byRelation = {};
|
|
1398
|
+
relationIndex.set(rootId, byRelation);
|
|
1399
|
+
}
|
|
1400
|
+
let seen = byRelation[relationName];
|
|
1401
|
+
if (!seen) {
|
|
1402
|
+
seen = /* @__PURE__ */ new Set();
|
|
1403
|
+
byRelation[relationName] = seen;
|
|
1404
|
+
}
|
|
1405
|
+
return seen;
|
|
1406
|
+
};
|
|
1407
|
+
for (const row of rows) {
|
|
1408
|
+
const rootId = row[plan.rootPrimaryKey];
|
|
1409
|
+
if (rootId === void 0) continue;
|
|
1410
|
+
const parent = getOrCreateParent(row);
|
|
1411
|
+
if (!parent) continue;
|
|
1412
|
+
for (const rel of plan.relations) {
|
|
1413
|
+
const childPkKey = makeRelationAlias(rel.aliasPrefix, rel.targetPrimaryKey);
|
|
1414
|
+
const childPk = row[childPkKey];
|
|
1415
|
+
if (childPk === null || childPk === void 0) continue;
|
|
1416
|
+
const seen = getRelationSeenSet(rootId, rel.name);
|
|
1417
|
+
if (seen.has(childPk)) continue;
|
|
1418
|
+
seen.add(childPk);
|
|
1419
|
+
const bucket = parent[rel.name];
|
|
1420
|
+
bucket.push(buildChild(row, rel));
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
return Array.from(rootMap.values());
|
|
1424
|
+
};
|
|
1425
|
+
var createBaseRow = (row, plan) => {
|
|
1426
|
+
const base = {};
|
|
1427
|
+
const baseKeys = plan.rootColumns.length ? plan.rootColumns : Object.keys(row).filter((k) => !isRelationAlias(k));
|
|
1428
|
+
for (const key of baseKeys) {
|
|
1429
|
+
base[key] = row[key];
|
|
1430
|
+
}
|
|
1431
|
+
for (const rel of plan.relations) {
|
|
1432
|
+
base[rel.name] = [];
|
|
1433
|
+
}
|
|
1434
|
+
return base;
|
|
1435
|
+
};
|
|
1436
|
+
var buildChild = (row, rel) => {
|
|
1437
|
+
const child = {};
|
|
1438
|
+
for (const col of rel.columns) {
|
|
1439
|
+
const key = makeRelationAlias(rel.aliasPrefix, col);
|
|
1440
|
+
child[col] = row[key];
|
|
1441
|
+
}
|
|
1442
|
+
const pivot = buildPivot(row, rel);
|
|
1443
|
+
if (pivot) {
|
|
1444
|
+
child._pivot = pivot;
|
|
1445
|
+
}
|
|
1446
|
+
return child;
|
|
1447
|
+
};
|
|
1448
|
+
var buildPivot = (row, rel) => {
|
|
1449
|
+
if (!rel.pivot) return void 0;
|
|
1450
|
+
const pivot = {};
|
|
1451
|
+
for (const col of rel.pivot.columns) {
|
|
1452
|
+
const key = makeRelationAlias(rel.pivot.aliasPrefix, col);
|
|
1453
|
+
pivot[col] = row[key];
|
|
1454
|
+
}
|
|
1455
|
+
const hasValue = Object.values(pivot).some((v) => v !== null && v !== void 0);
|
|
1456
|
+
return hasValue ? pivot : void 0;
|
|
1457
|
+
};
|
|
1458
|
+
|
|
1459
|
+
// src/orm/entity-meta.ts
|
|
1460
|
+
var ENTITY_META = Symbol("EntityMeta");
|
|
1461
|
+
var toKey = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1462
|
+
var getHydrationRows = (meta, relationName, key) => {
|
|
1463
|
+
const map = meta.relationHydration.get(relationName);
|
|
1464
|
+
if (!map) return void 0;
|
|
1465
|
+
const rows = map.get(toKey(key));
|
|
1466
|
+
if (!rows) return void 0;
|
|
1467
|
+
return Array.isArray(rows) ? rows : void 0;
|
|
1468
|
+
};
|
|
1469
|
+
var getHydrationRecord = (meta, relationName, key) => {
|
|
1470
|
+
const map = meta.relationHydration.get(relationName);
|
|
1471
|
+
if (!map) return void 0;
|
|
1472
|
+
const value = map.get(toKey(key));
|
|
1473
|
+
if (!value) return void 0;
|
|
1474
|
+
if (Array.isArray(value)) {
|
|
1475
|
+
return value[0];
|
|
1476
|
+
}
|
|
1477
|
+
return value;
|
|
1478
|
+
};
|
|
1479
|
+
var getEntityMeta = (entity) => {
|
|
1480
|
+
if (!entity || typeof entity !== "object") return void 0;
|
|
1481
|
+
return entity[ENTITY_META];
|
|
1482
|
+
};
|
|
1483
|
+
var hasEntityMeta = (entity) => {
|
|
1484
|
+
return Boolean(getEntityMeta(entity));
|
|
1485
|
+
};
|
|
1486
|
+
|
|
1487
|
+
// src/orm/relations/has-many.ts
|
|
1488
|
+
var toKey2 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1489
|
+
var DefaultHasManyCollection = class {
|
|
1490
|
+
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
1491
|
+
this.ctx = ctx;
|
|
1492
|
+
this.meta = meta;
|
|
1493
|
+
this.root = root;
|
|
1494
|
+
this.relationName = relationName;
|
|
1495
|
+
this.relation = relation;
|
|
1496
|
+
this.rootTable = rootTable;
|
|
1497
|
+
this.loader = loader;
|
|
1498
|
+
this.createEntity = createEntity;
|
|
1499
|
+
this.localKey = localKey;
|
|
1500
|
+
this.loaded = false;
|
|
1501
|
+
this.items = [];
|
|
1502
|
+
this.added = /* @__PURE__ */ new Set();
|
|
1503
|
+
this.removed = /* @__PURE__ */ new Set();
|
|
1504
|
+
this.hydrateFromCache();
|
|
1505
|
+
}
|
|
1506
|
+
async load() {
|
|
1507
|
+
if (this.loaded) return this.items;
|
|
1508
|
+
const map = await this.loader();
|
|
1509
|
+
const key = toKey2(this.root[this.localKey]);
|
|
1510
|
+
const rows = map.get(key) ?? [];
|
|
1511
|
+
this.items = rows.map((row) => this.createEntity(row));
|
|
1512
|
+
this.loaded = true;
|
|
1513
|
+
return this.items;
|
|
1514
|
+
}
|
|
1515
|
+
getItems() {
|
|
1516
|
+
return this.items;
|
|
1517
|
+
}
|
|
1518
|
+
add(data) {
|
|
1519
|
+
const keyValue = this.root[this.localKey];
|
|
1520
|
+
const childRow = {
|
|
1521
|
+
...data,
|
|
1522
|
+
[this.relation.foreignKey]: keyValue
|
|
1523
|
+
};
|
|
1524
|
+
const entity = this.createEntity(childRow);
|
|
1525
|
+
this.added.add(entity);
|
|
1526
|
+
this.items.push(entity);
|
|
1527
|
+
this.ctx.registerRelationChange(
|
|
1528
|
+
this.root,
|
|
1529
|
+
this.relationKey,
|
|
1530
|
+
this.rootTable,
|
|
1531
|
+
this.relationName,
|
|
1532
|
+
this.relation,
|
|
1533
|
+
{ kind: "add", entity }
|
|
1534
|
+
);
|
|
1535
|
+
return entity;
|
|
1536
|
+
}
|
|
1537
|
+
attach(entity) {
|
|
1538
|
+
const keyValue = this.root[this.localKey];
|
|
1539
|
+
entity[this.relation.foreignKey] = keyValue;
|
|
1540
|
+
this.ctx.markDirty(entity);
|
|
1541
|
+
this.items.push(entity);
|
|
1542
|
+
this.ctx.registerRelationChange(
|
|
1543
|
+
this.root,
|
|
1544
|
+
this.relationKey,
|
|
1545
|
+
this.rootTable,
|
|
1546
|
+
this.relationName,
|
|
1547
|
+
this.relation,
|
|
1548
|
+
{ kind: "attach", entity }
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
remove(entity) {
|
|
1552
|
+
this.items = this.items.filter((item) => item !== entity);
|
|
1553
|
+
this.removed.add(entity);
|
|
1554
|
+
this.ctx.registerRelationChange(
|
|
1555
|
+
this.root,
|
|
1556
|
+
this.relationKey,
|
|
1557
|
+
this.rootTable,
|
|
1558
|
+
this.relationName,
|
|
1559
|
+
this.relation,
|
|
1560
|
+
{ kind: "remove", entity }
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
clear() {
|
|
1564
|
+
for (const entity of [...this.items]) {
|
|
1565
|
+
this.remove(entity);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
get relationKey() {
|
|
1569
|
+
return `${this.rootTable.name}.${this.relationName}`;
|
|
1570
|
+
}
|
|
1571
|
+
hydrateFromCache() {
|
|
1572
|
+
const keyValue = this.root[this.localKey];
|
|
1573
|
+
if (keyValue === void 0 || keyValue === null) return;
|
|
1574
|
+
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
1575
|
+
if (!rows?.length) return;
|
|
1576
|
+
this.items = rows.map((row) => this.createEntity(row));
|
|
1577
|
+
this.loaded = true;
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
|
|
1581
|
+
// src/orm/relations/belongs-to.ts
|
|
1582
|
+
var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1583
|
+
var DefaultBelongsToReference = class {
|
|
1584
|
+
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, targetKey) {
|
|
1585
|
+
this.ctx = ctx;
|
|
1586
|
+
this.meta = meta;
|
|
1587
|
+
this.root = root;
|
|
1588
|
+
this.relationName = relationName;
|
|
1589
|
+
this.relation = relation;
|
|
1590
|
+
this.rootTable = rootTable;
|
|
1591
|
+
this.loader = loader;
|
|
1592
|
+
this.createEntity = createEntity;
|
|
1593
|
+
this.targetKey = targetKey;
|
|
1594
|
+
this.loaded = false;
|
|
1595
|
+
this.current = null;
|
|
1596
|
+
this.populateFromHydrationCache();
|
|
1597
|
+
}
|
|
1598
|
+
async load() {
|
|
1599
|
+
if (this.loaded) return this.current;
|
|
1600
|
+
const map = await this.loader();
|
|
1601
|
+
const fkValue = this.root[this.relation.foreignKey];
|
|
1602
|
+
if (fkValue === null || fkValue === void 0) {
|
|
1603
|
+
this.current = null;
|
|
1604
|
+
} else {
|
|
1605
|
+
const row = map.get(toKey3(fkValue));
|
|
1606
|
+
this.current = row ? this.createEntity(row) : null;
|
|
1607
|
+
}
|
|
1608
|
+
this.loaded = true;
|
|
1609
|
+
return this.current;
|
|
1610
|
+
}
|
|
1611
|
+
get() {
|
|
1612
|
+
return this.current;
|
|
1613
|
+
}
|
|
1614
|
+
set(data) {
|
|
1615
|
+
if (data === null) {
|
|
1616
|
+
const previous = this.current;
|
|
1617
|
+
this.root[this.relation.foreignKey] = null;
|
|
1618
|
+
this.current = null;
|
|
1619
|
+
this.ctx.registerRelationChange(
|
|
1620
|
+
this.root,
|
|
1621
|
+
this.relationKey,
|
|
1622
|
+
this.rootTable,
|
|
1623
|
+
this.relationName,
|
|
1624
|
+
this.relation,
|
|
1625
|
+
{ kind: "remove", entity: previous }
|
|
1626
|
+
);
|
|
1627
|
+
return null;
|
|
1628
|
+
}
|
|
1629
|
+
const entity = hasEntityMeta(data) ? data : this.createEntity(data);
|
|
1630
|
+
const pkValue = entity[this.targetKey];
|
|
1631
|
+
if (pkValue !== void 0) {
|
|
1632
|
+
this.root[this.relation.foreignKey] = pkValue;
|
|
1633
|
+
}
|
|
1634
|
+
this.current = entity;
|
|
1635
|
+
this.ctx.registerRelationChange(
|
|
1636
|
+
this.root,
|
|
1637
|
+
this.relationKey,
|
|
1638
|
+
this.rootTable,
|
|
1639
|
+
this.relationName,
|
|
1640
|
+
this.relation,
|
|
1641
|
+
{ kind: "attach", entity }
|
|
1642
|
+
);
|
|
1643
|
+
return entity;
|
|
1644
|
+
}
|
|
1645
|
+
get relationKey() {
|
|
1646
|
+
return `${this.rootTable.name}.${this.relationName}`;
|
|
1647
|
+
}
|
|
1648
|
+
populateFromHydrationCache() {
|
|
1649
|
+
const fkValue = this.root[this.relation.foreignKey];
|
|
1650
|
+
if (fkValue === void 0 || fkValue === null) return;
|
|
1651
|
+
const row = getHydrationRecord(this.meta, this.relationName, fkValue);
|
|
1652
|
+
if (!row) return;
|
|
1653
|
+
this.current = this.createEntity(row);
|
|
1654
|
+
this.loaded = true;
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
// src/orm/relations/many-to-many.ts
|
|
1659
|
+
var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1660
|
+
var DefaultManyToManyCollection = class {
|
|
1661
|
+
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
1662
|
+
this.ctx = ctx;
|
|
1663
|
+
this.meta = meta;
|
|
1664
|
+
this.root = root;
|
|
1665
|
+
this.relationName = relationName;
|
|
1666
|
+
this.relation = relation;
|
|
1667
|
+
this.rootTable = rootTable;
|
|
1668
|
+
this.loader = loader;
|
|
1669
|
+
this.createEntity = createEntity;
|
|
1670
|
+
this.localKey = localKey;
|
|
1671
|
+
this.loaded = false;
|
|
1672
|
+
this.items = [];
|
|
1673
|
+
this.hydrateFromCache();
|
|
1674
|
+
}
|
|
1675
|
+
async load() {
|
|
1676
|
+
if (this.loaded) return this.items;
|
|
1677
|
+
const map = await this.loader();
|
|
1678
|
+
const key = toKey4(this.root[this.localKey]);
|
|
1679
|
+
const rows = map.get(key) ?? [];
|
|
1680
|
+
this.items = rows.map((row) => {
|
|
1681
|
+
const entity = this.createEntity(row);
|
|
1682
|
+
if (row._pivot) {
|
|
1683
|
+
entity._pivot = row._pivot;
|
|
1684
|
+
}
|
|
1685
|
+
return entity;
|
|
1686
|
+
});
|
|
1687
|
+
this.loaded = true;
|
|
1688
|
+
return this.items;
|
|
1689
|
+
}
|
|
1690
|
+
getItems() {
|
|
1691
|
+
return this.items;
|
|
1692
|
+
}
|
|
1693
|
+
attach(target) {
|
|
1694
|
+
const entity = this.ensureEntity(target);
|
|
1695
|
+
const id = this.extractId(entity);
|
|
1696
|
+
if (id == null) return;
|
|
1697
|
+
if (this.items.some((item) => this.extractId(item) === id)) {
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
this.items.push(entity);
|
|
1701
|
+
this.ctx.registerRelationChange(
|
|
1702
|
+
this.root,
|
|
1703
|
+
this.relationKey,
|
|
1704
|
+
this.rootTable,
|
|
1705
|
+
this.relationName,
|
|
1706
|
+
this.relation,
|
|
1707
|
+
{ kind: "attach", entity }
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
detach(target) {
|
|
1711
|
+
const id = typeof target === "number" || typeof target === "string" ? target : this.extractId(target);
|
|
1712
|
+
if (id == null) return;
|
|
1713
|
+
const existing = this.items.find((item) => this.extractId(item) === id);
|
|
1714
|
+
if (!existing) return;
|
|
1715
|
+
this.items = this.items.filter((item) => this.extractId(item) !== id);
|
|
1716
|
+
this.ctx.registerRelationChange(
|
|
1717
|
+
this.root,
|
|
1718
|
+
this.relationKey,
|
|
1719
|
+
this.rootTable,
|
|
1720
|
+
this.relationName,
|
|
1721
|
+
this.relation,
|
|
1722
|
+
{ kind: "detach", entity: existing }
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
async syncByIds(ids) {
|
|
1726
|
+
await this.load();
|
|
1727
|
+
const targetKey = this.relation.targetKey || findPrimaryKey(this.relation.target);
|
|
1728
|
+
const normalized = new Set(ids.map((id) => toKey4(id)));
|
|
1729
|
+
const currentIds = new Set(this.items.map((item) => toKey4(this.extractId(item))));
|
|
1730
|
+
for (const id of normalized) {
|
|
1731
|
+
if (!currentIds.has(id)) {
|
|
1732
|
+
this.attach(id);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
for (const item of [...this.items]) {
|
|
1736
|
+
const itemId = toKey4(this.extractId(item));
|
|
1737
|
+
if (!normalized.has(itemId)) {
|
|
1738
|
+
this.detach(item);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
ensureEntity(target) {
|
|
1743
|
+
if (typeof target === "number" || typeof target === "string") {
|
|
1744
|
+
const stub = {
|
|
1745
|
+
[this.targetKey]: target
|
|
1746
|
+
};
|
|
1747
|
+
return this.createEntity(stub);
|
|
1748
|
+
}
|
|
1749
|
+
return target;
|
|
1750
|
+
}
|
|
1751
|
+
extractId(entity) {
|
|
1752
|
+
if (entity === null || entity === void 0) return null;
|
|
1753
|
+
if (typeof entity === "number" || typeof entity === "string") {
|
|
1754
|
+
return entity;
|
|
1755
|
+
}
|
|
1756
|
+
return entity[this.targetKey] ?? null;
|
|
1757
|
+
}
|
|
1758
|
+
get relationKey() {
|
|
1759
|
+
return `${this.rootTable.name}.${this.relationName}`;
|
|
1760
|
+
}
|
|
1761
|
+
get targetKey() {
|
|
1762
|
+
return this.relation.targetKey || findPrimaryKey(this.relation.target);
|
|
1763
|
+
}
|
|
1764
|
+
hydrateFromCache() {
|
|
1765
|
+
const keyValue = this.root[this.localKey];
|
|
1766
|
+
if (keyValue === void 0 || keyValue === null) return;
|
|
1767
|
+
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
1768
|
+
if (!rows?.length) return;
|
|
1769
|
+
this.items = rows.map((row) => {
|
|
1770
|
+
const entity = this.createEntity(row);
|
|
1771
|
+
if (row._pivot) {
|
|
1772
|
+
entity._pivot = row._pivot;
|
|
1773
|
+
}
|
|
1774
|
+
return entity;
|
|
1775
|
+
});
|
|
1776
|
+
this.loaded = true;
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
|
|
1780
|
+
// src/orm/lazy-batch.ts
|
|
1781
|
+
var selectAllColumns = (table) => Object.entries(table.columns).reduce((acc, [name, def]) => {
|
|
1782
|
+
acc[name] = def;
|
|
1783
|
+
return acc;
|
|
1784
|
+
}, {});
|
|
1785
|
+
var rowsFromResults = (results) => {
|
|
1786
|
+
const rows = [];
|
|
1787
|
+
for (const result of results) {
|
|
1788
|
+
const { columns, values } = result;
|
|
1789
|
+
for (const valueRow of values) {
|
|
1790
|
+
const row = {};
|
|
1791
|
+
columns.forEach((column, idx) => {
|
|
1792
|
+
row[column] = valueRow[idx];
|
|
1793
|
+
});
|
|
1794
|
+
rows.push(row);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
return rows;
|
|
1798
|
+
};
|
|
1799
|
+
var executeQuery = async (ctx, qb) => {
|
|
1800
|
+
const compiled = ctx.dialect.compileSelect(qb.getAST());
|
|
1801
|
+
const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
1802
|
+
return rowsFromResults(results);
|
|
1803
|
+
};
|
|
1804
|
+
var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1805
|
+
var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
1806
|
+
const localKey = relation.localKey || findPrimaryKey(rootTable);
|
|
1807
|
+
const roots = ctx.getEntitiesForTable(rootTable);
|
|
1808
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1809
|
+
for (const tracked of roots) {
|
|
1810
|
+
const value = tracked.entity[localKey];
|
|
1811
|
+
if (value !== null && value !== void 0) {
|
|
1812
|
+
keys.add(value);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
if (!keys.size) {
|
|
1816
|
+
return /* @__PURE__ */ new Map();
|
|
1817
|
+
}
|
|
1818
|
+
const selectMap = selectAllColumns(relation.target);
|
|
1819
|
+
const fb = new SelectQueryBuilder(relation.target).select(selectMap);
|
|
1820
|
+
const fkColumn = relation.target.columns[relation.foreignKey];
|
|
1821
|
+
if (!fkColumn) return /* @__PURE__ */ new Map();
|
|
1822
|
+
fb.where(inList(fkColumn, Array.from(keys)));
|
|
1823
|
+
const rows = await executeQuery(ctx, fb);
|
|
1824
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1825
|
+
for (const row of rows) {
|
|
1826
|
+
const fkValue = row[relation.foreignKey];
|
|
1827
|
+
if (fkValue === null || fkValue === void 0) continue;
|
|
1828
|
+
const key = toKey5(fkValue);
|
|
1829
|
+
const bucket = grouped.get(key) ?? [];
|
|
1830
|
+
bucket.push(row);
|
|
1831
|
+
grouped.set(key, bucket);
|
|
1832
|
+
}
|
|
1833
|
+
return grouped;
|
|
1834
|
+
};
|
|
1835
|
+
var loadBelongsToRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
1836
|
+
const roots = ctx.getEntitiesForTable(rootTable);
|
|
1837
|
+
const foreignKeys = /* @__PURE__ */ new Set();
|
|
1838
|
+
for (const tracked of roots) {
|
|
1839
|
+
const value = tracked.entity[relation.foreignKey];
|
|
1840
|
+
if (value !== null && value !== void 0) {
|
|
1841
|
+
foreignKeys.add(value);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
if (!foreignKeys.size) {
|
|
1845
|
+
return /* @__PURE__ */ new Map();
|
|
1846
|
+
}
|
|
1847
|
+
const selectMap = selectAllColumns(relation.target);
|
|
1848
|
+
const qb = new SelectQueryBuilder(relation.target).select(selectMap);
|
|
1849
|
+
const targetKey = relation.localKey || findPrimaryKey(relation.target);
|
|
1850
|
+
const pkColumn = relation.target.columns[targetKey];
|
|
1851
|
+
if (!pkColumn) return /* @__PURE__ */ new Map();
|
|
1852
|
+
qb.where(inList(pkColumn, Array.from(foreignKeys)));
|
|
1853
|
+
const rows = await executeQuery(ctx, qb);
|
|
1854
|
+
const map = /* @__PURE__ */ new Map();
|
|
1855
|
+
for (const row of rows) {
|
|
1856
|
+
const keyValue = row[targetKey];
|
|
1857
|
+
if (keyValue === null || keyValue === void 0) continue;
|
|
1858
|
+
map.set(toKey5(keyValue), row);
|
|
1859
|
+
}
|
|
1860
|
+
return map;
|
|
1861
|
+
};
|
|
1862
|
+
var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
1863
|
+
const rootKey = relation.localKey || findPrimaryKey(rootTable);
|
|
1864
|
+
const roots = ctx.getEntitiesForTable(rootTable);
|
|
1865
|
+
const rootIds = /* @__PURE__ */ new Set();
|
|
1866
|
+
for (const tracked of roots) {
|
|
1867
|
+
const value = tracked.entity[rootKey];
|
|
1868
|
+
if (value !== null && value !== void 0) {
|
|
1869
|
+
rootIds.add(value);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
if (!rootIds.size) {
|
|
1873
|
+
return /* @__PURE__ */ new Map();
|
|
1874
|
+
}
|
|
1875
|
+
const pivotSelect = selectAllColumns(relation.pivotTable);
|
|
1876
|
+
const pivotQb = new SelectQueryBuilder(relation.pivotTable).select(pivotSelect);
|
|
1877
|
+
const pivotFkCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
|
|
1878
|
+
if (!pivotFkCol) return /* @__PURE__ */ new Map();
|
|
1879
|
+
pivotQb.where(inList(pivotFkCol, Array.from(rootIds)));
|
|
1880
|
+
const pivotRows = await executeQuery(ctx, pivotQb);
|
|
1881
|
+
const rootLookup = /* @__PURE__ */ new Map();
|
|
1882
|
+
const targetIds = /* @__PURE__ */ new Set();
|
|
1883
|
+
for (const pivot of pivotRows) {
|
|
1884
|
+
const rootValue = pivot[relation.pivotForeignKeyToRoot];
|
|
1885
|
+
const targetValue = pivot[relation.pivotForeignKeyToTarget];
|
|
1886
|
+
if (rootValue === null || rootValue === void 0 || targetValue === null || targetValue === void 0) {
|
|
1887
|
+
continue;
|
|
1888
|
+
}
|
|
1889
|
+
const bucket = rootLookup.get(toKey5(rootValue)) ?? [];
|
|
1890
|
+
bucket.push({
|
|
1891
|
+
targetId: targetValue,
|
|
1892
|
+
pivot: { ...pivot }
|
|
1893
|
+
});
|
|
1894
|
+
rootLookup.set(toKey5(rootValue), bucket);
|
|
1895
|
+
targetIds.add(targetValue);
|
|
1896
|
+
}
|
|
1897
|
+
if (!targetIds.size) {
|
|
1898
|
+
return /* @__PURE__ */ new Map();
|
|
1899
|
+
}
|
|
1900
|
+
const targetSelect = selectAllColumns(relation.target);
|
|
1901
|
+
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
1902
|
+
const targetPkColumn = relation.target.columns[targetKey];
|
|
1903
|
+
if (!targetPkColumn) return /* @__PURE__ */ new Map();
|
|
1904
|
+
const targetQb = new SelectQueryBuilder(relation.target).select(targetSelect);
|
|
1905
|
+
targetQb.where(inList(targetPkColumn, Array.from(targetIds)));
|
|
1906
|
+
const targetRows = await executeQuery(ctx, targetQb);
|
|
1907
|
+
const targetMap = /* @__PURE__ */ new Map();
|
|
1908
|
+
for (const row of targetRows) {
|
|
1909
|
+
const pkValue = row[targetKey];
|
|
1910
|
+
if (pkValue === null || pkValue === void 0) continue;
|
|
1911
|
+
targetMap.set(toKey5(pkValue), row);
|
|
1912
|
+
}
|
|
1913
|
+
const result = /* @__PURE__ */ new Map();
|
|
1914
|
+
for (const [rootId, entries] of rootLookup.entries()) {
|
|
1915
|
+
const bucket = [];
|
|
1916
|
+
for (const entry of entries) {
|
|
1917
|
+
const targetRow = targetMap.get(toKey5(entry.targetId));
|
|
1918
|
+
if (!targetRow) continue;
|
|
1919
|
+
bucket.push({
|
|
1920
|
+
...targetRow,
|
|
1921
|
+
_pivot: entry.pivot
|
|
1922
|
+
});
|
|
1923
|
+
}
|
|
1924
|
+
result.set(rootId, bucket);
|
|
1925
|
+
}
|
|
1926
|
+
return result;
|
|
1927
|
+
};
|
|
1928
|
+
|
|
1929
|
+
// src/orm/entity.ts
|
|
1930
|
+
var relationLoaderCache = (meta, relationName, factory) => {
|
|
1931
|
+
if (meta.relationCache.has(relationName)) {
|
|
1932
|
+
return meta.relationCache.get(relationName);
|
|
1933
|
+
}
|
|
1934
|
+
const promise = factory().then((value) => {
|
|
1935
|
+
for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
|
|
1936
|
+
const otherMeta = getEntityMeta(tracked.entity);
|
|
1937
|
+
if (!otherMeta) continue;
|
|
1938
|
+
otherMeta.relationHydration.set(relationName, value);
|
|
1939
|
+
}
|
|
1940
|
+
return value;
|
|
1941
|
+
});
|
|
1942
|
+
meta.relationCache.set(relationName, promise);
|
|
1943
|
+
for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
|
|
1944
|
+
const otherMeta = getEntityMeta(tracked.entity);
|
|
1945
|
+
if (!otherMeta) continue;
|
|
1946
|
+
otherMeta.relationCache.set(relationName, promise);
|
|
1947
|
+
}
|
|
1948
|
+
return promise;
|
|
1949
|
+
};
|
|
1950
|
+
var createEntityProxy = (ctx, table, row, lazyRelations = []) => {
|
|
1951
|
+
const target = { ...row };
|
|
1952
|
+
const meta = {
|
|
1953
|
+
ctx,
|
|
1954
|
+
table,
|
|
1955
|
+
lazyRelations: [...lazyRelations],
|
|
1956
|
+
relationCache: /* @__PURE__ */ new Map(),
|
|
1957
|
+
relationHydration: /* @__PURE__ */ new Map(),
|
|
1958
|
+
relationWrappers: /* @__PURE__ */ new Map()
|
|
1959
|
+
};
|
|
1960
|
+
Object.defineProperty(target, ENTITY_META, {
|
|
1961
|
+
value: meta,
|
|
1962
|
+
enumerable: false,
|
|
1963
|
+
writable: false
|
|
1964
|
+
});
|
|
1965
|
+
let proxy;
|
|
1966
|
+
const handler = {
|
|
1967
|
+
get(targetObj, prop, receiver) {
|
|
1968
|
+
if (prop === ENTITY_META) {
|
|
1969
|
+
return meta;
|
|
1970
|
+
}
|
|
1971
|
+
if (prop === "$load") {
|
|
1972
|
+
return async (relationName) => {
|
|
1973
|
+
const wrapper = getRelationWrapper(meta, relationName, proxy);
|
|
1974
|
+
if (wrapper && typeof wrapper.load === "function") {
|
|
1975
|
+
return wrapper.load();
|
|
1976
|
+
}
|
|
1977
|
+
return void 0;
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
if (typeof prop === "string" && table.relations[prop]) {
|
|
1981
|
+
return getRelationWrapper(meta, prop, proxy);
|
|
1982
|
+
}
|
|
1983
|
+
return Reflect.get(targetObj, prop, receiver);
|
|
1984
|
+
},
|
|
1985
|
+
set(targetObj, prop, value, receiver) {
|
|
1986
|
+
const result = Reflect.set(targetObj, prop, value, receiver);
|
|
1987
|
+
if (typeof prop === "string" && table.columns[prop]) {
|
|
1988
|
+
ctx.markDirty(proxy);
|
|
1989
|
+
}
|
|
1990
|
+
return result;
|
|
1991
|
+
}
|
|
1992
|
+
};
|
|
1993
|
+
proxy = new Proxy(target, handler);
|
|
1994
|
+
populateHydrationCache(proxy, row, meta);
|
|
1995
|
+
return proxy;
|
|
1996
|
+
};
|
|
1997
|
+
var createEntityFromRow = (ctx, table, row, lazyRelations = []) => {
|
|
1998
|
+
const pkName = findPrimaryKey(table);
|
|
1999
|
+
const pkValue = row[pkName];
|
|
2000
|
+
if (pkValue !== void 0 && pkValue !== null) {
|
|
2001
|
+
const tracked = ctx.getEntity(table, pkValue);
|
|
2002
|
+
if (tracked) return tracked;
|
|
2003
|
+
}
|
|
2004
|
+
const entity = createEntityProxy(ctx, table, row, lazyRelations);
|
|
2005
|
+
if (pkValue !== void 0 && pkValue !== null) {
|
|
2006
|
+
ctx.trackManaged(table, pkValue, entity);
|
|
2007
|
+
} else {
|
|
2008
|
+
ctx.trackNew(table, entity);
|
|
2009
|
+
}
|
|
2010
|
+
return entity;
|
|
2011
|
+
};
|
|
2012
|
+
var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
2013
|
+
var populateHydrationCache = (entity, row, meta) => {
|
|
2014
|
+
for (const relationName of Object.keys(meta.table.relations)) {
|
|
2015
|
+
const relation = meta.table.relations[relationName];
|
|
2016
|
+
const data = row[relationName];
|
|
2017
|
+
if (!Array.isArray(data)) continue;
|
|
2018
|
+
if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
|
|
2019
|
+
const localKey = relation.localKey || findPrimaryKey(meta.table);
|
|
2020
|
+
const rootValue = entity[localKey];
|
|
2021
|
+
if (rootValue === void 0 || rootValue === null) continue;
|
|
2022
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2023
|
+
cache.set(toKey6(rootValue), data);
|
|
2024
|
+
meta.relationHydration.set(relationName, cache);
|
|
2025
|
+
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
2026
|
+
continue;
|
|
2027
|
+
}
|
|
2028
|
+
if (relation.type === RelationKinds.BelongsTo) {
|
|
2029
|
+
const targetKey = relation.localKey || findPrimaryKey(relation.target);
|
|
2030
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2031
|
+
for (const item of data) {
|
|
2032
|
+
const pkValue = item[targetKey];
|
|
2033
|
+
if (pkValue === void 0 || pkValue === null) continue;
|
|
2034
|
+
cache.set(toKey6(pkValue), item);
|
|
2035
|
+
}
|
|
2036
|
+
if (cache.size) {
|
|
2037
|
+
meta.relationHydration.set(relationName, cache);
|
|
2038
|
+
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
};
|
|
2043
|
+
var getRelationWrapper = (meta, relationName, owner) => {
|
|
2044
|
+
if (meta.relationWrappers.has(relationName)) {
|
|
2045
|
+
return meta.relationWrappers.get(relationName);
|
|
2046
|
+
}
|
|
2047
|
+
const relation = meta.table.relations[relationName];
|
|
2048
|
+
if (!relation) return void 0;
|
|
2049
|
+
const wrapper = instantiateWrapper(meta, relationName, relation, owner);
|
|
2050
|
+
if (wrapper) {
|
|
2051
|
+
meta.relationWrappers.set(relationName, wrapper);
|
|
2052
|
+
}
|
|
2053
|
+
return wrapper;
|
|
2054
|
+
};
|
|
2055
|
+
var instantiateWrapper = (meta, relationName, relation, owner) => {
|
|
2056
|
+
switch (relation.type) {
|
|
2057
|
+
case RelationKinds.HasMany: {
|
|
2058
|
+
const hasMany2 = relation;
|
|
2059
|
+
const localKey = hasMany2.localKey || findPrimaryKey(meta.table);
|
|
2060
|
+
const loader = () => relationLoaderCache(
|
|
2061
|
+
meta,
|
|
2062
|
+
relationName,
|
|
2063
|
+
() => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2)
|
|
2064
|
+
);
|
|
2065
|
+
return new DefaultHasManyCollection(
|
|
2066
|
+
meta.ctx,
|
|
2067
|
+
meta,
|
|
2068
|
+
owner,
|
|
2069
|
+
relationName,
|
|
2070
|
+
hasMany2,
|
|
2071
|
+
meta.table,
|
|
2072
|
+
loader,
|
|
2073
|
+
(row) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
2074
|
+
localKey
|
|
2075
|
+
);
|
|
2076
|
+
}
|
|
2077
|
+
case RelationKinds.BelongsTo: {
|
|
2078
|
+
const belongsTo2 = relation;
|
|
2079
|
+
const targetKey = belongsTo2.localKey || findPrimaryKey(belongsTo2.target);
|
|
2080
|
+
const loader = () => relationLoaderCache(
|
|
2081
|
+
meta,
|
|
2082
|
+
relationName,
|
|
2083
|
+
() => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2)
|
|
2084
|
+
);
|
|
2085
|
+
return new DefaultBelongsToReference(
|
|
2086
|
+
meta.ctx,
|
|
2087
|
+
meta,
|
|
2088
|
+
owner,
|
|
2089
|
+
relationName,
|
|
2090
|
+
belongsTo2,
|
|
2091
|
+
meta.table,
|
|
2092
|
+
loader,
|
|
2093
|
+
(row) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
2094
|
+
targetKey
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
case RelationKinds.BelongsToMany: {
|
|
2098
|
+
const many = relation;
|
|
2099
|
+
const localKey = many.localKey || findPrimaryKey(meta.table);
|
|
2100
|
+
const loader = () => relationLoaderCache(
|
|
2101
|
+
meta,
|
|
2102
|
+
relationName,
|
|
2103
|
+
() => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many)
|
|
2104
|
+
);
|
|
2105
|
+
return new DefaultManyToManyCollection(
|
|
2106
|
+
meta.ctx,
|
|
2107
|
+
meta,
|
|
2108
|
+
owner,
|
|
2109
|
+
relationName,
|
|
2110
|
+
many,
|
|
2111
|
+
meta.table,
|
|
2112
|
+
loader,
|
|
2113
|
+
(row) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
2114
|
+
localKey
|
|
2115
|
+
);
|
|
2116
|
+
}
|
|
2117
|
+
default:
|
|
2118
|
+
return void 0;
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2121
|
+
|
|
2122
|
+
// src/orm/execute.ts
|
|
2123
|
+
var flattenResults = (results) => {
|
|
2124
|
+
const rows = [];
|
|
2125
|
+
for (const result of results) {
|
|
2126
|
+
const { columns, values } = result;
|
|
2127
|
+
for (const valueRow of values) {
|
|
2128
|
+
const row = {};
|
|
2129
|
+
columns.forEach((column, idx) => {
|
|
2130
|
+
row[column] = valueRow[idx];
|
|
2131
|
+
});
|
|
2132
|
+
rows.push(row);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
return rows;
|
|
2136
|
+
};
|
|
2137
|
+
async function executeHydrated(ctx, qb) {
|
|
2138
|
+
const compiled = ctx.dialect.compileSelect(qb.getAST());
|
|
2139
|
+
const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
2140
|
+
const rows = flattenResults(executed);
|
|
2141
|
+
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
2142
|
+
return hydrated.map(
|
|
2143
|
+
(row) => createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
// src/query-builder/select.ts
|
|
2148
|
+
var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
2149
|
+
/**
|
|
2150
|
+
* Creates a new SelectQueryBuilder instance
|
|
2151
|
+
* @param table - Table definition to query
|
|
2152
|
+
* @param state - Optional initial query state
|
|
2153
|
+
* @param hydration - Optional hydration manager
|
|
2154
|
+
* @param dependencies - Optional query builder dependencies
|
|
2155
|
+
*/
|
|
2156
|
+
constructor(table, state, hydration, dependencies, lazyRelations) {
|
|
2157
|
+
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
2158
|
+
this.env = { table, deps };
|
|
2159
|
+
const initialState = state ?? deps.createState(table);
|
|
2160
|
+
const initialHydration = hydration ?? deps.createHydration(table);
|
|
2161
|
+
this.context = {
|
|
2162
|
+
state: initialState,
|
|
2163
|
+
hydration: initialHydration
|
|
2164
|
+
};
|
|
2165
|
+
this.lazyRelations = new Set(lazyRelations ?? []);
|
|
2166
|
+
this.columnSelector = new ColumnSelector(this.env);
|
|
2167
|
+
this.relationManager = new RelationManager(this.env);
|
|
2168
|
+
}
|
|
2169
|
+
clone(context = this.context, lazyRelations = new Set(this.lazyRelations)) {
|
|
2170
|
+
return new _SelectQueryBuilder(this.env.table, context.state, context.hydration, this.env.deps, lazyRelations);
|
|
2171
|
+
}
|
|
2172
|
+
resolveQueryNode(query) {
|
|
2173
|
+
return typeof query.getAST === "function" ? query.getAST() : query;
|
|
2174
|
+
}
|
|
2175
|
+
createChildBuilder(table) {
|
|
2176
|
+
return new _SelectQueryBuilder(table, void 0, void 0, this.env.deps);
|
|
2177
|
+
}
|
|
2178
|
+
applyAst(context, mutator) {
|
|
2179
|
+
const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
|
|
2180
|
+
const nextState = mutator(astService);
|
|
2181
|
+
return { state: nextState, hydration: context.hydration };
|
|
2182
|
+
}
|
|
2183
|
+
applyJoin(context, table, condition, kind) {
|
|
2184
|
+
const joinNode = createJoinNode(kind, table.name, condition);
|
|
2185
|
+
return this.applyAst(context, (service) => service.withJoin(joinNode));
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Selects specific columns for the query
|
|
2189
|
+
* @param columns - Record of column definitions, function nodes, case expressions, or window functions
|
|
2190
|
+
* @returns New query builder instance with selected columns
|
|
2191
|
+
*/
|
|
2192
|
+
select(columns) {
|
|
2193
|
+
return this.clone(this.columnSelector.select(this.context, columns));
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Selects raw column expressions
|
|
2197
|
+
* @param cols - Column expressions as strings
|
|
2198
|
+
* @returns New query builder instance with raw column selections
|
|
2199
|
+
*/
|
|
2200
|
+
selectRaw(...cols) {
|
|
2201
|
+
return this.clone(this.columnSelector.selectRaw(this.context, cols));
|
|
2202
|
+
}
|
|
2203
|
+
/**
|
|
2204
|
+
* Adds a Common Table Expression (CTE) to the query
|
|
2205
|
+
* @param name - Name of the CTE
|
|
2206
|
+
* @param query - Query builder or query node for the CTE
|
|
2207
|
+
* @param columns - Optional column names for the CTE
|
|
2208
|
+
* @returns New query builder instance with the CTE
|
|
2209
|
+
*/
|
|
2210
|
+
with(name, query, columns) {
|
|
2211
|
+
const subAst = this.resolveQueryNode(query);
|
|
2212
|
+
const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, false));
|
|
2213
|
+
return this.clone(nextContext);
|
|
2214
|
+
}
|
|
2215
|
+
/**
|
|
2216
|
+
* Adds a recursive Common Table Expression (CTE) to the query
|
|
2217
|
+
* @param name - Name of the CTE
|
|
2218
|
+
* @param query - Query builder or query node for the CTE
|
|
2219
|
+
* @param columns - Optional column names for the CTE
|
|
2220
|
+
* @returns New query builder instance with the recursive CTE
|
|
2221
|
+
*/
|
|
2222
|
+
withRecursive(name, query, columns) {
|
|
2223
|
+
const subAst = this.resolveQueryNode(query);
|
|
2224
|
+
const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, true));
|
|
2225
|
+
return this.clone(nextContext);
|
|
2226
|
+
}
|
|
2227
|
+
/**
|
|
2228
|
+
* Selects a subquery as a column
|
|
2229
|
+
* @param alias - Alias for the subquery column
|
|
2230
|
+
* @param sub - Query builder or query node for the subquery
|
|
2231
|
+
* @returns New query builder instance with the subquery selection
|
|
2232
|
+
*/
|
|
2233
|
+
selectSubquery(alias, sub) {
|
|
2234
|
+
const query = this.resolveQueryNode(sub);
|
|
2235
|
+
return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Adds an INNER JOIN to the query
|
|
2239
|
+
* @param table - Table to join
|
|
2240
|
+
* @param condition - Join condition expression
|
|
2241
|
+
* @returns New query builder instance with the INNER JOIN
|
|
2242
|
+
*/
|
|
2243
|
+
innerJoin(table, condition) {
|
|
2244
|
+
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
|
|
2245
|
+
return this.clone(nextContext);
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Adds a LEFT JOIN to the query
|
|
2249
|
+
* @param table - Table to join
|
|
2250
|
+
* @param condition - Join condition expression
|
|
2251
|
+
* @returns New query builder instance with the LEFT JOIN
|
|
2252
|
+
*/
|
|
2253
|
+
leftJoin(table, condition) {
|
|
2254
|
+
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
|
|
2255
|
+
return this.clone(nextContext);
|
|
2256
|
+
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Adds a RIGHT JOIN to the query
|
|
2259
|
+
* @param table - Table to join
|
|
2260
|
+
* @param condition - Join condition expression
|
|
2261
|
+
* @returns New query builder instance with the RIGHT JOIN
|
|
2262
|
+
*/
|
|
2263
|
+
rightJoin(table, condition) {
|
|
2264
|
+
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
|
|
2265
|
+
return this.clone(nextContext);
|
|
2266
|
+
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Matches records based on a relationship
|
|
2269
|
+
* @param relationName - Name of the relationship to match
|
|
2270
|
+
* @param predicate - Optional predicate expression
|
|
2271
|
+
* @returns New query builder instance with the relationship match
|
|
2272
|
+
*/
|
|
2273
|
+
match(relationName, predicate) {
|
|
2274
|
+
const nextContext = this.relationManager.match(this.context, relationName, predicate);
|
|
2275
|
+
return this.clone(nextContext);
|
|
2276
|
+
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Joins a related table
|
|
2279
|
+
* @param relationName - Name of the relationship to join
|
|
2280
|
+
* @param joinKind - Type of join (defaults to INNER)
|
|
2281
|
+
* @param extraCondition - Optional additional join condition
|
|
2282
|
+
* @returns New query builder instance with the relationship join
|
|
2283
|
+
*/
|
|
2284
|
+
joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
|
|
2285
|
+
const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
|
|
2286
|
+
return this.clone(nextContext);
|
|
2287
|
+
}
|
|
2288
|
+
/**
|
|
2289
|
+
* Includes related data in the query results
|
|
2290
|
+
* @param relationName - Name of the relationship to include
|
|
2291
|
+
* @param options - Optional include options
|
|
2292
|
+
* @returns New query builder instance with the relationship inclusion
|
|
2293
|
+
*/
|
|
2294
|
+
include(relationName, options) {
|
|
2295
|
+
const nextContext = this.relationManager.include(this.context, relationName, options);
|
|
2296
|
+
return this.clone(nextContext);
|
|
2297
|
+
}
|
|
2298
|
+
includeLazy(relationName) {
|
|
2299
|
+
const nextLazy = new Set(this.lazyRelations);
|
|
2300
|
+
nextLazy.add(relationName);
|
|
2301
|
+
return this.clone(this.context, nextLazy);
|
|
2302
|
+
}
|
|
2303
|
+
getLazyRelations() {
|
|
2304
|
+
return Array.from(this.lazyRelations);
|
|
2305
|
+
}
|
|
2306
|
+
getTable() {
|
|
2307
|
+
return this.env.table;
|
|
2308
|
+
}
|
|
2309
|
+
async execute(ctx) {
|
|
2310
|
+
return executeHydrated(ctx, this);
|
|
2311
|
+
}
|
|
2312
|
+
/**
|
|
2313
|
+
* Adds a WHERE condition to the query
|
|
2314
|
+
* @param expr - Expression for the WHERE clause
|
|
2315
|
+
* @returns New query builder instance with the WHERE condition
|
|
2316
|
+
*/
|
|
2317
|
+
where(expr) {
|
|
2318
|
+
const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
|
|
2319
|
+
return this.clone(nextContext);
|
|
2320
|
+
}
|
|
2321
|
+
/**
|
|
2322
|
+
* Adds a GROUP BY clause to the query
|
|
2323
|
+
* @param col - Column definition or column node to group by
|
|
2324
|
+
* @returns New query builder instance with the GROUP BY clause
|
|
2325
|
+
*/
|
|
2326
|
+
groupBy(col) {
|
|
2327
|
+
const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(col));
|
|
2328
|
+
return this.clone(nextContext);
|
|
2329
|
+
}
|
|
2330
|
+
/**
|
|
2331
|
+
* Adds a HAVING condition to the query
|
|
2332
|
+
* @param expr - Expression for the HAVING clause
|
|
2333
|
+
* @returns New query builder instance with the HAVING condition
|
|
2334
|
+
*/
|
|
2335
|
+
having(expr) {
|
|
2336
|
+
const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
|
|
2337
|
+
return this.clone(nextContext);
|
|
2338
|
+
}
|
|
2339
|
+
/**
|
|
2340
|
+
* Adds an ORDER BY clause to the query
|
|
2341
|
+
* @param col - Column definition or column node to order by
|
|
2342
|
+
* @param direction - Order direction (defaults to ASC)
|
|
2343
|
+
* @returns New query builder instance with the ORDER BY clause
|
|
2344
|
+
*/
|
|
2345
|
+
orderBy(col, direction = ORDER_DIRECTIONS.ASC) {
|
|
2346
|
+
const nextContext = this.applyAst(this.context, (service) => service.withOrderBy(col, direction));
|
|
2347
|
+
return this.clone(nextContext);
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Adds a DISTINCT clause to the query
|
|
2351
|
+
* @param cols - Columns to make distinct
|
|
2352
|
+
* @returns New query builder instance with the DISTINCT clause
|
|
2353
|
+
*/
|
|
2354
|
+
distinct(...cols) {
|
|
2355
|
+
return this.clone(this.columnSelector.distinct(this.context, cols));
|
|
2356
|
+
}
|
|
2357
|
+
/**
|
|
2358
|
+
* Adds a LIMIT clause to the query
|
|
2359
|
+
* @param n - Maximum number of rows to return
|
|
2360
|
+
* @returns New query builder instance with the LIMIT clause
|
|
2361
|
+
*/
|
|
2362
|
+
limit(n) {
|
|
2363
|
+
const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
|
|
2364
|
+
return this.clone(nextContext);
|
|
2365
|
+
}
|
|
2366
|
+
/**
|
|
2367
|
+
* Adds an OFFSET clause to the query
|
|
2368
|
+
* @param n - Number of rows to skip
|
|
2369
|
+
* @returns New query builder instance with the OFFSET clause
|
|
2370
|
+
*/
|
|
2371
|
+
offset(n) {
|
|
2372
|
+
const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
|
|
2373
|
+
return this.clone(nextContext);
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Adds a WHERE EXISTS condition to the query
|
|
2377
|
+
* @param subquery - Subquery to check for existence
|
|
2378
|
+
* @returns New query builder instance with the WHERE EXISTS condition
|
|
2379
|
+
*/
|
|
2380
|
+
whereExists(subquery) {
|
|
2381
|
+
const subAst = this.resolveQueryNode(subquery);
|
|
2382
|
+
return this.where(exists(subAst));
|
|
2383
|
+
}
|
|
2384
|
+
/**
|
|
2385
|
+
* Adds a WHERE NOT EXISTS condition to the query
|
|
2386
|
+
* @param subquery - Subquery to check for non-existence
|
|
2387
|
+
* @returns New query builder instance with the WHERE NOT EXISTS condition
|
|
2388
|
+
*/
|
|
2389
|
+
whereNotExists(subquery) {
|
|
2390
|
+
const subAst = this.resolveQueryNode(subquery);
|
|
2391
|
+
return this.where(notExists(subAst));
|
|
2392
|
+
}
|
|
2393
|
+
/**
|
|
2394
|
+
* Adds a WHERE EXISTS condition based on a relationship
|
|
2395
|
+
* @param relationName - Name of the relationship to check
|
|
2396
|
+
* @param callback - Optional callback to modify the relationship query
|
|
2397
|
+
* @returns New query builder instance with the relationship existence check
|
|
2398
|
+
*/
|
|
2399
|
+
whereHas(relationName, callback) {
|
|
2400
|
+
const relation = this.env.table.relations[relationName];
|
|
2401
|
+
if (!relation) {
|
|
2402
|
+
throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
|
|
2403
|
+
}
|
|
2404
|
+
let subQb = this.createChildBuilder(relation.target);
|
|
2405
|
+
if (callback) {
|
|
2406
|
+
subQb = callback(subQb);
|
|
2407
|
+
}
|
|
2408
|
+
const subAst = subQb.getAST();
|
|
2409
|
+
const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
|
|
2410
|
+
return this.where(exists(finalSubAst));
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Adds a WHERE NOT EXISTS condition based on a relationship
|
|
2414
|
+
* @param relationName - Name of the relationship to check
|
|
2415
|
+
* @param callback - Optional callback to modify the relationship query
|
|
2416
|
+
* @returns New query builder instance with the relationship non-existence check
|
|
2417
|
+
*/
|
|
2418
|
+
whereHasNot(relationName, callback) {
|
|
2419
|
+
const relation = this.env.table.relations[relationName];
|
|
2420
|
+
if (!relation) {
|
|
2421
|
+
throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
|
|
2422
|
+
}
|
|
2423
|
+
let subQb = this.createChildBuilder(relation.target);
|
|
2424
|
+
if (callback) {
|
|
2425
|
+
subQb = callback(subQb);
|
|
2426
|
+
}
|
|
2427
|
+
const subAst = subQb.getAST();
|
|
2428
|
+
const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
|
|
2429
|
+
return this.where(notExists(finalSubAst));
|
|
2430
|
+
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Compiles the query to SQL for a specific dialect
|
|
2433
|
+
* @param dialect - Database dialect to compile for
|
|
2434
|
+
* @returns Compiled query with SQL and parameters
|
|
2435
|
+
*/
|
|
2436
|
+
compile(dialect) {
|
|
2437
|
+
return dialect.compileSelect(this.context.state.ast);
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Converts the query to SQL string for a specific dialect
|
|
2441
|
+
* @param dialect - Database dialect to generate SQL for
|
|
2442
|
+
* @returns SQL string representation of the query
|
|
2443
|
+
*/
|
|
2444
|
+
toSql(dialect) {
|
|
2445
|
+
return this.compile(dialect).sql;
|
|
2446
|
+
}
|
|
2447
|
+
/**
|
|
2448
|
+
* Gets the hydration plan for the query
|
|
2449
|
+
* @returns Hydration plan or undefined if none exists
|
|
2450
|
+
*/
|
|
2451
|
+
getHydrationPlan() {
|
|
2452
|
+
return this.context.hydration.getPlan();
|
|
2453
|
+
}
|
|
2454
|
+
/**
|
|
2455
|
+
* Gets the Abstract Syntax Tree (AST) representation of the query
|
|
2456
|
+
* @returns Query AST with hydration applied
|
|
2457
|
+
*/
|
|
2458
|
+
getAST() {
|
|
2459
|
+
return this.context.hydration.applyToAst(this.context.state.ast);
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
|
|
2463
|
+
// src/decorators/bootstrap.ts
|
|
2464
|
+
var isTableDef = (value) => {
|
|
2465
|
+
return typeof value === "object" && value !== null && "columns" in value;
|
|
2466
|
+
};
|
|
2467
|
+
var unwrapTarget = (target) => {
|
|
2468
|
+
if (typeof target === "function" && target.prototype === void 0) {
|
|
2469
|
+
return target();
|
|
2470
|
+
}
|
|
2471
|
+
return target;
|
|
2472
|
+
};
|
|
2473
|
+
var resolveTableTarget = (target, tableMap) => {
|
|
2474
|
+
const resolved = unwrapTarget(target);
|
|
2475
|
+
if (isTableDef(resolved)) {
|
|
2476
|
+
return resolved;
|
|
2477
|
+
}
|
|
2478
|
+
const table = tableMap.get(resolved);
|
|
2479
|
+
if (!table) {
|
|
2480
|
+
throw new Error(`Entity '${resolved.name}' is not registered with decorators`);
|
|
2481
|
+
}
|
|
2482
|
+
return table;
|
|
2483
|
+
};
|
|
2484
|
+
var buildRelationDefinitions = (meta, tableMap) => {
|
|
2485
|
+
const relations = {};
|
|
2486
|
+
for (const [name, relation] of Object.entries(meta.relations)) {
|
|
2487
|
+
switch (relation.kind) {
|
|
2488
|
+
case RelationKinds.HasMany: {
|
|
2489
|
+
relations[name] = hasMany(
|
|
2490
|
+
resolveTableTarget(relation.target, tableMap),
|
|
2491
|
+
relation.foreignKey,
|
|
2492
|
+
relation.localKey,
|
|
2493
|
+
relation.cascade
|
|
2494
|
+
);
|
|
2495
|
+
break;
|
|
2496
|
+
}
|
|
2497
|
+
case RelationKinds.BelongsTo: {
|
|
2498
|
+
relations[name] = belongsTo(
|
|
2499
|
+
resolveTableTarget(relation.target, tableMap),
|
|
2500
|
+
relation.foreignKey,
|
|
2501
|
+
relation.localKey,
|
|
2502
|
+
relation.cascade
|
|
2503
|
+
);
|
|
2504
|
+
break;
|
|
2505
|
+
}
|
|
2506
|
+
case RelationKinds.BelongsToMany: {
|
|
2507
|
+
relations[name] = belongsToMany(
|
|
2508
|
+
resolveTableTarget(relation.target, tableMap),
|
|
2509
|
+
resolveTableTarget(relation.pivotTable, tableMap),
|
|
2510
|
+
{
|
|
2511
|
+
pivotForeignKeyToRoot: relation.pivotForeignKeyToRoot,
|
|
2512
|
+
pivotForeignKeyToTarget: relation.pivotForeignKeyToTarget,
|
|
2513
|
+
localKey: relation.localKey,
|
|
2514
|
+
targetKey: relation.targetKey,
|
|
2515
|
+
pivotPrimaryKey: relation.pivotPrimaryKey,
|
|
2516
|
+
defaultPivotColumns: relation.defaultPivotColumns,
|
|
2517
|
+
cascade: relation.cascade
|
|
2518
|
+
}
|
|
2519
|
+
);
|
|
2520
|
+
break;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
return relations;
|
|
2525
|
+
};
|
|
2526
|
+
var bootstrapEntities = () => {
|
|
2527
|
+
const metas = getAllEntityMetadata();
|
|
2528
|
+
const tableMap = /* @__PURE__ */ new Map();
|
|
2529
|
+
for (const meta of metas) {
|
|
2530
|
+
const table = buildTableDef(meta);
|
|
2531
|
+
tableMap.set(meta.target, table);
|
|
2532
|
+
}
|
|
2533
|
+
for (const meta of metas) {
|
|
2534
|
+
const table = meta.table;
|
|
2535
|
+
const relations = buildRelationDefinitions(meta, tableMap);
|
|
2536
|
+
table.relations = relations;
|
|
2537
|
+
}
|
|
2538
|
+
return metas.map((meta) => meta.table);
|
|
2539
|
+
};
|
|
2540
|
+
var getTableDefFromEntity = (ctor) => {
|
|
2541
|
+
const meta = getEntityMetadata(ctor);
|
|
2542
|
+
if (!meta) return void 0;
|
|
2543
|
+
return meta.table;
|
|
2544
|
+
};
|
|
2545
|
+
var selectFromEntity = (ctor) => {
|
|
2546
|
+
const table = getTableDefFromEntity(ctor);
|
|
2547
|
+
if (!table) {
|
|
2548
|
+
throw new Error("Entity metadata has not been bootstrapped");
|
|
2549
|
+
}
|
|
2550
|
+
return new SelectQueryBuilder(table);
|
|
2551
|
+
};
|
|
2552
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2553
|
+
0 && (module.exports = {
|
|
2554
|
+
BelongsTo,
|
|
2555
|
+
BelongsToMany,
|
|
2556
|
+
Column,
|
|
2557
|
+
Entity,
|
|
2558
|
+
HasMany,
|
|
2559
|
+
PrimaryKey,
|
|
2560
|
+
bootstrapEntities,
|
|
2561
|
+
getTableDefFromEntity,
|
|
2562
|
+
selectFromEntity
|
|
2563
|
+
});
|
|
2564
|
+
//# sourceMappingURL=index.cjs.map
|