metal-orm 1.0.8 → 1.0.10
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 +341 -146
- 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
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,4227 @@
|
|
|
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/index.ts
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
AsyncLocalStorage: () => AsyncLocalStorage,
|
|
23
|
+
DefaultBelongsToReference: () => DefaultBelongsToReference,
|
|
24
|
+
DefaultHasManyCollection: () => DefaultHasManyCollection,
|
|
25
|
+
DefaultManyToManyCollection: () => DefaultManyToManyCollection,
|
|
26
|
+
DeleteQueryBuilder: () => DeleteQueryBuilder,
|
|
27
|
+
EntityStatus: () => EntityStatus,
|
|
28
|
+
InsertQueryBuilder: () => InsertQueryBuilder,
|
|
29
|
+
MySqlDialect: () => MySqlDialect,
|
|
30
|
+
OrmContext: () => OrmContext,
|
|
31
|
+
RelationKinds: () => RelationKinds,
|
|
32
|
+
SelectQueryBuilder: () => SelectQueryBuilder,
|
|
33
|
+
SqlServerDialect: () => SqlServerDialect,
|
|
34
|
+
SqliteDialect: () => SqliteDialect,
|
|
35
|
+
TypeScriptGenerator: () => TypeScriptGenerator,
|
|
36
|
+
UpdateQueryBuilder: () => UpdateQueryBuilder,
|
|
37
|
+
addDomainEvent: () => addDomainEvent,
|
|
38
|
+
and: () => and,
|
|
39
|
+
avg: () => avg,
|
|
40
|
+
belongsTo: () => belongsTo,
|
|
41
|
+
belongsToMany: () => belongsToMany,
|
|
42
|
+
between: () => between,
|
|
43
|
+
caseWhen: () => caseWhen,
|
|
44
|
+
col: () => col,
|
|
45
|
+
columnOperand: () => columnOperand,
|
|
46
|
+
count: () => count,
|
|
47
|
+
createColumn: () => createColumn,
|
|
48
|
+
createEntityFromRow: () => createEntityFromRow,
|
|
49
|
+
createEntityProxy: () => createEntityProxy,
|
|
50
|
+
createLiteral: () => createLiteral,
|
|
51
|
+
defineTable: () => defineTable,
|
|
52
|
+
denseRank: () => denseRank,
|
|
53
|
+
eq: () => eq,
|
|
54
|
+
executeHydrated: () => executeHydrated,
|
|
55
|
+
exists: () => exists,
|
|
56
|
+
firstValue: () => firstValue,
|
|
57
|
+
gt: () => gt,
|
|
58
|
+
gte: () => gte,
|
|
59
|
+
hasMany: () => hasMany,
|
|
60
|
+
hydrateRows: () => hydrateRows,
|
|
61
|
+
inList: () => inList,
|
|
62
|
+
isCaseExpressionNode: () => isCaseExpressionNode,
|
|
63
|
+
isExpressionSelectionNode: () => isExpressionSelectionNode,
|
|
64
|
+
isFunctionNode: () => isFunctionNode,
|
|
65
|
+
isNotNull: () => isNotNull,
|
|
66
|
+
isNull: () => isNull,
|
|
67
|
+
isOperandNode: () => isOperandNode,
|
|
68
|
+
isWindowFunctionNode: () => isWindowFunctionNode,
|
|
69
|
+
jsonPath: () => jsonPath,
|
|
70
|
+
lag: () => lag,
|
|
71
|
+
lastValue: () => lastValue,
|
|
72
|
+
lead: () => lead,
|
|
73
|
+
like: () => like,
|
|
74
|
+
loadBelongsToManyRelation: () => loadBelongsToManyRelation,
|
|
75
|
+
loadBelongsToRelation: () => loadBelongsToRelation,
|
|
76
|
+
loadHasManyRelation: () => loadHasManyRelation,
|
|
77
|
+
lt: () => lt,
|
|
78
|
+
lte: () => lte,
|
|
79
|
+
neq: () => neq,
|
|
80
|
+
notBetween: () => notBetween,
|
|
81
|
+
notExists: () => notExists,
|
|
82
|
+
notInList: () => notInList,
|
|
83
|
+
notLike: () => notLike,
|
|
84
|
+
ntile: () => ntile,
|
|
85
|
+
or: () => or,
|
|
86
|
+
rank: () => rank,
|
|
87
|
+
rowNumber: () => rowNumber,
|
|
88
|
+
sum: () => sum,
|
|
89
|
+
valueToOperand: () => valueToOperand,
|
|
90
|
+
visitExpression: () => visitExpression,
|
|
91
|
+
visitOperand: () => visitOperand,
|
|
92
|
+
windowFunction: () => windowFunction
|
|
93
|
+
});
|
|
94
|
+
module.exports = __toCommonJS(index_exports);
|
|
95
|
+
|
|
96
|
+
// src/schema/table.ts
|
|
97
|
+
var defineTable = (name, columns, relations = {}, hooks) => {
|
|
98
|
+
const colsWithNames = Object.entries(columns).reduce((acc, [key, def]) => {
|
|
99
|
+
acc[key] = { ...def, name: key, table: name };
|
|
100
|
+
return acc;
|
|
101
|
+
}, {});
|
|
102
|
+
return { name, columns: colsWithNames, relations, hooks };
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// src/schema/column.ts
|
|
106
|
+
var col = {
|
|
107
|
+
/**
|
|
108
|
+
* Creates an integer column definition
|
|
109
|
+
* @returns ColumnDef with INT type
|
|
110
|
+
*/
|
|
111
|
+
int: () => ({ name: "", type: "INT" }),
|
|
112
|
+
/**
|
|
113
|
+
* Creates a variable character column definition
|
|
114
|
+
* @param length - Maximum length of the string
|
|
115
|
+
* @returns ColumnDef with VARCHAR type
|
|
116
|
+
*/
|
|
117
|
+
varchar: (length) => ({ name: "", type: "VARCHAR", args: [length] }),
|
|
118
|
+
/**
|
|
119
|
+
* Creates a JSON column definition
|
|
120
|
+
* @returns ColumnDef with JSON type
|
|
121
|
+
*/
|
|
122
|
+
json: () => ({ name: "", type: "JSON" }),
|
|
123
|
+
/**
|
|
124
|
+
* Creates a boolean column definition
|
|
125
|
+
* @returns ColumnDef with BOOLEAN type
|
|
126
|
+
*/
|
|
127
|
+
boolean: () => ({ name: "", type: "BOOLEAN" }),
|
|
128
|
+
/**
|
|
129
|
+
* Marks a column definition as a primary key
|
|
130
|
+
* @param def - Column definition to modify
|
|
131
|
+
* @returns Modified ColumnDef with primary: true
|
|
132
|
+
*/
|
|
133
|
+
primaryKey: (def) => ({ ...def, primary: true })
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// src/schema/relation.ts
|
|
137
|
+
var RelationKinds = {
|
|
138
|
+
/** One-to-many relationship */
|
|
139
|
+
HasMany: "HAS_MANY",
|
|
140
|
+
/** Many-to-one relationship */
|
|
141
|
+
BelongsTo: "BELONGS_TO",
|
|
142
|
+
/** Many-to-many relationship with pivot metadata */
|
|
143
|
+
BelongsToMany: "BELONGS_TO_MANY"
|
|
144
|
+
};
|
|
145
|
+
var hasMany = (target, foreignKey, localKey, cascade) => ({
|
|
146
|
+
type: RelationKinds.HasMany,
|
|
147
|
+
target,
|
|
148
|
+
foreignKey,
|
|
149
|
+
localKey,
|
|
150
|
+
cascade
|
|
151
|
+
});
|
|
152
|
+
var belongsTo = (target, foreignKey, localKey, cascade) => ({
|
|
153
|
+
type: RelationKinds.BelongsTo,
|
|
154
|
+
target,
|
|
155
|
+
foreignKey,
|
|
156
|
+
localKey,
|
|
157
|
+
cascade
|
|
158
|
+
});
|
|
159
|
+
var belongsToMany = (target, pivotTable, options) => ({
|
|
160
|
+
type: RelationKinds.BelongsToMany,
|
|
161
|
+
target,
|
|
162
|
+
pivotTable,
|
|
163
|
+
pivotForeignKeyToRoot: options.pivotForeignKeyToRoot,
|
|
164
|
+
pivotForeignKeyToTarget: options.pivotForeignKeyToTarget,
|
|
165
|
+
localKey: options.localKey,
|
|
166
|
+
targetKey: options.targetKey,
|
|
167
|
+
pivotPrimaryKey: options.pivotPrimaryKey,
|
|
168
|
+
defaultPivotColumns: options.defaultPivotColumns,
|
|
169
|
+
cascade: options.cascade
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// src/core/ast/expression-nodes.ts
|
|
173
|
+
var operandTypes = /* @__PURE__ */ new Set([
|
|
174
|
+
"Column",
|
|
175
|
+
"Literal",
|
|
176
|
+
"Function",
|
|
177
|
+
"JsonPath",
|
|
178
|
+
"ScalarSubquery",
|
|
179
|
+
"CaseExpression",
|
|
180
|
+
"WindowFunction"
|
|
181
|
+
]);
|
|
182
|
+
var isOperandNode = (node) => node && operandTypes.has(node.type);
|
|
183
|
+
var isFunctionNode = (node) => node?.type === "Function";
|
|
184
|
+
var isCaseExpressionNode = (node) => node?.type === "CaseExpression";
|
|
185
|
+
var isWindowFunctionNode = (node) => node?.type === "WindowFunction";
|
|
186
|
+
var isExpressionSelectionNode = (node) => isFunctionNode(node) || isCaseExpressionNode(node) || isWindowFunctionNode(node);
|
|
187
|
+
|
|
188
|
+
// src/core/ast/expression-builders.ts
|
|
189
|
+
var valueToOperand = (value) => {
|
|
190
|
+
if (value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
191
|
+
return { type: "Literal", value: value === void 0 ? null : value };
|
|
192
|
+
}
|
|
193
|
+
return value;
|
|
194
|
+
};
|
|
195
|
+
var toNode = (col2) => {
|
|
196
|
+
if (isOperandNode(col2)) return col2;
|
|
197
|
+
const def = col2;
|
|
198
|
+
return { type: "Column", table: def.table || "unknown", name: def.name };
|
|
199
|
+
};
|
|
200
|
+
var toLiteralNode = (value) => ({
|
|
201
|
+
type: "Literal",
|
|
202
|
+
value
|
|
203
|
+
});
|
|
204
|
+
var toOperand = (val) => {
|
|
205
|
+
if (val === null) return { type: "Literal", value: null };
|
|
206
|
+
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
|
|
207
|
+
return { type: "Literal", value: val };
|
|
208
|
+
}
|
|
209
|
+
return toNode(val);
|
|
210
|
+
};
|
|
211
|
+
var columnOperand = (col2) => toNode(col2);
|
|
212
|
+
var createBinaryExpression = (operator, left, right, escape) => {
|
|
213
|
+
const node = {
|
|
214
|
+
type: "BinaryExpression",
|
|
215
|
+
left: toNode(left),
|
|
216
|
+
operator,
|
|
217
|
+
right: toOperand(right)
|
|
218
|
+
};
|
|
219
|
+
if (escape !== void 0) {
|
|
220
|
+
node.escape = toLiteralNode(escape);
|
|
221
|
+
}
|
|
222
|
+
return node;
|
|
223
|
+
};
|
|
224
|
+
var eq = (left, right) => createBinaryExpression("=", left, right);
|
|
225
|
+
var neq = (left, right) => createBinaryExpression("!=", left, right);
|
|
226
|
+
var gt = (left, right) => createBinaryExpression(">", left, right);
|
|
227
|
+
var gte = (left, right) => createBinaryExpression(">=", left, right);
|
|
228
|
+
var lt = (left, right) => createBinaryExpression("<", left, right);
|
|
229
|
+
var lte = (left, right) => createBinaryExpression("<=", left, right);
|
|
230
|
+
var like = (left, pattern, escape) => createBinaryExpression("LIKE", left, pattern, escape);
|
|
231
|
+
var notLike = (left, pattern, escape) => createBinaryExpression("NOT LIKE", left, pattern, escape);
|
|
232
|
+
var and = (...operands) => ({
|
|
233
|
+
type: "LogicalExpression",
|
|
234
|
+
operator: "AND",
|
|
235
|
+
operands
|
|
236
|
+
});
|
|
237
|
+
var or = (...operands) => ({
|
|
238
|
+
type: "LogicalExpression",
|
|
239
|
+
operator: "OR",
|
|
240
|
+
operands
|
|
241
|
+
});
|
|
242
|
+
var isNull = (left) => ({
|
|
243
|
+
type: "NullExpression",
|
|
244
|
+
left: toNode(left),
|
|
245
|
+
operator: "IS NULL"
|
|
246
|
+
});
|
|
247
|
+
var isNotNull = (left) => ({
|
|
248
|
+
type: "NullExpression",
|
|
249
|
+
left: toNode(left),
|
|
250
|
+
operator: "IS NOT NULL"
|
|
251
|
+
});
|
|
252
|
+
var createInExpression = (operator, left, values) => ({
|
|
253
|
+
type: "InExpression",
|
|
254
|
+
left: toNode(left),
|
|
255
|
+
operator,
|
|
256
|
+
right: values.map((v) => toOperand(v))
|
|
257
|
+
});
|
|
258
|
+
var inList = (left, values) => createInExpression("IN", left, values);
|
|
259
|
+
var notInList = (left, values) => createInExpression("NOT IN", left, values);
|
|
260
|
+
var createBetweenExpression = (operator, left, lower, upper) => ({
|
|
261
|
+
type: "BetweenExpression",
|
|
262
|
+
left: toNode(left),
|
|
263
|
+
operator,
|
|
264
|
+
lower: toOperand(lower),
|
|
265
|
+
upper: toOperand(upper)
|
|
266
|
+
});
|
|
267
|
+
var between = (left, lower, upper) => createBetweenExpression("BETWEEN", left, lower, upper);
|
|
268
|
+
var notBetween = (left, lower, upper) => createBetweenExpression("NOT BETWEEN", left, lower, upper);
|
|
269
|
+
var jsonPath = (col2, path) => ({
|
|
270
|
+
type: "JsonPath",
|
|
271
|
+
column: columnOperand(col2),
|
|
272
|
+
path
|
|
273
|
+
});
|
|
274
|
+
var caseWhen = (conditions, elseValue) => ({
|
|
275
|
+
type: "CaseExpression",
|
|
276
|
+
conditions: conditions.map((c) => ({
|
|
277
|
+
when: c.when,
|
|
278
|
+
then: toOperand(c.then)
|
|
279
|
+
})),
|
|
280
|
+
else: elseValue !== void 0 ? toOperand(elseValue) : void 0
|
|
281
|
+
});
|
|
282
|
+
var exists = (subquery) => ({
|
|
283
|
+
type: "ExistsExpression",
|
|
284
|
+
operator: "EXISTS",
|
|
285
|
+
subquery
|
|
286
|
+
});
|
|
287
|
+
var notExists = (subquery) => ({
|
|
288
|
+
type: "ExistsExpression",
|
|
289
|
+
operator: "NOT EXISTS",
|
|
290
|
+
subquery
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// src/core/ast/window-functions.ts
|
|
294
|
+
var buildWindowFunction = (name, args = [], partitionBy, orderBy) => {
|
|
295
|
+
const node = {
|
|
296
|
+
type: "WindowFunction",
|
|
297
|
+
name,
|
|
298
|
+
args
|
|
299
|
+
};
|
|
300
|
+
if (partitionBy && partitionBy.length) {
|
|
301
|
+
node.partitionBy = partitionBy;
|
|
302
|
+
}
|
|
303
|
+
if (orderBy && orderBy.length) {
|
|
304
|
+
node.orderBy = orderBy;
|
|
305
|
+
}
|
|
306
|
+
return node;
|
|
307
|
+
};
|
|
308
|
+
var rowNumber = () => buildWindowFunction("ROW_NUMBER");
|
|
309
|
+
var rank = () => buildWindowFunction("RANK");
|
|
310
|
+
var denseRank = () => buildWindowFunction("DENSE_RANK");
|
|
311
|
+
var ntile = (n) => buildWindowFunction("NTILE", [{ type: "Literal", value: n }]);
|
|
312
|
+
var lag = (col2, offset = 1, defaultValue) => {
|
|
313
|
+
const args = [
|
|
314
|
+
columnOperand(col2),
|
|
315
|
+
{ type: "Literal", value: offset }
|
|
316
|
+
];
|
|
317
|
+
if (defaultValue !== void 0) {
|
|
318
|
+
args.push({ type: "Literal", value: defaultValue });
|
|
319
|
+
}
|
|
320
|
+
return buildWindowFunction("LAG", args);
|
|
321
|
+
};
|
|
322
|
+
var lead = (col2, offset = 1, defaultValue) => {
|
|
323
|
+
const args = [
|
|
324
|
+
columnOperand(col2),
|
|
325
|
+
{ type: "Literal", value: offset }
|
|
326
|
+
];
|
|
327
|
+
if (defaultValue !== void 0) {
|
|
328
|
+
args.push({ type: "Literal", value: defaultValue });
|
|
329
|
+
}
|
|
330
|
+
return buildWindowFunction("LEAD", args);
|
|
331
|
+
};
|
|
332
|
+
var firstValue = (col2) => buildWindowFunction("FIRST_VALUE", [columnOperand(col2)]);
|
|
333
|
+
var lastValue = (col2) => buildWindowFunction("LAST_VALUE", [columnOperand(col2)]);
|
|
334
|
+
var windowFunction = (name, args = [], partitionBy, orderBy) => {
|
|
335
|
+
const nodeArgs = args.map((arg) => {
|
|
336
|
+
if (typeof arg.value !== "undefined") {
|
|
337
|
+
return arg;
|
|
338
|
+
}
|
|
339
|
+
if ("path" in arg) {
|
|
340
|
+
return arg;
|
|
341
|
+
}
|
|
342
|
+
return columnOperand(arg);
|
|
343
|
+
});
|
|
344
|
+
const partitionNodes = partitionBy?.map((col2) => columnOperand(col2)) ?? void 0;
|
|
345
|
+
const orderNodes = orderBy?.map((o) => ({
|
|
346
|
+
type: "OrderBy",
|
|
347
|
+
column: columnOperand(o.column),
|
|
348
|
+
direction: o.direction
|
|
349
|
+
}));
|
|
350
|
+
return buildWindowFunction(name, nodeArgs, partitionNodes, orderNodes);
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// src/core/ast/aggregate-functions.ts
|
|
354
|
+
var buildAggregate = (name) => (col2) => ({
|
|
355
|
+
type: "Function",
|
|
356
|
+
name,
|
|
357
|
+
args: [columnOperand(col2)]
|
|
358
|
+
});
|
|
359
|
+
var count = buildAggregate("COUNT");
|
|
360
|
+
var sum = buildAggregate("SUM");
|
|
361
|
+
var avg = buildAggregate("AVG");
|
|
362
|
+
|
|
363
|
+
// src/core/ast/expression-visitor.ts
|
|
364
|
+
var unsupportedExpression = (node) => {
|
|
365
|
+
throw new Error(`Unsupported expression type "${node?.type ?? "unknown"}"`);
|
|
366
|
+
};
|
|
367
|
+
var unsupportedOperand = (node) => {
|
|
368
|
+
throw new Error(`Unsupported operand type "${node?.type ?? "unknown"}"`);
|
|
369
|
+
};
|
|
370
|
+
var visitExpression = (node, visitor) => {
|
|
371
|
+
switch (node.type) {
|
|
372
|
+
case "BinaryExpression":
|
|
373
|
+
return visitor.visitBinaryExpression(node);
|
|
374
|
+
case "LogicalExpression":
|
|
375
|
+
return visitor.visitLogicalExpression(node);
|
|
376
|
+
case "NullExpression":
|
|
377
|
+
return visitor.visitNullExpression(node);
|
|
378
|
+
case "InExpression":
|
|
379
|
+
return visitor.visitInExpression(node);
|
|
380
|
+
case "ExistsExpression":
|
|
381
|
+
return visitor.visitExistsExpression(node);
|
|
382
|
+
case "BetweenExpression":
|
|
383
|
+
return visitor.visitBetweenExpression(node);
|
|
384
|
+
default:
|
|
385
|
+
return unsupportedExpression(node);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
var visitOperand = (node, visitor) => {
|
|
389
|
+
switch (node.type) {
|
|
390
|
+
case "Column":
|
|
391
|
+
return visitor.visitColumn(node);
|
|
392
|
+
case "Literal":
|
|
393
|
+
return visitor.visitLiteral(node);
|
|
394
|
+
case "Function":
|
|
395
|
+
return visitor.visitFunction(node);
|
|
396
|
+
case "JsonPath":
|
|
397
|
+
return visitor.visitJsonPath(node);
|
|
398
|
+
case "ScalarSubquery":
|
|
399
|
+
return visitor.visitScalarSubquery(node);
|
|
400
|
+
case "CaseExpression":
|
|
401
|
+
return visitor.visitCaseExpression(node);
|
|
402
|
+
case "WindowFunction":
|
|
403
|
+
return visitor.visitWindowFunction(node);
|
|
404
|
+
default:
|
|
405
|
+
return unsupportedOperand(node);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// src/query-builder/select-query-state.ts
|
|
410
|
+
var SelectQueryState = class _SelectQueryState {
|
|
411
|
+
/**
|
|
412
|
+
* Creates a new SelectQueryState instance
|
|
413
|
+
* @param table - Table definition
|
|
414
|
+
* @param ast - Optional existing AST
|
|
415
|
+
*/
|
|
416
|
+
constructor(table, ast) {
|
|
417
|
+
this.table = table;
|
|
418
|
+
this.ast = ast ?? {
|
|
419
|
+
type: "SelectQuery",
|
|
420
|
+
from: { type: "Table", name: table.name },
|
|
421
|
+
columns: [],
|
|
422
|
+
joins: []
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Creates a new SelectQueryState with updated AST
|
|
427
|
+
* @param nextAst - Updated AST
|
|
428
|
+
* @returns New SelectQueryState instance
|
|
429
|
+
*/
|
|
430
|
+
clone(nextAst) {
|
|
431
|
+
return new _SelectQueryState(this.table, nextAst);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Adds columns to the query
|
|
435
|
+
* @param newCols - Columns to add
|
|
436
|
+
* @returns New SelectQueryState with added columns
|
|
437
|
+
*/
|
|
438
|
+
withColumns(newCols) {
|
|
439
|
+
return this.clone({
|
|
440
|
+
...this.ast,
|
|
441
|
+
columns: [...this.ast.columns ?? [], ...newCols]
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Adds a join to the query
|
|
446
|
+
* @param join - Join node to add
|
|
447
|
+
* @returns New SelectQueryState with added join
|
|
448
|
+
*/
|
|
449
|
+
withJoin(join) {
|
|
450
|
+
return this.clone({
|
|
451
|
+
...this.ast,
|
|
452
|
+
joins: [...this.ast.joins ?? [], join]
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Adds a WHERE clause to the query
|
|
457
|
+
* @param predicate - WHERE predicate expression
|
|
458
|
+
* @returns New SelectQueryState with WHERE clause
|
|
459
|
+
*/
|
|
460
|
+
withWhere(predicate) {
|
|
461
|
+
return this.clone({
|
|
462
|
+
...this.ast,
|
|
463
|
+
where: predicate
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Adds a HAVING clause to the query
|
|
468
|
+
* @param predicate - HAVING predicate expression
|
|
469
|
+
* @returns New SelectQueryState with HAVING clause
|
|
470
|
+
*/
|
|
471
|
+
withHaving(predicate) {
|
|
472
|
+
return this.clone({
|
|
473
|
+
...this.ast,
|
|
474
|
+
having: predicate
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Adds GROUP BY columns to the query
|
|
479
|
+
* @param columns - Columns to group by
|
|
480
|
+
* @returns New SelectQueryState with GROUP BY clause
|
|
481
|
+
*/
|
|
482
|
+
withGroupBy(columns) {
|
|
483
|
+
return this.clone({
|
|
484
|
+
...this.ast,
|
|
485
|
+
groupBy: [...this.ast.groupBy ?? [], ...columns]
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Adds ORDER BY clauses to the query
|
|
490
|
+
* @param orderBy - ORDER BY nodes
|
|
491
|
+
* @returns New SelectQueryState with ORDER BY clause
|
|
492
|
+
*/
|
|
493
|
+
withOrderBy(orderBy) {
|
|
494
|
+
return this.clone({
|
|
495
|
+
...this.ast,
|
|
496
|
+
orderBy: [...this.ast.orderBy ?? [], ...orderBy]
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Adds DISTINCT columns to the query
|
|
501
|
+
* @param columns - Columns to make distinct
|
|
502
|
+
* @returns New SelectQueryState with DISTINCT clause
|
|
503
|
+
*/
|
|
504
|
+
withDistinct(columns) {
|
|
505
|
+
return this.clone({
|
|
506
|
+
...this.ast,
|
|
507
|
+
distinct: [...this.ast.distinct ?? [], ...columns]
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Adds a LIMIT clause to the query
|
|
512
|
+
* @param limit - Maximum number of rows to return
|
|
513
|
+
* @returns New SelectQueryState with LIMIT clause
|
|
514
|
+
*/
|
|
515
|
+
withLimit(limit) {
|
|
516
|
+
return this.clone({
|
|
517
|
+
...this.ast,
|
|
518
|
+
limit
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Adds an OFFSET clause to the query
|
|
523
|
+
* @param offset - Number of rows to skip
|
|
524
|
+
* @returns New SelectQueryState with OFFSET clause
|
|
525
|
+
*/
|
|
526
|
+
withOffset(offset) {
|
|
527
|
+
return this.clone({
|
|
528
|
+
...this.ast,
|
|
529
|
+
offset
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Adds a Common Table Expression (CTE) to the query
|
|
534
|
+
* @param cte - CTE node to add
|
|
535
|
+
* @returns New SelectQueryState with CTE
|
|
536
|
+
*/
|
|
537
|
+
withCte(cte) {
|
|
538
|
+
return this.clone({
|
|
539
|
+
...this.ast,
|
|
540
|
+
ctes: [...this.ast.ctes ?? [], cte]
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// src/query-builder/hydration-manager.ts
|
|
546
|
+
var HydrationManager = class _HydrationManager {
|
|
547
|
+
/**
|
|
548
|
+
* Creates a new HydrationManager instance
|
|
549
|
+
* @param table - Table definition
|
|
550
|
+
* @param planner - Hydration planner
|
|
551
|
+
*/
|
|
552
|
+
constructor(table, planner) {
|
|
553
|
+
this.table = table;
|
|
554
|
+
this.planner = planner;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Creates a new HydrationManager with updated planner
|
|
558
|
+
* @param nextPlanner - Updated hydration planner
|
|
559
|
+
* @returns New HydrationManager instance
|
|
560
|
+
*/
|
|
561
|
+
clone(nextPlanner) {
|
|
562
|
+
return new _HydrationManager(this.table, nextPlanner);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Handles column selection for hydration planning
|
|
566
|
+
* @param state - Current query state
|
|
567
|
+
* @param newColumns - Newly selected columns
|
|
568
|
+
* @returns Updated HydrationManager with captured columns
|
|
569
|
+
*/
|
|
570
|
+
onColumnsSelected(state, newColumns) {
|
|
571
|
+
const updated = this.planner.captureRootColumns(newColumns);
|
|
572
|
+
return this.clone(updated);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Handles relation inclusion for hydration planning
|
|
576
|
+
* @param state - Current query state
|
|
577
|
+
* @param relation - Relation definition
|
|
578
|
+
* @param relationName - Name of the relation
|
|
579
|
+
* @param aliasPrefix - Alias prefix for the relation
|
|
580
|
+
* @param targetColumns - Target columns to include
|
|
581
|
+
* @returns Updated HydrationManager with included relation
|
|
582
|
+
*/
|
|
583
|
+
onRelationIncluded(state, relation, relationName, aliasPrefix, targetColumns, pivot) {
|
|
584
|
+
const withRoots = this.planner.captureRootColumns(state.ast.columns);
|
|
585
|
+
const next = withRoots.includeRelation(relation, relationName, aliasPrefix, targetColumns, pivot);
|
|
586
|
+
return this.clone(next);
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Applies hydration plan to the AST
|
|
590
|
+
* @param ast - Query AST to modify
|
|
591
|
+
* @returns AST with hydration metadata
|
|
592
|
+
*/
|
|
593
|
+
applyToAst(ast) {
|
|
594
|
+
const plan = this.planner.getPlan();
|
|
595
|
+
if (!plan) return ast;
|
|
596
|
+
return {
|
|
597
|
+
...ast,
|
|
598
|
+
meta: {
|
|
599
|
+
...ast.meta || {},
|
|
600
|
+
hydration: plan
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Gets the current hydration plan
|
|
606
|
+
* @returns Hydration plan or undefined if none exists
|
|
607
|
+
*/
|
|
608
|
+
getPlan() {
|
|
609
|
+
return this.planner.getPlan();
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// src/query-builder/relation-alias.ts
|
|
614
|
+
var RELATION_SEPARATOR = "__";
|
|
615
|
+
var makeRelationAlias = (relationName, columnName) => `${relationName}${RELATION_SEPARATOR}${columnName}`;
|
|
616
|
+
var isRelationAlias = (alias) => !!alias && alias.includes(RELATION_SEPARATOR);
|
|
617
|
+
|
|
618
|
+
// src/query-builder/relation-utils.ts
|
|
619
|
+
var buildDefaultPivotColumns = (rel, pivotPk) => {
|
|
620
|
+
const excluded = /* @__PURE__ */ new Set([pivotPk, rel.pivotForeignKeyToRoot, rel.pivotForeignKeyToTarget]);
|
|
621
|
+
return Object.keys(rel.pivotTable.columns).filter((col2) => !excluded.has(col2));
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
// src/query-builder/hydration-planner.ts
|
|
625
|
+
var findPrimaryKey = (table) => {
|
|
626
|
+
const pk = Object.values(table.columns).find((c) => c.primary);
|
|
627
|
+
return pk?.name || "id";
|
|
628
|
+
};
|
|
629
|
+
var HydrationPlanner = class _HydrationPlanner {
|
|
630
|
+
/**
|
|
631
|
+
* Creates a new HydrationPlanner instance
|
|
632
|
+
* @param table - Table definition
|
|
633
|
+
* @param plan - Optional existing hydration plan
|
|
634
|
+
*/
|
|
635
|
+
constructor(table, plan) {
|
|
636
|
+
this.table = table;
|
|
637
|
+
this.plan = plan;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Captures root table columns for hydration planning
|
|
641
|
+
* @param columns - Columns to capture
|
|
642
|
+
* @returns Updated HydrationPlanner with captured columns
|
|
643
|
+
*/
|
|
644
|
+
captureRootColumns(columns) {
|
|
645
|
+
const currentPlan = this.getPlanOrDefault();
|
|
646
|
+
const rootCols = new Set(currentPlan.rootColumns);
|
|
647
|
+
let changed = false;
|
|
648
|
+
columns.forEach((node) => {
|
|
649
|
+
if (node.type !== "Column") return;
|
|
650
|
+
if (node.table !== this.table.name) return;
|
|
651
|
+
const alias = node.alias || node.name;
|
|
652
|
+
if (isRelationAlias(alias)) return;
|
|
653
|
+
if (!rootCols.has(alias)) {
|
|
654
|
+
rootCols.add(alias);
|
|
655
|
+
changed = true;
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
if (!changed) return this;
|
|
659
|
+
return new _HydrationPlanner(this.table, {
|
|
660
|
+
...currentPlan,
|
|
661
|
+
rootColumns: Array.from(rootCols)
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Includes a relation in the hydration plan
|
|
666
|
+
* @param rel - Relation definition
|
|
667
|
+
* @param relationName - Name of the relation
|
|
668
|
+
* @param aliasPrefix - Alias prefix for relation columns
|
|
669
|
+
* @param columns - Columns to include from the relation
|
|
670
|
+
* @returns Updated HydrationPlanner with included relation
|
|
671
|
+
*/
|
|
672
|
+
includeRelation(rel, relationName, aliasPrefix, columns, pivot) {
|
|
673
|
+
const currentPlan = this.getPlanOrDefault();
|
|
674
|
+
const relations = currentPlan.relations.filter((r) => r.name !== relationName);
|
|
675
|
+
relations.push(this.buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot));
|
|
676
|
+
return new _HydrationPlanner(this.table, {
|
|
677
|
+
...currentPlan,
|
|
678
|
+
relations
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Gets the current hydration plan
|
|
683
|
+
* @returns Current hydration plan or undefined
|
|
684
|
+
*/
|
|
685
|
+
getPlan() {
|
|
686
|
+
return this.plan;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Gets the current hydration plan or creates a default one
|
|
690
|
+
* @returns Current hydration plan or default plan
|
|
691
|
+
*/
|
|
692
|
+
getPlanOrDefault() {
|
|
693
|
+
return this.plan ?? buildDefaultHydrationPlan(this.table);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Builds a relation plan for hydration
|
|
697
|
+
* @param rel - Relation definition
|
|
698
|
+
* @param relationName - Name of the relation
|
|
699
|
+
* @param aliasPrefix - Alias prefix for relation columns
|
|
700
|
+
* @param columns - Columns to include from the relation
|
|
701
|
+
* @returns Hydration relation plan
|
|
702
|
+
*/
|
|
703
|
+
buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot) {
|
|
704
|
+
switch (rel.type) {
|
|
705
|
+
case RelationKinds.HasMany: {
|
|
706
|
+
const localKey = rel.localKey || findPrimaryKey(this.table);
|
|
707
|
+
return {
|
|
708
|
+
name: relationName,
|
|
709
|
+
aliasPrefix,
|
|
710
|
+
type: rel.type,
|
|
711
|
+
targetTable: rel.target.name,
|
|
712
|
+
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
713
|
+
foreignKey: rel.foreignKey,
|
|
714
|
+
localKey,
|
|
715
|
+
columns
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
case RelationKinds.BelongsTo: {
|
|
719
|
+
const localKey = rel.localKey || findPrimaryKey(rel.target);
|
|
720
|
+
return {
|
|
721
|
+
name: relationName,
|
|
722
|
+
aliasPrefix,
|
|
723
|
+
type: rel.type,
|
|
724
|
+
targetTable: rel.target.name,
|
|
725
|
+
targetPrimaryKey: findPrimaryKey(rel.target),
|
|
726
|
+
foreignKey: rel.foreignKey,
|
|
727
|
+
localKey,
|
|
728
|
+
columns
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
case RelationKinds.BelongsToMany: {
|
|
732
|
+
const many = rel;
|
|
733
|
+
const localKey = many.localKey || findPrimaryKey(this.table);
|
|
734
|
+
const targetPk = many.targetKey || findPrimaryKey(many.target);
|
|
735
|
+
const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
|
|
736
|
+
const pivotAliasPrefix = pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
|
|
737
|
+
const pivotColumns = pivot?.columns ?? many.defaultPivotColumns ?? buildDefaultPivotColumns(many, pivotPk);
|
|
738
|
+
return {
|
|
739
|
+
name: relationName,
|
|
740
|
+
aliasPrefix,
|
|
741
|
+
type: rel.type,
|
|
742
|
+
targetTable: many.target.name,
|
|
743
|
+
targetPrimaryKey: targetPk,
|
|
744
|
+
foreignKey: many.pivotForeignKeyToRoot,
|
|
745
|
+
localKey,
|
|
746
|
+
columns,
|
|
747
|
+
pivot: {
|
|
748
|
+
table: many.pivotTable.name,
|
|
749
|
+
primaryKey: pivotPk,
|
|
750
|
+
aliasPrefix: pivotAliasPrefix,
|
|
751
|
+
columns: pivotColumns
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
var buildDefaultHydrationPlan = (table) => ({
|
|
759
|
+
rootTable: table.name,
|
|
760
|
+
rootPrimaryKey: findPrimaryKey(table),
|
|
761
|
+
rootColumns: [],
|
|
762
|
+
relations: []
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
// src/core/ast/builders.ts
|
|
766
|
+
var buildColumnNode = (table, column) => {
|
|
767
|
+
if (column.type === "Column") {
|
|
768
|
+
return column;
|
|
769
|
+
}
|
|
770
|
+
const def = column;
|
|
771
|
+
return {
|
|
772
|
+
type: "Column",
|
|
773
|
+
table: def.table || table.name,
|
|
774
|
+
name: def.name
|
|
775
|
+
};
|
|
776
|
+
};
|
|
777
|
+
var buildColumnNodes = (table, names) => names.map((name) => ({
|
|
778
|
+
type: "Column",
|
|
779
|
+
table: table.name,
|
|
780
|
+
name
|
|
781
|
+
}));
|
|
782
|
+
var createTableNode = (table) => ({
|
|
783
|
+
type: "Table",
|
|
784
|
+
name: table.name
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// src/query-builder/raw-column-parser.ts
|
|
788
|
+
var parseRawColumn = (col2, tableName, ctes) => {
|
|
789
|
+
if (col2.includes("(")) {
|
|
790
|
+
const [fn, rest] = col2.split("(");
|
|
791
|
+
const colName = rest.replace(")", "");
|
|
792
|
+
const [table, name] = colName.includes(".") ? colName.split(".") : [tableName, colName];
|
|
793
|
+
return { type: "Column", table, name, alias: col2 };
|
|
794
|
+
}
|
|
795
|
+
if (col2.includes(".")) {
|
|
796
|
+
const [potentialCteName, columnName] = col2.split(".");
|
|
797
|
+
const hasCte = ctes?.some((cte) => cte.name === potentialCteName);
|
|
798
|
+
if (hasCte) {
|
|
799
|
+
return { type: "Column", table: tableName, name: col2 };
|
|
800
|
+
}
|
|
801
|
+
return { type: "Column", table: potentialCteName, name: columnName };
|
|
802
|
+
}
|
|
803
|
+
return { type: "Column", table: tableName, name: col2 };
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
// src/query-builder/query-ast-service.ts
|
|
807
|
+
var QueryAstService = class {
|
|
808
|
+
/**
|
|
809
|
+
* Creates a new QueryAstService instance
|
|
810
|
+
* @param table - Table definition
|
|
811
|
+
* @param state - Current query state
|
|
812
|
+
*/
|
|
813
|
+
constructor(table, state) {
|
|
814
|
+
this.table = table;
|
|
815
|
+
this.state = state;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Selects columns for the query
|
|
819
|
+
* @param columns - Columns to select (key: alias, value: column definition or expression)
|
|
820
|
+
* @returns Column selection result with updated state and added columns
|
|
821
|
+
*/
|
|
822
|
+
select(columns) {
|
|
823
|
+
const existingAliases = new Set(
|
|
824
|
+
this.state.ast.columns.map((c) => c.alias || c.name)
|
|
825
|
+
);
|
|
826
|
+
const newCols = Object.entries(columns).reduce((acc, [alias, val]) => {
|
|
827
|
+
if (existingAliases.has(alias)) return acc;
|
|
828
|
+
if (isExpressionSelectionNode(val)) {
|
|
829
|
+
acc.push({ ...val, alias });
|
|
830
|
+
return acc;
|
|
831
|
+
}
|
|
832
|
+
const colDef = val;
|
|
833
|
+
acc.push({
|
|
834
|
+
type: "Column",
|
|
835
|
+
table: colDef.table || this.table.name,
|
|
836
|
+
name: colDef.name,
|
|
837
|
+
alias
|
|
838
|
+
});
|
|
839
|
+
return acc;
|
|
840
|
+
}, []);
|
|
841
|
+
const nextState = this.state.withColumns(newCols);
|
|
842
|
+
return { state: nextState, addedColumns: newCols };
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Selects raw column expressions (best-effort parser for simple references/functions)
|
|
846
|
+
* @param cols - Raw column expressions
|
|
847
|
+
* @returns Column selection result with updated state and added columns
|
|
848
|
+
*/
|
|
849
|
+
selectRaw(cols) {
|
|
850
|
+
const newCols = cols.map((col2) => parseRawColumn(col2, this.table.name, this.state.ast.ctes));
|
|
851
|
+
const nextState = this.state.withColumns(newCols);
|
|
852
|
+
return { state: nextState, addedColumns: newCols };
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Adds a Common Table Expression (CTE) to the query
|
|
856
|
+
* @param name - Name of the CTE
|
|
857
|
+
* @param query - Query for the CTE
|
|
858
|
+
* @param columns - Optional column names for the CTE
|
|
859
|
+
* @param recursive - Whether the CTE is recursive
|
|
860
|
+
* @returns Updated query state with CTE
|
|
861
|
+
*/
|
|
862
|
+
withCte(name, query, columns, recursive = false) {
|
|
863
|
+
const cte = {
|
|
864
|
+
type: "CommonTableExpression",
|
|
865
|
+
name,
|
|
866
|
+
query,
|
|
867
|
+
columns,
|
|
868
|
+
recursive
|
|
869
|
+
};
|
|
870
|
+
return this.state.withCte(cte);
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Selects a subquery as a column
|
|
874
|
+
* @param alias - Alias for the subquery
|
|
875
|
+
* @param query - Subquery to select
|
|
876
|
+
* @returns Updated query state with subquery selection
|
|
877
|
+
*/
|
|
878
|
+
selectSubquery(alias, query) {
|
|
879
|
+
const node = { type: "ScalarSubquery", query, alias };
|
|
880
|
+
return this.state.withColumns([node]);
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Adds a JOIN clause to the query
|
|
884
|
+
* @param join - Join node to add
|
|
885
|
+
* @returns Updated query state with JOIN
|
|
886
|
+
*/
|
|
887
|
+
withJoin(join) {
|
|
888
|
+
return this.state.withJoin(join);
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Adds a WHERE clause to the query
|
|
892
|
+
* @param expr - Expression for the WHERE clause
|
|
893
|
+
* @returns Updated query state with WHERE clause
|
|
894
|
+
*/
|
|
895
|
+
withWhere(expr) {
|
|
896
|
+
const combined = this.combineExpressions(this.state.ast.where, expr);
|
|
897
|
+
return this.state.withWhere(combined);
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Adds a GROUP BY clause to the query
|
|
901
|
+
* @param col - Column to group by
|
|
902
|
+
* @returns Updated query state with GROUP BY clause
|
|
903
|
+
*/
|
|
904
|
+
withGroupBy(col2) {
|
|
905
|
+
const node = buildColumnNode(this.table, col2);
|
|
906
|
+
return this.state.withGroupBy([node]);
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Adds a HAVING clause to the query
|
|
910
|
+
* @param expr - Expression for the HAVING clause
|
|
911
|
+
* @returns Updated query state with HAVING clause
|
|
912
|
+
*/
|
|
913
|
+
withHaving(expr) {
|
|
914
|
+
const combined = this.combineExpressions(this.state.ast.having, expr);
|
|
915
|
+
return this.state.withHaving(combined);
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Adds an ORDER BY clause to the query
|
|
919
|
+
* @param col - Column to order by
|
|
920
|
+
* @param direction - Order direction (ASC/DESC)
|
|
921
|
+
* @returns Updated query state with ORDER BY clause
|
|
922
|
+
*/
|
|
923
|
+
withOrderBy(col2, direction) {
|
|
924
|
+
const node = buildColumnNode(this.table, col2);
|
|
925
|
+
return this.state.withOrderBy([{ type: "OrderBy", column: node, direction }]);
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Adds a DISTINCT clause to the query
|
|
929
|
+
* @param cols - Columns to make distinct
|
|
930
|
+
* @returns Updated query state with DISTINCT clause
|
|
931
|
+
*/
|
|
932
|
+
withDistinct(cols) {
|
|
933
|
+
return this.state.withDistinct(cols);
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Adds a LIMIT clause to the query
|
|
937
|
+
* @param limit - Maximum number of rows to return
|
|
938
|
+
* @returns Updated query state with LIMIT clause
|
|
939
|
+
*/
|
|
940
|
+
withLimit(limit) {
|
|
941
|
+
return this.state.withLimit(limit);
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Adds an OFFSET clause to the query
|
|
945
|
+
* @param offset - Number of rows to skip
|
|
946
|
+
* @returns Updated query state with OFFSET clause
|
|
947
|
+
*/
|
|
948
|
+
withOffset(offset) {
|
|
949
|
+
return this.state.withOffset(offset);
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Combines expressions with AND operator
|
|
953
|
+
* @param existing - Existing expression
|
|
954
|
+
* @param next - New expression to combine
|
|
955
|
+
* @returns Combined expression
|
|
956
|
+
*/
|
|
957
|
+
combineExpressions(existing, next) {
|
|
958
|
+
return existing ? and(existing, next) : next;
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
// src/query-builder/relation-projection-helper.ts
|
|
963
|
+
var RelationProjectionHelper = class {
|
|
964
|
+
/**
|
|
965
|
+
* Creates a new RelationProjectionHelper instance
|
|
966
|
+
* @param table - Table definition
|
|
967
|
+
* @param selectColumns - Callback for selecting columns
|
|
968
|
+
*/
|
|
969
|
+
constructor(table, selectColumns) {
|
|
970
|
+
this.table = table;
|
|
971
|
+
this.selectColumns = selectColumns;
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Ensures base projection is included in the query
|
|
975
|
+
* @param state - Current query state
|
|
976
|
+
* @param hydration - Hydration manager
|
|
977
|
+
* @returns Relation result with updated state and hydration
|
|
978
|
+
*/
|
|
979
|
+
ensureBaseProjection(state, hydration) {
|
|
980
|
+
const primaryKey = findPrimaryKey(this.table);
|
|
981
|
+
if (!this.hasBaseProjection(state)) {
|
|
982
|
+
return this.selectColumns(state, hydration, this.getBaseColumns());
|
|
983
|
+
}
|
|
984
|
+
if (primaryKey && !this.hasPrimarySelected(state, primaryKey) && this.table.columns[primaryKey]) {
|
|
985
|
+
return this.selectColumns(state, hydration, {
|
|
986
|
+
[primaryKey]: this.table.columns[primaryKey]
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
return { state, hydration };
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Checks if base projection exists in the query
|
|
993
|
+
* @param state - Current query state
|
|
994
|
+
* @returns True if base projection exists
|
|
995
|
+
*/
|
|
996
|
+
hasBaseProjection(state) {
|
|
997
|
+
return state.ast.columns.some((col2) => !isRelationAlias(col2.alias));
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Checks if primary key is selected in the query
|
|
1001
|
+
* @param state - Current query state
|
|
1002
|
+
* @param primaryKey - Primary key name
|
|
1003
|
+
* @returns True if primary key is selected
|
|
1004
|
+
*/
|
|
1005
|
+
hasPrimarySelected(state, primaryKey) {
|
|
1006
|
+
return state.ast.columns.some((col2) => {
|
|
1007
|
+
const alias = col2.alias;
|
|
1008
|
+
const name = alias || col2.name;
|
|
1009
|
+
return !isRelationAlias(alias) && name === primaryKey;
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Gets all base columns for the table
|
|
1014
|
+
* @returns Record of all table columns
|
|
1015
|
+
*/
|
|
1016
|
+
getBaseColumns() {
|
|
1017
|
+
return Object.keys(this.table.columns).reduce((acc, key) => {
|
|
1018
|
+
acc[key] = this.table.columns[key];
|
|
1019
|
+
return acc;
|
|
1020
|
+
}, {});
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
// src/core/ast/join-node.ts
|
|
1025
|
+
var createJoinNode = (kind, tableName, condition, relationName) => ({
|
|
1026
|
+
type: "Join",
|
|
1027
|
+
kind,
|
|
1028
|
+
table: { type: "Table", name: tableName },
|
|
1029
|
+
condition,
|
|
1030
|
+
relationName
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
// src/query-builder/relation-conditions.ts
|
|
1034
|
+
var assertNever = (value) => {
|
|
1035
|
+
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
1036
|
+
};
|
|
1037
|
+
var baseRelationCondition = (root, relation) => {
|
|
1038
|
+
const defaultLocalKey = relation.type === RelationKinds.HasMany ? findPrimaryKey(root) : findPrimaryKey(relation.target);
|
|
1039
|
+
const localKey = relation.localKey || defaultLocalKey;
|
|
1040
|
+
switch (relation.type) {
|
|
1041
|
+
case RelationKinds.HasMany:
|
|
1042
|
+
return eq(
|
|
1043
|
+
{ type: "Column", table: relation.target.name, name: relation.foreignKey },
|
|
1044
|
+
{ type: "Column", table: root.name, name: localKey }
|
|
1045
|
+
);
|
|
1046
|
+
case RelationKinds.BelongsTo:
|
|
1047
|
+
return eq(
|
|
1048
|
+
{ type: "Column", table: relation.target.name, name: localKey },
|
|
1049
|
+
{ type: "Column", table: root.name, name: relation.foreignKey }
|
|
1050
|
+
);
|
|
1051
|
+
case RelationKinds.BelongsToMany:
|
|
1052
|
+
throw new Error("BelongsToMany relations do not support the standard join condition builder");
|
|
1053
|
+
default:
|
|
1054
|
+
return assertNever(relation);
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra) => {
|
|
1058
|
+
const rootKey = relation.localKey || findPrimaryKey(root);
|
|
1059
|
+
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
1060
|
+
const pivotCondition = eq(
|
|
1061
|
+
{ type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
|
|
1062
|
+
{ type: "Column", table: root.name, name: rootKey }
|
|
1063
|
+
);
|
|
1064
|
+
const pivotJoin = createJoinNode(joinKind, relation.pivotTable.name, pivotCondition);
|
|
1065
|
+
let targetCondition = eq(
|
|
1066
|
+
{ type: "Column", table: relation.target.name, name: targetKey },
|
|
1067
|
+
{ type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToTarget }
|
|
1068
|
+
);
|
|
1069
|
+
if (extra) {
|
|
1070
|
+
targetCondition = and(targetCondition, extra);
|
|
1071
|
+
}
|
|
1072
|
+
const targetJoin = createJoinNode(
|
|
1073
|
+
joinKind,
|
|
1074
|
+
relation.target.name,
|
|
1075
|
+
targetCondition,
|
|
1076
|
+
relationName
|
|
1077
|
+
);
|
|
1078
|
+
return [pivotJoin, targetJoin];
|
|
1079
|
+
};
|
|
1080
|
+
var buildRelationJoinCondition = (root, relation, extra) => {
|
|
1081
|
+
const base = baseRelationCondition(root, relation);
|
|
1082
|
+
return extra ? and(base, extra) : base;
|
|
1083
|
+
};
|
|
1084
|
+
var buildRelationCorrelation = (root, relation) => {
|
|
1085
|
+
return baseRelationCondition(root, relation);
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
// src/core/sql/sql.ts
|
|
1089
|
+
var SQL_OPERATORS = {
|
|
1090
|
+
/** Equality operator */
|
|
1091
|
+
EQUALS: "=",
|
|
1092
|
+
/** Not equals operator */
|
|
1093
|
+
NOT_EQUALS: "!=",
|
|
1094
|
+
/** Greater than operator */
|
|
1095
|
+
GREATER_THAN: ">",
|
|
1096
|
+
/** Greater than or equal operator */
|
|
1097
|
+
GREATER_OR_EQUAL: ">=",
|
|
1098
|
+
/** Less than operator */
|
|
1099
|
+
LESS_THAN: "<",
|
|
1100
|
+
/** Less than or equal operator */
|
|
1101
|
+
LESS_OR_EQUAL: "<=",
|
|
1102
|
+
/** LIKE pattern matching operator */
|
|
1103
|
+
LIKE: "LIKE",
|
|
1104
|
+
/** NOT LIKE pattern matching operator */
|
|
1105
|
+
NOT_LIKE: "NOT LIKE",
|
|
1106
|
+
/** IN membership operator */
|
|
1107
|
+
IN: "IN",
|
|
1108
|
+
/** NOT IN membership operator */
|
|
1109
|
+
NOT_IN: "NOT IN",
|
|
1110
|
+
/** BETWEEN range operator */
|
|
1111
|
+
BETWEEN: "BETWEEN",
|
|
1112
|
+
/** NOT BETWEEN range operator */
|
|
1113
|
+
NOT_BETWEEN: "NOT BETWEEN",
|
|
1114
|
+
/** IS NULL null check operator */
|
|
1115
|
+
IS_NULL: "IS NULL",
|
|
1116
|
+
/** IS NOT NULL null check operator */
|
|
1117
|
+
IS_NOT_NULL: "IS NOT NULL",
|
|
1118
|
+
/** Logical AND operator */
|
|
1119
|
+
AND: "AND",
|
|
1120
|
+
/** Logical OR operator */
|
|
1121
|
+
OR: "OR",
|
|
1122
|
+
/** EXISTS operator */
|
|
1123
|
+
EXISTS: "EXISTS",
|
|
1124
|
+
/** NOT EXISTS operator */
|
|
1125
|
+
NOT_EXISTS: "NOT EXISTS"
|
|
1126
|
+
};
|
|
1127
|
+
var JOIN_KINDS = {
|
|
1128
|
+
/** INNER JOIN type */
|
|
1129
|
+
INNER: "INNER",
|
|
1130
|
+
/** LEFT JOIN type */
|
|
1131
|
+
LEFT: "LEFT",
|
|
1132
|
+
/** RIGHT JOIN type */
|
|
1133
|
+
RIGHT: "RIGHT",
|
|
1134
|
+
/** CROSS JOIN type */
|
|
1135
|
+
CROSS: "CROSS"
|
|
1136
|
+
};
|
|
1137
|
+
var ORDER_DIRECTIONS = {
|
|
1138
|
+
/** Ascending order */
|
|
1139
|
+
ASC: "ASC",
|
|
1140
|
+
/** Descending order */
|
|
1141
|
+
DESC: "DESC"
|
|
1142
|
+
};
|
|
1143
|
+
|
|
1144
|
+
// src/query-builder/relation-service.ts
|
|
1145
|
+
var RelationService = class {
|
|
1146
|
+
/**
|
|
1147
|
+
* Creates a new RelationService instance
|
|
1148
|
+
* @param table - Table definition
|
|
1149
|
+
* @param state - Current query state
|
|
1150
|
+
* @param hydration - Hydration manager
|
|
1151
|
+
*/
|
|
1152
|
+
constructor(table, state, hydration, createQueryAstService) {
|
|
1153
|
+
this.table = table;
|
|
1154
|
+
this.state = state;
|
|
1155
|
+
this.hydration = hydration;
|
|
1156
|
+
this.createQueryAstService = createQueryAstService;
|
|
1157
|
+
this.projectionHelper = new RelationProjectionHelper(
|
|
1158
|
+
table,
|
|
1159
|
+
(state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Joins a relation to the query
|
|
1164
|
+
* @param relationName - Name of the relation to join
|
|
1165
|
+
* @param joinKind - Type of join to use
|
|
1166
|
+
* @param extraCondition - Additional join condition
|
|
1167
|
+
* @returns Relation result with updated state and hydration
|
|
1168
|
+
*/
|
|
1169
|
+
joinRelation(relationName, joinKind, extraCondition) {
|
|
1170
|
+
const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition);
|
|
1171
|
+
return { state: nextState, hydration: this.hydration };
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Matches records based on a relation with an optional predicate
|
|
1175
|
+
* @param relationName - Name of the relation to match
|
|
1176
|
+
* @param predicate - Optional predicate expression
|
|
1177
|
+
* @returns Relation result with updated state and hydration
|
|
1178
|
+
*/
|
|
1179
|
+
match(relationName, predicate) {
|
|
1180
|
+
const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
|
|
1181
|
+
const pk = findPrimaryKey(this.table);
|
|
1182
|
+
const distinctCols = [{ type: "Column", table: this.table.name, name: pk }];
|
|
1183
|
+
const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
|
|
1184
|
+
const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
|
|
1185
|
+
return { state: nextState, hydration: joined.hydration };
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Includes a relation in the query result
|
|
1189
|
+
* @param relationName - Name of the relation to include
|
|
1190
|
+
* @param options - Options for relation inclusion
|
|
1191
|
+
* @returns Relation result with updated state and hydration
|
|
1192
|
+
*/
|
|
1193
|
+
include(relationName, options) {
|
|
1194
|
+
let state = this.state;
|
|
1195
|
+
let hydration = this.hydration;
|
|
1196
|
+
const relation = this.getRelation(relationName);
|
|
1197
|
+
const aliasPrefix = options?.aliasPrefix ?? relationName;
|
|
1198
|
+
const alreadyJoined = state.ast.joins.some((j) => j.relationName === relationName);
|
|
1199
|
+
if (!alreadyJoined) {
|
|
1200
|
+
const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
|
|
1201
|
+
state = joined.state;
|
|
1202
|
+
}
|
|
1203
|
+
const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
|
|
1204
|
+
state = projectionResult.state;
|
|
1205
|
+
hydration = projectionResult.hydration;
|
|
1206
|
+
const targetColumns = options?.columns?.length ? options.columns : Object.keys(relation.target.columns);
|
|
1207
|
+
const buildTypedSelection = (columns, prefix, keys, missingMsg) => {
|
|
1208
|
+
return keys.reduce((acc, key) => {
|
|
1209
|
+
const def = columns[key];
|
|
1210
|
+
if (!def) {
|
|
1211
|
+
throw new Error(missingMsg(key));
|
|
1212
|
+
}
|
|
1213
|
+
acc[makeRelationAlias(prefix, key)] = def;
|
|
1214
|
+
return acc;
|
|
1215
|
+
}, {});
|
|
1216
|
+
};
|
|
1217
|
+
const targetSelection = buildTypedSelection(
|
|
1218
|
+
relation.target.columns,
|
|
1219
|
+
aliasPrefix,
|
|
1220
|
+
targetColumns,
|
|
1221
|
+
(key) => `Column '${key}' not found on relation '${relationName}'`
|
|
1222
|
+
);
|
|
1223
|
+
if (relation.type !== RelationKinds.BelongsToMany) {
|
|
1224
|
+
const relationSelectionResult2 = this.selectColumns(state, hydration, targetSelection);
|
|
1225
|
+
state = relationSelectionResult2.state;
|
|
1226
|
+
hydration = relationSelectionResult2.hydration;
|
|
1227
|
+
hydration = hydration.onRelationIncluded(
|
|
1228
|
+
state,
|
|
1229
|
+
relation,
|
|
1230
|
+
relationName,
|
|
1231
|
+
aliasPrefix,
|
|
1232
|
+
targetColumns
|
|
1233
|
+
);
|
|
1234
|
+
return { state, hydration };
|
|
1235
|
+
}
|
|
1236
|
+
const many = relation;
|
|
1237
|
+
const pivotAliasPrefix = options?.pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
|
|
1238
|
+
const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
|
|
1239
|
+
const pivotColumns = options?.pivot?.columns ?? many.defaultPivotColumns ?? buildDefaultPivotColumns(many, pivotPk);
|
|
1240
|
+
const pivotSelection = buildTypedSelection(
|
|
1241
|
+
many.pivotTable.columns,
|
|
1242
|
+
pivotAliasPrefix,
|
|
1243
|
+
pivotColumns,
|
|
1244
|
+
(key) => `Column '${key}' not found on pivot table '${many.pivotTable.name}'`
|
|
1245
|
+
);
|
|
1246
|
+
const combinedSelection = {
|
|
1247
|
+
...targetSelection,
|
|
1248
|
+
...pivotSelection
|
|
1249
|
+
};
|
|
1250
|
+
const relationSelectionResult = this.selectColumns(state, hydration, combinedSelection);
|
|
1251
|
+
state = relationSelectionResult.state;
|
|
1252
|
+
hydration = relationSelectionResult.hydration;
|
|
1253
|
+
hydration = hydration.onRelationIncluded(
|
|
1254
|
+
state,
|
|
1255
|
+
relation,
|
|
1256
|
+
relationName,
|
|
1257
|
+
aliasPrefix,
|
|
1258
|
+
targetColumns,
|
|
1259
|
+
{ aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
|
|
1260
|
+
);
|
|
1261
|
+
return { state, hydration };
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Applies relation correlation to a query AST
|
|
1265
|
+
* @param relationName - Name of the relation
|
|
1266
|
+
* @param ast - Query AST to modify
|
|
1267
|
+
* @returns Modified query AST with relation correlation
|
|
1268
|
+
*/
|
|
1269
|
+
applyRelationCorrelation(relationName, ast) {
|
|
1270
|
+
const relation = this.getRelation(relationName);
|
|
1271
|
+
const correlation = buildRelationCorrelation(this.table, relation);
|
|
1272
|
+
const whereInSubquery = ast.where ? and(correlation, ast.where) : correlation;
|
|
1273
|
+
return {
|
|
1274
|
+
...ast,
|
|
1275
|
+
where: whereInSubquery
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Creates a join node for a relation
|
|
1280
|
+
* @param state - Current query state
|
|
1281
|
+
* @param relationName - Name of the relation
|
|
1282
|
+
* @param joinKind - Type of join to use
|
|
1283
|
+
* @param extraCondition - Additional join condition
|
|
1284
|
+
* @returns Updated query state with join
|
|
1285
|
+
*/
|
|
1286
|
+
withJoin(state, relationName, joinKind, extraCondition) {
|
|
1287
|
+
const relation = this.getRelation(relationName);
|
|
1288
|
+
if (relation.type === RelationKinds.BelongsToMany) {
|
|
1289
|
+
const joins = buildBelongsToManyJoins(
|
|
1290
|
+
this.table,
|
|
1291
|
+
relationName,
|
|
1292
|
+
relation,
|
|
1293
|
+
joinKind,
|
|
1294
|
+
extraCondition
|
|
1295
|
+
);
|
|
1296
|
+
return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
|
|
1297
|
+
}
|
|
1298
|
+
const condition = buildRelationJoinCondition(this.table, relation, extraCondition);
|
|
1299
|
+
const joinNode = createJoinNode(joinKind, relation.target.name, condition, relationName);
|
|
1300
|
+
return this.astService(state).withJoin(joinNode);
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Selects columns for a relation
|
|
1304
|
+
* @param state - Current query state
|
|
1305
|
+
* @param hydration - Hydration manager
|
|
1306
|
+
* @param columns - Columns to select
|
|
1307
|
+
* @returns Relation result with updated state and hydration
|
|
1308
|
+
*/
|
|
1309
|
+
selectColumns(state, hydration, columns) {
|
|
1310
|
+
const { state: nextState, addedColumns } = this.astService(state).select(columns);
|
|
1311
|
+
return {
|
|
1312
|
+
state: nextState,
|
|
1313
|
+
hydration: hydration.onColumnsSelected(nextState, addedColumns)
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Gets a relation definition by name
|
|
1318
|
+
* @param relationName - Name of the relation
|
|
1319
|
+
* @returns Relation definition
|
|
1320
|
+
* @throws Error if relation is not found
|
|
1321
|
+
*/
|
|
1322
|
+
getRelation(relationName) {
|
|
1323
|
+
const relation = this.table.relations[relationName];
|
|
1324
|
+
if (!relation) {
|
|
1325
|
+
throw new Error(`Relation '${relationName}' not found on table '${this.table.name}'`);
|
|
1326
|
+
}
|
|
1327
|
+
return relation;
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Creates a QueryAstService instance
|
|
1331
|
+
* @param state - Current query state
|
|
1332
|
+
* @returns QueryAstService instance
|
|
1333
|
+
*/
|
|
1334
|
+
astService(state = this.state) {
|
|
1335
|
+
return this.createQueryAstService(this.table, state);
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
|
|
1339
|
+
// src/query-builder/select-query-builder-deps.ts
|
|
1340
|
+
var defaultCreateQueryAstService = (table, state) => new QueryAstService(table, state);
|
|
1341
|
+
var defaultCreateHydrationPlanner = (table) => new HydrationPlanner(table);
|
|
1342
|
+
var defaultCreateHydration = (table, plannerFactory) => new HydrationManager(table, plannerFactory(table));
|
|
1343
|
+
var resolveSelectQueryBuilderDependencies = (overrides = {}) => {
|
|
1344
|
+
const createQueryAstService = overrides.createQueryAstService ?? defaultCreateQueryAstService;
|
|
1345
|
+
const createHydrationPlanner = overrides.createHydrationPlanner ?? defaultCreateHydrationPlanner;
|
|
1346
|
+
const createHydration = overrides.createHydration ?? ((table) => defaultCreateHydration(table, createHydrationPlanner));
|
|
1347
|
+
const createRelationService = overrides.createRelationService ?? ((table, state, hydration) => new RelationService(table, state, hydration, createQueryAstService));
|
|
1348
|
+
return {
|
|
1349
|
+
createState: overrides.createState ?? ((table) => new SelectQueryState(table)),
|
|
1350
|
+
createHydration,
|
|
1351
|
+
createHydrationPlanner,
|
|
1352
|
+
createQueryAstService,
|
|
1353
|
+
createRelationService
|
|
1354
|
+
};
|
|
1355
|
+
};
|
|
1356
|
+
var defaultSelectQueryBuilderDependencies = resolveSelectQueryBuilderDependencies();
|
|
1357
|
+
|
|
1358
|
+
// src/query-builder/column-selector.ts
|
|
1359
|
+
var ColumnSelector = class {
|
|
1360
|
+
/**
|
|
1361
|
+
* Creates a new ColumnSelector instance
|
|
1362
|
+
* @param env - Query builder environment
|
|
1363
|
+
*/
|
|
1364
|
+
constructor(env) {
|
|
1365
|
+
this.env = env;
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Selects columns for the query
|
|
1369
|
+
* @param context - Current query context
|
|
1370
|
+
* @param columns - Columns to select
|
|
1371
|
+
* @returns Updated query context with selected columns
|
|
1372
|
+
*/
|
|
1373
|
+
select(context, columns) {
|
|
1374
|
+
const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
|
|
1375
|
+
const { state: nextState, addedColumns } = astService.select(columns);
|
|
1376
|
+
return {
|
|
1377
|
+
state: nextState,
|
|
1378
|
+
hydration: context.hydration.onColumnsSelected(nextState, addedColumns)
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Selects raw column expressions
|
|
1383
|
+
* @param context - Current query context
|
|
1384
|
+
* @param columns - Raw column expressions
|
|
1385
|
+
* @returns Updated query context with raw column selections
|
|
1386
|
+
*/
|
|
1387
|
+
selectRaw(context, columns) {
|
|
1388
|
+
const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
|
|
1389
|
+
const nextState = astService.selectRaw(columns).state;
|
|
1390
|
+
return { state: nextState, hydration: context.hydration };
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Selects a subquery as a column
|
|
1394
|
+
* @param context - Current query context
|
|
1395
|
+
* @param alias - Alias for the subquery
|
|
1396
|
+
* @param query - Subquery to select
|
|
1397
|
+
* @returns Updated query context with subquery selection
|
|
1398
|
+
*/
|
|
1399
|
+
selectSubquery(context, alias, query) {
|
|
1400
|
+
const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
|
|
1401
|
+
const nextState = astService.selectSubquery(alias, query);
|
|
1402
|
+
return { state: nextState, hydration: context.hydration };
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Adds DISTINCT clause to the query
|
|
1406
|
+
* @param context - Current query context
|
|
1407
|
+
* @param columns - Columns to make distinct
|
|
1408
|
+
* @returns Updated query context with DISTINCT clause
|
|
1409
|
+
*/
|
|
1410
|
+
distinct(context, columns) {
|
|
1411
|
+
const nodes = columns.map((col2) => buildColumnNode(this.env.table, col2));
|
|
1412
|
+
const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
|
|
1413
|
+
const nextState = astService.withDistinct(nodes);
|
|
1414
|
+
return { state: nextState, hydration: context.hydration };
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
|
|
1418
|
+
// src/query-builder/relation-manager.ts
|
|
1419
|
+
var RelationManager = class {
|
|
1420
|
+
/**
|
|
1421
|
+
* Creates a new RelationManager instance
|
|
1422
|
+
* @param env - Query builder environment
|
|
1423
|
+
*/
|
|
1424
|
+
constructor(env) {
|
|
1425
|
+
this.env = env;
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Matches records based on a relation with an optional predicate
|
|
1429
|
+
* @param context - Current query context
|
|
1430
|
+
* @param relationName - Name of the relation to match
|
|
1431
|
+
* @param predicate - Optional predicate expression
|
|
1432
|
+
* @returns Updated query context with relation match
|
|
1433
|
+
*/
|
|
1434
|
+
match(context, relationName, predicate) {
|
|
1435
|
+
const result = this.createService(context).match(relationName, predicate);
|
|
1436
|
+
return { state: result.state, hydration: result.hydration };
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Joins a relation to the query
|
|
1440
|
+
* @param context - Current query context
|
|
1441
|
+
* @param relationName - Name of the relation to join
|
|
1442
|
+
* @param joinKind - Type of join to use
|
|
1443
|
+
* @param extraCondition - Additional join condition
|
|
1444
|
+
* @returns Updated query context with relation join
|
|
1445
|
+
*/
|
|
1446
|
+
joinRelation(context, relationName, joinKind, extraCondition) {
|
|
1447
|
+
const result = this.createService(context).joinRelation(relationName, joinKind, extraCondition);
|
|
1448
|
+
return { state: result.state, hydration: result.hydration };
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Includes a relation in the query result
|
|
1452
|
+
* @param context - Current query context
|
|
1453
|
+
* @param relationName - Name of the relation to include
|
|
1454
|
+
* @param options - Options for relation inclusion
|
|
1455
|
+
* @returns Updated query context with included relation
|
|
1456
|
+
*/
|
|
1457
|
+
include(context, relationName, options) {
|
|
1458
|
+
const result = this.createService(context).include(relationName, options);
|
|
1459
|
+
return { state: result.state, hydration: result.hydration };
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Applies relation correlation to a query AST
|
|
1463
|
+
* @param context - Current query context
|
|
1464
|
+
* @param relationName - Name of the relation
|
|
1465
|
+
* @param ast - Query AST to modify
|
|
1466
|
+
* @returns Modified query AST with relation correlation
|
|
1467
|
+
*/
|
|
1468
|
+
applyRelationCorrelation(context, relationName, ast) {
|
|
1469
|
+
return this.createService(context).applyRelationCorrelation(relationName, ast);
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Creates a relation service instance
|
|
1473
|
+
* @param context - Current query context
|
|
1474
|
+
* @returns Relation service instance
|
|
1475
|
+
*/
|
|
1476
|
+
createService(context) {
|
|
1477
|
+
return this.env.deps.createRelationService(this.env.table, context.state, context.hydration);
|
|
1478
|
+
}
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1481
|
+
// src/orm/hydration.ts
|
|
1482
|
+
var hydrateRows = (rows, plan) => {
|
|
1483
|
+
if (!plan || !rows.length) return rows;
|
|
1484
|
+
const rootMap = /* @__PURE__ */ new Map();
|
|
1485
|
+
const relationIndex = /* @__PURE__ */ new Map();
|
|
1486
|
+
const getOrCreateParent = (row) => {
|
|
1487
|
+
const rootId = row[plan.rootPrimaryKey];
|
|
1488
|
+
if (rootId === void 0) return void 0;
|
|
1489
|
+
if (!rootMap.has(rootId)) {
|
|
1490
|
+
rootMap.set(rootId, createBaseRow(row, plan));
|
|
1491
|
+
}
|
|
1492
|
+
return rootMap.get(rootId);
|
|
1493
|
+
};
|
|
1494
|
+
const getRelationSeenSet = (rootId, relationName) => {
|
|
1495
|
+
let byRelation = relationIndex.get(rootId);
|
|
1496
|
+
if (!byRelation) {
|
|
1497
|
+
byRelation = {};
|
|
1498
|
+
relationIndex.set(rootId, byRelation);
|
|
1499
|
+
}
|
|
1500
|
+
let seen = byRelation[relationName];
|
|
1501
|
+
if (!seen) {
|
|
1502
|
+
seen = /* @__PURE__ */ new Set();
|
|
1503
|
+
byRelation[relationName] = seen;
|
|
1504
|
+
}
|
|
1505
|
+
return seen;
|
|
1506
|
+
};
|
|
1507
|
+
for (const row of rows) {
|
|
1508
|
+
const rootId = row[plan.rootPrimaryKey];
|
|
1509
|
+
if (rootId === void 0) continue;
|
|
1510
|
+
const parent = getOrCreateParent(row);
|
|
1511
|
+
if (!parent) continue;
|
|
1512
|
+
for (const rel of plan.relations) {
|
|
1513
|
+
const childPkKey = makeRelationAlias(rel.aliasPrefix, rel.targetPrimaryKey);
|
|
1514
|
+
const childPk = row[childPkKey];
|
|
1515
|
+
if (childPk === null || childPk === void 0) continue;
|
|
1516
|
+
const seen = getRelationSeenSet(rootId, rel.name);
|
|
1517
|
+
if (seen.has(childPk)) continue;
|
|
1518
|
+
seen.add(childPk);
|
|
1519
|
+
const bucket = parent[rel.name];
|
|
1520
|
+
bucket.push(buildChild(row, rel));
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return Array.from(rootMap.values());
|
|
1524
|
+
};
|
|
1525
|
+
var createBaseRow = (row, plan) => {
|
|
1526
|
+
const base = {};
|
|
1527
|
+
const baseKeys = plan.rootColumns.length ? plan.rootColumns : Object.keys(row).filter((k) => !isRelationAlias(k));
|
|
1528
|
+
for (const key of baseKeys) {
|
|
1529
|
+
base[key] = row[key];
|
|
1530
|
+
}
|
|
1531
|
+
for (const rel of plan.relations) {
|
|
1532
|
+
base[rel.name] = [];
|
|
1533
|
+
}
|
|
1534
|
+
return base;
|
|
1535
|
+
};
|
|
1536
|
+
var buildChild = (row, rel) => {
|
|
1537
|
+
const child = {};
|
|
1538
|
+
for (const col2 of rel.columns) {
|
|
1539
|
+
const key = makeRelationAlias(rel.aliasPrefix, col2);
|
|
1540
|
+
child[col2] = row[key];
|
|
1541
|
+
}
|
|
1542
|
+
const pivot = buildPivot(row, rel);
|
|
1543
|
+
if (pivot) {
|
|
1544
|
+
child._pivot = pivot;
|
|
1545
|
+
}
|
|
1546
|
+
return child;
|
|
1547
|
+
};
|
|
1548
|
+
var buildPivot = (row, rel) => {
|
|
1549
|
+
if (!rel.pivot) return void 0;
|
|
1550
|
+
const pivot = {};
|
|
1551
|
+
for (const col2 of rel.pivot.columns) {
|
|
1552
|
+
const key = makeRelationAlias(rel.pivot.aliasPrefix, col2);
|
|
1553
|
+
pivot[col2] = row[key];
|
|
1554
|
+
}
|
|
1555
|
+
const hasValue = Object.values(pivot).some((v) => v !== null && v !== void 0);
|
|
1556
|
+
return hasValue ? pivot : void 0;
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
// src/orm/entity-meta.ts
|
|
1560
|
+
var ENTITY_META = Symbol("EntityMeta");
|
|
1561
|
+
var toKey = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1562
|
+
var getHydrationRows = (meta, relationName, key) => {
|
|
1563
|
+
const map = meta.relationHydration.get(relationName);
|
|
1564
|
+
if (!map) return void 0;
|
|
1565
|
+
const rows = map.get(toKey(key));
|
|
1566
|
+
if (!rows) return void 0;
|
|
1567
|
+
return Array.isArray(rows) ? rows : void 0;
|
|
1568
|
+
};
|
|
1569
|
+
var getHydrationRecord = (meta, relationName, key) => {
|
|
1570
|
+
const map = meta.relationHydration.get(relationName);
|
|
1571
|
+
if (!map) return void 0;
|
|
1572
|
+
const value = map.get(toKey(key));
|
|
1573
|
+
if (!value) return void 0;
|
|
1574
|
+
if (Array.isArray(value)) {
|
|
1575
|
+
return value[0];
|
|
1576
|
+
}
|
|
1577
|
+
return value;
|
|
1578
|
+
};
|
|
1579
|
+
var getEntityMeta = (entity) => {
|
|
1580
|
+
if (!entity || typeof entity !== "object") return void 0;
|
|
1581
|
+
return entity[ENTITY_META];
|
|
1582
|
+
};
|
|
1583
|
+
var hasEntityMeta = (entity) => {
|
|
1584
|
+
return Boolean(getEntityMeta(entity));
|
|
1585
|
+
};
|
|
1586
|
+
|
|
1587
|
+
// src/orm/relations/has-many.ts
|
|
1588
|
+
var toKey2 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1589
|
+
var DefaultHasManyCollection = class {
|
|
1590
|
+
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
1591
|
+
this.ctx = ctx;
|
|
1592
|
+
this.meta = meta;
|
|
1593
|
+
this.root = root;
|
|
1594
|
+
this.relationName = relationName;
|
|
1595
|
+
this.relation = relation;
|
|
1596
|
+
this.rootTable = rootTable;
|
|
1597
|
+
this.loader = loader;
|
|
1598
|
+
this.createEntity = createEntity;
|
|
1599
|
+
this.localKey = localKey;
|
|
1600
|
+
this.loaded = false;
|
|
1601
|
+
this.items = [];
|
|
1602
|
+
this.added = /* @__PURE__ */ new Set();
|
|
1603
|
+
this.removed = /* @__PURE__ */ new Set();
|
|
1604
|
+
this.hydrateFromCache();
|
|
1605
|
+
}
|
|
1606
|
+
async load() {
|
|
1607
|
+
if (this.loaded) return this.items;
|
|
1608
|
+
const map = await this.loader();
|
|
1609
|
+
const key = toKey2(this.root[this.localKey]);
|
|
1610
|
+
const rows = map.get(key) ?? [];
|
|
1611
|
+
this.items = rows.map((row) => this.createEntity(row));
|
|
1612
|
+
this.loaded = true;
|
|
1613
|
+
return this.items;
|
|
1614
|
+
}
|
|
1615
|
+
getItems() {
|
|
1616
|
+
return this.items;
|
|
1617
|
+
}
|
|
1618
|
+
add(data) {
|
|
1619
|
+
const keyValue = this.root[this.localKey];
|
|
1620
|
+
const childRow = {
|
|
1621
|
+
...data,
|
|
1622
|
+
[this.relation.foreignKey]: keyValue
|
|
1623
|
+
};
|
|
1624
|
+
const entity = this.createEntity(childRow);
|
|
1625
|
+
this.added.add(entity);
|
|
1626
|
+
this.items.push(entity);
|
|
1627
|
+
this.ctx.registerRelationChange(
|
|
1628
|
+
this.root,
|
|
1629
|
+
this.relationKey,
|
|
1630
|
+
this.rootTable,
|
|
1631
|
+
this.relationName,
|
|
1632
|
+
this.relation,
|
|
1633
|
+
{ kind: "add", entity }
|
|
1634
|
+
);
|
|
1635
|
+
return entity;
|
|
1636
|
+
}
|
|
1637
|
+
attach(entity) {
|
|
1638
|
+
const keyValue = this.root[this.localKey];
|
|
1639
|
+
entity[this.relation.foreignKey] = keyValue;
|
|
1640
|
+
this.ctx.markDirty(entity);
|
|
1641
|
+
this.items.push(entity);
|
|
1642
|
+
this.ctx.registerRelationChange(
|
|
1643
|
+
this.root,
|
|
1644
|
+
this.relationKey,
|
|
1645
|
+
this.rootTable,
|
|
1646
|
+
this.relationName,
|
|
1647
|
+
this.relation,
|
|
1648
|
+
{ kind: "attach", entity }
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
remove(entity) {
|
|
1652
|
+
this.items = this.items.filter((item) => item !== entity);
|
|
1653
|
+
this.removed.add(entity);
|
|
1654
|
+
this.ctx.registerRelationChange(
|
|
1655
|
+
this.root,
|
|
1656
|
+
this.relationKey,
|
|
1657
|
+
this.rootTable,
|
|
1658
|
+
this.relationName,
|
|
1659
|
+
this.relation,
|
|
1660
|
+
{ kind: "remove", entity }
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
clear() {
|
|
1664
|
+
for (const entity of [...this.items]) {
|
|
1665
|
+
this.remove(entity);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
get relationKey() {
|
|
1669
|
+
return `${this.rootTable.name}.${this.relationName}`;
|
|
1670
|
+
}
|
|
1671
|
+
hydrateFromCache() {
|
|
1672
|
+
const keyValue = this.root[this.localKey];
|
|
1673
|
+
if (keyValue === void 0 || keyValue === null) return;
|
|
1674
|
+
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
1675
|
+
if (!rows?.length) return;
|
|
1676
|
+
this.items = rows.map((row) => this.createEntity(row));
|
|
1677
|
+
this.loaded = true;
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
// src/orm/relations/belongs-to.ts
|
|
1682
|
+
var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1683
|
+
var DefaultBelongsToReference = class {
|
|
1684
|
+
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, targetKey) {
|
|
1685
|
+
this.ctx = ctx;
|
|
1686
|
+
this.meta = meta;
|
|
1687
|
+
this.root = root;
|
|
1688
|
+
this.relationName = relationName;
|
|
1689
|
+
this.relation = relation;
|
|
1690
|
+
this.rootTable = rootTable;
|
|
1691
|
+
this.loader = loader;
|
|
1692
|
+
this.createEntity = createEntity;
|
|
1693
|
+
this.targetKey = targetKey;
|
|
1694
|
+
this.loaded = false;
|
|
1695
|
+
this.current = null;
|
|
1696
|
+
this.populateFromHydrationCache();
|
|
1697
|
+
}
|
|
1698
|
+
async load() {
|
|
1699
|
+
if (this.loaded) return this.current;
|
|
1700
|
+
const map = await this.loader();
|
|
1701
|
+
const fkValue = this.root[this.relation.foreignKey];
|
|
1702
|
+
if (fkValue === null || fkValue === void 0) {
|
|
1703
|
+
this.current = null;
|
|
1704
|
+
} else {
|
|
1705
|
+
const row = map.get(toKey3(fkValue));
|
|
1706
|
+
this.current = row ? this.createEntity(row) : null;
|
|
1707
|
+
}
|
|
1708
|
+
this.loaded = true;
|
|
1709
|
+
return this.current;
|
|
1710
|
+
}
|
|
1711
|
+
get() {
|
|
1712
|
+
return this.current;
|
|
1713
|
+
}
|
|
1714
|
+
set(data) {
|
|
1715
|
+
if (data === null) {
|
|
1716
|
+
const previous = this.current;
|
|
1717
|
+
this.root[this.relation.foreignKey] = null;
|
|
1718
|
+
this.current = null;
|
|
1719
|
+
this.ctx.registerRelationChange(
|
|
1720
|
+
this.root,
|
|
1721
|
+
this.relationKey,
|
|
1722
|
+
this.rootTable,
|
|
1723
|
+
this.relationName,
|
|
1724
|
+
this.relation,
|
|
1725
|
+
{ kind: "remove", entity: previous }
|
|
1726
|
+
);
|
|
1727
|
+
return null;
|
|
1728
|
+
}
|
|
1729
|
+
const entity = hasEntityMeta(data) ? data : this.createEntity(data);
|
|
1730
|
+
const pkValue = entity[this.targetKey];
|
|
1731
|
+
if (pkValue !== void 0) {
|
|
1732
|
+
this.root[this.relation.foreignKey] = pkValue;
|
|
1733
|
+
}
|
|
1734
|
+
this.current = entity;
|
|
1735
|
+
this.ctx.registerRelationChange(
|
|
1736
|
+
this.root,
|
|
1737
|
+
this.relationKey,
|
|
1738
|
+
this.rootTable,
|
|
1739
|
+
this.relationName,
|
|
1740
|
+
this.relation,
|
|
1741
|
+
{ kind: "attach", entity }
|
|
1742
|
+
);
|
|
1743
|
+
return entity;
|
|
1744
|
+
}
|
|
1745
|
+
get relationKey() {
|
|
1746
|
+
return `${this.rootTable.name}.${this.relationName}`;
|
|
1747
|
+
}
|
|
1748
|
+
populateFromHydrationCache() {
|
|
1749
|
+
const fkValue = this.root[this.relation.foreignKey];
|
|
1750
|
+
if (fkValue === void 0 || fkValue === null) return;
|
|
1751
|
+
const row = getHydrationRecord(this.meta, this.relationName, fkValue);
|
|
1752
|
+
if (!row) return;
|
|
1753
|
+
this.current = this.createEntity(row);
|
|
1754
|
+
this.loaded = true;
|
|
1755
|
+
}
|
|
1756
|
+
};
|
|
1757
|
+
|
|
1758
|
+
// src/orm/relations/many-to-many.ts
|
|
1759
|
+
var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1760
|
+
var DefaultManyToManyCollection = class {
|
|
1761
|
+
constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
|
|
1762
|
+
this.ctx = ctx;
|
|
1763
|
+
this.meta = meta;
|
|
1764
|
+
this.root = root;
|
|
1765
|
+
this.relationName = relationName;
|
|
1766
|
+
this.relation = relation;
|
|
1767
|
+
this.rootTable = rootTable;
|
|
1768
|
+
this.loader = loader;
|
|
1769
|
+
this.createEntity = createEntity;
|
|
1770
|
+
this.localKey = localKey;
|
|
1771
|
+
this.loaded = false;
|
|
1772
|
+
this.items = [];
|
|
1773
|
+
this.hydrateFromCache();
|
|
1774
|
+
}
|
|
1775
|
+
async load() {
|
|
1776
|
+
if (this.loaded) return this.items;
|
|
1777
|
+
const map = await this.loader();
|
|
1778
|
+
const key = toKey4(this.root[this.localKey]);
|
|
1779
|
+
const rows = map.get(key) ?? [];
|
|
1780
|
+
this.items = rows.map((row) => {
|
|
1781
|
+
const entity = this.createEntity(row);
|
|
1782
|
+
if (row._pivot) {
|
|
1783
|
+
entity._pivot = row._pivot;
|
|
1784
|
+
}
|
|
1785
|
+
return entity;
|
|
1786
|
+
});
|
|
1787
|
+
this.loaded = true;
|
|
1788
|
+
return this.items;
|
|
1789
|
+
}
|
|
1790
|
+
getItems() {
|
|
1791
|
+
return this.items;
|
|
1792
|
+
}
|
|
1793
|
+
attach(target) {
|
|
1794
|
+
const entity = this.ensureEntity(target);
|
|
1795
|
+
const id = this.extractId(entity);
|
|
1796
|
+
if (id == null) return;
|
|
1797
|
+
if (this.items.some((item) => this.extractId(item) === id)) {
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
this.items.push(entity);
|
|
1801
|
+
this.ctx.registerRelationChange(
|
|
1802
|
+
this.root,
|
|
1803
|
+
this.relationKey,
|
|
1804
|
+
this.rootTable,
|
|
1805
|
+
this.relationName,
|
|
1806
|
+
this.relation,
|
|
1807
|
+
{ kind: "attach", entity }
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
detach(target) {
|
|
1811
|
+
const id = typeof target === "number" || typeof target === "string" ? target : this.extractId(target);
|
|
1812
|
+
if (id == null) return;
|
|
1813
|
+
const existing = this.items.find((item) => this.extractId(item) === id);
|
|
1814
|
+
if (!existing) return;
|
|
1815
|
+
this.items = this.items.filter((item) => this.extractId(item) !== id);
|
|
1816
|
+
this.ctx.registerRelationChange(
|
|
1817
|
+
this.root,
|
|
1818
|
+
this.relationKey,
|
|
1819
|
+
this.rootTable,
|
|
1820
|
+
this.relationName,
|
|
1821
|
+
this.relation,
|
|
1822
|
+
{ kind: "detach", entity: existing }
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
async syncByIds(ids) {
|
|
1826
|
+
await this.load();
|
|
1827
|
+
const targetKey = this.relation.targetKey || findPrimaryKey(this.relation.target);
|
|
1828
|
+
const normalized = new Set(ids.map((id) => toKey4(id)));
|
|
1829
|
+
const currentIds = new Set(this.items.map((item) => toKey4(this.extractId(item))));
|
|
1830
|
+
for (const id of normalized) {
|
|
1831
|
+
if (!currentIds.has(id)) {
|
|
1832
|
+
this.attach(id);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
for (const item of [...this.items]) {
|
|
1836
|
+
const itemId = toKey4(this.extractId(item));
|
|
1837
|
+
if (!normalized.has(itemId)) {
|
|
1838
|
+
this.detach(item);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
ensureEntity(target) {
|
|
1843
|
+
if (typeof target === "number" || typeof target === "string") {
|
|
1844
|
+
const stub = {
|
|
1845
|
+
[this.targetKey]: target
|
|
1846
|
+
};
|
|
1847
|
+
return this.createEntity(stub);
|
|
1848
|
+
}
|
|
1849
|
+
return target;
|
|
1850
|
+
}
|
|
1851
|
+
extractId(entity) {
|
|
1852
|
+
if (entity === null || entity === void 0) return null;
|
|
1853
|
+
if (typeof entity === "number" || typeof entity === "string") {
|
|
1854
|
+
return entity;
|
|
1855
|
+
}
|
|
1856
|
+
return entity[this.targetKey] ?? null;
|
|
1857
|
+
}
|
|
1858
|
+
get relationKey() {
|
|
1859
|
+
return `${this.rootTable.name}.${this.relationName}`;
|
|
1860
|
+
}
|
|
1861
|
+
get targetKey() {
|
|
1862
|
+
return this.relation.targetKey || findPrimaryKey(this.relation.target);
|
|
1863
|
+
}
|
|
1864
|
+
hydrateFromCache() {
|
|
1865
|
+
const keyValue = this.root[this.localKey];
|
|
1866
|
+
if (keyValue === void 0 || keyValue === null) return;
|
|
1867
|
+
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
1868
|
+
if (!rows?.length) return;
|
|
1869
|
+
this.items = rows.map((row) => {
|
|
1870
|
+
const entity = this.createEntity(row);
|
|
1871
|
+
if (row._pivot) {
|
|
1872
|
+
entity._pivot = row._pivot;
|
|
1873
|
+
}
|
|
1874
|
+
return entity;
|
|
1875
|
+
});
|
|
1876
|
+
this.loaded = true;
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
|
|
1880
|
+
// src/orm/lazy-batch.ts
|
|
1881
|
+
var selectAllColumns = (table) => Object.entries(table.columns).reduce((acc, [name, def]) => {
|
|
1882
|
+
acc[name] = def;
|
|
1883
|
+
return acc;
|
|
1884
|
+
}, {});
|
|
1885
|
+
var rowsFromResults = (results) => {
|
|
1886
|
+
const rows = [];
|
|
1887
|
+
for (const result of results) {
|
|
1888
|
+
const { columns, values } = result;
|
|
1889
|
+
for (const valueRow of values) {
|
|
1890
|
+
const row = {};
|
|
1891
|
+
columns.forEach((column, idx) => {
|
|
1892
|
+
row[column] = valueRow[idx];
|
|
1893
|
+
});
|
|
1894
|
+
rows.push(row);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return rows;
|
|
1898
|
+
};
|
|
1899
|
+
var executeQuery = async (ctx, qb) => {
|
|
1900
|
+
const compiled = ctx.dialect.compileSelect(qb.getAST());
|
|
1901
|
+
const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
1902
|
+
return rowsFromResults(results);
|
|
1903
|
+
};
|
|
1904
|
+
var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
1905
|
+
var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
1906
|
+
const localKey = relation.localKey || findPrimaryKey(rootTable);
|
|
1907
|
+
const roots = ctx.getEntitiesForTable(rootTable);
|
|
1908
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1909
|
+
for (const tracked of roots) {
|
|
1910
|
+
const value = tracked.entity[localKey];
|
|
1911
|
+
if (value !== null && value !== void 0) {
|
|
1912
|
+
keys.add(value);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
if (!keys.size) {
|
|
1916
|
+
return /* @__PURE__ */ new Map();
|
|
1917
|
+
}
|
|
1918
|
+
const selectMap = selectAllColumns(relation.target);
|
|
1919
|
+
const fb = new SelectQueryBuilder(relation.target).select(selectMap);
|
|
1920
|
+
const fkColumn = relation.target.columns[relation.foreignKey];
|
|
1921
|
+
if (!fkColumn) return /* @__PURE__ */ new Map();
|
|
1922
|
+
fb.where(inList(fkColumn, Array.from(keys)));
|
|
1923
|
+
const rows = await executeQuery(ctx, fb);
|
|
1924
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1925
|
+
for (const row of rows) {
|
|
1926
|
+
const fkValue = row[relation.foreignKey];
|
|
1927
|
+
if (fkValue === null || fkValue === void 0) continue;
|
|
1928
|
+
const key = toKey5(fkValue);
|
|
1929
|
+
const bucket = grouped.get(key) ?? [];
|
|
1930
|
+
bucket.push(row);
|
|
1931
|
+
grouped.set(key, bucket);
|
|
1932
|
+
}
|
|
1933
|
+
return grouped;
|
|
1934
|
+
};
|
|
1935
|
+
var loadBelongsToRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
1936
|
+
const roots = ctx.getEntitiesForTable(rootTable);
|
|
1937
|
+
const foreignKeys = /* @__PURE__ */ new Set();
|
|
1938
|
+
for (const tracked of roots) {
|
|
1939
|
+
const value = tracked.entity[relation.foreignKey];
|
|
1940
|
+
if (value !== null && value !== void 0) {
|
|
1941
|
+
foreignKeys.add(value);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
if (!foreignKeys.size) {
|
|
1945
|
+
return /* @__PURE__ */ new Map();
|
|
1946
|
+
}
|
|
1947
|
+
const selectMap = selectAllColumns(relation.target);
|
|
1948
|
+
const qb = new SelectQueryBuilder(relation.target).select(selectMap);
|
|
1949
|
+
const targetKey = relation.localKey || findPrimaryKey(relation.target);
|
|
1950
|
+
const pkColumn = relation.target.columns[targetKey];
|
|
1951
|
+
if (!pkColumn) return /* @__PURE__ */ new Map();
|
|
1952
|
+
qb.where(inList(pkColumn, Array.from(foreignKeys)));
|
|
1953
|
+
const rows = await executeQuery(ctx, qb);
|
|
1954
|
+
const map = /* @__PURE__ */ new Map();
|
|
1955
|
+
for (const row of rows) {
|
|
1956
|
+
const keyValue = row[targetKey];
|
|
1957
|
+
if (keyValue === null || keyValue === void 0) continue;
|
|
1958
|
+
map.set(toKey5(keyValue), row);
|
|
1959
|
+
}
|
|
1960
|
+
return map;
|
|
1961
|
+
};
|
|
1962
|
+
var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
1963
|
+
const rootKey = relation.localKey || findPrimaryKey(rootTable);
|
|
1964
|
+
const roots = ctx.getEntitiesForTable(rootTable);
|
|
1965
|
+
const rootIds = /* @__PURE__ */ new Set();
|
|
1966
|
+
for (const tracked of roots) {
|
|
1967
|
+
const value = tracked.entity[rootKey];
|
|
1968
|
+
if (value !== null && value !== void 0) {
|
|
1969
|
+
rootIds.add(value);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
if (!rootIds.size) {
|
|
1973
|
+
return /* @__PURE__ */ new Map();
|
|
1974
|
+
}
|
|
1975
|
+
const pivotSelect = selectAllColumns(relation.pivotTable);
|
|
1976
|
+
const pivotQb = new SelectQueryBuilder(relation.pivotTable).select(pivotSelect);
|
|
1977
|
+
const pivotFkCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
|
|
1978
|
+
if (!pivotFkCol) return /* @__PURE__ */ new Map();
|
|
1979
|
+
pivotQb.where(inList(pivotFkCol, Array.from(rootIds)));
|
|
1980
|
+
const pivotRows = await executeQuery(ctx, pivotQb);
|
|
1981
|
+
const rootLookup = /* @__PURE__ */ new Map();
|
|
1982
|
+
const targetIds = /* @__PURE__ */ new Set();
|
|
1983
|
+
for (const pivot of pivotRows) {
|
|
1984
|
+
const rootValue = pivot[relation.pivotForeignKeyToRoot];
|
|
1985
|
+
const targetValue = pivot[relation.pivotForeignKeyToTarget];
|
|
1986
|
+
if (rootValue === null || rootValue === void 0 || targetValue === null || targetValue === void 0) {
|
|
1987
|
+
continue;
|
|
1988
|
+
}
|
|
1989
|
+
const bucket = rootLookup.get(toKey5(rootValue)) ?? [];
|
|
1990
|
+
bucket.push({
|
|
1991
|
+
targetId: targetValue,
|
|
1992
|
+
pivot: { ...pivot }
|
|
1993
|
+
});
|
|
1994
|
+
rootLookup.set(toKey5(rootValue), bucket);
|
|
1995
|
+
targetIds.add(targetValue);
|
|
1996
|
+
}
|
|
1997
|
+
if (!targetIds.size) {
|
|
1998
|
+
return /* @__PURE__ */ new Map();
|
|
1999
|
+
}
|
|
2000
|
+
const targetSelect = selectAllColumns(relation.target);
|
|
2001
|
+
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
2002
|
+
const targetPkColumn = relation.target.columns[targetKey];
|
|
2003
|
+
if (!targetPkColumn) return /* @__PURE__ */ new Map();
|
|
2004
|
+
const targetQb = new SelectQueryBuilder(relation.target).select(targetSelect);
|
|
2005
|
+
targetQb.where(inList(targetPkColumn, Array.from(targetIds)));
|
|
2006
|
+
const targetRows = await executeQuery(ctx, targetQb);
|
|
2007
|
+
const targetMap = /* @__PURE__ */ new Map();
|
|
2008
|
+
for (const row of targetRows) {
|
|
2009
|
+
const pkValue = row[targetKey];
|
|
2010
|
+
if (pkValue === null || pkValue === void 0) continue;
|
|
2011
|
+
targetMap.set(toKey5(pkValue), row);
|
|
2012
|
+
}
|
|
2013
|
+
const result = /* @__PURE__ */ new Map();
|
|
2014
|
+
for (const [rootId, entries] of rootLookup.entries()) {
|
|
2015
|
+
const bucket = [];
|
|
2016
|
+
for (const entry of entries) {
|
|
2017
|
+
const targetRow = targetMap.get(toKey5(entry.targetId));
|
|
2018
|
+
if (!targetRow) continue;
|
|
2019
|
+
bucket.push({
|
|
2020
|
+
...targetRow,
|
|
2021
|
+
_pivot: entry.pivot
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
result.set(rootId, bucket);
|
|
2025
|
+
}
|
|
2026
|
+
return result;
|
|
2027
|
+
};
|
|
2028
|
+
|
|
2029
|
+
// src/orm/entity.ts
|
|
2030
|
+
var relationLoaderCache = (meta, relationName, factory) => {
|
|
2031
|
+
if (meta.relationCache.has(relationName)) {
|
|
2032
|
+
return meta.relationCache.get(relationName);
|
|
2033
|
+
}
|
|
2034
|
+
const promise = factory().then((value) => {
|
|
2035
|
+
for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
|
|
2036
|
+
const otherMeta = getEntityMeta(tracked.entity);
|
|
2037
|
+
if (!otherMeta) continue;
|
|
2038
|
+
otherMeta.relationHydration.set(relationName, value);
|
|
2039
|
+
}
|
|
2040
|
+
return value;
|
|
2041
|
+
});
|
|
2042
|
+
meta.relationCache.set(relationName, promise);
|
|
2043
|
+
for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
|
|
2044
|
+
const otherMeta = getEntityMeta(tracked.entity);
|
|
2045
|
+
if (!otherMeta) continue;
|
|
2046
|
+
otherMeta.relationCache.set(relationName, promise);
|
|
2047
|
+
}
|
|
2048
|
+
return promise;
|
|
2049
|
+
};
|
|
2050
|
+
var createEntityProxy = (ctx, table, row, lazyRelations = []) => {
|
|
2051
|
+
const target = { ...row };
|
|
2052
|
+
const meta = {
|
|
2053
|
+
ctx,
|
|
2054
|
+
table,
|
|
2055
|
+
lazyRelations: [...lazyRelations],
|
|
2056
|
+
relationCache: /* @__PURE__ */ new Map(),
|
|
2057
|
+
relationHydration: /* @__PURE__ */ new Map(),
|
|
2058
|
+
relationWrappers: /* @__PURE__ */ new Map()
|
|
2059
|
+
};
|
|
2060
|
+
Object.defineProperty(target, ENTITY_META, {
|
|
2061
|
+
value: meta,
|
|
2062
|
+
enumerable: false,
|
|
2063
|
+
writable: false
|
|
2064
|
+
});
|
|
2065
|
+
let proxy;
|
|
2066
|
+
const handler = {
|
|
2067
|
+
get(targetObj, prop, receiver) {
|
|
2068
|
+
if (prop === ENTITY_META) {
|
|
2069
|
+
return meta;
|
|
2070
|
+
}
|
|
2071
|
+
if (prop === "$load") {
|
|
2072
|
+
return async (relationName) => {
|
|
2073
|
+
const wrapper = getRelationWrapper(meta, relationName, proxy);
|
|
2074
|
+
if (wrapper && typeof wrapper.load === "function") {
|
|
2075
|
+
return wrapper.load();
|
|
2076
|
+
}
|
|
2077
|
+
return void 0;
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
if (typeof prop === "string" && table.relations[prop]) {
|
|
2081
|
+
return getRelationWrapper(meta, prop, proxy);
|
|
2082
|
+
}
|
|
2083
|
+
return Reflect.get(targetObj, prop, receiver);
|
|
2084
|
+
},
|
|
2085
|
+
set(targetObj, prop, value, receiver) {
|
|
2086
|
+
const result = Reflect.set(targetObj, prop, value, receiver);
|
|
2087
|
+
if (typeof prop === "string" && table.columns[prop]) {
|
|
2088
|
+
ctx.markDirty(proxy);
|
|
2089
|
+
}
|
|
2090
|
+
return result;
|
|
2091
|
+
}
|
|
2092
|
+
};
|
|
2093
|
+
proxy = new Proxy(target, handler);
|
|
2094
|
+
populateHydrationCache(proxy, row, meta);
|
|
2095
|
+
return proxy;
|
|
2096
|
+
};
|
|
2097
|
+
var createEntityFromRow = (ctx, table, row, lazyRelations = []) => {
|
|
2098
|
+
const pkName = findPrimaryKey(table);
|
|
2099
|
+
const pkValue = row[pkName];
|
|
2100
|
+
if (pkValue !== void 0 && pkValue !== null) {
|
|
2101
|
+
const tracked = ctx.getEntity(table, pkValue);
|
|
2102
|
+
if (tracked) return tracked;
|
|
2103
|
+
}
|
|
2104
|
+
const entity = createEntityProxy(ctx, table, row, lazyRelations);
|
|
2105
|
+
if (pkValue !== void 0 && pkValue !== null) {
|
|
2106
|
+
ctx.trackManaged(table, pkValue, entity);
|
|
2107
|
+
} else {
|
|
2108
|
+
ctx.trackNew(table, entity);
|
|
2109
|
+
}
|
|
2110
|
+
return entity;
|
|
2111
|
+
};
|
|
2112
|
+
var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
2113
|
+
var populateHydrationCache = (entity, row, meta) => {
|
|
2114
|
+
for (const relationName of Object.keys(meta.table.relations)) {
|
|
2115
|
+
const relation = meta.table.relations[relationName];
|
|
2116
|
+
const data = row[relationName];
|
|
2117
|
+
if (!Array.isArray(data)) continue;
|
|
2118
|
+
if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
|
|
2119
|
+
const localKey = relation.localKey || findPrimaryKey(meta.table);
|
|
2120
|
+
const rootValue = entity[localKey];
|
|
2121
|
+
if (rootValue === void 0 || rootValue === null) continue;
|
|
2122
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2123
|
+
cache.set(toKey6(rootValue), data);
|
|
2124
|
+
meta.relationHydration.set(relationName, cache);
|
|
2125
|
+
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
2126
|
+
continue;
|
|
2127
|
+
}
|
|
2128
|
+
if (relation.type === RelationKinds.BelongsTo) {
|
|
2129
|
+
const targetKey = relation.localKey || findPrimaryKey(relation.target);
|
|
2130
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2131
|
+
for (const item of data) {
|
|
2132
|
+
const pkValue = item[targetKey];
|
|
2133
|
+
if (pkValue === void 0 || pkValue === null) continue;
|
|
2134
|
+
cache.set(toKey6(pkValue), item);
|
|
2135
|
+
}
|
|
2136
|
+
if (cache.size) {
|
|
2137
|
+
meta.relationHydration.set(relationName, cache);
|
|
2138
|
+
meta.relationCache.set(relationName, Promise.resolve(cache));
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
var getRelationWrapper = (meta, relationName, owner) => {
|
|
2144
|
+
if (meta.relationWrappers.has(relationName)) {
|
|
2145
|
+
return meta.relationWrappers.get(relationName);
|
|
2146
|
+
}
|
|
2147
|
+
const relation = meta.table.relations[relationName];
|
|
2148
|
+
if (!relation) return void 0;
|
|
2149
|
+
const wrapper = instantiateWrapper(meta, relationName, relation, owner);
|
|
2150
|
+
if (wrapper) {
|
|
2151
|
+
meta.relationWrappers.set(relationName, wrapper);
|
|
2152
|
+
}
|
|
2153
|
+
return wrapper;
|
|
2154
|
+
};
|
|
2155
|
+
var instantiateWrapper = (meta, relationName, relation, owner) => {
|
|
2156
|
+
switch (relation.type) {
|
|
2157
|
+
case RelationKinds.HasMany: {
|
|
2158
|
+
const hasMany2 = relation;
|
|
2159
|
+
const localKey = hasMany2.localKey || findPrimaryKey(meta.table);
|
|
2160
|
+
const loader = () => relationLoaderCache(
|
|
2161
|
+
meta,
|
|
2162
|
+
relationName,
|
|
2163
|
+
() => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2)
|
|
2164
|
+
);
|
|
2165
|
+
return new DefaultHasManyCollection(
|
|
2166
|
+
meta.ctx,
|
|
2167
|
+
meta,
|
|
2168
|
+
owner,
|
|
2169
|
+
relationName,
|
|
2170
|
+
hasMany2,
|
|
2171
|
+
meta.table,
|
|
2172
|
+
loader,
|
|
2173
|
+
(row) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
2174
|
+
localKey
|
|
2175
|
+
);
|
|
2176
|
+
}
|
|
2177
|
+
case RelationKinds.BelongsTo: {
|
|
2178
|
+
const belongsTo2 = relation;
|
|
2179
|
+
const targetKey = belongsTo2.localKey || findPrimaryKey(belongsTo2.target);
|
|
2180
|
+
const loader = () => relationLoaderCache(
|
|
2181
|
+
meta,
|
|
2182
|
+
relationName,
|
|
2183
|
+
() => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2)
|
|
2184
|
+
);
|
|
2185
|
+
return new DefaultBelongsToReference(
|
|
2186
|
+
meta.ctx,
|
|
2187
|
+
meta,
|
|
2188
|
+
owner,
|
|
2189
|
+
relationName,
|
|
2190
|
+
belongsTo2,
|
|
2191
|
+
meta.table,
|
|
2192
|
+
loader,
|
|
2193
|
+
(row) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
2194
|
+
targetKey
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
case RelationKinds.BelongsToMany: {
|
|
2198
|
+
const many = relation;
|
|
2199
|
+
const localKey = many.localKey || findPrimaryKey(meta.table);
|
|
2200
|
+
const loader = () => relationLoaderCache(
|
|
2201
|
+
meta,
|
|
2202
|
+
relationName,
|
|
2203
|
+
() => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many)
|
|
2204
|
+
);
|
|
2205
|
+
return new DefaultManyToManyCollection(
|
|
2206
|
+
meta.ctx,
|
|
2207
|
+
meta,
|
|
2208
|
+
owner,
|
|
2209
|
+
relationName,
|
|
2210
|
+
many,
|
|
2211
|
+
meta.table,
|
|
2212
|
+
loader,
|
|
2213
|
+
(row) => createEntityFromRow(meta.ctx, relation.target, row),
|
|
2214
|
+
localKey
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
default:
|
|
2218
|
+
return void 0;
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
|
|
2222
|
+
// src/orm/execute.ts
|
|
2223
|
+
var flattenResults = (results) => {
|
|
2224
|
+
const rows = [];
|
|
2225
|
+
for (const result of results) {
|
|
2226
|
+
const { columns, values } = result;
|
|
2227
|
+
for (const valueRow of values) {
|
|
2228
|
+
const row = {};
|
|
2229
|
+
columns.forEach((column, idx) => {
|
|
2230
|
+
row[column] = valueRow[idx];
|
|
2231
|
+
});
|
|
2232
|
+
rows.push(row);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
return rows;
|
|
2236
|
+
};
|
|
2237
|
+
async function executeHydrated(ctx, qb) {
|
|
2238
|
+
const compiled = ctx.dialect.compileSelect(qb.getAST());
|
|
2239
|
+
const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
2240
|
+
const rows = flattenResults(executed);
|
|
2241
|
+
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
2242
|
+
return hydrated.map(
|
|
2243
|
+
(row) => createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
2244
|
+
);
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
// src/query-builder/select.ts
|
|
2248
|
+
var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
2249
|
+
/**
|
|
2250
|
+
* Creates a new SelectQueryBuilder instance
|
|
2251
|
+
* @param table - Table definition to query
|
|
2252
|
+
* @param state - Optional initial query state
|
|
2253
|
+
* @param hydration - Optional hydration manager
|
|
2254
|
+
* @param dependencies - Optional query builder dependencies
|
|
2255
|
+
*/
|
|
2256
|
+
constructor(table, state, hydration, dependencies, lazyRelations) {
|
|
2257
|
+
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
2258
|
+
this.env = { table, deps };
|
|
2259
|
+
const initialState = state ?? deps.createState(table);
|
|
2260
|
+
const initialHydration = hydration ?? deps.createHydration(table);
|
|
2261
|
+
this.context = {
|
|
2262
|
+
state: initialState,
|
|
2263
|
+
hydration: initialHydration
|
|
2264
|
+
};
|
|
2265
|
+
this.lazyRelations = new Set(lazyRelations ?? []);
|
|
2266
|
+
this.columnSelector = new ColumnSelector(this.env);
|
|
2267
|
+
this.relationManager = new RelationManager(this.env);
|
|
2268
|
+
}
|
|
2269
|
+
clone(context = this.context, lazyRelations = new Set(this.lazyRelations)) {
|
|
2270
|
+
return new _SelectQueryBuilder(this.env.table, context.state, context.hydration, this.env.deps, lazyRelations);
|
|
2271
|
+
}
|
|
2272
|
+
resolveQueryNode(query) {
|
|
2273
|
+
return typeof query.getAST === "function" ? query.getAST() : query;
|
|
2274
|
+
}
|
|
2275
|
+
createChildBuilder(table) {
|
|
2276
|
+
return new _SelectQueryBuilder(table, void 0, void 0, this.env.deps);
|
|
2277
|
+
}
|
|
2278
|
+
applyAst(context, mutator) {
|
|
2279
|
+
const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
|
|
2280
|
+
const nextState = mutator(astService);
|
|
2281
|
+
return { state: nextState, hydration: context.hydration };
|
|
2282
|
+
}
|
|
2283
|
+
applyJoin(context, table, condition, kind) {
|
|
2284
|
+
const joinNode = createJoinNode(kind, table.name, condition);
|
|
2285
|
+
return this.applyAst(context, (service) => service.withJoin(joinNode));
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Selects specific columns for the query
|
|
2289
|
+
* @param columns - Record of column definitions, function nodes, case expressions, or window functions
|
|
2290
|
+
* @returns New query builder instance with selected columns
|
|
2291
|
+
*/
|
|
2292
|
+
select(columns) {
|
|
2293
|
+
return this.clone(this.columnSelector.select(this.context, columns));
|
|
2294
|
+
}
|
|
2295
|
+
/**
|
|
2296
|
+
* Selects raw column expressions
|
|
2297
|
+
* @param cols - Column expressions as strings
|
|
2298
|
+
* @returns New query builder instance with raw column selections
|
|
2299
|
+
*/
|
|
2300
|
+
selectRaw(...cols) {
|
|
2301
|
+
return this.clone(this.columnSelector.selectRaw(this.context, cols));
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Adds a Common Table Expression (CTE) to the query
|
|
2305
|
+
* @param name - Name of the CTE
|
|
2306
|
+
* @param query - Query builder or query node for the CTE
|
|
2307
|
+
* @param columns - Optional column names for the CTE
|
|
2308
|
+
* @returns New query builder instance with the CTE
|
|
2309
|
+
*/
|
|
2310
|
+
with(name, query, columns) {
|
|
2311
|
+
const subAst = this.resolveQueryNode(query);
|
|
2312
|
+
const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, false));
|
|
2313
|
+
return this.clone(nextContext);
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* Adds a recursive Common Table Expression (CTE) to the query
|
|
2317
|
+
* @param name - Name of the CTE
|
|
2318
|
+
* @param query - Query builder or query node for the CTE
|
|
2319
|
+
* @param columns - Optional column names for the CTE
|
|
2320
|
+
* @returns New query builder instance with the recursive CTE
|
|
2321
|
+
*/
|
|
2322
|
+
withRecursive(name, query, columns) {
|
|
2323
|
+
const subAst = this.resolveQueryNode(query);
|
|
2324
|
+
const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, true));
|
|
2325
|
+
return this.clone(nextContext);
|
|
2326
|
+
}
|
|
2327
|
+
/**
|
|
2328
|
+
* Selects a subquery as a column
|
|
2329
|
+
* @param alias - Alias for the subquery column
|
|
2330
|
+
* @param sub - Query builder or query node for the subquery
|
|
2331
|
+
* @returns New query builder instance with the subquery selection
|
|
2332
|
+
*/
|
|
2333
|
+
selectSubquery(alias, sub) {
|
|
2334
|
+
const query = this.resolveQueryNode(sub);
|
|
2335
|
+
return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Adds an INNER JOIN to the query
|
|
2339
|
+
* @param table - Table to join
|
|
2340
|
+
* @param condition - Join condition expression
|
|
2341
|
+
* @returns New query builder instance with the INNER JOIN
|
|
2342
|
+
*/
|
|
2343
|
+
innerJoin(table, condition) {
|
|
2344
|
+
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
|
|
2345
|
+
return this.clone(nextContext);
|
|
2346
|
+
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Adds a LEFT JOIN to the query
|
|
2349
|
+
* @param table - Table to join
|
|
2350
|
+
* @param condition - Join condition expression
|
|
2351
|
+
* @returns New query builder instance with the LEFT JOIN
|
|
2352
|
+
*/
|
|
2353
|
+
leftJoin(table, condition) {
|
|
2354
|
+
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
|
|
2355
|
+
return this.clone(nextContext);
|
|
2356
|
+
}
|
|
2357
|
+
/**
|
|
2358
|
+
* Adds a RIGHT JOIN to the query
|
|
2359
|
+
* @param table - Table to join
|
|
2360
|
+
* @param condition - Join condition expression
|
|
2361
|
+
* @returns New query builder instance with the RIGHT JOIN
|
|
2362
|
+
*/
|
|
2363
|
+
rightJoin(table, condition) {
|
|
2364
|
+
const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
|
|
2365
|
+
return this.clone(nextContext);
|
|
2366
|
+
}
|
|
2367
|
+
/**
|
|
2368
|
+
* Matches records based on a relationship
|
|
2369
|
+
* @param relationName - Name of the relationship to match
|
|
2370
|
+
* @param predicate - Optional predicate expression
|
|
2371
|
+
* @returns New query builder instance with the relationship match
|
|
2372
|
+
*/
|
|
2373
|
+
match(relationName, predicate) {
|
|
2374
|
+
const nextContext = this.relationManager.match(this.context, relationName, predicate);
|
|
2375
|
+
return this.clone(nextContext);
|
|
2376
|
+
}
|
|
2377
|
+
/**
|
|
2378
|
+
* Joins a related table
|
|
2379
|
+
* @param relationName - Name of the relationship to join
|
|
2380
|
+
* @param joinKind - Type of join (defaults to INNER)
|
|
2381
|
+
* @param extraCondition - Optional additional join condition
|
|
2382
|
+
* @returns New query builder instance with the relationship join
|
|
2383
|
+
*/
|
|
2384
|
+
joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
|
|
2385
|
+
const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
|
|
2386
|
+
return this.clone(nextContext);
|
|
2387
|
+
}
|
|
2388
|
+
/**
|
|
2389
|
+
* Includes related data in the query results
|
|
2390
|
+
* @param relationName - Name of the relationship to include
|
|
2391
|
+
* @param options - Optional include options
|
|
2392
|
+
* @returns New query builder instance with the relationship inclusion
|
|
2393
|
+
*/
|
|
2394
|
+
include(relationName, options) {
|
|
2395
|
+
const nextContext = this.relationManager.include(this.context, relationName, options);
|
|
2396
|
+
return this.clone(nextContext);
|
|
2397
|
+
}
|
|
2398
|
+
includeLazy(relationName) {
|
|
2399
|
+
const nextLazy = new Set(this.lazyRelations);
|
|
2400
|
+
nextLazy.add(relationName);
|
|
2401
|
+
return this.clone(this.context, nextLazy);
|
|
2402
|
+
}
|
|
2403
|
+
getLazyRelations() {
|
|
2404
|
+
return Array.from(this.lazyRelations);
|
|
2405
|
+
}
|
|
2406
|
+
getTable() {
|
|
2407
|
+
return this.env.table;
|
|
2408
|
+
}
|
|
2409
|
+
async execute(ctx) {
|
|
2410
|
+
return executeHydrated(ctx, this);
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Adds a WHERE condition to the query
|
|
2414
|
+
* @param expr - Expression for the WHERE clause
|
|
2415
|
+
* @returns New query builder instance with the WHERE condition
|
|
2416
|
+
*/
|
|
2417
|
+
where(expr) {
|
|
2418
|
+
const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
|
|
2419
|
+
return this.clone(nextContext);
|
|
2420
|
+
}
|
|
2421
|
+
/**
|
|
2422
|
+
* Adds a GROUP BY clause to the query
|
|
2423
|
+
* @param col - Column definition or column node to group by
|
|
2424
|
+
* @returns New query builder instance with the GROUP BY clause
|
|
2425
|
+
*/
|
|
2426
|
+
groupBy(col2) {
|
|
2427
|
+
const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(col2));
|
|
2428
|
+
return this.clone(nextContext);
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Adds a HAVING condition to the query
|
|
2432
|
+
* @param expr - Expression for the HAVING clause
|
|
2433
|
+
* @returns New query builder instance with the HAVING condition
|
|
2434
|
+
*/
|
|
2435
|
+
having(expr) {
|
|
2436
|
+
const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
|
|
2437
|
+
return this.clone(nextContext);
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Adds an ORDER BY clause to the query
|
|
2441
|
+
* @param col - Column definition or column node to order by
|
|
2442
|
+
* @param direction - Order direction (defaults to ASC)
|
|
2443
|
+
* @returns New query builder instance with the ORDER BY clause
|
|
2444
|
+
*/
|
|
2445
|
+
orderBy(col2, direction = ORDER_DIRECTIONS.ASC) {
|
|
2446
|
+
const nextContext = this.applyAst(this.context, (service) => service.withOrderBy(col2, direction));
|
|
2447
|
+
return this.clone(nextContext);
|
|
2448
|
+
}
|
|
2449
|
+
/**
|
|
2450
|
+
* Adds a DISTINCT clause to the query
|
|
2451
|
+
* @param cols - Columns to make distinct
|
|
2452
|
+
* @returns New query builder instance with the DISTINCT clause
|
|
2453
|
+
*/
|
|
2454
|
+
distinct(...cols) {
|
|
2455
|
+
return this.clone(this.columnSelector.distinct(this.context, cols));
|
|
2456
|
+
}
|
|
2457
|
+
/**
|
|
2458
|
+
* Adds a LIMIT clause to the query
|
|
2459
|
+
* @param n - Maximum number of rows to return
|
|
2460
|
+
* @returns New query builder instance with the LIMIT clause
|
|
2461
|
+
*/
|
|
2462
|
+
limit(n) {
|
|
2463
|
+
const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
|
|
2464
|
+
return this.clone(nextContext);
|
|
2465
|
+
}
|
|
2466
|
+
/**
|
|
2467
|
+
* Adds an OFFSET clause to the query
|
|
2468
|
+
* @param n - Number of rows to skip
|
|
2469
|
+
* @returns New query builder instance with the OFFSET clause
|
|
2470
|
+
*/
|
|
2471
|
+
offset(n) {
|
|
2472
|
+
const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
|
|
2473
|
+
return this.clone(nextContext);
|
|
2474
|
+
}
|
|
2475
|
+
/**
|
|
2476
|
+
* Adds a WHERE EXISTS condition to the query
|
|
2477
|
+
* @param subquery - Subquery to check for existence
|
|
2478
|
+
* @returns New query builder instance with the WHERE EXISTS condition
|
|
2479
|
+
*/
|
|
2480
|
+
whereExists(subquery) {
|
|
2481
|
+
const subAst = this.resolveQueryNode(subquery);
|
|
2482
|
+
return this.where(exists(subAst));
|
|
2483
|
+
}
|
|
2484
|
+
/**
|
|
2485
|
+
* Adds a WHERE NOT EXISTS condition to the query
|
|
2486
|
+
* @param subquery - Subquery to check for non-existence
|
|
2487
|
+
* @returns New query builder instance with the WHERE NOT EXISTS condition
|
|
2488
|
+
*/
|
|
2489
|
+
whereNotExists(subquery) {
|
|
2490
|
+
const subAst = this.resolveQueryNode(subquery);
|
|
2491
|
+
return this.where(notExists(subAst));
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* Adds a WHERE EXISTS condition based on a relationship
|
|
2495
|
+
* @param relationName - Name of the relationship to check
|
|
2496
|
+
* @param callback - Optional callback to modify the relationship query
|
|
2497
|
+
* @returns New query builder instance with the relationship existence check
|
|
2498
|
+
*/
|
|
2499
|
+
whereHas(relationName, callback) {
|
|
2500
|
+
const relation = this.env.table.relations[relationName];
|
|
2501
|
+
if (!relation) {
|
|
2502
|
+
throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
|
|
2503
|
+
}
|
|
2504
|
+
let subQb = this.createChildBuilder(relation.target);
|
|
2505
|
+
if (callback) {
|
|
2506
|
+
subQb = callback(subQb);
|
|
2507
|
+
}
|
|
2508
|
+
const subAst = subQb.getAST();
|
|
2509
|
+
const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
|
|
2510
|
+
return this.where(exists(finalSubAst));
|
|
2511
|
+
}
|
|
2512
|
+
/**
|
|
2513
|
+
* Adds a WHERE NOT EXISTS condition based on a relationship
|
|
2514
|
+
* @param relationName - Name of the relationship to check
|
|
2515
|
+
* @param callback - Optional callback to modify the relationship query
|
|
2516
|
+
* @returns New query builder instance with the relationship non-existence check
|
|
2517
|
+
*/
|
|
2518
|
+
whereHasNot(relationName, callback) {
|
|
2519
|
+
const relation = this.env.table.relations[relationName];
|
|
2520
|
+
if (!relation) {
|
|
2521
|
+
throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
|
|
2522
|
+
}
|
|
2523
|
+
let subQb = this.createChildBuilder(relation.target);
|
|
2524
|
+
if (callback) {
|
|
2525
|
+
subQb = callback(subQb);
|
|
2526
|
+
}
|
|
2527
|
+
const subAst = subQb.getAST();
|
|
2528
|
+
const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
|
|
2529
|
+
return this.where(notExists(finalSubAst));
|
|
2530
|
+
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Compiles the query to SQL for a specific dialect
|
|
2533
|
+
* @param dialect - Database dialect to compile for
|
|
2534
|
+
* @returns Compiled query with SQL and parameters
|
|
2535
|
+
*/
|
|
2536
|
+
compile(dialect) {
|
|
2537
|
+
return dialect.compileSelect(this.context.state.ast);
|
|
2538
|
+
}
|
|
2539
|
+
/**
|
|
2540
|
+
* Converts the query to SQL string for a specific dialect
|
|
2541
|
+
* @param dialect - Database dialect to generate SQL for
|
|
2542
|
+
* @returns SQL string representation of the query
|
|
2543
|
+
*/
|
|
2544
|
+
toSql(dialect) {
|
|
2545
|
+
return this.compile(dialect).sql;
|
|
2546
|
+
}
|
|
2547
|
+
/**
|
|
2548
|
+
* Gets the hydration plan for the query
|
|
2549
|
+
* @returns Hydration plan or undefined if none exists
|
|
2550
|
+
*/
|
|
2551
|
+
getHydrationPlan() {
|
|
2552
|
+
return this.context.hydration.getPlan();
|
|
2553
|
+
}
|
|
2554
|
+
/**
|
|
2555
|
+
* Gets the Abstract Syntax Tree (AST) representation of the query
|
|
2556
|
+
* @returns Query AST with hydration applied
|
|
2557
|
+
*/
|
|
2558
|
+
getAST() {
|
|
2559
|
+
return this.context.hydration.applyToAst(this.context.state.ast);
|
|
2560
|
+
}
|
|
2561
|
+
};
|
|
2562
|
+
var createColumn = (table, name) => ({ type: "Column", table, name });
|
|
2563
|
+
var createLiteral = (val) => ({ type: "Literal", value: val });
|
|
2564
|
+
|
|
2565
|
+
// src/query-builder/insert-query-state.ts
|
|
2566
|
+
var InsertQueryState = class _InsertQueryState {
|
|
2567
|
+
constructor(table, ast) {
|
|
2568
|
+
this.table = table;
|
|
2569
|
+
this.ast = ast ?? {
|
|
2570
|
+
type: "InsertQuery",
|
|
2571
|
+
into: createTableNode(table),
|
|
2572
|
+
columns: [],
|
|
2573
|
+
values: []
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
clone(nextAst) {
|
|
2577
|
+
return new _InsertQueryState(this.table, nextAst);
|
|
2578
|
+
}
|
|
2579
|
+
withValues(rows) {
|
|
2580
|
+
if (!rows.length) return this;
|
|
2581
|
+
const definedColumns = this.ast.columns.length ? this.ast.columns : buildColumnNodes(this.table, Object.keys(rows[0]));
|
|
2582
|
+
const newRows = rows.map(
|
|
2583
|
+
(row) => definedColumns.map((column) => valueToOperand(row[column.name]))
|
|
2584
|
+
);
|
|
2585
|
+
return this.clone({
|
|
2586
|
+
...this.ast,
|
|
2587
|
+
columns: definedColumns,
|
|
2588
|
+
values: [...this.ast.values, ...newRows]
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
withReturning(columns) {
|
|
2592
|
+
return this.clone({
|
|
2593
|
+
...this.ast,
|
|
2594
|
+
returning: [...columns]
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
};
|
|
2598
|
+
|
|
2599
|
+
// src/query-builder/insert.ts
|
|
2600
|
+
var InsertQueryBuilder = class _InsertQueryBuilder {
|
|
2601
|
+
constructor(table, state) {
|
|
2602
|
+
this.table = table;
|
|
2603
|
+
this.state = state ?? new InsertQueryState(table);
|
|
2604
|
+
}
|
|
2605
|
+
clone(state) {
|
|
2606
|
+
return new _InsertQueryBuilder(this.table, state);
|
|
2607
|
+
}
|
|
2608
|
+
values(rowOrRows) {
|
|
2609
|
+
const rows = Array.isArray(rowOrRows) ? rowOrRows : [rowOrRows];
|
|
2610
|
+
if (!rows.length) return this;
|
|
2611
|
+
return this.clone(this.state.withValues(rows));
|
|
2612
|
+
}
|
|
2613
|
+
returning(...columns) {
|
|
2614
|
+
if (!columns.length) return this;
|
|
2615
|
+
const nodes = columns.map((column) => buildColumnNode(this.table, column));
|
|
2616
|
+
return this.clone(this.state.withReturning(nodes));
|
|
2617
|
+
}
|
|
2618
|
+
compile(compiler) {
|
|
2619
|
+
return compiler.compileInsert(this.state.ast);
|
|
2620
|
+
}
|
|
2621
|
+
toSql(compiler) {
|
|
2622
|
+
return this.compile(compiler).sql;
|
|
2623
|
+
}
|
|
2624
|
+
getAST() {
|
|
2625
|
+
return this.state.ast;
|
|
2626
|
+
}
|
|
2627
|
+
};
|
|
2628
|
+
|
|
2629
|
+
// src/query-builder/update-query-state.ts
|
|
2630
|
+
var UpdateQueryState = class _UpdateQueryState {
|
|
2631
|
+
constructor(table, ast) {
|
|
2632
|
+
this.table = table;
|
|
2633
|
+
this.ast = ast ?? {
|
|
2634
|
+
type: "UpdateQuery",
|
|
2635
|
+
table: createTableNode(table),
|
|
2636
|
+
set: []
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
clone(nextAst) {
|
|
2640
|
+
return new _UpdateQueryState(this.table, nextAst);
|
|
2641
|
+
}
|
|
2642
|
+
withSet(values) {
|
|
2643
|
+
const assignments = Object.entries(values).map(([column, value]) => ({
|
|
2644
|
+
column: {
|
|
2645
|
+
type: "Column",
|
|
2646
|
+
table: this.table.name,
|
|
2647
|
+
name: column
|
|
2648
|
+
},
|
|
2649
|
+
value: valueToOperand(value)
|
|
2650
|
+
}));
|
|
2651
|
+
return this.clone({
|
|
2652
|
+
...this.ast,
|
|
2653
|
+
set: assignments
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
withWhere(expr) {
|
|
2657
|
+
return this.clone({
|
|
2658
|
+
...this.ast,
|
|
2659
|
+
where: expr
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2662
|
+
withReturning(columns) {
|
|
2663
|
+
return this.clone({
|
|
2664
|
+
...this.ast,
|
|
2665
|
+
returning: [...columns]
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
};
|
|
2669
|
+
|
|
2670
|
+
// src/query-builder/update.ts
|
|
2671
|
+
var UpdateQueryBuilder = class _UpdateQueryBuilder {
|
|
2672
|
+
constructor(table, state) {
|
|
2673
|
+
this.table = table;
|
|
2674
|
+
this.state = state ?? new UpdateQueryState(table);
|
|
2675
|
+
}
|
|
2676
|
+
clone(state) {
|
|
2677
|
+
return new _UpdateQueryBuilder(this.table, state);
|
|
2678
|
+
}
|
|
2679
|
+
set(values) {
|
|
2680
|
+
return this.clone(this.state.withSet(values));
|
|
2681
|
+
}
|
|
2682
|
+
where(expr) {
|
|
2683
|
+
return this.clone(this.state.withWhere(expr));
|
|
2684
|
+
}
|
|
2685
|
+
returning(...columns) {
|
|
2686
|
+
if (!columns.length) return this;
|
|
2687
|
+
const nodes = columns.map((column) => buildColumnNode(this.table, column));
|
|
2688
|
+
return this.clone(this.state.withReturning(nodes));
|
|
2689
|
+
}
|
|
2690
|
+
compile(compiler) {
|
|
2691
|
+
return compiler.compileUpdate(this.state.ast);
|
|
2692
|
+
}
|
|
2693
|
+
toSql(compiler) {
|
|
2694
|
+
return this.compile(compiler).sql;
|
|
2695
|
+
}
|
|
2696
|
+
getAST() {
|
|
2697
|
+
return this.state.ast;
|
|
2698
|
+
}
|
|
2699
|
+
};
|
|
2700
|
+
|
|
2701
|
+
// src/query-builder/delete-query-state.ts
|
|
2702
|
+
var DeleteQueryState = class _DeleteQueryState {
|
|
2703
|
+
constructor(table, ast) {
|
|
2704
|
+
this.table = table;
|
|
2705
|
+
this.ast = ast ?? {
|
|
2706
|
+
type: "DeleteQuery",
|
|
2707
|
+
from: createTableNode(table)
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
clone(nextAst) {
|
|
2711
|
+
return new _DeleteQueryState(this.table, nextAst);
|
|
2712
|
+
}
|
|
2713
|
+
withWhere(expr) {
|
|
2714
|
+
return this.clone({
|
|
2715
|
+
...this.ast,
|
|
2716
|
+
where: expr
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
withReturning(columns) {
|
|
2720
|
+
return this.clone({
|
|
2721
|
+
...this.ast,
|
|
2722
|
+
returning: [...columns]
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2725
|
+
};
|
|
2726
|
+
|
|
2727
|
+
// src/query-builder/delete.ts
|
|
2728
|
+
var DeleteQueryBuilder = class _DeleteQueryBuilder {
|
|
2729
|
+
constructor(table, state) {
|
|
2730
|
+
this.table = table;
|
|
2731
|
+
this.state = state ?? new DeleteQueryState(table);
|
|
2732
|
+
}
|
|
2733
|
+
clone(state) {
|
|
2734
|
+
return new _DeleteQueryBuilder(this.table, state);
|
|
2735
|
+
}
|
|
2736
|
+
where(expr) {
|
|
2737
|
+
return this.clone(this.state.withWhere(expr));
|
|
2738
|
+
}
|
|
2739
|
+
returning(...columns) {
|
|
2740
|
+
if (!columns.length) return this;
|
|
2741
|
+
const nodes = columns.map((column) => buildColumnNode(this.table, column));
|
|
2742
|
+
return this.clone(this.state.withReturning(nodes));
|
|
2743
|
+
}
|
|
2744
|
+
compile(compiler) {
|
|
2745
|
+
return compiler.compileDelete(this.state.ast);
|
|
2746
|
+
}
|
|
2747
|
+
toSql(compiler) {
|
|
2748
|
+
return this.compile(compiler).sql;
|
|
2749
|
+
}
|
|
2750
|
+
getAST() {
|
|
2751
|
+
return this.state.ast;
|
|
2752
|
+
}
|
|
2753
|
+
};
|
|
2754
|
+
|
|
2755
|
+
// src/core/dialect/abstract.ts
|
|
2756
|
+
var Dialect = class {
|
|
2757
|
+
/**
|
|
2758
|
+
* Compiles a SELECT query AST to SQL
|
|
2759
|
+
* @param ast - Query AST to compile
|
|
2760
|
+
* @returns Compiled query with SQL and parameters
|
|
2761
|
+
*/
|
|
2762
|
+
compileSelect(ast) {
|
|
2763
|
+
const ctx = this.createCompilerContext();
|
|
2764
|
+
const rawSql = this.compileSelectAst(ast, ctx).trim();
|
|
2765
|
+
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
2766
|
+
return {
|
|
2767
|
+
sql,
|
|
2768
|
+
params: [...ctx.params]
|
|
2769
|
+
};
|
|
2770
|
+
}
|
|
2771
|
+
compileInsert(ast) {
|
|
2772
|
+
const ctx = this.createCompilerContext();
|
|
2773
|
+
const rawSql = this.compileInsertAst(ast, ctx).trim();
|
|
2774
|
+
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
2775
|
+
return {
|
|
2776
|
+
sql,
|
|
2777
|
+
params: [...ctx.params]
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
compileUpdate(ast) {
|
|
2781
|
+
const ctx = this.createCompilerContext();
|
|
2782
|
+
const rawSql = this.compileUpdateAst(ast, ctx).trim();
|
|
2783
|
+
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
2784
|
+
return {
|
|
2785
|
+
sql,
|
|
2786
|
+
params: [...ctx.params]
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2789
|
+
compileDelete(ast) {
|
|
2790
|
+
const ctx = this.createCompilerContext();
|
|
2791
|
+
const rawSql = this.compileDeleteAst(ast, ctx).trim();
|
|
2792
|
+
const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
|
|
2793
|
+
return {
|
|
2794
|
+
sql,
|
|
2795
|
+
params: [...ctx.params]
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
/**
|
|
2799
|
+
* Compiles a WHERE clause
|
|
2800
|
+
* @param where - WHERE expression
|
|
2801
|
+
* @param ctx - Compiler context
|
|
2802
|
+
* @returns SQL WHERE clause or empty string
|
|
2803
|
+
*/
|
|
2804
|
+
compileWhere(where, ctx) {
|
|
2805
|
+
if (!where) return "";
|
|
2806
|
+
return ` WHERE ${this.compileExpression(where, ctx)}`;
|
|
2807
|
+
}
|
|
2808
|
+
compileReturning(returning, ctx) {
|
|
2809
|
+
if (!returning || returning.length === 0) return "";
|
|
2810
|
+
throw new Error("RETURNING is not supported by this dialect.");
|
|
2811
|
+
}
|
|
2812
|
+
/**
|
|
2813
|
+
* Generates subquery for EXISTS expressions
|
|
2814
|
+
* Rule: Always forces SELECT 1, ignoring column list
|
|
2815
|
+
* Maintains FROM, JOINs, WHERE, GROUP BY, ORDER BY, LIMIT/OFFSET
|
|
2816
|
+
* Does not add ';' at the end
|
|
2817
|
+
* @param ast - Query AST
|
|
2818
|
+
* @param ctx - Compiler context
|
|
2819
|
+
* @returns SQL for EXISTS subquery
|
|
2820
|
+
*/
|
|
2821
|
+
compileSelectForExists(ast, ctx) {
|
|
2822
|
+
const full = this.compileSelectAst(ast, ctx).trim().replace(/;$/, "");
|
|
2823
|
+
const upper = full.toUpperCase();
|
|
2824
|
+
const fromIndex = upper.indexOf(" FROM ");
|
|
2825
|
+
if (fromIndex === -1) {
|
|
2826
|
+
return full;
|
|
2827
|
+
}
|
|
2828
|
+
const tail = full.slice(fromIndex);
|
|
2829
|
+
return `SELECT 1${tail}`;
|
|
2830
|
+
}
|
|
2831
|
+
/**
|
|
2832
|
+
* Creates a new compiler context
|
|
2833
|
+
* @returns Compiler context with parameter management
|
|
2834
|
+
*/
|
|
2835
|
+
createCompilerContext() {
|
|
2836
|
+
const params = [];
|
|
2837
|
+
let counter = 0;
|
|
2838
|
+
return {
|
|
2839
|
+
params,
|
|
2840
|
+
addParameter: (value) => {
|
|
2841
|
+
counter += 1;
|
|
2842
|
+
params.push(value);
|
|
2843
|
+
return this.formatPlaceholder(counter);
|
|
2844
|
+
}
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
/**
|
|
2848
|
+
* Formats a parameter placeholder
|
|
2849
|
+
* @param index - Parameter index
|
|
2850
|
+
* @returns Formatted placeholder string
|
|
2851
|
+
*/
|
|
2852
|
+
formatPlaceholder(index) {
|
|
2853
|
+
return "?";
|
|
2854
|
+
}
|
|
2855
|
+
constructor() {
|
|
2856
|
+
this.expressionCompilers = /* @__PURE__ */ new Map();
|
|
2857
|
+
this.operandCompilers = /* @__PURE__ */ new Map();
|
|
2858
|
+
this.registerDefaultOperandCompilers();
|
|
2859
|
+
this.registerDefaultExpressionCompilers();
|
|
2860
|
+
}
|
|
2861
|
+
/**
|
|
2862
|
+
* Registers an expression compiler for a specific node type
|
|
2863
|
+
* @param type - Expression node type
|
|
2864
|
+
* @param compiler - Compiler function
|
|
2865
|
+
*/
|
|
2866
|
+
registerExpressionCompiler(type, compiler) {
|
|
2867
|
+
this.expressionCompilers.set(type, compiler);
|
|
2868
|
+
}
|
|
2869
|
+
/**
|
|
2870
|
+
* Registers an operand compiler for a specific node type
|
|
2871
|
+
* @param type - Operand node type
|
|
2872
|
+
* @param compiler - Compiler function
|
|
2873
|
+
*/
|
|
2874
|
+
registerOperandCompiler(type, compiler) {
|
|
2875
|
+
this.operandCompilers.set(type, compiler);
|
|
2876
|
+
}
|
|
2877
|
+
/**
|
|
2878
|
+
* Compiles an expression node
|
|
2879
|
+
* @param node - Expression node to compile
|
|
2880
|
+
* @param ctx - Compiler context
|
|
2881
|
+
* @returns Compiled SQL expression
|
|
2882
|
+
*/
|
|
2883
|
+
compileExpression(node, ctx) {
|
|
2884
|
+
const compiler = this.expressionCompilers.get(node.type);
|
|
2885
|
+
if (!compiler) {
|
|
2886
|
+
throw new Error(`Unsupported expression node type "${node.type}" for ${this.constructor.name}`);
|
|
2887
|
+
}
|
|
2888
|
+
return compiler(node, ctx);
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* Compiles an operand node
|
|
2892
|
+
* @param node - Operand node to compile
|
|
2893
|
+
* @param ctx - Compiler context
|
|
2894
|
+
* @returns Compiled SQL operand
|
|
2895
|
+
*/
|
|
2896
|
+
compileOperand(node, ctx) {
|
|
2897
|
+
const compiler = this.operandCompilers.get(node.type);
|
|
2898
|
+
if (!compiler) {
|
|
2899
|
+
throw new Error(`Unsupported operand node type "${node.type}" for ${this.constructor.name}`);
|
|
2900
|
+
}
|
|
2901
|
+
return compiler(node, ctx);
|
|
2902
|
+
}
|
|
2903
|
+
registerDefaultExpressionCompilers() {
|
|
2904
|
+
this.registerExpressionCompiler("BinaryExpression", (binary, ctx) => {
|
|
2905
|
+
const left = this.compileOperand(binary.left, ctx);
|
|
2906
|
+
const right = this.compileOperand(binary.right, ctx);
|
|
2907
|
+
const base = `${left} ${binary.operator} ${right}`;
|
|
2908
|
+
if (binary.escape) {
|
|
2909
|
+
const escapeOperand = this.compileOperand(binary.escape, ctx);
|
|
2910
|
+
return `${base} ESCAPE ${escapeOperand}`;
|
|
2911
|
+
}
|
|
2912
|
+
return base;
|
|
2913
|
+
});
|
|
2914
|
+
this.registerExpressionCompiler("LogicalExpression", (logical, ctx) => {
|
|
2915
|
+
if (logical.operands.length === 0) return "";
|
|
2916
|
+
const parts = logical.operands.map((op) => {
|
|
2917
|
+
const compiled = this.compileExpression(op, ctx);
|
|
2918
|
+
return op.type === "LogicalExpression" ? `(${compiled})` : compiled;
|
|
2919
|
+
});
|
|
2920
|
+
return parts.join(` ${logical.operator} `);
|
|
2921
|
+
});
|
|
2922
|
+
this.registerExpressionCompiler("NullExpression", (nullExpr, ctx) => {
|
|
2923
|
+
const left = this.compileOperand(nullExpr.left, ctx);
|
|
2924
|
+
return `${left} ${nullExpr.operator}`;
|
|
2925
|
+
});
|
|
2926
|
+
this.registerExpressionCompiler("InExpression", (inExpr, ctx) => {
|
|
2927
|
+
const left = this.compileOperand(inExpr.left, ctx);
|
|
2928
|
+
const values = inExpr.right.map((v) => this.compileOperand(v, ctx)).join(", ");
|
|
2929
|
+
return `${left} ${inExpr.operator} (${values})`;
|
|
2930
|
+
});
|
|
2931
|
+
this.registerExpressionCompiler("ExistsExpression", (existsExpr, ctx) => {
|
|
2932
|
+
const subquerySql = this.compileSelectForExists(existsExpr.subquery, ctx);
|
|
2933
|
+
return `${existsExpr.operator} (${subquerySql})`;
|
|
2934
|
+
});
|
|
2935
|
+
this.registerExpressionCompiler("BetweenExpression", (betweenExpr, ctx) => {
|
|
2936
|
+
const left = this.compileOperand(betweenExpr.left, ctx);
|
|
2937
|
+
const lower = this.compileOperand(betweenExpr.lower, ctx);
|
|
2938
|
+
const upper = this.compileOperand(betweenExpr.upper, ctx);
|
|
2939
|
+
return `${left} ${betweenExpr.operator} ${lower} AND ${upper}`;
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
registerDefaultOperandCompilers() {
|
|
2943
|
+
this.registerOperandCompiler("Literal", (literal, ctx) => ctx.addParameter(literal.value));
|
|
2944
|
+
this.registerOperandCompiler("Column", (column, _ctx) => {
|
|
2945
|
+
return `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`;
|
|
2946
|
+
});
|
|
2947
|
+
this.registerOperandCompiler("Function", (fnNode, ctx) => {
|
|
2948
|
+
const args = fnNode.args.map((arg) => this.compileOperand(arg, ctx)).join(", ");
|
|
2949
|
+
return `${fnNode.name}(${args})`;
|
|
2950
|
+
});
|
|
2951
|
+
this.registerOperandCompiler("JsonPath", (path, _ctx) => this.compileJsonPath(path));
|
|
2952
|
+
this.registerOperandCompiler("ScalarSubquery", (node, ctx) => {
|
|
2953
|
+
const sql = this.compileSelectAst(node.query, ctx).trim().replace(/;$/, "");
|
|
2954
|
+
return `(${sql})`;
|
|
2955
|
+
});
|
|
2956
|
+
this.registerOperandCompiler("CaseExpression", (node, ctx) => {
|
|
2957
|
+
const parts = ["CASE"];
|
|
2958
|
+
for (const { when, then } of node.conditions) {
|
|
2959
|
+
parts.push(`WHEN ${this.compileExpression(when, ctx)} THEN ${this.compileOperand(then, ctx)}`);
|
|
2960
|
+
}
|
|
2961
|
+
if (node.else) {
|
|
2962
|
+
parts.push(`ELSE ${this.compileOperand(node.else, ctx)}`);
|
|
2963
|
+
}
|
|
2964
|
+
parts.push("END");
|
|
2965
|
+
return parts.join(" ");
|
|
2966
|
+
});
|
|
2967
|
+
this.registerOperandCompiler("WindowFunction", (node, ctx) => {
|
|
2968
|
+
let result = `${node.name}(`;
|
|
2969
|
+
if (node.args.length > 0) {
|
|
2970
|
+
result += node.args.map((arg) => this.compileOperand(arg, ctx)).join(", ");
|
|
2971
|
+
}
|
|
2972
|
+
result += ") OVER (";
|
|
2973
|
+
const parts = [];
|
|
2974
|
+
if (node.partitionBy && node.partitionBy.length > 0) {
|
|
2975
|
+
const partitionClause = "PARTITION BY " + node.partitionBy.map(
|
|
2976
|
+
(col2) => `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`
|
|
2977
|
+
).join(", ");
|
|
2978
|
+
parts.push(partitionClause);
|
|
2979
|
+
}
|
|
2980
|
+
if (node.orderBy && node.orderBy.length > 0) {
|
|
2981
|
+
const orderClause = "ORDER BY " + node.orderBy.map(
|
|
2982
|
+
(o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`
|
|
2983
|
+
).join(", ");
|
|
2984
|
+
parts.push(orderClause);
|
|
2985
|
+
}
|
|
2986
|
+
result += parts.join(" ");
|
|
2987
|
+
result += ")";
|
|
2988
|
+
return result;
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2991
|
+
// Default fallback, should be overridden by dialects if supported
|
|
2992
|
+
compileJsonPath(node) {
|
|
2993
|
+
throw new Error("JSON Path not supported by this dialect");
|
|
2994
|
+
}
|
|
2995
|
+
};
|
|
2996
|
+
|
|
2997
|
+
// src/core/dialect/mysql/index.ts
|
|
2998
|
+
var MySqlDialect = class extends Dialect {
|
|
2999
|
+
/**
|
|
3000
|
+
* Creates a new MySqlDialect instance
|
|
3001
|
+
*/
|
|
3002
|
+
constructor() {
|
|
3003
|
+
super();
|
|
3004
|
+
}
|
|
3005
|
+
/**
|
|
3006
|
+
* Quotes an identifier using MySQL backtick syntax
|
|
3007
|
+
* @param id - Identifier to quote
|
|
3008
|
+
* @returns Quoted identifier
|
|
3009
|
+
*/
|
|
3010
|
+
quoteIdentifier(id) {
|
|
3011
|
+
return `\`${id}\``;
|
|
3012
|
+
}
|
|
3013
|
+
/**
|
|
3014
|
+
* Compiles JSON path expression using MySQL syntax
|
|
3015
|
+
* @param node - JSON path node
|
|
3016
|
+
* @returns MySQL JSON path expression
|
|
3017
|
+
*/
|
|
3018
|
+
compileJsonPath(node) {
|
|
3019
|
+
const col2 = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
3020
|
+
return `${col2}->'${node.path}'`;
|
|
3021
|
+
}
|
|
3022
|
+
/**
|
|
3023
|
+
* Compiles SELECT query AST to MySQL SQL
|
|
3024
|
+
* @param ast - Query AST
|
|
3025
|
+
* @param ctx - Compiler context
|
|
3026
|
+
* @returns MySQL SQL string
|
|
3027
|
+
*/
|
|
3028
|
+
compileSelectAst(ast, ctx) {
|
|
3029
|
+
const columns = ast.columns.map((c) => {
|
|
3030
|
+
let expr = "";
|
|
3031
|
+
if (c.type === "Function") {
|
|
3032
|
+
expr = this.compileOperand(c, ctx);
|
|
3033
|
+
} else if (c.type === "Column") {
|
|
3034
|
+
expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
|
|
3035
|
+
} else if (c.type === "ScalarSubquery") {
|
|
3036
|
+
expr = this.compileOperand(c, ctx);
|
|
3037
|
+
} else if (c.type === "WindowFunction") {
|
|
3038
|
+
expr = this.compileOperand(c, ctx);
|
|
3039
|
+
}
|
|
3040
|
+
if (c.alias) {
|
|
3041
|
+
if (c.alias.includes("(")) return c.alias;
|
|
3042
|
+
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
3043
|
+
}
|
|
3044
|
+
return expr;
|
|
3045
|
+
}).join(", ");
|
|
3046
|
+
const distinct = ast.distinct ? "DISTINCT " : "";
|
|
3047
|
+
const from = `${this.quoteIdentifier(ast.from.name)}`;
|
|
3048
|
+
const joins = ast.joins.map((j) => {
|
|
3049
|
+
const table = this.quoteIdentifier(j.table.name);
|
|
3050
|
+
const cond = this.compileExpression(j.condition, ctx);
|
|
3051
|
+
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
3052
|
+
}).join(" ");
|
|
3053
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3054
|
+
const groupBy = ast.groupBy && ast.groupBy.length > 0 ? " GROUP BY " + ast.groupBy.map((c) => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(", ") : "";
|
|
3055
|
+
const having = ast.having ? ` HAVING ${this.compileExpression(ast.having, ctx)}` : "";
|
|
3056
|
+
const orderBy = ast.orderBy && ast.orderBy.length > 0 ? " ORDER BY " + ast.orderBy.map((o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(", ") : "";
|
|
3057
|
+
const limit = ast.limit ? ` LIMIT ${ast.limit}` : "";
|
|
3058
|
+
const offset = ast.offset ? ` OFFSET ${ast.offset}` : "";
|
|
3059
|
+
const ctes = ast.ctes && ast.ctes.length > 0 ? (() => {
|
|
3060
|
+
const hasRecursive = ast.ctes.some((cte) => cte.recursive);
|
|
3061
|
+
const prefix = hasRecursive ? "WITH RECURSIVE " : "WITH ";
|
|
3062
|
+
const cteDefs = ast.ctes.map((cte) => {
|
|
3063
|
+
const name = this.quoteIdentifier(cte.name);
|
|
3064
|
+
const cols = cte.columns ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
|
|
3065
|
+
const query = this.compileSelectAst(cte.query, ctx).trim().replace(/;$/, "");
|
|
3066
|
+
return `${name}${cols} AS (${query})`;
|
|
3067
|
+
}).join(", ");
|
|
3068
|
+
return prefix + cteDefs + " ";
|
|
3069
|
+
})() : "";
|
|
3070
|
+
return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy}${limit}${offset};`;
|
|
3071
|
+
}
|
|
3072
|
+
compileInsertAst(ast, ctx) {
|
|
3073
|
+
const table = this.quoteIdentifier(ast.into.name);
|
|
3074
|
+
const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
|
|
3075
|
+
const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
|
|
3076
|
+
return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
|
|
3077
|
+
}
|
|
3078
|
+
compileUpdateAst(ast, ctx) {
|
|
3079
|
+
const table = this.quoteIdentifier(ast.table.name);
|
|
3080
|
+
const assignments = ast.set.map((assignment) => {
|
|
3081
|
+
const col2 = assignment.column;
|
|
3082
|
+
const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
|
|
3083
|
+
const value = this.compileOperand(assignment.value, ctx);
|
|
3084
|
+
return `${target} = ${value}`;
|
|
3085
|
+
}).join(", ");
|
|
3086
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3087
|
+
return `UPDATE ${table} SET ${assignments}${whereClause};`;
|
|
3088
|
+
}
|
|
3089
|
+
compileDeleteAst(ast, ctx) {
|
|
3090
|
+
const table = this.quoteIdentifier(ast.from.name);
|
|
3091
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3092
|
+
return `DELETE FROM ${table}${whereClause};`;
|
|
3093
|
+
}
|
|
3094
|
+
};
|
|
3095
|
+
|
|
3096
|
+
// src/core/dialect/mssql/index.ts
|
|
3097
|
+
var SqlServerDialect = class extends Dialect {
|
|
3098
|
+
/**
|
|
3099
|
+
* Creates a new SqlServerDialect instance
|
|
3100
|
+
*/
|
|
3101
|
+
constructor() {
|
|
3102
|
+
super();
|
|
3103
|
+
}
|
|
3104
|
+
/**
|
|
3105
|
+
* Quotes an identifier using SQL Server bracket syntax
|
|
3106
|
+
* @param id - Identifier to quote
|
|
3107
|
+
* @returns Quoted identifier
|
|
3108
|
+
*/
|
|
3109
|
+
quoteIdentifier(id) {
|
|
3110
|
+
return `[${id}]`;
|
|
3111
|
+
}
|
|
3112
|
+
/**
|
|
3113
|
+
* Compiles JSON path expression using SQL Server syntax
|
|
3114
|
+
* @param node - JSON path node
|
|
3115
|
+
* @returns SQL Server JSON path expression
|
|
3116
|
+
*/
|
|
3117
|
+
compileJsonPath(node) {
|
|
3118
|
+
const col2 = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
3119
|
+
return `JSON_VALUE(${col2}, '${node.path}')`;
|
|
3120
|
+
}
|
|
3121
|
+
/**
|
|
3122
|
+
* Formats parameter placeholders using SQL Server named parameter syntax
|
|
3123
|
+
* @param index - Parameter index
|
|
3124
|
+
* @returns Named parameter placeholder
|
|
3125
|
+
*/
|
|
3126
|
+
formatPlaceholder(index) {
|
|
3127
|
+
return `@p${index}`;
|
|
3128
|
+
}
|
|
3129
|
+
/**
|
|
3130
|
+
* Compiles SELECT query AST to SQL Server SQL
|
|
3131
|
+
* @param ast - Query AST
|
|
3132
|
+
* @param ctx - Compiler context
|
|
3133
|
+
* @returns SQL Server SQL string
|
|
3134
|
+
*/
|
|
3135
|
+
compileSelectAst(ast, ctx) {
|
|
3136
|
+
const columns = ast.columns.map((c) => {
|
|
3137
|
+
let expr = "";
|
|
3138
|
+
if (c.type === "Function") {
|
|
3139
|
+
expr = this.compileOperand(c, ctx);
|
|
3140
|
+
} else if (c.type === "Column") {
|
|
3141
|
+
expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
|
|
3142
|
+
} else if (c.type === "ScalarSubquery") {
|
|
3143
|
+
expr = this.compileOperand(c, ctx);
|
|
3144
|
+
} else if (c.type === "WindowFunction") {
|
|
3145
|
+
expr = this.compileOperand(c, ctx);
|
|
3146
|
+
}
|
|
3147
|
+
if (c.alias) {
|
|
3148
|
+
if (c.alias.includes("(")) return c.alias;
|
|
3149
|
+
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
3150
|
+
}
|
|
3151
|
+
return expr;
|
|
3152
|
+
}).join(", ");
|
|
3153
|
+
const distinct = ast.distinct ? "DISTINCT " : "";
|
|
3154
|
+
const from = `${this.quoteIdentifier(ast.from.name)}`;
|
|
3155
|
+
const joins = ast.joins.map((j) => {
|
|
3156
|
+
const table = this.quoteIdentifier(j.table.name);
|
|
3157
|
+
const cond = this.compileExpression(j.condition, ctx);
|
|
3158
|
+
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
3159
|
+
}).join(" ");
|
|
3160
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3161
|
+
const groupBy = ast.groupBy && ast.groupBy.length > 0 ? " GROUP BY " + ast.groupBy.map((c) => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(", ") : "";
|
|
3162
|
+
const having = ast.having ? ` HAVING ${this.compileExpression(ast.having, ctx)}` : "";
|
|
3163
|
+
const orderBy = ast.orderBy && ast.orderBy.length > 0 ? " ORDER BY " + ast.orderBy.map((o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(", ") : "";
|
|
3164
|
+
let pagination = "";
|
|
3165
|
+
if (ast.limit || ast.offset) {
|
|
3166
|
+
const off = ast.offset || 0;
|
|
3167
|
+
const orderClause = orderBy || " ORDER BY (SELECT NULL)";
|
|
3168
|
+
pagination = `${orderClause} OFFSET ${off} ROWS`;
|
|
3169
|
+
if (ast.limit) {
|
|
3170
|
+
pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
|
|
3171
|
+
}
|
|
3172
|
+
return `SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${pagination};`;
|
|
3173
|
+
}
|
|
3174
|
+
const ctes = ast.ctes && ast.ctes.length > 0 ? "WITH " + ast.ctes.map((cte) => {
|
|
3175
|
+
const name = this.quoteIdentifier(cte.name);
|
|
3176
|
+
const cols = cte.columns ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
|
|
3177
|
+
const query = this.compileSelectAst(cte.query, ctx).trim().replace(/;$/, "");
|
|
3178
|
+
return `${name}${cols} AS (${query})`;
|
|
3179
|
+
}).join(", ") + " " : "";
|
|
3180
|
+
return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy};`;
|
|
3181
|
+
}
|
|
3182
|
+
compileInsertAst(ast, ctx) {
|
|
3183
|
+
const table = this.quoteIdentifier(ast.into.name);
|
|
3184
|
+
const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
|
|
3185
|
+
const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
|
|
3186
|
+
return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
|
|
3187
|
+
}
|
|
3188
|
+
compileUpdateAst(ast, ctx) {
|
|
3189
|
+
const table = this.quoteIdentifier(ast.table.name);
|
|
3190
|
+
const assignments = ast.set.map((assignment) => {
|
|
3191
|
+
const col2 = assignment.column;
|
|
3192
|
+
const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
|
|
3193
|
+
const value = this.compileOperand(assignment.value, ctx);
|
|
3194
|
+
return `${target} = ${value}`;
|
|
3195
|
+
}).join(", ");
|
|
3196
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3197
|
+
return `UPDATE ${table} SET ${assignments}${whereClause};`;
|
|
3198
|
+
}
|
|
3199
|
+
compileDeleteAst(ast, ctx) {
|
|
3200
|
+
const table = this.quoteIdentifier(ast.from.name);
|
|
3201
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3202
|
+
return `DELETE FROM ${table}${whereClause};`;
|
|
3203
|
+
}
|
|
3204
|
+
};
|
|
3205
|
+
|
|
3206
|
+
// src/core/dialect/sqlite/index.ts
|
|
3207
|
+
var SqliteDialect = class extends Dialect {
|
|
3208
|
+
/**
|
|
3209
|
+
* Creates a new SqliteDialect instance
|
|
3210
|
+
*/
|
|
3211
|
+
constructor() {
|
|
3212
|
+
super();
|
|
3213
|
+
}
|
|
3214
|
+
/**
|
|
3215
|
+
* Quotes an identifier using SQLite double-quote syntax
|
|
3216
|
+
* @param id - Identifier to quote
|
|
3217
|
+
* @returns Quoted identifier
|
|
3218
|
+
*/
|
|
3219
|
+
quoteIdentifier(id) {
|
|
3220
|
+
return `"${id}"`;
|
|
3221
|
+
}
|
|
3222
|
+
/**
|
|
3223
|
+
* Compiles JSON path expression using SQLite syntax
|
|
3224
|
+
* @param node - JSON path node
|
|
3225
|
+
* @returns SQLite JSON path expression
|
|
3226
|
+
*/
|
|
3227
|
+
compileJsonPath(node) {
|
|
3228
|
+
const col2 = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
3229
|
+
return `json_extract(${col2}, '${node.path}')`;
|
|
3230
|
+
}
|
|
3231
|
+
/**
|
|
3232
|
+
* Compiles SELECT query AST to SQLite SQL
|
|
3233
|
+
* @param ast - Query AST
|
|
3234
|
+
* @param ctx - Compiler context
|
|
3235
|
+
* @returns SQLite SQL string
|
|
3236
|
+
*/
|
|
3237
|
+
compileSelectAst(ast, ctx) {
|
|
3238
|
+
const columns = ast.columns.map((c) => {
|
|
3239
|
+
let expr = "";
|
|
3240
|
+
if (c.type === "Function") {
|
|
3241
|
+
expr = this.compileOperand(c, ctx);
|
|
3242
|
+
} else if (c.type === "Column") {
|
|
3243
|
+
expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
|
|
3244
|
+
} else if (c.type === "ScalarSubquery") {
|
|
3245
|
+
expr = this.compileOperand(c, ctx);
|
|
3246
|
+
} else if (c.type === "CaseExpression") {
|
|
3247
|
+
expr = this.compileOperand(c, ctx);
|
|
3248
|
+
} else if (c.type === "WindowFunction") {
|
|
3249
|
+
expr = this.compileOperand(c, ctx);
|
|
3250
|
+
}
|
|
3251
|
+
if (c.alias) {
|
|
3252
|
+
if (c.alias.includes("(")) return c.alias;
|
|
3253
|
+
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
3254
|
+
}
|
|
3255
|
+
return expr;
|
|
3256
|
+
}).join(", ");
|
|
3257
|
+
const distinct = ast.distinct ? "DISTINCT " : "";
|
|
3258
|
+
const from = `${this.quoteIdentifier(ast.from.name)}`;
|
|
3259
|
+
const joins = ast.joins.map((j) => {
|
|
3260
|
+
const table = this.quoteIdentifier(j.table.name);
|
|
3261
|
+
const cond = this.compileExpression(j.condition, ctx);
|
|
3262
|
+
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
3263
|
+
}).join(" ");
|
|
3264
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3265
|
+
const groupBy = ast.groupBy && ast.groupBy.length > 0 ? " GROUP BY " + ast.groupBy.map((c) => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(", ") : "";
|
|
3266
|
+
const having = ast.having ? ` HAVING ${this.compileExpression(ast.having, ctx)}` : "";
|
|
3267
|
+
const orderBy = ast.orderBy && ast.orderBy.length > 0 ? " ORDER BY " + ast.orderBy.map((o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(", ") : "";
|
|
3268
|
+
const limit = ast.limit ? ` LIMIT ${ast.limit}` : "";
|
|
3269
|
+
const offset = ast.offset ? ` OFFSET ${ast.offset}` : "";
|
|
3270
|
+
const ctes = ast.ctes && ast.ctes.length > 0 ? (() => {
|
|
3271
|
+
const hasRecursive = ast.ctes.some((cte) => cte.recursive);
|
|
3272
|
+
const prefix = hasRecursive ? "WITH RECURSIVE " : "WITH ";
|
|
3273
|
+
const cteDefs = ast.ctes.map((cte) => {
|
|
3274
|
+
const name = this.quoteIdentifier(cte.name);
|
|
3275
|
+
const cols = cte.columns ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
|
|
3276
|
+
const query = this.compileSelectAst(cte.query, ctx).trim().replace(/;$/, "");
|
|
3277
|
+
return `${name}${cols} AS (${query})`;
|
|
3278
|
+
}).join(", ");
|
|
3279
|
+
return prefix + cteDefs + " ";
|
|
3280
|
+
})() : "";
|
|
3281
|
+
return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy}${limit}${offset};`;
|
|
3282
|
+
}
|
|
3283
|
+
compileInsertAst(ast, ctx) {
|
|
3284
|
+
const table = this.quoteIdentifier(ast.into.name);
|
|
3285
|
+
const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
|
|
3286
|
+
const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
|
|
3287
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
3288
|
+
return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning};`;
|
|
3289
|
+
}
|
|
3290
|
+
compileUpdateAst(ast, ctx) {
|
|
3291
|
+
const table = this.quoteIdentifier(ast.table.name);
|
|
3292
|
+
const assignments = ast.set.map((assignment) => {
|
|
3293
|
+
const col2 = assignment.column;
|
|
3294
|
+
const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
|
|
3295
|
+
const value = this.compileOperand(assignment.value, ctx);
|
|
3296
|
+
return `${target} = ${value}`;
|
|
3297
|
+
}).join(", ");
|
|
3298
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3299
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
3300
|
+
return `UPDATE ${table} SET ${assignments}${whereClause}${returning};`;
|
|
3301
|
+
}
|
|
3302
|
+
compileDeleteAst(ast, ctx) {
|
|
3303
|
+
const table = this.quoteIdentifier(ast.from.name);
|
|
3304
|
+
const whereClause = this.compileWhere(ast.where, ctx);
|
|
3305
|
+
const returning = this.compileReturning(ast.returning, ctx);
|
|
3306
|
+
return `DELETE FROM ${table}${whereClause}${returning};`;
|
|
3307
|
+
}
|
|
3308
|
+
compileReturning(returning, ctx) {
|
|
3309
|
+
if (!returning || returning.length === 0) return "";
|
|
3310
|
+
const columns = returning.map((column) => {
|
|
3311
|
+
const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : "";
|
|
3312
|
+
return `${tablePart}${this.quoteIdentifier(column.name)}`;
|
|
3313
|
+
}).join(", ");
|
|
3314
|
+
return ` RETURNING ${columns}`;
|
|
3315
|
+
}
|
|
3316
|
+
};
|
|
3317
|
+
|
|
3318
|
+
// src/orm/als.ts
|
|
3319
|
+
var AsyncLocalStorage = class {
|
|
3320
|
+
/**
|
|
3321
|
+
* Executes a callback with the specified store value
|
|
3322
|
+
* @param store - Value to store during callback execution
|
|
3323
|
+
* @param callback - Function to execute with the store value
|
|
3324
|
+
* @returns Result of the callback function
|
|
3325
|
+
*/
|
|
3326
|
+
run(store, callback) {
|
|
3327
|
+
this.store = store;
|
|
3328
|
+
try {
|
|
3329
|
+
return callback();
|
|
3330
|
+
} finally {
|
|
3331
|
+
this.store = void 0;
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
/**
|
|
3335
|
+
* Gets the currently stored value
|
|
3336
|
+
* @returns Current store value or undefined if none exists
|
|
3337
|
+
*/
|
|
3338
|
+
getStore() {
|
|
3339
|
+
return this.store;
|
|
3340
|
+
}
|
|
3341
|
+
};
|
|
3342
|
+
|
|
3343
|
+
// src/core/sql/sql-operator-config.ts
|
|
3344
|
+
var SQL_OPERATOR_REGISTRY = {
|
|
3345
|
+
[SQL_OPERATORS.EQUALS]: { sql: SQL_OPERATORS.EQUALS, tsName: "eq" },
|
|
3346
|
+
[SQL_OPERATORS.NOT_EQUALS]: { sql: SQL_OPERATORS.NOT_EQUALS, tsName: "neq" },
|
|
3347
|
+
[SQL_OPERATORS.GREATER_THAN]: { sql: SQL_OPERATORS.GREATER_THAN, tsName: "gt" },
|
|
3348
|
+
[SQL_OPERATORS.GREATER_OR_EQUAL]: { sql: SQL_OPERATORS.GREATER_OR_EQUAL, tsName: "gte" },
|
|
3349
|
+
[SQL_OPERATORS.LESS_THAN]: { sql: SQL_OPERATORS.LESS_THAN, tsName: "lt" },
|
|
3350
|
+
[SQL_OPERATORS.LESS_OR_EQUAL]: { sql: SQL_OPERATORS.LESS_OR_EQUAL, tsName: "lte" },
|
|
3351
|
+
[SQL_OPERATORS.LIKE]: { sql: SQL_OPERATORS.LIKE, tsName: "like" },
|
|
3352
|
+
[SQL_OPERATORS.NOT_LIKE]: { sql: SQL_OPERATORS.NOT_LIKE, tsName: "notLike" },
|
|
3353
|
+
[SQL_OPERATORS.IN]: { sql: SQL_OPERATORS.IN, tsName: "inList" },
|
|
3354
|
+
[SQL_OPERATORS.NOT_IN]: { sql: SQL_OPERATORS.NOT_IN, tsName: "notInList" },
|
|
3355
|
+
[SQL_OPERATORS.IS_NULL]: { sql: SQL_OPERATORS.IS_NULL, tsName: "isNull" },
|
|
3356
|
+
[SQL_OPERATORS.IS_NOT_NULL]: { sql: SQL_OPERATORS.IS_NOT_NULL, tsName: "isNotNull" },
|
|
3357
|
+
[SQL_OPERATORS.AND]: { sql: SQL_OPERATORS.AND, tsName: "and" },
|
|
3358
|
+
[SQL_OPERATORS.OR]: { sql: SQL_OPERATORS.OR, tsName: "or" },
|
|
3359
|
+
[SQL_OPERATORS.BETWEEN]: { sql: SQL_OPERATORS.BETWEEN, tsName: "between" },
|
|
3360
|
+
[SQL_OPERATORS.NOT_BETWEEN]: { sql: SQL_OPERATORS.NOT_BETWEEN, tsName: "notBetween" },
|
|
3361
|
+
[SQL_OPERATORS.EXISTS]: { sql: SQL_OPERATORS.EXISTS, tsName: "exists" },
|
|
3362
|
+
[SQL_OPERATORS.NOT_EXISTS]: { sql: SQL_OPERATORS.NOT_EXISTS, tsName: "notExists" }
|
|
3363
|
+
};
|
|
3364
|
+
|
|
3365
|
+
// src/codegen/typescript.ts
|
|
3366
|
+
var capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
3367
|
+
var assertNever2 = (value) => {
|
|
3368
|
+
throw new Error(`Unhandled SQL operator: ${value}`);
|
|
3369
|
+
};
|
|
3370
|
+
var TypeScriptGenerator = class {
|
|
3371
|
+
/**
|
|
3372
|
+
* Generates TypeScript code from a query AST
|
|
3373
|
+
* @param ast - Query AST to generate code from
|
|
3374
|
+
* @returns Generated TypeScript code
|
|
3375
|
+
*/
|
|
3376
|
+
generate(ast) {
|
|
3377
|
+
const chainLines = this.buildSelectLines(ast);
|
|
3378
|
+
const lines = chainLines.map((line, index) => index === 0 ? `const query = ${line}` : line);
|
|
3379
|
+
lines.push(";", "", "await query.execute();");
|
|
3380
|
+
return lines.join("\n");
|
|
3381
|
+
}
|
|
3382
|
+
/**
|
|
3383
|
+
* Builds TypeScript method chain lines from query AST
|
|
3384
|
+
* @param ast - Query AST
|
|
3385
|
+
* @returns Array of TypeScript method chain lines
|
|
3386
|
+
*/
|
|
3387
|
+
buildSelectLines(ast) {
|
|
3388
|
+
const lines = [];
|
|
3389
|
+
const hydration = ast.meta?.hydration;
|
|
3390
|
+
const hydratedRelations = new Set(hydration?.relations?.map((r) => r.name) ?? []);
|
|
3391
|
+
const selections = ast.columns.filter((col2) => !(hydration && isRelationAlias(col2.alias))).map((col2) => {
|
|
3392
|
+
const key = col2.alias || col2.name;
|
|
3393
|
+
const operand = col2;
|
|
3394
|
+
return `${key}: ${this.printOperand(operand)}`;
|
|
3395
|
+
});
|
|
3396
|
+
lines.push(`db.select({`);
|
|
3397
|
+
selections.forEach((sel, index) => {
|
|
3398
|
+
lines.push(` ${sel}${index < selections.length - 1 ? "," : ""}`);
|
|
3399
|
+
});
|
|
3400
|
+
lines.push(`})`);
|
|
3401
|
+
lines.push(`.from(${capitalize(ast.from.name)})`);
|
|
3402
|
+
if (ast.distinct && ast.distinct.length) {
|
|
3403
|
+
const cols = ast.distinct.map((c) => `${capitalize(c.table)}.${c.name}`).join(", ");
|
|
3404
|
+
lines.push(`.distinct(${cols})`);
|
|
3405
|
+
}
|
|
3406
|
+
ast.joins.forEach((join) => {
|
|
3407
|
+
if (join.relationName && hydratedRelations.has(join.relationName)) {
|
|
3408
|
+
return;
|
|
3409
|
+
}
|
|
3410
|
+
if (join.relationName) {
|
|
3411
|
+
if (join.kind === "INNER") {
|
|
3412
|
+
lines.push(`.joinRelation('${join.relationName}')`);
|
|
3413
|
+
} else {
|
|
3414
|
+
lines.push(`.joinRelation('${join.relationName}', '${join.kind}')`);
|
|
3415
|
+
}
|
|
3416
|
+
} else {
|
|
3417
|
+
const table = capitalize(join.table.name);
|
|
3418
|
+
const cond = this.printExpression(join.condition);
|
|
3419
|
+
let method = "innerJoin";
|
|
3420
|
+
if (join.kind === "LEFT") method = "leftJoin";
|
|
3421
|
+
if (join.kind === "RIGHT") method = "rightJoin";
|
|
3422
|
+
lines.push(`.${method}(${table}, ${cond})`);
|
|
3423
|
+
}
|
|
3424
|
+
});
|
|
3425
|
+
if (hydration?.relations?.length) {
|
|
3426
|
+
hydration.relations.forEach((rel) => {
|
|
3427
|
+
const options = [];
|
|
3428
|
+
if (rel.columns.length) options.push(`columns: [${rel.columns.map((c) => `'${c}'`).join(", ")}]`);
|
|
3429
|
+
if (rel.aliasPrefix !== rel.name) options.push(`aliasPrefix: '${rel.aliasPrefix}'`);
|
|
3430
|
+
const opts = options.length ? `, { ${options.join(", ")} }` : "";
|
|
3431
|
+
lines.push(`.include('${rel.name}'${opts})`);
|
|
3432
|
+
});
|
|
3433
|
+
}
|
|
3434
|
+
if (ast.where) {
|
|
3435
|
+
lines.push(`.where(${this.printExpression(ast.where)})`);
|
|
3436
|
+
}
|
|
3437
|
+
if (ast.groupBy && ast.groupBy.length) {
|
|
3438
|
+
const cols = ast.groupBy.map((c) => `${capitalize(c.table)}.${c.name}`).join(", ");
|
|
3439
|
+
lines.push(`.groupBy(${cols})`);
|
|
3440
|
+
}
|
|
3441
|
+
if (ast.having) {
|
|
3442
|
+
lines.push(`.having(${this.printExpression(ast.having)})`);
|
|
3443
|
+
}
|
|
3444
|
+
if (ast.orderBy && ast.orderBy.length) {
|
|
3445
|
+
ast.orderBy.forEach((o) => {
|
|
3446
|
+
lines.push(`.orderBy(${capitalize(o.column.table)}.${o.column.name}, '${o.direction}')`);
|
|
3447
|
+
});
|
|
3448
|
+
}
|
|
3449
|
+
if (ast.limit) lines.push(`.limit(${ast.limit})`);
|
|
3450
|
+
if (ast.offset) lines.push(`.offset(${ast.offset})`);
|
|
3451
|
+
return lines;
|
|
3452
|
+
}
|
|
3453
|
+
/**
|
|
3454
|
+
* Prints an expression node to TypeScript code
|
|
3455
|
+
* @param expr - Expression node to print
|
|
3456
|
+
* @returns TypeScript code representation
|
|
3457
|
+
*/
|
|
3458
|
+
printExpression(expr) {
|
|
3459
|
+
return visitExpression(expr, this);
|
|
3460
|
+
}
|
|
3461
|
+
/**
|
|
3462
|
+
* Prints an operand node to TypeScript code
|
|
3463
|
+
* @param node - Operand node to print
|
|
3464
|
+
* @returns TypeScript code representation
|
|
3465
|
+
*/
|
|
3466
|
+
printOperand(node) {
|
|
3467
|
+
return visitOperand(node, this);
|
|
3468
|
+
}
|
|
3469
|
+
visitBinaryExpression(binary) {
|
|
3470
|
+
return this.printBinaryExpression(binary);
|
|
3471
|
+
}
|
|
3472
|
+
visitLogicalExpression(logical) {
|
|
3473
|
+
return this.printLogicalExpression(logical);
|
|
3474
|
+
}
|
|
3475
|
+
visitNullExpression(nullExpr) {
|
|
3476
|
+
return this.printNullExpression(nullExpr);
|
|
3477
|
+
}
|
|
3478
|
+
visitInExpression(inExpr) {
|
|
3479
|
+
return this.printInExpression(inExpr);
|
|
3480
|
+
}
|
|
3481
|
+
visitExistsExpression(existsExpr) {
|
|
3482
|
+
return this.printExistsExpression(existsExpr);
|
|
3483
|
+
}
|
|
3484
|
+
visitBetweenExpression(betweenExpr) {
|
|
3485
|
+
return this.printBetweenExpression(betweenExpr);
|
|
3486
|
+
}
|
|
3487
|
+
visitColumn(node) {
|
|
3488
|
+
return this.printColumnOperand(node);
|
|
3489
|
+
}
|
|
3490
|
+
visitLiteral(node) {
|
|
3491
|
+
return this.printLiteralOperand(node);
|
|
3492
|
+
}
|
|
3493
|
+
visitFunction(node) {
|
|
3494
|
+
return this.printFunctionOperand(node);
|
|
3495
|
+
}
|
|
3496
|
+
visitJsonPath(node) {
|
|
3497
|
+
return this.printJsonPathOperand(node);
|
|
3498
|
+
}
|
|
3499
|
+
visitScalarSubquery(node) {
|
|
3500
|
+
return this.printScalarSubqueryOperand(node);
|
|
3501
|
+
}
|
|
3502
|
+
visitCaseExpression(node) {
|
|
3503
|
+
return this.printCaseExpressionOperand(node);
|
|
3504
|
+
}
|
|
3505
|
+
visitWindowFunction(node) {
|
|
3506
|
+
return this.printWindowFunctionOperand(node);
|
|
3507
|
+
}
|
|
3508
|
+
/**
|
|
3509
|
+
* Prints a binary expression to TypeScript code
|
|
3510
|
+
* @param binary - Binary expression node
|
|
3511
|
+
* @returns TypeScript code representation
|
|
3512
|
+
*/
|
|
3513
|
+
printBinaryExpression(binary) {
|
|
3514
|
+
const left = this.printOperand(binary.left);
|
|
3515
|
+
const right = this.printOperand(binary.right);
|
|
3516
|
+
const fn = this.mapOp(binary.operator);
|
|
3517
|
+
const args = [left, right];
|
|
3518
|
+
if (binary.escape) {
|
|
3519
|
+
args.push(this.printOperand(binary.escape));
|
|
3520
|
+
}
|
|
3521
|
+
return `${fn}(${args.join(", ")})`;
|
|
3522
|
+
}
|
|
3523
|
+
/**
|
|
3524
|
+
* Prints a logical expression to TypeScript code
|
|
3525
|
+
* @param logical - Logical expression node
|
|
3526
|
+
* @returns TypeScript code representation
|
|
3527
|
+
*/
|
|
3528
|
+
printLogicalExpression(logical) {
|
|
3529
|
+
if (logical.operands.length === 0) return "";
|
|
3530
|
+
const parts = logical.operands.map((op) => {
|
|
3531
|
+
const compiled = this.printExpression(op);
|
|
3532
|
+
return op.type === "LogicalExpression" ? `(${compiled})` : compiled;
|
|
3533
|
+
});
|
|
3534
|
+
return `${this.mapOp(logical.operator)}(
|
|
3535
|
+
${parts.join(",\n ")}
|
|
3536
|
+
)`;
|
|
3537
|
+
}
|
|
3538
|
+
/**
|
|
3539
|
+
* Prints an IN expression to TypeScript code
|
|
3540
|
+
* @param inExpr - IN expression node
|
|
3541
|
+
* @returns TypeScript code representation
|
|
3542
|
+
*/
|
|
3543
|
+
printInExpression(inExpr) {
|
|
3544
|
+
const left = this.printOperand(inExpr.left);
|
|
3545
|
+
const values = inExpr.right.map((v) => this.printOperand(v)).join(", ");
|
|
3546
|
+
const fn = this.mapOp(inExpr.operator);
|
|
3547
|
+
return `${fn}(${left}, [${values}])`;
|
|
3548
|
+
}
|
|
3549
|
+
/**
|
|
3550
|
+
* Prints a null expression to TypeScript code
|
|
3551
|
+
* @param nullExpr - Null expression node
|
|
3552
|
+
* @returns TypeScript code representation
|
|
3553
|
+
*/
|
|
3554
|
+
printNullExpression(nullExpr) {
|
|
3555
|
+
const left = this.printOperand(nullExpr.left);
|
|
3556
|
+
const fn = this.mapOp(nullExpr.operator);
|
|
3557
|
+
return `${fn}(${left})`;
|
|
3558
|
+
}
|
|
3559
|
+
/**
|
|
3560
|
+
* Prints a BETWEEN expression to TypeScript code
|
|
3561
|
+
* @param betweenExpr - BETWEEN expression node
|
|
3562
|
+
* @returns TypeScript code representation
|
|
3563
|
+
*/
|
|
3564
|
+
printBetweenExpression(betweenExpr) {
|
|
3565
|
+
const left = this.printOperand(betweenExpr.left);
|
|
3566
|
+
const lower = this.printOperand(betweenExpr.lower);
|
|
3567
|
+
const upper = this.printOperand(betweenExpr.upper);
|
|
3568
|
+
return `${this.mapOp(betweenExpr.operator)}(${left}, ${lower}, ${upper})`;
|
|
3569
|
+
}
|
|
3570
|
+
/**
|
|
3571
|
+
* Prints an EXISTS expression to TypeScript code
|
|
3572
|
+
* @param existsExpr - EXISTS expression node
|
|
3573
|
+
* @returns TypeScript code representation
|
|
3574
|
+
*/
|
|
3575
|
+
printExistsExpression(existsExpr) {
|
|
3576
|
+
const subquery = this.inlineChain(this.buildSelectLines(existsExpr.subquery));
|
|
3577
|
+
return `${this.mapOp(existsExpr.operator)}(${subquery})`;
|
|
3578
|
+
}
|
|
3579
|
+
/**
|
|
3580
|
+
* Prints a column operand to TypeScript code
|
|
3581
|
+
* @param column - Column node
|
|
3582
|
+
* @returns TypeScript code representation
|
|
3583
|
+
*/
|
|
3584
|
+
printColumnOperand(column) {
|
|
3585
|
+
return `${capitalize(column.table)}.${column.name}`;
|
|
3586
|
+
}
|
|
3587
|
+
/**
|
|
3588
|
+
* Prints a literal operand to TypeScript code
|
|
3589
|
+
* @param literal - Literal node
|
|
3590
|
+
* @returns TypeScript code representation
|
|
3591
|
+
*/
|
|
3592
|
+
printLiteralOperand(literal) {
|
|
3593
|
+
if (literal.value === null) return "null";
|
|
3594
|
+
return typeof literal.value === "string" ? `'${literal.value}'` : String(literal.value);
|
|
3595
|
+
}
|
|
3596
|
+
/**
|
|
3597
|
+
* Prints a function operand to TypeScript code
|
|
3598
|
+
* @param fn - Function node
|
|
3599
|
+
* @returns TypeScript code representation
|
|
3600
|
+
*/
|
|
3601
|
+
printFunctionOperand(fn) {
|
|
3602
|
+
const args = fn.args.map((a) => this.printOperand(a)).join(", ");
|
|
3603
|
+
return `${fn.name.toLowerCase()}(${args})`;
|
|
3604
|
+
}
|
|
3605
|
+
/**
|
|
3606
|
+
* Prints a JSON path operand to TypeScript code
|
|
3607
|
+
* @param json - JSON path node
|
|
3608
|
+
* @returns TypeScript code representation
|
|
3609
|
+
*/
|
|
3610
|
+
printJsonPathOperand(json) {
|
|
3611
|
+
return `jsonPath(${capitalize(json.column.table)}.${json.column.name}, '${json.path}')`;
|
|
3612
|
+
}
|
|
3613
|
+
/**
|
|
3614
|
+
* Prints a scalar subquery operand to TypeScript code
|
|
3615
|
+
* @param node - Scalar subquery node
|
|
3616
|
+
* @returns TypeScript code representation
|
|
3617
|
+
*/
|
|
3618
|
+
printScalarSubqueryOperand(node) {
|
|
3619
|
+
const subquery = this.inlineChain(this.buildSelectLines(node.query));
|
|
3620
|
+
return `(${subquery})`;
|
|
3621
|
+
}
|
|
3622
|
+
/**
|
|
3623
|
+
* Prints a CASE expression operand to TypeScript code
|
|
3624
|
+
* @param node - CASE expression node
|
|
3625
|
+
* @returns TypeScript code representation
|
|
3626
|
+
*/
|
|
3627
|
+
printCaseExpressionOperand(node) {
|
|
3628
|
+
const clauses = node.conditions.map(
|
|
3629
|
+
(condition) => `{ when: ${this.printExpression(condition.when)}, then: ${this.printOperand(condition.then)} }`
|
|
3630
|
+
);
|
|
3631
|
+
const elseValue = node.else ? `, ${this.printOperand(node.else)}` : "";
|
|
3632
|
+
return `caseWhen([${clauses.join(", ")}]${elseValue})`;
|
|
3633
|
+
}
|
|
3634
|
+
/**
|
|
3635
|
+
* Prints a window function operand to TypeScript code
|
|
3636
|
+
* @param node - Window function node
|
|
3637
|
+
* @returns TypeScript code representation
|
|
3638
|
+
*/
|
|
3639
|
+
printWindowFunctionOperand(node) {
|
|
3640
|
+
let result = `${node.name}(`;
|
|
3641
|
+
if (node.args.length > 0) {
|
|
3642
|
+
result += node.args.map((arg) => this.printOperand(arg)).join(", ");
|
|
3643
|
+
}
|
|
3644
|
+
result += ") OVER (";
|
|
3645
|
+
const parts = [];
|
|
3646
|
+
if (node.partitionBy && node.partitionBy.length > 0) {
|
|
3647
|
+
const partitionClause = "PARTITION BY " + node.partitionBy.map((col2) => `${capitalize(col2.table)}.${col2.name}`).join(", ");
|
|
3648
|
+
parts.push(partitionClause);
|
|
3649
|
+
}
|
|
3650
|
+
if (node.orderBy && node.orderBy.length > 0) {
|
|
3651
|
+
const orderClause = "ORDER BY " + node.orderBy.map((o) => `${capitalize(o.column.table)}.${o.column.name} ${o.direction}`).join(", ");
|
|
3652
|
+
parts.push(orderClause);
|
|
3653
|
+
}
|
|
3654
|
+
result += parts.join(" ");
|
|
3655
|
+
result += ")";
|
|
3656
|
+
return result;
|
|
3657
|
+
}
|
|
3658
|
+
/**
|
|
3659
|
+
* Converts method chain lines to inline format
|
|
3660
|
+
* @param lines - Method chain lines
|
|
3661
|
+
* @returns Inline method chain string
|
|
3662
|
+
*/
|
|
3663
|
+
inlineChain(lines) {
|
|
3664
|
+
return lines.map((line) => line.trim()).filter((line) => line.length > 0).join(" ");
|
|
3665
|
+
}
|
|
3666
|
+
/**
|
|
3667
|
+
* Maps SQL operators to TypeScript function names
|
|
3668
|
+
* @param op - SQL operator
|
|
3669
|
+
* @returns TypeScript function name
|
|
3670
|
+
*/
|
|
3671
|
+
mapOp(op) {
|
|
3672
|
+
const config = SQL_OPERATOR_REGISTRY[op];
|
|
3673
|
+
if (!config) {
|
|
3674
|
+
return assertNever2(op);
|
|
3675
|
+
}
|
|
3676
|
+
return config.tsName;
|
|
3677
|
+
}
|
|
3678
|
+
};
|
|
3679
|
+
|
|
3680
|
+
// src/orm/domain-event-bus.ts
|
|
3681
|
+
var DomainEventBus = class {
|
|
3682
|
+
constructor(initialHandlers) {
|
|
3683
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
3684
|
+
const handlers = initialHandlers ?? {};
|
|
3685
|
+
Object.entries(handlers).forEach(([name, list]) => {
|
|
3686
|
+
this.handlers.set(name, [...list]);
|
|
3687
|
+
});
|
|
3688
|
+
}
|
|
3689
|
+
register(name, handler) {
|
|
3690
|
+
const existing = this.handlers.get(name) ?? [];
|
|
3691
|
+
existing.push(handler);
|
|
3692
|
+
this.handlers.set(name, existing);
|
|
3693
|
+
}
|
|
3694
|
+
async dispatch(trackedEntities, ctx) {
|
|
3695
|
+
for (const tracked of trackedEntities) {
|
|
3696
|
+
const entity = tracked.entity;
|
|
3697
|
+
if (!entity.domainEvents || !entity.domainEvents.length) continue;
|
|
3698
|
+
for (const event of entity.domainEvents) {
|
|
3699
|
+
const eventName = this.getEventName(event);
|
|
3700
|
+
const handlers = this.handlers.get(eventName);
|
|
3701
|
+
if (!handlers) continue;
|
|
3702
|
+
for (const handler of handlers) {
|
|
3703
|
+
await handler(event, ctx);
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
entity.domainEvents = [];
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
getEventName(event) {
|
|
3710
|
+
if (!event) return "Unknown";
|
|
3711
|
+
if (typeof event === "string") return event;
|
|
3712
|
+
return event.constructor?.name ?? "Unknown";
|
|
3713
|
+
}
|
|
3714
|
+
};
|
|
3715
|
+
var addDomainEvent = (entity, event) => {
|
|
3716
|
+
if (!entity.domainEvents) {
|
|
3717
|
+
entity.domainEvents = [];
|
|
3718
|
+
}
|
|
3719
|
+
entity.domainEvents.push(event);
|
|
3720
|
+
};
|
|
3721
|
+
|
|
3722
|
+
// src/orm/identity-map.ts
|
|
3723
|
+
var IdentityMap = class {
|
|
3724
|
+
constructor() {
|
|
3725
|
+
this.buckets = /* @__PURE__ */ new Map();
|
|
3726
|
+
}
|
|
3727
|
+
get bucketsMap() {
|
|
3728
|
+
return this.buckets;
|
|
3729
|
+
}
|
|
3730
|
+
getEntity(table, pk) {
|
|
3731
|
+
const bucket = this.buckets.get(table.name);
|
|
3732
|
+
return bucket?.get(this.toIdentityKey(pk))?.entity;
|
|
3733
|
+
}
|
|
3734
|
+
register(tracked) {
|
|
3735
|
+
if (tracked.pk == null) return;
|
|
3736
|
+
const bucket = this.buckets.get(tracked.table.name) ?? /* @__PURE__ */ new Map();
|
|
3737
|
+
bucket.set(this.toIdentityKey(tracked.pk), tracked);
|
|
3738
|
+
this.buckets.set(tracked.table.name, bucket);
|
|
3739
|
+
}
|
|
3740
|
+
remove(tracked) {
|
|
3741
|
+
if (tracked.pk == null) return;
|
|
3742
|
+
const bucket = this.buckets.get(tracked.table.name);
|
|
3743
|
+
bucket?.delete(this.toIdentityKey(tracked.pk));
|
|
3744
|
+
}
|
|
3745
|
+
getEntitiesForTable(table) {
|
|
3746
|
+
const bucket = this.buckets.get(table.name);
|
|
3747
|
+
return bucket ? Array.from(bucket.values()) : [];
|
|
3748
|
+
}
|
|
3749
|
+
toIdentityKey(pk) {
|
|
3750
|
+
return String(pk);
|
|
3751
|
+
}
|
|
3752
|
+
};
|
|
3753
|
+
|
|
3754
|
+
// src/orm/relation-change-processor.ts
|
|
3755
|
+
var RelationChangeProcessor = class {
|
|
3756
|
+
constructor(unitOfWork, dialect, executor) {
|
|
3757
|
+
this.unitOfWork = unitOfWork;
|
|
3758
|
+
this.dialect = dialect;
|
|
3759
|
+
this.executor = executor;
|
|
3760
|
+
this.relationChanges = [];
|
|
3761
|
+
}
|
|
3762
|
+
registerChange(entry) {
|
|
3763
|
+
this.relationChanges.push(entry);
|
|
3764
|
+
}
|
|
3765
|
+
async process() {
|
|
3766
|
+
if (!this.relationChanges.length) return;
|
|
3767
|
+
const entries = [...this.relationChanges];
|
|
3768
|
+
this.relationChanges.length = 0;
|
|
3769
|
+
for (const entry of entries) {
|
|
3770
|
+
switch (entry.relation.type) {
|
|
3771
|
+
case RelationKinds.HasMany:
|
|
3772
|
+
await this.handleHasManyChange(entry);
|
|
3773
|
+
break;
|
|
3774
|
+
case RelationKinds.BelongsToMany:
|
|
3775
|
+
await this.handleBelongsToManyChange(entry);
|
|
3776
|
+
break;
|
|
3777
|
+
case RelationKinds.BelongsTo:
|
|
3778
|
+
await this.handleBelongsToChange(entry);
|
|
3779
|
+
break;
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
async handleHasManyChange(entry) {
|
|
3784
|
+
const relation = entry.relation;
|
|
3785
|
+
const target = entry.change.entity;
|
|
3786
|
+
if (!target) return;
|
|
3787
|
+
const tracked = this.unitOfWork.findTracked(target);
|
|
3788
|
+
if (!tracked) return;
|
|
3789
|
+
const localKey = relation.localKey || findPrimaryKey(entry.rootTable);
|
|
3790
|
+
const rootValue = entry.root[localKey];
|
|
3791
|
+
if (rootValue === void 0 || rootValue === null) return;
|
|
3792
|
+
if (entry.change.kind === "add" || entry.change.kind === "attach") {
|
|
3793
|
+
this.assignHasManyForeignKey(tracked.entity, relation, rootValue);
|
|
3794
|
+
this.unitOfWork.markDirty(tracked.entity);
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3797
|
+
if (entry.change.kind === "remove") {
|
|
3798
|
+
this.detachHasManyChild(tracked.entity, relation);
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
async handleBelongsToChange(_entry) {
|
|
3802
|
+
}
|
|
3803
|
+
async handleBelongsToManyChange(entry) {
|
|
3804
|
+
const relation = entry.relation;
|
|
3805
|
+
const rootKey = relation.localKey || findPrimaryKey(entry.rootTable);
|
|
3806
|
+
const rootId = entry.root[rootKey];
|
|
3807
|
+
if (rootId === void 0 || rootId === null) return;
|
|
3808
|
+
const targetId = this.resolvePrimaryKeyValue(entry.change.entity, relation.target);
|
|
3809
|
+
if (targetId === null) return;
|
|
3810
|
+
if (entry.change.kind === "attach" || entry.change.kind === "add") {
|
|
3811
|
+
await this.insertPivotRow(relation, rootId, targetId);
|
|
3812
|
+
return;
|
|
3813
|
+
}
|
|
3814
|
+
if (entry.change.kind === "detach" || entry.change.kind === "remove") {
|
|
3815
|
+
await this.deletePivotRow(relation, rootId, targetId);
|
|
3816
|
+
if (relation.cascade === "all" || relation.cascade === "remove") {
|
|
3817
|
+
this.unitOfWork.markRemoved(entry.change.entity);
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
assignHasManyForeignKey(child, relation, rootValue) {
|
|
3822
|
+
const current = child[relation.foreignKey];
|
|
3823
|
+
if (current === rootValue) return;
|
|
3824
|
+
child[relation.foreignKey] = rootValue;
|
|
3825
|
+
}
|
|
3826
|
+
detachHasManyChild(child, relation) {
|
|
3827
|
+
if (relation.cascade === "all" || relation.cascade === "remove") {
|
|
3828
|
+
this.unitOfWork.markRemoved(child);
|
|
3829
|
+
return;
|
|
3830
|
+
}
|
|
3831
|
+
child[relation.foreignKey] = null;
|
|
3832
|
+
this.unitOfWork.markDirty(child);
|
|
3833
|
+
}
|
|
3834
|
+
async insertPivotRow(relation, rootId, targetId) {
|
|
3835
|
+
const payload = {
|
|
3836
|
+
[relation.pivotForeignKeyToRoot]: rootId,
|
|
3837
|
+
[relation.pivotForeignKeyToTarget]: targetId
|
|
3838
|
+
};
|
|
3839
|
+
const builder = new InsertQueryBuilder(relation.pivotTable).values(payload);
|
|
3840
|
+
const compiled = builder.compile(this.dialect);
|
|
3841
|
+
await this.executor.executeSql(compiled.sql, compiled.params);
|
|
3842
|
+
}
|
|
3843
|
+
async deletePivotRow(relation, rootId, targetId) {
|
|
3844
|
+
const rootCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
|
|
3845
|
+
const targetCol = relation.pivotTable.columns[relation.pivotForeignKeyToTarget];
|
|
3846
|
+
if (!rootCol || !targetCol) return;
|
|
3847
|
+
const builder = new DeleteQueryBuilder(relation.pivotTable).where(
|
|
3848
|
+
and(eq(rootCol, rootId), eq(targetCol, targetId))
|
|
3849
|
+
);
|
|
3850
|
+
const compiled = builder.compile(this.dialect);
|
|
3851
|
+
await this.executor.executeSql(compiled.sql, compiled.params);
|
|
3852
|
+
}
|
|
3853
|
+
resolvePrimaryKeyValue(entity, table) {
|
|
3854
|
+
if (!entity) return null;
|
|
3855
|
+
const key = findPrimaryKey(table);
|
|
3856
|
+
const value = entity[key];
|
|
3857
|
+
if (value === void 0 || value === null) return null;
|
|
3858
|
+
return value;
|
|
3859
|
+
}
|
|
3860
|
+
};
|
|
3861
|
+
|
|
3862
|
+
// src/orm/transaction-runner.ts
|
|
3863
|
+
var runInTransaction = async (executor, action) => {
|
|
3864
|
+
if (!executor.beginTransaction) {
|
|
3865
|
+
await action();
|
|
3866
|
+
return;
|
|
3867
|
+
}
|
|
3868
|
+
await executor.beginTransaction();
|
|
3869
|
+
try {
|
|
3870
|
+
await action();
|
|
3871
|
+
await executor.commitTransaction?.();
|
|
3872
|
+
} catch (error) {
|
|
3873
|
+
await executor.rollbackTransaction?.();
|
|
3874
|
+
throw error;
|
|
3875
|
+
}
|
|
3876
|
+
};
|
|
3877
|
+
|
|
3878
|
+
// src/orm/runtime-types.ts
|
|
3879
|
+
var EntityStatus = /* @__PURE__ */ ((EntityStatus2) => {
|
|
3880
|
+
EntityStatus2["New"] = "new";
|
|
3881
|
+
EntityStatus2["Managed"] = "managed";
|
|
3882
|
+
EntityStatus2["Dirty"] = "dirty";
|
|
3883
|
+
EntityStatus2["Removed"] = "removed";
|
|
3884
|
+
EntityStatus2["Detached"] = "detached";
|
|
3885
|
+
return EntityStatus2;
|
|
3886
|
+
})(EntityStatus || {});
|
|
3887
|
+
|
|
3888
|
+
// src/orm/unit-of-work.ts
|
|
3889
|
+
var UnitOfWork = class {
|
|
3890
|
+
constructor(dialect, executor, identityMap, hookContext) {
|
|
3891
|
+
this.dialect = dialect;
|
|
3892
|
+
this.executor = executor;
|
|
3893
|
+
this.identityMap = identityMap;
|
|
3894
|
+
this.hookContext = hookContext;
|
|
3895
|
+
this.trackedEntities = /* @__PURE__ */ new Map();
|
|
3896
|
+
}
|
|
3897
|
+
get identityBuckets() {
|
|
3898
|
+
return this.identityMap.bucketsMap;
|
|
3899
|
+
}
|
|
3900
|
+
getTracked() {
|
|
3901
|
+
return Array.from(this.trackedEntities.values());
|
|
3902
|
+
}
|
|
3903
|
+
getEntity(table, pk) {
|
|
3904
|
+
return this.identityMap.getEntity(table, pk);
|
|
3905
|
+
}
|
|
3906
|
+
getEntitiesForTable(table) {
|
|
3907
|
+
return this.identityMap.getEntitiesForTable(table);
|
|
3908
|
+
}
|
|
3909
|
+
findTracked(entity) {
|
|
3910
|
+
return this.trackedEntities.get(entity);
|
|
3911
|
+
}
|
|
3912
|
+
setEntity(table, pk, entity) {
|
|
3913
|
+
if (pk === null || pk === void 0) return;
|
|
3914
|
+
let tracked = this.trackedEntities.get(entity);
|
|
3915
|
+
if (!tracked) {
|
|
3916
|
+
tracked = {
|
|
3917
|
+
table,
|
|
3918
|
+
entity,
|
|
3919
|
+
pk,
|
|
3920
|
+
status: "managed" /* Managed */,
|
|
3921
|
+
original: this.createSnapshot(table, entity)
|
|
3922
|
+
};
|
|
3923
|
+
this.trackedEntities.set(entity, tracked);
|
|
3924
|
+
} else {
|
|
3925
|
+
tracked.pk = pk;
|
|
3926
|
+
}
|
|
3927
|
+
this.registerIdentity(tracked);
|
|
3928
|
+
}
|
|
3929
|
+
trackNew(table, entity, pk) {
|
|
3930
|
+
const tracked = {
|
|
3931
|
+
table,
|
|
3932
|
+
entity,
|
|
3933
|
+
pk: pk ?? null,
|
|
3934
|
+
status: "new" /* New */,
|
|
3935
|
+
original: null
|
|
3936
|
+
};
|
|
3937
|
+
this.trackedEntities.set(entity, tracked);
|
|
3938
|
+
if (pk != null) {
|
|
3939
|
+
this.registerIdentity(tracked);
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
trackManaged(table, pk, entity) {
|
|
3943
|
+
const tracked = {
|
|
3944
|
+
table,
|
|
3945
|
+
entity,
|
|
3946
|
+
pk,
|
|
3947
|
+
status: "managed" /* Managed */,
|
|
3948
|
+
original: this.createSnapshot(table, entity)
|
|
3949
|
+
};
|
|
3950
|
+
this.trackedEntities.set(entity, tracked);
|
|
3951
|
+
this.registerIdentity(tracked);
|
|
3952
|
+
}
|
|
3953
|
+
markDirty(entity) {
|
|
3954
|
+
const tracked = this.trackedEntities.get(entity);
|
|
3955
|
+
if (!tracked) return;
|
|
3956
|
+
if (tracked.status === "new" /* New */ || tracked.status === "removed" /* Removed */) return;
|
|
3957
|
+
tracked.status = "dirty" /* Dirty */;
|
|
3958
|
+
}
|
|
3959
|
+
markRemoved(entity) {
|
|
3960
|
+
const tracked = this.trackedEntities.get(entity);
|
|
3961
|
+
if (!tracked) return;
|
|
3962
|
+
tracked.status = "removed" /* Removed */;
|
|
3963
|
+
}
|
|
3964
|
+
async flush() {
|
|
3965
|
+
const toFlush = Array.from(this.trackedEntities.values());
|
|
3966
|
+
for (const tracked of toFlush) {
|
|
3967
|
+
switch (tracked.status) {
|
|
3968
|
+
case "new" /* New */:
|
|
3969
|
+
await this.flushInsert(tracked);
|
|
3970
|
+
break;
|
|
3971
|
+
case "dirty" /* Dirty */:
|
|
3972
|
+
await this.flushUpdate(tracked);
|
|
3973
|
+
break;
|
|
3974
|
+
case "removed" /* Removed */:
|
|
3975
|
+
await this.flushDelete(tracked);
|
|
3976
|
+
break;
|
|
3977
|
+
default:
|
|
3978
|
+
break;
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
async flushInsert(tracked) {
|
|
3983
|
+
await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
|
|
3984
|
+
const payload = this.extractColumns(tracked.table, tracked.entity);
|
|
3985
|
+
const builder = new InsertQueryBuilder(tracked.table).values(payload);
|
|
3986
|
+
const compiled = builder.compile(this.dialect);
|
|
3987
|
+
await this.executeCompiled(compiled);
|
|
3988
|
+
tracked.status = "managed" /* Managed */;
|
|
3989
|
+
tracked.original = this.createSnapshot(tracked.table, tracked.entity);
|
|
3990
|
+
tracked.pk = this.getPrimaryKeyValue(tracked);
|
|
3991
|
+
this.registerIdentity(tracked);
|
|
3992
|
+
await this.runHook(tracked.table.hooks?.afterInsert, tracked);
|
|
3993
|
+
}
|
|
3994
|
+
async flushUpdate(tracked) {
|
|
3995
|
+
if (tracked.pk == null) return;
|
|
3996
|
+
const changes = this.computeChanges(tracked);
|
|
3997
|
+
if (!Object.keys(changes).length) {
|
|
3998
|
+
tracked.status = "managed" /* Managed */;
|
|
3999
|
+
return;
|
|
4000
|
+
}
|
|
4001
|
+
await this.runHook(tracked.table.hooks?.beforeUpdate, tracked);
|
|
4002
|
+
const pkColumn = tracked.table.columns[findPrimaryKey(tracked.table)];
|
|
4003
|
+
if (!pkColumn) return;
|
|
4004
|
+
const builder = new UpdateQueryBuilder(tracked.table).set(changes).where(eq(pkColumn, tracked.pk));
|
|
4005
|
+
const compiled = builder.compile(this.dialect);
|
|
4006
|
+
await this.executeCompiled(compiled);
|
|
4007
|
+
tracked.status = "managed" /* Managed */;
|
|
4008
|
+
tracked.original = this.createSnapshot(tracked.table, tracked.entity);
|
|
4009
|
+
this.registerIdentity(tracked);
|
|
4010
|
+
await this.runHook(tracked.table.hooks?.afterUpdate, tracked);
|
|
4011
|
+
}
|
|
4012
|
+
async flushDelete(tracked) {
|
|
4013
|
+
if (tracked.pk == null) return;
|
|
4014
|
+
await this.runHook(tracked.table.hooks?.beforeDelete, tracked);
|
|
4015
|
+
const pkColumn = tracked.table.columns[findPrimaryKey(tracked.table)];
|
|
4016
|
+
if (!pkColumn) return;
|
|
4017
|
+
const builder = new DeleteQueryBuilder(tracked.table).where(eq(pkColumn, tracked.pk));
|
|
4018
|
+
const compiled = builder.compile(this.dialect);
|
|
4019
|
+
await this.executeCompiled(compiled);
|
|
4020
|
+
tracked.status = "detached" /* Detached */;
|
|
4021
|
+
this.trackedEntities.delete(tracked.entity);
|
|
4022
|
+
this.identityMap.remove(tracked);
|
|
4023
|
+
await this.runHook(tracked.table.hooks?.afterDelete, tracked);
|
|
4024
|
+
}
|
|
4025
|
+
async runHook(hook, tracked) {
|
|
4026
|
+
if (!hook) return;
|
|
4027
|
+
await hook(this.hookContext(), tracked.entity);
|
|
4028
|
+
}
|
|
4029
|
+
computeChanges(tracked) {
|
|
4030
|
+
const snapshot = tracked.original ?? {};
|
|
4031
|
+
const changes = {};
|
|
4032
|
+
for (const column of Object.keys(tracked.table.columns)) {
|
|
4033
|
+
const current = tracked.entity[column];
|
|
4034
|
+
if (snapshot[column] !== current) {
|
|
4035
|
+
changes[column] = current;
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
return changes;
|
|
4039
|
+
}
|
|
4040
|
+
extractColumns(table, entity) {
|
|
4041
|
+
const payload = {};
|
|
4042
|
+
for (const column of Object.keys(table.columns)) {
|
|
4043
|
+
payload[column] = entity[column];
|
|
4044
|
+
}
|
|
4045
|
+
return payload;
|
|
4046
|
+
}
|
|
4047
|
+
async executeCompiled(compiled) {
|
|
4048
|
+
await this.executor.executeSql(compiled.sql, compiled.params);
|
|
4049
|
+
}
|
|
4050
|
+
registerIdentity(tracked) {
|
|
4051
|
+
if (tracked.pk == null) return;
|
|
4052
|
+
this.identityMap.register(tracked);
|
|
4053
|
+
}
|
|
4054
|
+
createSnapshot(table, entity) {
|
|
4055
|
+
const snapshot = {};
|
|
4056
|
+
for (const column of Object.keys(table.columns)) {
|
|
4057
|
+
snapshot[column] = entity[column];
|
|
4058
|
+
}
|
|
4059
|
+
return snapshot;
|
|
4060
|
+
}
|
|
4061
|
+
getPrimaryKeyValue(tracked) {
|
|
4062
|
+
const key = findPrimaryKey(tracked.table);
|
|
4063
|
+
const val = tracked.entity[key];
|
|
4064
|
+
if (val === void 0 || val === null) return null;
|
|
4065
|
+
return val;
|
|
4066
|
+
}
|
|
4067
|
+
};
|
|
4068
|
+
|
|
4069
|
+
// src/orm/orm-context.ts
|
|
4070
|
+
var OrmContext = class {
|
|
4071
|
+
constructor(options) {
|
|
4072
|
+
this.options = options;
|
|
4073
|
+
this.identityMap = new IdentityMap();
|
|
4074
|
+
this.interceptors = [...options.interceptors ?? []];
|
|
4075
|
+
this.unitOfWork = new UnitOfWork(
|
|
4076
|
+
options.dialect,
|
|
4077
|
+
options.executor,
|
|
4078
|
+
this.identityMap,
|
|
4079
|
+
() => this
|
|
4080
|
+
);
|
|
4081
|
+
this.relationChanges = new RelationChangeProcessor(
|
|
4082
|
+
this.unitOfWork,
|
|
4083
|
+
options.dialect,
|
|
4084
|
+
options.executor
|
|
4085
|
+
);
|
|
4086
|
+
this.domainEvents = new DomainEventBus(options.domainEventHandlers);
|
|
4087
|
+
}
|
|
4088
|
+
get dialect() {
|
|
4089
|
+
return this.options.dialect;
|
|
4090
|
+
}
|
|
4091
|
+
get executor() {
|
|
4092
|
+
return this.options.executor;
|
|
4093
|
+
}
|
|
4094
|
+
get identityBuckets() {
|
|
4095
|
+
return this.unitOfWork.identityBuckets;
|
|
4096
|
+
}
|
|
4097
|
+
get tracked() {
|
|
4098
|
+
return this.unitOfWork.getTracked();
|
|
4099
|
+
}
|
|
4100
|
+
getEntity(table, pk) {
|
|
4101
|
+
return this.unitOfWork.getEntity(table, pk);
|
|
4102
|
+
}
|
|
4103
|
+
setEntity(table, pk, entity) {
|
|
4104
|
+
this.unitOfWork.setEntity(table, pk, entity);
|
|
4105
|
+
}
|
|
4106
|
+
trackNew(table, entity, pk) {
|
|
4107
|
+
this.unitOfWork.trackNew(table, entity, pk);
|
|
4108
|
+
}
|
|
4109
|
+
trackManaged(table, pk, entity) {
|
|
4110
|
+
this.unitOfWork.trackManaged(table, pk, entity);
|
|
4111
|
+
}
|
|
4112
|
+
markDirty(entity) {
|
|
4113
|
+
this.unitOfWork.markDirty(entity);
|
|
4114
|
+
}
|
|
4115
|
+
markRemoved(entity) {
|
|
4116
|
+
this.unitOfWork.markRemoved(entity);
|
|
4117
|
+
}
|
|
4118
|
+
registerRelationChange(root, relationKey, rootTable, relationName, relation, change) {
|
|
4119
|
+
const entry = {
|
|
4120
|
+
root,
|
|
4121
|
+
relationKey,
|
|
4122
|
+
rootTable,
|
|
4123
|
+
relationName,
|
|
4124
|
+
relation,
|
|
4125
|
+
change
|
|
4126
|
+
};
|
|
4127
|
+
this.relationChanges.registerChange(entry);
|
|
4128
|
+
}
|
|
4129
|
+
registerInterceptor(interceptor) {
|
|
4130
|
+
this.interceptors.push(interceptor);
|
|
4131
|
+
}
|
|
4132
|
+
registerDomainEventHandler(name, handler) {
|
|
4133
|
+
this.domainEvents.register(name, handler);
|
|
4134
|
+
}
|
|
4135
|
+
async saveChanges() {
|
|
4136
|
+
await runInTransaction(this.executor, async () => {
|
|
4137
|
+
for (const interceptor of this.interceptors) {
|
|
4138
|
+
await interceptor.beforeFlush?.(this);
|
|
4139
|
+
}
|
|
4140
|
+
await this.unitOfWork.flush();
|
|
4141
|
+
await this.relationChanges.process();
|
|
4142
|
+
await this.unitOfWork.flush();
|
|
4143
|
+
for (const interceptor of this.interceptors) {
|
|
4144
|
+
await interceptor.afterFlush?.(this);
|
|
4145
|
+
}
|
|
4146
|
+
});
|
|
4147
|
+
await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
|
|
4148
|
+
}
|
|
4149
|
+
getEntitiesForTable(table) {
|
|
4150
|
+
return this.unitOfWork.getEntitiesForTable(table);
|
|
4151
|
+
}
|
|
4152
|
+
};
|
|
4153
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4154
|
+
0 && (module.exports = {
|
|
4155
|
+
AsyncLocalStorage,
|
|
4156
|
+
DefaultBelongsToReference,
|
|
4157
|
+
DefaultHasManyCollection,
|
|
4158
|
+
DefaultManyToManyCollection,
|
|
4159
|
+
DeleteQueryBuilder,
|
|
4160
|
+
EntityStatus,
|
|
4161
|
+
InsertQueryBuilder,
|
|
4162
|
+
MySqlDialect,
|
|
4163
|
+
OrmContext,
|
|
4164
|
+
RelationKinds,
|
|
4165
|
+
SelectQueryBuilder,
|
|
4166
|
+
SqlServerDialect,
|
|
4167
|
+
SqliteDialect,
|
|
4168
|
+
TypeScriptGenerator,
|
|
4169
|
+
UpdateQueryBuilder,
|
|
4170
|
+
addDomainEvent,
|
|
4171
|
+
and,
|
|
4172
|
+
avg,
|
|
4173
|
+
belongsTo,
|
|
4174
|
+
belongsToMany,
|
|
4175
|
+
between,
|
|
4176
|
+
caseWhen,
|
|
4177
|
+
col,
|
|
4178
|
+
columnOperand,
|
|
4179
|
+
count,
|
|
4180
|
+
createColumn,
|
|
4181
|
+
createEntityFromRow,
|
|
4182
|
+
createEntityProxy,
|
|
4183
|
+
createLiteral,
|
|
4184
|
+
defineTable,
|
|
4185
|
+
denseRank,
|
|
4186
|
+
eq,
|
|
4187
|
+
executeHydrated,
|
|
4188
|
+
exists,
|
|
4189
|
+
firstValue,
|
|
4190
|
+
gt,
|
|
4191
|
+
gte,
|
|
4192
|
+
hasMany,
|
|
4193
|
+
hydrateRows,
|
|
4194
|
+
inList,
|
|
4195
|
+
isCaseExpressionNode,
|
|
4196
|
+
isExpressionSelectionNode,
|
|
4197
|
+
isFunctionNode,
|
|
4198
|
+
isNotNull,
|
|
4199
|
+
isNull,
|
|
4200
|
+
isOperandNode,
|
|
4201
|
+
isWindowFunctionNode,
|
|
4202
|
+
jsonPath,
|
|
4203
|
+
lag,
|
|
4204
|
+
lastValue,
|
|
4205
|
+
lead,
|
|
4206
|
+
like,
|
|
4207
|
+
loadBelongsToManyRelation,
|
|
4208
|
+
loadBelongsToRelation,
|
|
4209
|
+
loadHasManyRelation,
|
|
4210
|
+
lt,
|
|
4211
|
+
lte,
|
|
4212
|
+
neq,
|
|
4213
|
+
notBetween,
|
|
4214
|
+
notExists,
|
|
4215
|
+
notInList,
|
|
4216
|
+
notLike,
|
|
4217
|
+
ntile,
|
|
4218
|
+
or,
|
|
4219
|
+
rank,
|
|
4220
|
+
rowNumber,
|
|
4221
|
+
sum,
|
|
4222
|
+
valueToOperand,
|
|
4223
|
+
visitExpression,
|
|
4224
|
+
visitOperand,
|
|
4225
|
+
windowFunction
|
|
4226
|
+
});
|
|
4227
|
+
//# sourceMappingURL=index.cjs.map
|