semola 0.5.4 → 0.6.0
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 +18 -45
- package/dist/chunk-CKQMccvm.cjs +28 -0
- package/dist/lib/api/index.cjs +29 -15
- package/dist/lib/api/index.mjs +30 -16
- package/dist/lib/cache/index.cjs +47 -22
- package/dist/lib/cache/index.d.cts +3 -24
- package/dist/lib/cache/index.d.mts +3 -24
- package/dist/lib/cache/index.mjs +48 -23
- package/dist/lib/cron/index.cjs +117 -117
- package/dist/lib/cron/index.mjs +118 -118
- package/dist/lib/errors/index.d.cts +12 -1
- package/dist/lib/errors/index.d.mts +12 -1
- package/dist/lib/logging/index.cjs +1 -0
- package/dist/lib/orm/index.cjs +1642 -0
- package/dist/lib/orm/index.d.cts +402 -0
- package/dist/lib/orm/index.d.mts +402 -0
- package/dist/lib/orm/index.mjs +1630 -0
- package/dist/lib/prompts/index.cjs +89 -89
- package/dist/lib/prompts/index.d.cts +12 -33
- package/dist/lib/prompts/index.d.mts +12 -33
- package/dist/lib/prompts/index.mjs +89 -90
- package/dist/lib/pubsub/index.cjs +43 -19
- package/dist/lib/pubsub/index.d.cts +3 -18
- package/dist/lib/pubsub/index.d.mts +3 -18
- package/dist/lib/pubsub/index.mjs +44 -20
- package/dist/lib/queue/index.cjs +40 -10
- package/dist/lib/queue/index.d.cts +11 -4
- package/dist/lib/queue/index.d.mts +11 -4
- package/dist/lib/queue/index.mjs +39 -11
- package/dist/lib/workflow/index.cjs +285 -282
- package/dist/lib/workflow/index.d.cts +76 -11
- package/dist/lib/workflow/index.d.mts +76 -11
- package/dist/lib/workflow/index.mjs +278 -284
- package/package.json +11 -1
- package/dist/index-BhGNDjPq.d.mts +0 -13
- package/dist/index-DxSbeGP-.d.cts +0 -13
|
@@ -0,0 +1,1642 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/lib/orm/column/index.ts
|
|
3
|
+
const createColumnBuilder = (column) => {
|
|
4
|
+
const primaryKey = () => {
|
|
5
|
+
return createColumnBuilder({
|
|
6
|
+
...column,
|
|
7
|
+
_meta: {
|
|
8
|
+
...column._meta,
|
|
9
|
+
isNullable: false,
|
|
10
|
+
isPrimaryKey: true
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
const notNull = () => {
|
|
15
|
+
return createColumnBuilder({
|
|
16
|
+
...column,
|
|
17
|
+
_meta: {
|
|
18
|
+
...column._meta,
|
|
19
|
+
isNullable: false
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
const nullable = (() => {
|
|
24
|
+
if (column._meta.isPrimaryKey) return createColumnBuilder({
|
|
25
|
+
...column,
|
|
26
|
+
_meta: {
|
|
27
|
+
...column._meta,
|
|
28
|
+
isNullable: false
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return createColumnBuilder({
|
|
32
|
+
...column,
|
|
33
|
+
_meta: {
|
|
34
|
+
...column._meta,
|
|
35
|
+
isNullable: true
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
const unique = () => {
|
|
40
|
+
return createColumnBuilder({
|
|
41
|
+
...column,
|
|
42
|
+
_meta: {
|
|
43
|
+
...column._meta,
|
|
44
|
+
isUnique: true
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
const defaultHandler = (value) => {
|
|
49
|
+
return createColumnBuilder({
|
|
50
|
+
...column,
|
|
51
|
+
_meta: {
|
|
52
|
+
...column._meta,
|
|
53
|
+
hasDefault: true
|
|
54
|
+
},
|
|
55
|
+
_default: value
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
const referencesBuilder = (tableColumn) => {
|
|
59
|
+
return createColumnBuilder({
|
|
60
|
+
...column,
|
|
61
|
+
references: { tableColumn }
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
const references = referencesBuilder;
|
|
65
|
+
references.tableColumn = column.references?.tableColumn;
|
|
66
|
+
return {
|
|
67
|
+
...column,
|
|
68
|
+
primaryKey,
|
|
69
|
+
notNull,
|
|
70
|
+
nullable,
|
|
71
|
+
unique,
|
|
72
|
+
default: defaultHandler,
|
|
73
|
+
references
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
const createBaseColumn = (sqlName, type, enumValues) => {
|
|
77
|
+
return createColumnBuilder({
|
|
78
|
+
sqlName,
|
|
79
|
+
type,
|
|
80
|
+
enumValues,
|
|
81
|
+
_meta: {
|
|
82
|
+
isNullable: true,
|
|
83
|
+
isPrimaryKey: false,
|
|
84
|
+
isUnique: false,
|
|
85
|
+
hasDefault: false
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
const string = (sqlName) => {
|
|
90
|
+
return createBaseColumn(sqlName, "string");
|
|
91
|
+
};
|
|
92
|
+
const uuid = (sqlName) => {
|
|
93
|
+
return string(sqlName);
|
|
94
|
+
};
|
|
95
|
+
const number = (sqlName) => {
|
|
96
|
+
return createBaseColumn(sqlName, "number");
|
|
97
|
+
};
|
|
98
|
+
const boolean = (sqlName) => {
|
|
99
|
+
return createBaseColumn(sqlName, "boolean");
|
|
100
|
+
};
|
|
101
|
+
const date = (sqlName) => {
|
|
102
|
+
return createBaseColumn(sqlName, "date");
|
|
103
|
+
};
|
|
104
|
+
const enumType = (sqlName, values) => {
|
|
105
|
+
return createBaseColumn(sqlName, "enum", values);
|
|
106
|
+
};
|
|
107
|
+
const json = (sqlName) => {
|
|
108
|
+
return createBaseColumn(sqlName, "json");
|
|
109
|
+
};
|
|
110
|
+
const jsonb = (sqlName) => {
|
|
111
|
+
return createBaseColumn(sqlName, "jsonb");
|
|
112
|
+
};
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/lib/orm/utils.ts
|
|
115
|
+
const quoteIdentifier = (identifier) => {
|
|
116
|
+
identifier = identifier.replaceAll("\"", "\"\"");
|
|
117
|
+
return `"${identifier}"`;
|
|
118
|
+
};
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/lib/orm/dialect/relation-fk.ts
|
|
121
|
+
const resolveHasManyForeignKeyColumn = (sourceTable, targetTable) => {
|
|
122
|
+
const sourceColumnValues = Object.values(sourceTable.columns);
|
|
123
|
+
const candidates = [];
|
|
124
|
+
for (const [, column] of Object.entries(targetTable.columns)) {
|
|
125
|
+
if (!column.references) continue;
|
|
126
|
+
const getReferencedColumn = column.references.tableColumn;
|
|
127
|
+
if (!getReferencedColumn) continue;
|
|
128
|
+
const referencedColumn = getReferencedColumn();
|
|
129
|
+
if (sourceColumnValues.some((sourceCol) => {
|
|
130
|
+
return sourceCol === referencedColumn;
|
|
131
|
+
})) candidates.push({
|
|
132
|
+
fk: column,
|
|
133
|
+
source: referencedColumn
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (candidates.length > 1) throw new Error(`Ambiguous hasMany foreign key from ${targetTable.sqlName} to ${sourceTable.sqlName}`);
|
|
137
|
+
const [candidate] = candidates;
|
|
138
|
+
if (!candidate) throw new Error(`Missing hasMany foreign key from ${targetTable.sqlName} to ${sourceTable.sqlName}`);
|
|
139
|
+
return candidate;
|
|
140
|
+
};
|
|
141
|
+
const resolveHasOneForeignKeyColumn = (input) => {
|
|
142
|
+
const { sourceTable, relationTable, relationForeignKey } = input;
|
|
143
|
+
const localForeignKey = sourceTable.columns[relationForeignKey];
|
|
144
|
+
if (!localForeignKey) throw new Error(`Missing hasOne foreign key column ${relationForeignKey} on ${sourceTable.sqlName}`);
|
|
145
|
+
if (!localForeignKey.references?.tableColumn) throw new Error(`Column ${relationForeignKey} on ${sourceTable.sqlName} is not a foreign key - call .references() on it`);
|
|
146
|
+
const referencedColumn = localForeignKey.references.tableColumn();
|
|
147
|
+
if (!Object.values(relationTable.columns).some((column) => {
|
|
148
|
+
return column === referencedColumn;
|
|
149
|
+
})) throw new Error(`Column ${relationForeignKey} on ${sourceTable.sqlName} does not reference ${relationTable.sqlName}`);
|
|
150
|
+
return {
|
|
151
|
+
localForeignKey,
|
|
152
|
+
target: referencedColumn
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/lib/orm/dialect/clauses.ts
|
|
157
|
+
const FALSE_WHERE_SQL = "(1 = 0)";
|
|
158
|
+
const TRUE_WHERE_SQL = "(1 = 1)";
|
|
159
|
+
const RELATION_FILTER_KEYS = [
|
|
160
|
+
"every",
|
|
161
|
+
"some",
|
|
162
|
+
"none"
|
|
163
|
+
];
|
|
164
|
+
const EMPTY_INCLUDE = {
|
|
165
|
+
sql: "",
|
|
166
|
+
params: [],
|
|
167
|
+
descriptors: []
|
|
168
|
+
};
|
|
169
|
+
const serializeParam = (value) => {
|
|
170
|
+
if (value instanceof Date) return value.toISOString();
|
|
171
|
+
return value;
|
|
172
|
+
};
|
|
173
|
+
const escapeLikeValue = (value) => {
|
|
174
|
+
return serializeParam(`${serializeParam(value)}`.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_"));
|
|
175
|
+
};
|
|
176
|
+
const OPERATORS = {
|
|
177
|
+
equals: {
|
|
178
|
+
sql: (ph) => `= ${ph}`,
|
|
179
|
+
transform: (v) => serializeParam(v)
|
|
180
|
+
},
|
|
181
|
+
gt: {
|
|
182
|
+
sql: (ph) => `> ${ph}`,
|
|
183
|
+
transform: (v) => serializeParam(v)
|
|
184
|
+
},
|
|
185
|
+
gte: {
|
|
186
|
+
sql: (ph) => `>= ${ph}`,
|
|
187
|
+
transform: (v) => serializeParam(v)
|
|
188
|
+
},
|
|
189
|
+
lt: {
|
|
190
|
+
sql: (ph) => `< ${ph}`,
|
|
191
|
+
transform: (v) => serializeParam(v)
|
|
192
|
+
},
|
|
193
|
+
lte: {
|
|
194
|
+
sql: (ph) => `<= ${ph}`,
|
|
195
|
+
transform: (v) => serializeParam(v)
|
|
196
|
+
},
|
|
197
|
+
startsWith: {
|
|
198
|
+
sql: (ph) => `LIKE ${ph} ESCAPE '\\'`,
|
|
199
|
+
transform: (v) => serializeParam(`${escapeLikeValue(v)}%`)
|
|
200
|
+
},
|
|
201
|
+
endsWith: {
|
|
202
|
+
sql: (ph) => `LIKE ${ph} ESCAPE '\\'`,
|
|
203
|
+
transform: (v) => serializeParam(`%${escapeLikeValue(v)}`)
|
|
204
|
+
},
|
|
205
|
+
contains: {
|
|
206
|
+
sql: (ph) => `LIKE ${ph} ESCAPE '\\'`,
|
|
207
|
+
transform: (v) => serializeParam(`%${escapeLikeValue(v)}%`)
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const createNextPlaceholder = (spec) => {
|
|
211
|
+
let index = 0;
|
|
212
|
+
return () => {
|
|
213
|
+
index += 1;
|
|
214
|
+
return spec.formatPlaceholder(index);
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
const buildSelectList = (columns, include) => {
|
|
218
|
+
if (include.sql) return `${columns}, ${include.sql}`;
|
|
219
|
+
return columns;
|
|
220
|
+
};
|
|
221
|
+
const serializeColumnValue = (column, value) => {
|
|
222
|
+
if (column.type !== "json" && column.type !== "jsonb") return serializeParam(value);
|
|
223
|
+
if (value === null) return value;
|
|
224
|
+
if (value === void 0) return null;
|
|
225
|
+
return JSON.stringify(value);
|
|
226
|
+
};
|
|
227
|
+
const isPlainObject = (value) => {
|
|
228
|
+
if (value === null) return false;
|
|
229
|
+
if (typeof value !== "object") return false;
|
|
230
|
+
if (Array.isArray(value)) return false;
|
|
231
|
+
if (value instanceof Date) return false;
|
|
232
|
+
const prototype = Object.getPrototypeOf(value);
|
|
233
|
+
if (prototype === null) return true;
|
|
234
|
+
if (prototype === Object.prototype) return true;
|
|
235
|
+
return false;
|
|
236
|
+
};
|
|
237
|
+
const appendDirectWhereClause = (input) => {
|
|
238
|
+
const { clauses, params, nextPlaceholder, column, sqlName, value } = input;
|
|
239
|
+
if (value === null) {
|
|
240
|
+
clauses.push(`${sqlName} IS NULL`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
clauses.push(`${sqlName} = ${nextPlaceholder()}`);
|
|
244
|
+
params.push(serializeColumnValue(column, value));
|
|
245
|
+
};
|
|
246
|
+
const appendOperatorWhereClauses = (input) => {
|
|
247
|
+
const { clauses, params, nextPlaceholder, column, sqlName, jsKey, value } = input;
|
|
248
|
+
const entries = Object.entries(value);
|
|
249
|
+
if (!entries.length) throw new Error(`Missing where operator for field ${jsKey}`);
|
|
250
|
+
for (const [op, operand] of entries) {
|
|
251
|
+
if (op === "in" || op === "notIn") {
|
|
252
|
+
if (!Array.isArray(operand)) throw new Error(`Expected array for where operator: ${op} for field ${jsKey}`);
|
|
253
|
+
if (op === "in" && operand.length === 0) {
|
|
254
|
+
clauses.push(FALSE_WHERE_SQL);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (op === "notIn" && operand.length === 0) continue;
|
|
258
|
+
const placeholders = operand.map(() => nextPlaceholder());
|
|
259
|
+
const keyword = op === "in" ? "IN" : "NOT IN";
|
|
260
|
+
clauses.push(`${sqlName} ${keyword} (${placeholders.join(", ")})`);
|
|
261
|
+
for (const item of operand) params.push(serializeColumnValue(column, item));
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (op === "between") {
|
|
265
|
+
if (!Array.isArray(operand)) throw new Error(`Expected array for where operator: ${op} for field ${jsKey}`);
|
|
266
|
+
if (operand.length !== 2) throw new Error(`Expected 2-element array for where operator: ${op} for field ${jsKey}`);
|
|
267
|
+
const [min, max] = operand;
|
|
268
|
+
const ph1 = nextPlaceholder();
|
|
269
|
+
const ph2 = nextPlaceholder();
|
|
270
|
+
clauses.push(`${sqlName} BETWEEN ${ph1} AND ${ph2}`);
|
|
271
|
+
params.push(serializeColumnValue(column, min), serializeColumnValue(column, max));
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const operator = OPERATORS[op];
|
|
275
|
+
if (!operator) throw new Error(`Unknown where operator: ${op} for field ${jsKey}`);
|
|
276
|
+
if (op === "equals" && operand === null) {
|
|
277
|
+
clauses.push(`${sqlName} IS NULL`);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
clauses.push(`${sqlName} ${operator.sql(nextPlaceholder())}`);
|
|
281
|
+
params.push(operator.transform(serializeColumnValue(column, operand)));
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const parseRelationFilters = (relationName, value) => {
|
|
285
|
+
if (!isPlainObject(value)) throw new Error(`Relation where filter for ${relationName} must be an object`);
|
|
286
|
+
const filter = value;
|
|
287
|
+
const filters = [];
|
|
288
|
+
for (const filterKey of RELATION_FILTER_KEYS) {
|
|
289
|
+
if (!(filterKey in filter)) continue;
|
|
290
|
+
const nestedWhere = filter[filterKey];
|
|
291
|
+
if (!isPlainObject(nestedWhere)) throw new Error(`Relation where filter ${filterKey} must be an object`);
|
|
292
|
+
filters.push({
|
|
293
|
+
key: filterKey,
|
|
294
|
+
where: nestedWhere
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
if (filters.length === 0) throw new Error(`Relation where filter for ${relationName} must include at least one of every, some, or none`);
|
|
298
|
+
return filters;
|
|
299
|
+
};
|
|
300
|
+
const isRelationFilterValue = (value) => {
|
|
301
|
+
if (!isPlainObject(value)) return false;
|
|
302
|
+
const filter = value;
|
|
303
|
+
for (const key of RELATION_FILTER_KEYS) if (key in filter) return true;
|
|
304
|
+
return Object.keys(filter).length === 0;
|
|
305
|
+
};
|
|
306
|
+
const buildRelationForeignKeyCondition = (input) => {
|
|
307
|
+
const { parentTable, parentAlias, relation, relationTable, relationAlias } = input;
|
|
308
|
+
if (relation._type === "hasMany") {
|
|
309
|
+
const { fk: foreignKey, source: sourceColumn } = resolveHasManyForeignKeyColumn(parentTable, relationTable);
|
|
310
|
+
return `${relationAlias}.${quoteIdentifier(foreignKey.sqlName)} = ${parentAlias}.${quoteIdentifier(sourceColumn.sqlName)}`;
|
|
311
|
+
}
|
|
312
|
+
if (relation._type !== "hasOne") throw new Error("Expected hasOne relation");
|
|
313
|
+
const { localForeignKey, target } = resolveHasOneForeignKeyColumn({
|
|
314
|
+
sourceTable: parentTable,
|
|
315
|
+
relationTable,
|
|
316
|
+
relationForeignKey: relation._foreignKey
|
|
317
|
+
});
|
|
318
|
+
return `${relationAlias}.${quoteIdentifier(target.sqlName)} = ${parentAlias}.${quoteIdentifier(localForeignKey.sqlName)}`;
|
|
319
|
+
};
|
|
320
|
+
const appendRelationFilterClause = (input) => {
|
|
321
|
+
const { clauses, params, nextPlaceholder, parentAlias, table, relation, relationName, key, where: nestedWhere } = input;
|
|
322
|
+
if (!parentAlias) throw new Error("parentAlias is required for relation where filters");
|
|
323
|
+
const relationTable = relation._table;
|
|
324
|
+
const relationAlias = `where_${relationName}__${relationTable.sqlName}`;
|
|
325
|
+
const fkCondition = buildRelationForeignKeyCondition({
|
|
326
|
+
parentTable: table,
|
|
327
|
+
parentAlias,
|
|
328
|
+
relation,
|
|
329
|
+
relationTable,
|
|
330
|
+
relationAlias
|
|
331
|
+
});
|
|
332
|
+
const nested = buildWhereClause({
|
|
333
|
+
nextPlaceholder,
|
|
334
|
+
table: relationTable,
|
|
335
|
+
where: nestedWhere
|
|
336
|
+
});
|
|
337
|
+
const nestedCondition = nested.sql ? nested.sql : TRUE_WHERE_SQL;
|
|
338
|
+
params.push(...nested.params);
|
|
339
|
+
const relationFrom = `${quoteIdentifier(relationTable.sqlName)} AS ${relationAlias}`;
|
|
340
|
+
if (key === "some") {
|
|
341
|
+
clauses.push(`EXISTS (SELECT 1 FROM ${relationFrom} WHERE ${fkCondition} AND (${nestedCondition}))`);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (key === "none") {
|
|
345
|
+
clauses.push(`NOT EXISTS (SELECT 1 FROM ${relationFrom} WHERE ${fkCondition} AND (${nestedCondition}))`);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
clauses.push(`NOT EXISTS (SELECT 1 FROM ${relationFrom} WHERE ${fkCondition} AND NOT (${nestedCondition}))`);
|
|
349
|
+
};
|
|
350
|
+
const appendRelationWhereClause = (input) => {
|
|
351
|
+
const { parentAlias, relationName, value } = input;
|
|
352
|
+
if (!parentAlias) throw new Error("parentAlias is required for relation where filters");
|
|
353
|
+
const filters = parseRelationFilters(relationName, value);
|
|
354
|
+
for (const filter of filters) appendRelationFilterClause({
|
|
355
|
+
...input,
|
|
356
|
+
...filter,
|
|
357
|
+
parentAlias
|
|
358
|
+
});
|
|
359
|
+
};
|
|
360
|
+
const appendWhereClause = (input) => {
|
|
361
|
+
const { clauses, params, nextPlaceholder, table, jsKey, value } = input;
|
|
362
|
+
if (!(jsKey in table.columns)) throw new Error(`Unknown where key "${jsKey}" on table ${table.sqlName}`);
|
|
363
|
+
const column = table.columns[jsKey];
|
|
364
|
+
if (!column) throw new Error(`Unknown where key "${jsKey}" on table ${table.sqlName}`);
|
|
365
|
+
const sqlName = quoteIdentifier(column.sqlName);
|
|
366
|
+
if (!isPlainObject(value)) {
|
|
367
|
+
appendDirectWhereClause({
|
|
368
|
+
clauses,
|
|
369
|
+
params,
|
|
370
|
+
nextPlaceholder,
|
|
371
|
+
column,
|
|
372
|
+
sqlName,
|
|
373
|
+
value
|
|
374
|
+
});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
appendOperatorWhereClauses({
|
|
378
|
+
clauses,
|
|
379
|
+
params,
|
|
380
|
+
nextPlaceholder,
|
|
381
|
+
column,
|
|
382
|
+
sqlName,
|
|
383
|
+
jsKey,
|
|
384
|
+
value
|
|
385
|
+
});
|
|
386
|
+
};
|
|
387
|
+
const buildWhereClause = (input) => {
|
|
388
|
+
const { where, relations, parentAlias } = input;
|
|
389
|
+
const clauses = [];
|
|
390
|
+
const params = [];
|
|
391
|
+
if (!where) return {
|
|
392
|
+
sql: "",
|
|
393
|
+
params
|
|
394
|
+
};
|
|
395
|
+
for (const [jsKey, value] of Object.entries(where)) {
|
|
396
|
+
if (value === void 0) continue;
|
|
397
|
+
const logicalWhereKey = getLogicalWhereKey(jsKey);
|
|
398
|
+
if (logicalWhereKey) {
|
|
399
|
+
appendLogicalWhereClause({
|
|
400
|
+
...input,
|
|
401
|
+
clauses,
|
|
402
|
+
params,
|
|
403
|
+
jsKey: logicalWhereKey,
|
|
404
|
+
value
|
|
405
|
+
});
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
const relation = relations?.[jsKey];
|
|
409
|
+
if (relation && isRelationFilterValue(value)) {
|
|
410
|
+
appendRelationWhereClause({
|
|
411
|
+
...input,
|
|
412
|
+
clauses,
|
|
413
|
+
params,
|
|
414
|
+
jsKey,
|
|
415
|
+
value,
|
|
416
|
+
relation,
|
|
417
|
+
relationName: jsKey,
|
|
418
|
+
parentAlias: parentAlias ?? quoteIdentifier(input.table.sqlName)
|
|
419
|
+
});
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
appendWhereClause({
|
|
423
|
+
...input,
|
|
424
|
+
clauses,
|
|
425
|
+
params,
|
|
426
|
+
jsKey,
|
|
427
|
+
value
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
sql: clauses.join(` AND `),
|
|
432
|
+
params
|
|
433
|
+
};
|
|
434
|
+
};
|
|
435
|
+
const getLogicalWhereKey = (jsKey) => {
|
|
436
|
+
if (jsKey === "$and") return jsKey;
|
|
437
|
+
if (jsKey === "$not") return jsKey;
|
|
438
|
+
if (jsKey === "$or") return jsKey;
|
|
439
|
+
return null;
|
|
440
|
+
};
|
|
441
|
+
const getLogicalOperator = (jsKey) => {
|
|
442
|
+
if (jsKey === "$or") return "OR";
|
|
443
|
+
return "AND";
|
|
444
|
+
};
|
|
445
|
+
const getLogicalWhereValues = (jsKey, value) => {
|
|
446
|
+
if (jsKey === "$or" && !Array.isArray(value)) throw new Error("$or where value must be an array");
|
|
447
|
+
if (Array.isArray(value)) return value;
|
|
448
|
+
return [value];
|
|
449
|
+
};
|
|
450
|
+
const appendLogicalWhereClause = (input) => {
|
|
451
|
+
const { clauses, params, jsKey } = input;
|
|
452
|
+
const collected = collectLogicalWhereClauses(input);
|
|
453
|
+
if (typeof collected === "string") {
|
|
454
|
+
clauses.push(collected);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (!collected.nestedClauses.length) return;
|
|
458
|
+
params.push(...collected.nestedParams);
|
|
459
|
+
if (jsKey === "$not") {
|
|
460
|
+
const operator = "NOT";
|
|
461
|
+
const combinedNegatedClause = collected.nestedClauses.map((nestedClause) => `${operator} (${nestedClause})`).join(` AND `);
|
|
462
|
+
clauses.push(combinedNegatedClause);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const operator = getLogicalOperator(jsKey);
|
|
466
|
+
clauses.push(`(${collected.nestedClauses.join(` ${operator} `)})`);
|
|
467
|
+
};
|
|
468
|
+
const collectLogicalWhereClauses = (input) => {
|
|
469
|
+
const { jsKey, value } = input;
|
|
470
|
+
const values = getLogicalWhereValues(jsKey, value);
|
|
471
|
+
const nestedClauses = [];
|
|
472
|
+
const nestedParams = [];
|
|
473
|
+
for (const nestedValue of values) {
|
|
474
|
+
if (!isPlainObject(nestedValue)) throw new Error(`${jsKey} where value must contain object filters`);
|
|
475
|
+
const nested = buildWhereClause({
|
|
476
|
+
...input,
|
|
477
|
+
where: nestedValue
|
|
478
|
+
});
|
|
479
|
+
if (!nested.sql) {
|
|
480
|
+
if (jsKey === "$or") return TRUE_WHERE_SQL;
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
nestedClauses.push(`(${nested.sql})`);
|
|
484
|
+
nestedParams.push(...nested.params);
|
|
485
|
+
}
|
|
486
|
+
if (jsKey === "$or" && !nestedClauses.length) return FALSE_WHERE_SQL;
|
|
487
|
+
return {
|
|
488
|
+
nestedClauses,
|
|
489
|
+
nestedParams
|
|
490
|
+
};
|
|
491
|
+
};
|
|
492
|
+
const getColumnAlias = (sqlName, jsKey) => {
|
|
493
|
+
return `${quoteIdentifier(sqlName)} AS ${quoteIdentifier(jsKey)}`;
|
|
494
|
+
};
|
|
495
|
+
const buildSelectColumns = (table, select) => {
|
|
496
|
+
if (!select || Object.keys(select).length === 0) return Object.entries(table.columns).map(([key, column]) => getColumnAlias(column.sqlName, key)).join(", ");
|
|
497
|
+
const selectedColumns = [];
|
|
498
|
+
const keys = Object.keys(select);
|
|
499
|
+
for (const key of keys) {
|
|
500
|
+
const column = table.columns[key];
|
|
501
|
+
if (!column) throw new Error(`Unknown select key "${key}" on table ${table.sqlName}`);
|
|
502
|
+
selectedColumns.push(getColumnAlias(column.sqlName, key));
|
|
503
|
+
}
|
|
504
|
+
return selectedColumns.join(", ");
|
|
505
|
+
};
|
|
506
|
+
const buildOrderByClause = (table, orderBy) => {
|
|
507
|
+
if (!orderBy) return "";
|
|
508
|
+
const clauses = [];
|
|
509
|
+
for (const [jsKey, direction] of Object.entries(orderBy)) {
|
|
510
|
+
const column = table.columns[jsKey];
|
|
511
|
+
if (!column) throw new Error(`Unknown orderBy key "${jsKey}" on table ${table.sqlName}`);
|
|
512
|
+
if (direction === "desc") {
|
|
513
|
+
clauses.push(`${quoteIdentifier(column.sqlName)} DESC`);
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (direction === "asc") {
|
|
517
|
+
clauses.push(`${quoteIdentifier(column.sqlName)} ASC`);
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
throw new Error(`Unknown orderBy direction "${direction}" for key "${jsKey}" on table ${table.sqlName}`);
|
|
521
|
+
}
|
|
522
|
+
if (!clauses.length) return "";
|
|
523
|
+
return clauses.join(", ");
|
|
524
|
+
};
|
|
525
|
+
const buildPaginationClause = (input) => {
|
|
526
|
+
const { spec, nextPlaceholder, take, skip } = input;
|
|
527
|
+
const params = [];
|
|
528
|
+
if (take === void 0) {
|
|
529
|
+
if (skip === void 0) return {
|
|
530
|
+
sql: "",
|
|
531
|
+
params
|
|
532
|
+
};
|
|
533
|
+
const skipPh = nextPlaceholder();
|
|
534
|
+
params.push(skip);
|
|
535
|
+
return {
|
|
536
|
+
sql: `${spec.unlimitedOffsetKeyword} ${skipPh}`,
|
|
537
|
+
params
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
const takePh = nextPlaceholder();
|
|
541
|
+
params.push(take);
|
|
542
|
+
if (skip === void 0) return {
|
|
543
|
+
sql: `LIMIT ${takePh}`,
|
|
544
|
+
params
|
|
545
|
+
};
|
|
546
|
+
const skipPh = nextPlaceholder();
|
|
547
|
+
params.push(skip);
|
|
548
|
+
return {
|
|
549
|
+
sql: `LIMIT ${takePh} OFFSET ${skipPh}`,
|
|
550
|
+
params
|
|
551
|
+
};
|
|
552
|
+
};
|
|
553
|
+
const buildSelectStatement = (input) => {
|
|
554
|
+
const { tableName, columns, where, orderBy, pagination } = input;
|
|
555
|
+
let query = `SELECT ${columns} FROM ${tableName}`;
|
|
556
|
+
if (where) query = `${query} WHERE ${where}`;
|
|
557
|
+
if (orderBy) query = `${query} ORDER BY ${orderBy}`;
|
|
558
|
+
if (pagination) query = `${query} ${pagination}`;
|
|
559
|
+
return query;
|
|
560
|
+
};
|
|
561
|
+
const validateFindUniqueWhere = (table, where) => {
|
|
562
|
+
const entries = Object.entries(where).filter(([, value]) => value !== void 0);
|
|
563
|
+
if (!entries.map(([key]) => key).length) throw new Error("findUnique requires at least one where key");
|
|
564
|
+
let hasUniqueKey = false;
|
|
565
|
+
for (const [key] of entries) {
|
|
566
|
+
const column = table.columns[key];
|
|
567
|
+
if (!column) throw new Error(`Unknown where key ${key} on table ${table.sqlName}`);
|
|
568
|
+
if (column._meta.isPrimaryKey || column._meta.isUnique) hasUniqueKey = true;
|
|
569
|
+
}
|
|
570
|
+
if (!hasUniqueKey) throw new Error("findUnique where must include at least one unique or primary key column");
|
|
571
|
+
};
|
|
572
|
+
const resolveCreateValue = (column, provided) => {
|
|
573
|
+
if (provided !== void 0) return provided;
|
|
574
|
+
if (column._default) return column._default();
|
|
575
|
+
return null;
|
|
576
|
+
};
|
|
577
|
+
const buildSetClauses = (input) => {
|
|
578
|
+
const { nextPlaceholder, table, data } = input;
|
|
579
|
+
const setClauses = [];
|
|
580
|
+
const params = [];
|
|
581
|
+
for (const [jsKey, value] of Object.entries(data)) {
|
|
582
|
+
if (value === void 0) continue;
|
|
583
|
+
const column = table.columns[jsKey];
|
|
584
|
+
if (!column) continue;
|
|
585
|
+
setClauses.push(`${quoteIdentifier(column.sqlName)} = ${nextPlaceholder()}`);
|
|
586
|
+
params.push(serializeColumnValue(column, value));
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
setClauses,
|
|
590
|
+
params
|
|
591
|
+
};
|
|
592
|
+
};
|
|
593
|
+
//#endregion
|
|
594
|
+
//#region src/lib/orm/dialect/relations.ts
|
|
595
|
+
const buildJsonObjectExpression = (input) => {
|
|
596
|
+
const { spec, alias, table, extraPairs = [], select } = input;
|
|
597
|
+
const allEntries = Object.entries(table.columns);
|
|
598
|
+
const hasSelect = select !== void 0 && Object.keys(select).length > 0;
|
|
599
|
+
let visibleEntries = allEntries;
|
|
600
|
+
if (hasSelect) {
|
|
601
|
+
for (const key of Object.keys(select)) if (!(key in table.columns)) throw new Error(`Unknown select key "${key}" on table ${table.sqlName}`);
|
|
602
|
+
visibleEntries = allEntries.filter(([key]) => key in select);
|
|
603
|
+
}
|
|
604
|
+
const pairs = visibleEntries.flatMap(([jsKey, column]) => [`'${jsKey}'`, `${alias}.${quoteIdentifier(column.sqlName)}`]);
|
|
605
|
+
return `${spec.jsonObjectFunctionName}(${[...pairs, ...extraPairs].join(", ")})`;
|
|
606
|
+
};
|
|
607
|
+
const getRelationOptions = (includeValue) => {
|
|
608
|
+
let options = {};
|
|
609
|
+
if (typeof includeValue === "object" && includeValue !== null) options = includeValue;
|
|
610
|
+
return options;
|
|
611
|
+
};
|
|
612
|
+
const buildNestedIncludePairs = (input) => {
|
|
613
|
+
const { spec, nextPlaceholder, relationTable, relationAlias, tableRelationsMap, options } = input;
|
|
614
|
+
const nestedRelations = tableRelationsMap.get(relationTable) ?? {};
|
|
615
|
+
const nestedExtraPairs = [];
|
|
616
|
+
const nestedParams = [];
|
|
617
|
+
const nestedDescriptors = [];
|
|
618
|
+
if (!options.include) return {
|
|
619
|
+
nestedExtraPairs,
|
|
620
|
+
nestedParams,
|
|
621
|
+
nestedDescriptors
|
|
622
|
+
};
|
|
623
|
+
for (const [nestedName, nestedValue] of Object.entries(options.include)) {
|
|
624
|
+
if (!nestedValue) continue;
|
|
625
|
+
const nestedRelation = nestedRelations[nestedName];
|
|
626
|
+
if (!nestedRelation) throw new Error(`Unknown relation ${nestedName} on table ${relationTable.sqlName}`);
|
|
627
|
+
const result = buildRelationSubquery({
|
|
628
|
+
spec,
|
|
629
|
+
nextPlaceholder,
|
|
630
|
+
parentTable: relationTable,
|
|
631
|
+
parentAlias: relationAlias,
|
|
632
|
+
relation: nestedRelation,
|
|
633
|
+
relationName: nestedName,
|
|
634
|
+
includeValue: nestedValue,
|
|
635
|
+
tableRelationsMap
|
|
636
|
+
});
|
|
637
|
+
nestedExtraPairs.push(`'${nestedName}'`, result.sql);
|
|
638
|
+
nestedParams.push(...result.params);
|
|
639
|
+
nestedDescriptors.push(result.descriptor);
|
|
640
|
+
}
|
|
641
|
+
return {
|
|
642
|
+
nestedExtraPairs,
|
|
643
|
+
nestedParams,
|
|
644
|
+
nestedDescriptors
|
|
645
|
+
};
|
|
646
|
+
};
|
|
647
|
+
const joinWhereSql = (fkCondition, whereSql) => {
|
|
648
|
+
if (whereSql) return `${fkCondition} AND ${whereSql}`;
|
|
649
|
+
return fkCondition;
|
|
650
|
+
};
|
|
651
|
+
const buildNestedHasManySubquery = (input) => {
|
|
652
|
+
const { spec, relationTable, relationAlias, whereSql, orderBy, paginationSql, jsonObj } = input;
|
|
653
|
+
if (!orderBy && !paginationSql) return `SELECT ${spec.jsonArrayAggregateFunctionName}(${jsonObj}) FROM ${quoteIdentifier(relationTable.sqlName)} AS ${relationAlias} WHERE ${whereSql}`;
|
|
654
|
+
let innerQuery = `SELECT * FROM ${quoteIdentifier(relationTable.sqlName)} AS ${relationAlias} WHERE ${whereSql}`;
|
|
655
|
+
if (orderBy) innerQuery = `${innerQuery} ORDER BY ${orderBy}`;
|
|
656
|
+
if (paginationSql) innerQuery = `${innerQuery} ${paginationSql}`;
|
|
657
|
+
return `SELECT ${spec.jsonArrayAggregateFunctionName}(${jsonObj}) FROM (${innerQuery}) AS ${relationAlias}`;
|
|
658
|
+
};
|
|
659
|
+
const buildHasManyRelationSubquery = (input) => {
|
|
660
|
+
const { spec, parentTable, parentAlias, relationTable, relationAlias, relationName, jsonObj, nestedDescriptors, whereSql, orderBy, paginationSql, allParams } = input;
|
|
661
|
+
const { fk: foreignKey, source: sourceColumn } = resolveHasManyForeignKeyColumn(parentTable, relationTable);
|
|
662
|
+
return {
|
|
663
|
+
sql: `COALESCE((${buildNestedHasManySubquery({
|
|
664
|
+
spec,
|
|
665
|
+
relationTable,
|
|
666
|
+
relationAlias,
|
|
667
|
+
whereSql: joinWhereSql(`${relationAlias}.${quoteIdentifier(foreignKey.sqlName)} = ${parentAlias}.${quoteIdentifier(sourceColumn.sqlName)}`, whereSql),
|
|
668
|
+
orderBy,
|
|
669
|
+
paginationSql,
|
|
670
|
+
jsonObj
|
|
671
|
+
})}), ${spec.emptyJsonArrayLiteral})`,
|
|
672
|
+
params: allParams,
|
|
673
|
+
descriptor: {
|
|
674
|
+
name: relationName,
|
|
675
|
+
type: "hasMany",
|
|
676
|
+
table: relationTable,
|
|
677
|
+
nested: nestedDescriptors
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
};
|
|
681
|
+
const buildHasOneRelationSubquery = (input) => {
|
|
682
|
+
const { parentTable, parentAlias, relation, relationTable, relationAlias, relationName, jsonObj, nestedDescriptors, whereSql, allParams } = input;
|
|
683
|
+
if (relation._type !== "hasOne") throw new Error(`Expected hasOne relation for ${relationName}`);
|
|
684
|
+
const { localForeignKey, target } = resolveHasOneForeignKeyColumn({
|
|
685
|
+
sourceTable: parentTable,
|
|
686
|
+
relationTable,
|
|
687
|
+
relationForeignKey: relation._foreignKey
|
|
688
|
+
});
|
|
689
|
+
const combinedWhereSql = joinWhereSql(`${relationAlias}.${quoteIdentifier(target.sqlName)} = ${parentAlias}.${quoteIdentifier(localForeignKey.sqlName)}`, whereSql);
|
|
690
|
+
return {
|
|
691
|
+
sql: `(${`SELECT ${jsonObj} FROM ${quoteIdentifier(relationTable.sqlName)} AS ${relationAlias} WHERE ${combinedWhereSql} LIMIT 1`})`,
|
|
692
|
+
params: allParams,
|
|
693
|
+
descriptor: {
|
|
694
|
+
name: relationName,
|
|
695
|
+
type: "hasOne",
|
|
696
|
+
table: relationTable,
|
|
697
|
+
nested: nestedDescriptors
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
};
|
|
701
|
+
const buildRelationSubquery = (input) => {
|
|
702
|
+
const { spec, nextPlaceholder, relation, relationName } = input;
|
|
703
|
+
const options = getRelationOptions(input.includeValue);
|
|
704
|
+
const relationTable = relation._table;
|
|
705
|
+
const relationAlias = `${relationName}__${relationTable.sqlName}`;
|
|
706
|
+
const { nestedExtraPairs, nestedParams, nestedDescriptors } = buildNestedIncludePairs({
|
|
707
|
+
...input,
|
|
708
|
+
relationTable,
|
|
709
|
+
relationAlias,
|
|
710
|
+
options
|
|
711
|
+
});
|
|
712
|
+
const jsonObj = buildJsonObjectExpression({
|
|
713
|
+
spec,
|
|
714
|
+
alias: relationAlias,
|
|
715
|
+
table: relationTable,
|
|
716
|
+
extraPairs: nestedExtraPairs,
|
|
717
|
+
select: options.select
|
|
718
|
+
});
|
|
719
|
+
const where = buildWhereClause({
|
|
720
|
+
nextPlaceholder,
|
|
721
|
+
table: relationTable,
|
|
722
|
+
where: options.where
|
|
723
|
+
});
|
|
724
|
+
const orderBy = buildOrderByClause(relationTable, options.orderBy);
|
|
725
|
+
const pagination = buildPaginationClause({
|
|
726
|
+
spec,
|
|
727
|
+
nextPlaceholder,
|
|
728
|
+
take: options.take,
|
|
729
|
+
skip: options.skip
|
|
730
|
+
});
|
|
731
|
+
const allParams = [
|
|
732
|
+
...nestedParams,
|
|
733
|
+
...where.params,
|
|
734
|
+
...pagination.params
|
|
735
|
+
];
|
|
736
|
+
if (relation._type === "hasMany") return buildHasManyRelationSubquery({
|
|
737
|
+
...input,
|
|
738
|
+
relationTable,
|
|
739
|
+
relationAlias,
|
|
740
|
+
jsonObj,
|
|
741
|
+
nestedDescriptors,
|
|
742
|
+
whereSql: where.sql,
|
|
743
|
+
orderBy,
|
|
744
|
+
paginationSql: pagination.sql,
|
|
745
|
+
allParams
|
|
746
|
+
});
|
|
747
|
+
return buildHasOneRelationSubquery({
|
|
748
|
+
...input,
|
|
749
|
+
relationTable,
|
|
750
|
+
relationAlias,
|
|
751
|
+
jsonObj,
|
|
752
|
+
nestedDescriptors,
|
|
753
|
+
whereSql: where.sql,
|
|
754
|
+
allParams
|
|
755
|
+
});
|
|
756
|
+
};
|
|
757
|
+
const buildIncludeClause = (input) => {
|
|
758
|
+
const { spec, nextPlaceholder, table, parentAlias, relations, tableRelationsMap, include } = input;
|
|
759
|
+
if (!include) return EMPTY_INCLUDE;
|
|
760
|
+
const enabledRelations = Object.entries(include).filter(([, v]) => Boolean(v));
|
|
761
|
+
if (!enabledRelations.length) return EMPTY_INCLUDE;
|
|
762
|
+
const clauses = [];
|
|
763
|
+
const params = [];
|
|
764
|
+
const descriptors = [];
|
|
765
|
+
for (const [relationName, includeValue] of enabledRelations) {
|
|
766
|
+
const relation = relations[relationName];
|
|
767
|
+
if (!relation) throw new Error(`Unknown relation ${relationName} on table ${table.sqlName}`);
|
|
768
|
+
const result = buildRelationSubquery({
|
|
769
|
+
spec,
|
|
770
|
+
nextPlaceholder,
|
|
771
|
+
parentTable: table,
|
|
772
|
+
parentAlias,
|
|
773
|
+
relation,
|
|
774
|
+
relationName,
|
|
775
|
+
includeValue,
|
|
776
|
+
tableRelationsMap
|
|
777
|
+
});
|
|
778
|
+
clauses.push(`${result.sql} AS ${quoteIdentifier(relationName)}`);
|
|
779
|
+
params.push(...result.params);
|
|
780
|
+
descriptors.push(result.descriptor);
|
|
781
|
+
}
|
|
782
|
+
return {
|
|
783
|
+
sql: clauses.join(", "),
|
|
784
|
+
params,
|
|
785
|
+
descriptors
|
|
786
|
+
};
|
|
787
|
+
};
|
|
788
|
+
//#endregion
|
|
789
|
+
//#region src/lib/orm/dialect/query-builder.ts
|
|
790
|
+
var DialectQueryBuilder = class {
|
|
791
|
+
spec;
|
|
792
|
+
table;
|
|
793
|
+
relations;
|
|
794
|
+
tableRelationsMap;
|
|
795
|
+
constructor(input) {
|
|
796
|
+
this.spec = input.spec;
|
|
797
|
+
this.table = input.table;
|
|
798
|
+
this.relations = input.relations;
|
|
799
|
+
this.tableRelationsMap = input.tableRelationsMap ?? /* @__PURE__ */ new Map();
|
|
800
|
+
}
|
|
801
|
+
buildFindMany(options) {
|
|
802
|
+
const nextPlaceholder = createNextPlaceholder(this.spec);
|
|
803
|
+
const { include, where, selectColumns } = this.buildSelectIncludeWhere(nextPlaceholder, {
|
|
804
|
+
where: options?.where,
|
|
805
|
+
select: options?.select,
|
|
806
|
+
include: options?.include
|
|
807
|
+
});
|
|
808
|
+
const orderBy = buildOrderByClause(this.table, options?.orderBy);
|
|
809
|
+
const pagination = buildPaginationClause({
|
|
810
|
+
spec: this.spec,
|
|
811
|
+
nextPlaceholder,
|
|
812
|
+
take: options?.take,
|
|
813
|
+
skip: options?.skip
|
|
814
|
+
});
|
|
815
|
+
const params = [
|
|
816
|
+
...include.params,
|
|
817
|
+
...where.params,
|
|
818
|
+
...pagination.params
|
|
819
|
+
];
|
|
820
|
+
return {
|
|
821
|
+
statement: buildSelectStatement({
|
|
822
|
+
tableName: quoteIdentifier(this.table.sqlName),
|
|
823
|
+
columns: selectColumns,
|
|
824
|
+
where: where.sql,
|
|
825
|
+
orderBy,
|
|
826
|
+
pagination: pagination.sql
|
|
827
|
+
}),
|
|
828
|
+
params,
|
|
829
|
+
includeDescriptors: include.descriptors
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
buildFindFirst(options) {
|
|
833
|
+
return this.buildFindMany({
|
|
834
|
+
...options,
|
|
835
|
+
take: 1
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
buildFindUnique(options) {
|
|
839
|
+
validateFindUniqueWhere(this.table, options.where);
|
|
840
|
+
const nextPlaceholder = createNextPlaceholder(this.spec);
|
|
841
|
+
const { include, where, selectColumns } = this.buildSelectIncludeWhere(nextPlaceholder, {
|
|
842
|
+
where: options.where,
|
|
843
|
+
select: options.select,
|
|
844
|
+
include: options.include
|
|
845
|
+
});
|
|
846
|
+
return {
|
|
847
|
+
statement: buildSelectStatement({
|
|
848
|
+
tableName: quoteIdentifier(this.table.sqlName),
|
|
849
|
+
columns: selectColumns,
|
|
850
|
+
where: where.sql,
|
|
851
|
+
orderBy: "",
|
|
852
|
+
pagination: "LIMIT 1"
|
|
853
|
+
}),
|
|
854
|
+
params: [...include.params, ...where.params],
|
|
855
|
+
includeDescriptors: include.descriptors
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
buildCreate(options) {
|
|
859
|
+
const nextPlaceholder = createNextPlaceholder(this.spec);
|
|
860
|
+
const provided = new Map(Object.entries(options.data));
|
|
861
|
+
const sqlNames = [];
|
|
862
|
+
const placeholders = [];
|
|
863
|
+
const params = [];
|
|
864
|
+
for (const [jsKey, column] of Object.entries(this.table.columns)) {
|
|
865
|
+
const value = resolveCreateValue(column, provided.get(jsKey));
|
|
866
|
+
sqlNames.push(quoteIdentifier(column.sqlName));
|
|
867
|
+
placeholders.push(nextPlaceholder());
|
|
868
|
+
params.push(serializeColumnValue(column, value));
|
|
869
|
+
}
|
|
870
|
+
const columns = buildSelectColumns(this.table, options.select);
|
|
871
|
+
const include = this.buildInclude(nextPlaceholder, options.include);
|
|
872
|
+
const returning = buildSelectList(columns, include);
|
|
873
|
+
return {
|
|
874
|
+
statement: `INSERT INTO ${quoteIdentifier(this.table.sqlName)} (${sqlNames.join(", ")}) VALUES (${placeholders.join(", ")}) RETURNING ${returning}`,
|
|
875
|
+
params: [...params, ...include.params],
|
|
876
|
+
includeDescriptors: include.descriptors
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
buildUpdate(options) {
|
|
880
|
+
validateFindUniqueWhere(this.table, options.where);
|
|
881
|
+
const nextPlaceholder = createNextPlaceholder(this.spec);
|
|
882
|
+
const { setClauses, params } = buildSetClauses({
|
|
883
|
+
nextPlaceholder,
|
|
884
|
+
table: this.table,
|
|
885
|
+
data: options.data
|
|
886
|
+
});
|
|
887
|
+
if (!setClauses.length) throw new Error("update requires at least one field in data");
|
|
888
|
+
const { where, include, returning } = this.buildWhereIncludeReturning(nextPlaceholder, {
|
|
889
|
+
where: options.where,
|
|
890
|
+
select: options.select,
|
|
891
|
+
include: options.include
|
|
892
|
+
});
|
|
893
|
+
let statement = `UPDATE ${quoteIdentifier(this.table.sqlName)} SET ${setClauses.join(", ")}`;
|
|
894
|
+
if (where.sql) statement = `${statement} WHERE ${where.sql}`;
|
|
895
|
+
statement = `${statement} RETURNING ${returning}`;
|
|
896
|
+
params.push(...where.params, ...include.params);
|
|
897
|
+
return {
|
|
898
|
+
statement,
|
|
899
|
+
params,
|
|
900
|
+
includeDescriptors: include.descriptors
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
buildDelete(options) {
|
|
904
|
+
validateFindUniqueWhere(this.table, options.where);
|
|
905
|
+
const nextPlaceholder = createNextPlaceholder(this.spec);
|
|
906
|
+
const { where, include, returning } = this.buildWhereIncludeReturning(nextPlaceholder, {
|
|
907
|
+
where: options.where,
|
|
908
|
+
select: options.select,
|
|
909
|
+
include: options.include
|
|
910
|
+
});
|
|
911
|
+
let statement = `DELETE FROM ${quoteIdentifier(this.table.sqlName)}`;
|
|
912
|
+
if (where.sql) statement = `${statement} WHERE ${where.sql}`;
|
|
913
|
+
statement = `${statement} RETURNING ${returning}`;
|
|
914
|
+
return {
|
|
915
|
+
statement,
|
|
916
|
+
params: [...where.params, ...include.params],
|
|
917
|
+
includeDescriptors: include.descriptors
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
buildCreateMany(options) {
|
|
921
|
+
if (!options.data.length) return {
|
|
922
|
+
statement: "",
|
|
923
|
+
params: [],
|
|
924
|
+
includeDescriptors: []
|
|
925
|
+
};
|
|
926
|
+
const nextPlaceholder = createNextPlaceholder(this.spec);
|
|
927
|
+
const columnEntries = Object.entries(this.table.columns);
|
|
928
|
+
const sqlNames = columnEntries.map(([, col]) => {
|
|
929
|
+
return quoteIdentifier(col.sqlName);
|
|
930
|
+
});
|
|
931
|
+
const params = [];
|
|
932
|
+
const rowPlaceholders = [];
|
|
933
|
+
for (const row of options.data) {
|
|
934
|
+
const rowRecord = row;
|
|
935
|
+
const placeholders = [];
|
|
936
|
+
for (const [jsKey, column] of columnEntries) {
|
|
937
|
+
const value = resolveCreateValue(column, rowRecord[jsKey]);
|
|
938
|
+
placeholders.push(nextPlaceholder());
|
|
939
|
+
params.push(serializeColumnValue(column, value));
|
|
940
|
+
}
|
|
941
|
+
rowPlaceholders.push(`(${placeholders.join(", ")})`);
|
|
942
|
+
}
|
|
943
|
+
const returning = buildSelectColumns(this.table, void 0);
|
|
944
|
+
return {
|
|
945
|
+
statement: `INSERT INTO ${quoteIdentifier(this.table.sqlName)} (${sqlNames.join(", ")}) VALUES ${rowPlaceholders.join(", ")} RETURNING ${returning}`,
|
|
946
|
+
params,
|
|
947
|
+
includeDescriptors: []
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
buildUpdateMany(options) {
|
|
951
|
+
const nextPlaceholder = createNextPlaceholder(this.spec);
|
|
952
|
+
const { setClauses, params } = buildSetClauses({
|
|
953
|
+
nextPlaceholder,
|
|
954
|
+
table: this.table,
|
|
955
|
+
data: options.data
|
|
956
|
+
});
|
|
957
|
+
if (!setClauses.length) throw new Error("updateMany requires at least one field in data");
|
|
958
|
+
const where = buildWhereClause({
|
|
959
|
+
nextPlaceholder,
|
|
960
|
+
table: this.table,
|
|
961
|
+
where: options.where,
|
|
962
|
+
relations: this.relations,
|
|
963
|
+
parentAlias: quoteIdentifier(this.table.sqlName)
|
|
964
|
+
});
|
|
965
|
+
let statement = `UPDATE ${quoteIdentifier(this.table.sqlName)} SET ${setClauses.join(", ")}`;
|
|
966
|
+
if (where.sql) statement = `${statement} WHERE ${where.sql}`;
|
|
967
|
+
params.push(...where.params);
|
|
968
|
+
const returning = buildSelectColumns(this.table, void 0);
|
|
969
|
+
statement = `${statement} RETURNING ${returning}`;
|
|
970
|
+
return {
|
|
971
|
+
statement,
|
|
972
|
+
params,
|
|
973
|
+
includeDescriptors: []
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
buildDeleteMany(options) {
|
|
977
|
+
const where = buildWhereClause({
|
|
978
|
+
nextPlaceholder: createNextPlaceholder(this.spec),
|
|
979
|
+
table: this.table,
|
|
980
|
+
where: options.where,
|
|
981
|
+
relations: this.relations,
|
|
982
|
+
parentAlias: quoteIdentifier(this.table.sqlName)
|
|
983
|
+
});
|
|
984
|
+
let statement = `DELETE FROM ${quoteIdentifier(this.table.sqlName)}`;
|
|
985
|
+
if (where.sql) statement = `${statement} WHERE ${where.sql}`;
|
|
986
|
+
const returning = buildSelectColumns(this.table, void 0);
|
|
987
|
+
statement = `${statement} RETURNING ${returning}`;
|
|
988
|
+
return {
|
|
989
|
+
statement,
|
|
990
|
+
params: where.params,
|
|
991
|
+
includeDescriptors: []
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
buildInclude(nextPlaceholder, include) {
|
|
995
|
+
return buildIncludeClause({
|
|
996
|
+
spec: this.spec,
|
|
997
|
+
nextPlaceholder,
|
|
998
|
+
table: this.table,
|
|
999
|
+
parentAlias: quoteIdentifier(this.table.sqlName),
|
|
1000
|
+
relations: this.relations,
|
|
1001
|
+
tableRelationsMap: this.tableRelationsMap,
|
|
1002
|
+
include
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
buildSelectIncludeWhere(nextPlaceholder, input) {
|
|
1006
|
+
const include = this.buildInclude(nextPlaceholder, input.include);
|
|
1007
|
+
return {
|
|
1008
|
+
include,
|
|
1009
|
+
where: buildWhereClause({
|
|
1010
|
+
nextPlaceholder,
|
|
1011
|
+
table: this.table,
|
|
1012
|
+
where: input.where,
|
|
1013
|
+
relations: this.relations,
|
|
1014
|
+
parentAlias: quoteIdentifier(this.table.sqlName)
|
|
1015
|
+
}),
|
|
1016
|
+
selectColumns: buildSelectList(buildSelectColumns(this.table, input.select), include)
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
buildWhereIncludeReturning(nextPlaceholder, input) {
|
|
1020
|
+
const where = buildWhereClause({
|
|
1021
|
+
nextPlaceholder,
|
|
1022
|
+
table: this.table,
|
|
1023
|
+
where: input.where,
|
|
1024
|
+
relations: this.relations,
|
|
1025
|
+
parentAlias: quoteIdentifier(this.table.sqlName)
|
|
1026
|
+
});
|
|
1027
|
+
const columns = buildSelectColumns(this.table, input.select);
|
|
1028
|
+
const include = this.buildInclude(nextPlaceholder, input.include);
|
|
1029
|
+
return {
|
|
1030
|
+
where,
|
|
1031
|
+
include,
|
|
1032
|
+
returning: buildSelectList(columns, include)
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region src/lib/orm/dialect/rows.ts
|
|
1038
|
+
const coerceBooleanValue = (val) => {
|
|
1039
|
+
if (val === null) return val;
|
|
1040
|
+
if (val === void 0) return val;
|
|
1041
|
+
return Boolean(val);
|
|
1042
|
+
};
|
|
1043
|
+
const coerceRelationItems = (input) => {
|
|
1044
|
+
const { value, table, nested } = input;
|
|
1045
|
+
if (Array.isArray(value)) {
|
|
1046
|
+
for (const item of value) if (typeof item === "object" && item !== null) coerceRow({
|
|
1047
|
+
row: item,
|
|
1048
|
+
table,
|
|
1049
|
+
descriptors: nested
|
|
1050
|
+
});
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
if (typeof value === "object" && value !== null) coerceRow({
|
|
1054
|
+
row: value,
|
|
1055
|
+
table,
|
|
1056
|
+
descriptors: nested
|
|
1057
|
+
});
|
|
1058
|
+
};
|
|
1059
|
+
const getColumnKeysByType = (table) => {
|
|
1060
|
+
const boolKeys = /* @__PURE__ */ new Set();
|
|
1061
|
+
const jsonKeys = /* @__PURE__ */ new Set();
|
|
1062
|
+
for (const [key, col] of Object.entries(table.columns)) {
|
|
1063
|
+
if (col.type === "boolean") boolKeys.add(key);
|
|
1064
|
+
if (col.type === "json") jsonKeys.add(key);
|
|
1065
|
+
if (col.type === "jsonb") jsonKeys.add(key);
|
|
1066
|
+
}
|
|
1067
|
+
return {
|
|
1068
|
+
boolKeys,
|
|
1069
|
+
jsonKeys
|
|
1070
|
+
};
|
|
1071
|
+
};
|
|
1072
|
+
const coerceColumnValues = (row, table) => {
|
|
1073
|
+
const { boolKeys, jsonKeys } = getColumnKeysByType(table);
|
|
1074
|
+
for (const key of boolKeys) if (key in row) row[key] = coerceBooleanValue(row[key]);
|
|
1075
|
+
for (const key of jsonKeys) {
|
|
1076
|
+
if (!(key in row)) continue;
|
|
1077
|
+
const val = row[key];
|
|
1078
|
+
if (typeof val !== "string") continue;
|
|
1079
|
+
row[key] = JSON.parse(val);
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
const coerceRelationValue = (input) => {
|
|
1083
|
+
const { row, descriptor } = input;
|
|
1084
|
+
const value = row[descriptor.name];
|
|
1085
|
+
if (value === null) {
|
|
1086
|
+
if (descriptor.type === "hasMany") row[descriptor.name] = [];
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
const nested = descriptor.nested ?? [];
|
|
1090
|
+
if (typeof value === "string") {
|
|
1091
|
+
const parsed = JSON.parse(value);
|
|
1092
|
+
coerceRelationItems({
|
|
1093
|
+
value: parsed,
|
|
1094
|
+
table: descriptor.table,
|
|
1095
|
+
nested
|
|
1096
|
+
});
|
|
1097
|
+
row[descriptor.name] = parsed;
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
coerceRelationItems({
|
|
1101
|
+
value,
|
|
1102
|
+
table: descriptor.table,
|
|
1103
|
+
nested
|
|
1104
|
+
});
|
|
1105
|
+
};
|
|
1106
|
+
const coerceRelationValues = (row, descriptors) => {
|
|
1107
|
+
for (const descriptor of descriptors) coerceRelationValue({
|
|
1108
|
+
row,
|
|
1109
|
+
descriptor
|
|
1110
|
+
});
|
|
1111
|
+
};
|
|
1112
|
+
const coerceRow = (input) => {
|
|
1113
|
+
const { row, table, descriptors } = input;
|
|
1114
|
+
coerceColumnValues(row, table);
|
|
1115
|
+
coerceRelationValues(row, descriptors);
|
|
1116
|
+
};
|
|
1117
|
+
const parseIncludeRows = (input) => {
|
|
1118
|
+
const { table, rows, descriptors } = input;
|
|
1119
|
+
for (const row of rows) coerceRow({
|
|
1120
|
+
row,
|
|
1121
|
+
table,
|
|
1122
|
+
descriptors
|
|
1123
|
+
});
|
|
1124
|
+
};
|
|
1125
|
+
const executeQuery = async (sql, table, query) => {
|
|
1126
|
+
const rows = [...await sql.unsafe(query.statement, query.params)];
|
|
1127
|
+
parseIncludeRows({
|
|
1128
|
+
table,
|
|
1129
|
+
rows,
|
|
1130
|
+
descriptors: query.includeDescriptors
|
|
1131
|
+
});
|
|
1132
|
+
return rows;
|
|
1133
|
+
};
|
|
1134
|
+
//#endregion
|
|
1135
|
+
//#region src/lib/orm/dialect/shared.ts
|
|
1136
|
+
const createDialect = (input) => {
|
|
1137
|
+
const { spec, table, relations, tableRelationsMap = /* @__PURE__ */ new Map() } = input;
|
|
1138
|
+
const builder = new DialectQueryBuilder({
|
|
1139
|
+
spec,
|
|
1140
|
+
table,
|
|
1141
|
+
relations,
|
|
1142
|
+
tableRelationsMap
|
|
1143
|
+
});
|
|
1144
|
+
const executeAndUnwrap = async (sql, query, operation) => {
|
|
1145
|
+
const [row] = await executeQuery(sql, table, query);
|
|
1146
|
+
if (!row) throw new Error(`Record not found after ${operation} on table ${table.sqlName}`);
|
|
1147
|
+
return row;
|
|
1148
|
+
};
|
|
1149
|
+
return {
|
|
1150
|
+
name: spec.name,
|
|
1151
|
+
findMany: async (sql, options) => {
|
|
1152
|
+
return await executeQuery(sql, table, builder.buildFindMany(options));
|
|
1153
|
+
},
|
|
1154
|
+
findFirst: async (sql, options) => {
|
|
1155
|
+
const [row] = await executeQuery(sql, table, builder.buildFindFirst(options));
|
|
1156
|
+
return row ?? null;
|
|
1157
|
+
},
|
|
1158
|
+
findUnique: async (sql, options) => {
|
|
1159
|
+
const [row] = await executeQuery(sql, table, builder.buildFindUnique(options));
|
|
1160
|
+
return row ?? null;
|
|
1161
|
+
},
|
|
1162
|
+
create: async (sql, options) => {
|
|
1163
|
+
return executeAndUnwrap(sql, builder.buildCreate(options), "insert");
|
|
1164
|
+
},
|
|
1165
|
+
createMany: async (sql, options) => {
|
|
1166
|
+
if (!options.data.length) return [];
|
|
1167
|
+
return await executeQuery(sql, table, builder.buildCreateMany(options));
|
|
1168
|
+
},
|
|
1169
|
+
update: async (sql, options) => {
|
|
1170
|
+
return executeAndUnwrap(sql, builder.buildUpdate(options), "update");
|
|
1171
|
+
},
|
|
1172
|
+
updateMany: async (sql, options) => {
|
|
1173
|
+
return await executeQuery(sql, table, builder.buildUpdateMany(options));
|
|
1174
|
+
},
|
|
1175
|
+
delete: async (sql, options) => {
|
|
1176
|
+
return executeAndUnwrap(sql, builder.buildDelete(options), "delete");
|
|
1177
|
+
},
|
|
1178
|
+
deleteMany: async (sql, options) => {
|
|
1179
|
+
return await executeQuery(sql, table, builder.buildDeleteMany(options));
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
};
|
|
1183
|
+
//#endregion
|
|
1184
|
+
//#region src/lib/orm/dialect/postgres.ts
|
|
1185
|
+
const POSTGRES_SPEC = {
|
|
1186
|
+
name: "postgres",
|
|
1187
|
+
formatPlaceholder: (index) => `$${index}`,
|
|
1188
|
+
unlimitedOffsetKeyword: "LIMIT ALL OFFSET",
|
|
1189
|
+
jsonObjectFunctionName: "jsonb_build_object",
|
|
1190
|
+
jsonArrayAggregateFunctionName: "jsonb_agg",
|
|
1191
|
+
emptyJsonArrayLiteral: "'[]'::jsonb"
|
|
1192
|
+
};
|
|
1193
|
+
const createPostgresDialect = (input) => createDialect({
|
|
1194
|
+
spec: POSTGRES_SPEC,
|
|
1195
|
+
...input
|
|
1196
|
+
});
|
|
1197
|
+
//#endregion
|
|
1198
|
+
//#region src/lib/orm/dialect/sqlite.ts
|
|
1199
|
+
const SQLITE_SPEC = {
|
|
1200
|
+
name: "sqlite",
|
|
1201
|
+
formatPlaceholder: () => "?",
|
|
1202
|
+
unlimitedOffsetKeyword: "LIMIT -1 OFFSET",
|
|
1203
|
+
jsonObjectFunctionName: "json_object",
|
|
1204
|
+
jsonArrayAggregateFunctionName: "json_group_array",
|
|
1205
|
+
emptyJsonArrayLiteral: "'[]'"
|
|
1206
|
+
};
|
|
1207
|
+
const createSqliteDialect = (input) => createDialect({
|
|
1208
|
+
spec: SQLITE_SPEC,
|
|
1209
|
+
...input
|
|
1210
|
+
});
|
|
1211
|
+
//#endregion
|
|
1212
|
+
//#region src/lib/orm/dialect/index.ts
|
|
1213
|
+
const getDialect = (input) => {
|
|
1214
|
+
const { adapter, table, relations, tableRelationsMap = /* @__PURE__ */ new Map() } = input;
|
|
1215
|
+
switch (adapter) {
|
|
1216
|
+
case "sqlite": return createSqliteDialect({
|
|
1217
|
+
table,
|
|
1218
|
+
relations,
|
|
1219
|
+
tableRelationsMap
|
|
1220
|
+
});
|
|
1221
|
+
case "postgres": return createPostgresDialect({
|
|
1222
|
+
table,
|
|
1223
|
+
relations,
|
|
1224
|
+
tableRelationsMap
|
|
1225
|
+
});
|
|
1226
|
+
default: throw new Error(`Unsupported adapter: ${adapter}`);
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
//#endregion
|
|
1230
|
+
//#region src/lib/orm/orm/index.ts
|
|
1231
|
+
const many = (table) => {
|
|
1232
|
+
return {
|
|
1233
|
+
_type: "hasMany",
|
|
1234
|
+
_table: table()
|
|
1235
|
+
};
|
|
1236
|
+
};
|
|
1237
|
+
const one = (foreignKey, table) => {
|
|
1238
|
+
return {
|
|
1239
|
+
_type: "hasOne",
|
|
1240
|
+
_table: table(),
|
|
1241
|
+
_foreignKey: foreignKey
|
|
1242
|
+
};
|
|
1243
|
+
};
|
|
1244
|
+
const shouldSkipHooks = (options) => {
|
|
1245
|
+
return options?.$skipHooks === true;
|
|
1246
|
+
};
|
|
1247
|
+
const withoutSkipHooks = (options) => {
|
|
1248
|
+
const { $skipHooks, ...queryOptions } = options;
|
|
1249
|
+
return queryOptions;
|
|
1250
|
+
};
|
|
1251
|
+
const toHookOptions = (options) => {
|
|
1252
|
+
if (!options) return;
|
|
1253
|
+
return withoutSkipHooks(options);
|
|
1254
|
+
};
|
|
1255
|
+
const pickGlobalHooks = (hooksConfig) => {
|
|
1256
|
+
const { tables, ...globalHooks } = hooksConfig;
|
|
1257
|
+
return globalHooks;
|
|
1258
|
+
};
|
|
1259
|
+
const runHook = async (hook, ctx) => {
|
|
1260
|
+
await hook?.(ctx);
|
|
1261
|
+
};
|
|
1262
|
+
const runReadHooks = async (globalHook, tableHook, ctx) => {
|
|
1263
|
+
await runHook(globalHook, ctx);
|
|
1264
|
+
await runHook(tableHook, ctx);
|
|
1265
|
+
};
|
|
1266
|
+
const withReadHooks = async (input) => {
|
|
1267
|
+
const beforeCtx = {
|
|
1268
|
+
tableName: input.tableName,
|
|
1269
|
+
table: input.table,
|
|
1270
|
+
options: input.hookOptions
|
|
1271
|
+
};
|
|
1272
|
+
if (!input.skipHooks) await runReadHooks(input.beforeGlobal, input.beforeTable, beforeCtx);
|
|
1273
|
+
const result = await input.query();
|
|
1274
|
+
if (!input.skipHooks) await runReadHooks(input.afterGlobal, input.afterTable, {
|
|
1275
|
+
...beforeCtx,
|
|
1276
|
+
result
|
|
1277
|
+
});
|
|
1278
|
+
return result;
|
|
1279
|
+
};
|
|
1280
|
+
const createTableClient = (input) => {
|
|
1281
|
+
const { sql, tableName, table, adapter, relations, tableRelationsMap, globalHooks, tableHooks } = input;
|
|
1282
|
+
const dialect = getDialect({
|
|
1283
|
+
adapter,
|
|
1284
|
+
table,
|
|
1285
|
+
relations,
|
|
1286
|
+
tableRelationsMap
|
|
1287
|
+
});
|
|
1288
|
+
return {
|
|
1289
|
+
findMany: async (options) => {
|
|
1290
|
+
const skipHooks = shouldSkipHooks(options);
|
|
1291
|
+
const hookOptions = toHookOptions(options);
|
|
1292
|
+
return withReadHooks({
|
|
1293
|
+
skipHooks,
|
|
1294
|
+
tableName,
|
|
1295
|
+
table,
|
|
1296
|
+
hookOptions,
|
|
1297
|
+
beforeGlobal: globalHooks?.beforeFindMany,
|
|
1298
|
+
beforeTable: tableHooks?.beforeFindMany,
|
|
1299
|
+
afterGlobal: globalHooks?.afterFindMany,
|
|
1300
|
+
afterTable: tableHooks?.afterFindMany,
|
|
1301
|
+
query: () => dialect.findMany(sql, hookOptions)
|
|
1302
|
+
});
|
|
1303
|
+
},
|
|
1304
|
+
findFirst: async (options) => {
|
|
1305
|
+
const skipHooks = shouldSkipHooks(options);
|
|
1306
|
+
const hookOptions = toHookOptions(options);
|
|
1307
|
+
return withReadHooks({
|
|
1308
|
+
skipHooks,
|
|
1309
|
+
tableName,
|
|
1310
|
+
table,
|
|
1311
|
+
hookOptions,
|
|
1312
|
+
beforeGlobal: globalHooks?.beforeFindFirst,
|
|
1313
|
+
beforeTable: tableHooks?.beforeFindFirst,
|
|
1314
|
+
afterGlobal: globalHooks?.afterFindFirst,
|
|
1315
|
+
afterTable: tableHooks?.afterFindFirst,
|
|
1316
|
+
query: () => dialect.findFirst(sql, hookOptions)
|
|
1317
|
+
});
|
|
1318
|
+
},
|
|
1319
|
+
findUnique: async (options) => {
|
|
1320
|
+
const skipHooks = shouldSkipHooks(options);
|
|
1321
|
+
const hookOptions = withoutSkipHooks(options);
|
|
1322
|
+
return withReadHooks({
|
|
1323
|
+
skipHooks,
|
|
1324
|
+
tableName,
|
|
1325
|
+
table,
|
|
1326
|
+
hookOptions,
|
|
1327
|
+
beforeGlobal: globalHooks?.beforeFindUnique,
|
|
1328
|
+
beforeTable: tableHooks?.beforeFindUnique,
|
|
1329
|
+
afterGlobal: globalHooks?.afterFindUnique,
|
|
1330
|
+
afterTable: tableHooks?.afterFindUnique,
|
|
1331
|
+
query: () => dialect.findUnique(sql, hookOptions)
|
|
1332
|
+
});
|
|
1333
|
+
},
|
|
1334
|
+
create: async (options) => {
|
|
1335
|
+
const skipHooks = shouldSkipHooks(options);
|
|
1336
|
+
const hookOptions = withoutSkipHooks(options);
|
|
1337
|
+
let queryOptions = hookOptions;
|
|
1338
|
+
if (!skipHooks) {
|
|
1339
|
+
const createHookResult = await globalHooks?.beforeCreate?.({
|
|
1340
|
+
tableName,
|
|
1341
|
+
table,
|
|
1342
|
+
options: hookOptions
|
|
1343
|
+
});
|
|
1344
|
+
queryOptions = {
|
|
1345
|
+
...hookOptions,
|
|
1346
|
+
...createHookResult
|
|
1347
|
+
};
|
|
1348
|
+
const tableCreateHookResult = await tableHooks?.beforeCreate?.({
|
|
1349
|
+
tableName,
|
|
1350
|
+
table,
|
|
1351
|
+
options: queryOptions
|
|
1352
|
+
});
|
|
1353
|
+
queryOptions = {
|
|
1354
|
+
...queryOptions,
|
|
1355
|
+
...tableCreateHookResult
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
const result = await dialect.create(sql, queryOptions);
|
|
1359
|
+
if (!skipHooks) await runReadHooks(globalHooks?.afterCreate, tableHooks?.afterCreate, {
|
|
1360
|
+
tableName,
|
|
1361
|
+
table,
|
|
1362
|
+
options: queryOptions,
|
|
1363
|
+
result
|
|
1364
|
+
});
|
|
1365
|
+
return result;
|
|
1366
|
+
},
|
|
1367
|
+
createMany: async (options) => {
|
|
1368
|
+
const skipHooks = shouldSkipHooks(options);
|
|
1369
|
+
const hookOptions = withoutSkipHooks(options);
|
|
1370
|
+
let queryOptions = hookOptions;
|
|
1371
|
+
if (!skipHooks) {
|
|
1372
|
+
const createManyHookResult = await globalHooks?.beforeCreateMany?.({
|
|
1373
|
+
tableName,
|
|
1374
|
+
table,
|
|
1375
|
+
options: hookOptions
|
|
1376
|
+
});
|
|
1377
|
+
queryOptions = {
|
|
1378
|
+
...hookOptions,
|
|
1379
|
+
...createManyHookResult
|
|
1380
|
+
};
|
|
1381
|
+
const tableCreateManyHookResult = await tableHooks?.beforeCreateMany?.({
|
|
1382
|
+
tableName,
|
|
1383
|
+
table,
|
|
1384
|
+
options: queryOptions
|
|
1385
|
+
});
|
|
1386
|
+
queryOptions = {
|
|
1387
|
+
...queryOptions,
|
|
1388
|
+
...tableCreateManyHookResult
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
const result = await dialect.createMany(sql, queryOptions);
|
|
1392
|
+
if (!skipHooks) {
|
|
1393
|
+
const hookCtx = {
|
|
1394
|
+
tableName,
|
|
1395
|
+
table,
|
|
1396
|
+
options: queryOptions,
|
|
1397
|
+
result
|
|
1398
|
+
};
|
|
1399
|
+
await runHook(globalHooks?.afterCreateMany, hookCtx);
|
|
1400
|
+
await runHook(tableHooks?.afterCreateMany, hookCtx);
|
|
1401
|
+
}
|
|
1402
|
+
return result;
|
|
1403
|
+
},
|
|
1404
|
+
update: async (options) => {
|
|
1405
|
+
const skipHooks = shouldSkipHooks(options);
|
|
1406
|
+
const hookOptions = withoutSkipHooks(options);
|
|
1407
|
+
let queryOptions = hookOptions;
|
|
1408
|
+
if (!skipHooks) {
|
|
1409
|
+
const updateHookResult = await globalHooks?.beforeUpdate?.({
|
|
1410
|
+
tableName,
|
|
1411
|
+
table,
|
|
1412
|
+
options: hookOptions
|
|
1413
|
+
});
|
|
1414
|
+
queryOptions = {
|
|
1415
|
+
...hookOptions,
|
|
1416
|
+
...updateHookResult
|
|
1417
|
+
};
|
|
1418
|
+
const tableUpdateHookResult = await tableHooks?.beforeUpdate?.({
|
|
1419
|
+
tableName,
|
|
1420
|
+
table,
|
|
1421
|
+
options: queryOptions
|
|
1422
|
+
});
|
|
1423
|
+
queryOptions = {
|
|
1424
|
+
...queryOptions,
|
|
1425
|
+
...tableUpdateHookResult
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
const result = await dialect.update(sql, queryOptions);
|
|
1429
|
+
if (!skipHooks) await runReadHooks(globalHooks?.afterUpdate, tableHooks?.afterUpdate, {
|
|
1430
|
+
tableName,
|
|
1431
|
+
table,
|
|
1432
|
+
options: queryOptions,
|
|
1433
|
+
result
|
|
1434
|
+
});
|
|
1435
|
+
return result;
|
|
1436
|
+
},
|
|
1437
|
+
updateMany: async (options) => {
|
|
1438
|
+
const skipHooks = shouldSkipHooks(options);
|
|
1439
|
+
const hookOptions = withoutSkipHooks(options);
|
|
1440
|
+
let queryOptions = hookOptions;
|
|
1441
|
+
if (!skipHooks) {
|
|
1442
|
+
const updateManyHookResult = await globalHooks?.beforeUpdateMany?.({
|
|
1443
|
+
tableName,
|
|
1444
|
+
table,
|
|
1445
|
+
options: hookOptions
|
|
1446
|
+
});
|
|
1447
|
+
queryOptions = {
|
|
1448
|
+
...hookOptions,
|
|
1449
|
+
...updateManyHookResult
|
|
1450
|
+
};
|
|
1451
|
+
const tableUpdateManyHookResult = await tableHooks?.beforeUpdateMany?.({
|
|
1452
|
+
tableName,
|
|
1453
|
+
table,
|
|
1454
|
+
options: queryOptions
|
|
1455
|
+
});
|
|
1456
|
+
queryOptions = {
|
|
1457
|
+
...queryOptions,
|
|
1458
|
+
...tableUpdateManyHookResult
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
const result = await dialect.updateMany(sql, queryOptions);
|
|
1462
|
+
if (!skipHooks) {
|
|
1463
|
+
const hookCtx = {
|
|
1464
|
+
tableName,
|
|
1465
|
+
table,
|
|
1466
|
+
options: queryOptions,
|
|
1467
|
+
result
|
|
1468
|
+
};
|
|
1469
|
+
await runHook(globalHooks?.afterUpdateMany, hookCtx);
|
|
1470
|
+
await runHook(tableHooks?.afterUpdateMany, hookCtx);
|
|
1471
|
+
}
|
|
1472
|
+
return result;
|
|
1473
|
+
},
|
|
1474
|
+
delete: async (options) => {
|
|
1475
|
+
const skipHooks = shouldSkipHooks(options);
|
|
1476
|
+
const hookOptions = withoutSkipHooks(options);
|
|
1477
|
+
let queryOptions = hookOptions;
|
|
1478
|
+
if (!skipHooks) {
|
|
1479
|
+
const deleteHookResult = await globalHooks?.beforeDelete?.({
|
|
1480
|
+
tableName,
|
|
1481
|
+
table,
|
|
1482
|
+
options: hookOptions
|
|
1483
|
+
});
|
|
1484
|
+
queryOptions = {
|
|
1485
|
+
...hookOptions,
|
|
1486
|
+
...deleteHookResult
|
|
1487
|
+
};
|
|
1488
|
+
const tableDeleteHookResult = await tableHooks?.beforeDelete?.({
|
|
1489
|
+
tableName,
|
|
1490
|
+
table,
|
|
1491
|
+
options: queryOptions
|
|
1492
|
+
});
|
|
1493
|
+
queryOptions = {
|
|
1494
|
+
...queryOptions,
|
|
1495
|
+
...tableDeleteHookResult
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
const result = await dialect.delete(sql, queryOptions);
|
|
1499
|
+
if (!skipHooks) await runReadHooks(globalHooks?.afterDelete, tableHooks?.afterDelete, {
|
|
1500
|
+
tableName,
|
|
1501
|
+
table,
|
|
1502
|
+
options: queryOptions,
|
|
1503
|
+
result
|
|
1504
|
+
});
|
|
1505
|
+
return result;
|
|
1506
|
+
},
|
|
1507
|
+
deleteMany: async (options) => {
|
|
1508
|
+
const skipHooks = shouldSkipHooks(options);
|
|
1509
|
+
const hookOptions = withoutSkipHooks(options);
|
|
1510
|
+
let queryOptions = hookOptions;
|
|
1511
|
+
if (!skipHooks) {
|
|
1512
|
+
const deleteManyHookResult = await globalHooks?.beforeDeleteMany?.({
|
|
1513
|
+
tableName,
|
|
1514
|
+
table,
|
|
1515
|
+
options: hookOptions
|
|
1516
|
+
});
|
|
1517
|
+
queryOptions = {
|
|
1518
|
+
...hookOptions,
|
|
1519
|
+
...deleteManyHookResult
|
|
1520
|
+
};
|
|
1521
|
+
const tableDeleteManyHookResult = await tableHooks?.beforeDeleteMany?.({
|
|
1522
|
+
tableName,
|
|
1523
|
+
table,
|
|
1524
|
+
options: queryOptions
|
|
1525
|
+
});
|
|
1526
|
+
queryOptions = {
|
|
1527
|
+
...queryOptions,
|
|
1528
|
+
...tableDeleteManyHookResult
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
const result = await dialect.deleteMany(sql, queryOptions);
|
|
1532
|
+
if (!skipHooks) {
|
|
1533
|
+
const hookCtx = {
|
|
1534
|
+
tableName,
|
|
1535
|
+
table,
|
|
1536
|
+
options: queryOptions,
|
|
1537
|
+
result
|
|
1538
|
+
};
|
|
1539
|
+
await runHook(globalHooks?.afterDeleteMany, hookCtx);
|
|
1540
|
+
await runHook(tableHooks?.afterDeleteMany, hookCtx);
|
|
1541
|
+
}
|
|
1542
|
+
return result;
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
};
|
|
1546
|
+
const toObjectEntries = (object) => {
|
|
1547
|
+
const result = [];
|
|
1548
|
+
for (const key in object) {
|
|
1549
|
+
if (!Object.hasOwn(object, key)) continue;
|
|
1550
|
+
result.push([key, object[key]]);
|
|
1551
|
+
}
|
|
1552
|
+
return result;
|
|
1553
|
+
};
|
|
1554
|
+
const getTableRelations = (relations, tableName) => {
|
|
1555
|
+
if (!relations) return Object.create(null);
|
|
1556
|
+
if (!Object.hasOwn(relations, tableName)) return Object.create(null);
|
|
1557
|
+
const tableRelations = relations[tableName];
|
|
1558
|
+
if (!tableRelations) return Object.create(null);
|
|
1559
|
+
return tableRelations;
|
|
1560
|
+
};
|
|
1561
|
+
const buildTableClients = (input) => {
|
|
1562
|
+
const { tables, relations, sql, adapter, tableRelationsMap, hooks } = input;
|
|
1563
|
+
const clients = Object.create(null);
|
|
1564
|
+
for (const entry of toObjectEntries(tables)) {
|
|
1565
|
+
const setTableClient = (tableName, table) => {
|
|
1566
|
+
const tableRelations = getTableRelations(relations, tableName);
|
|
1567
|
+
tableRelationsMap.set(table, tableRelations);
|
|
1568
|
+
clients[tableName] = createTableClient({
|
|
1569
|
+
sql,
|
|
1570
|
+
tableName,
|
|
1571
|
+
table,
|
|
1572
|
+
adapter,
|
|
1573
|
+
relations: tableRelations,
|
|
1574
|
+
tableRelationsMap,
|
|
1575
|
+
globalHooks: hooks ? pickGlobalHooks(hooks) : void 0,
|
|
1576
|
+
tableHooks: hooks?.tables?.[tableName]
|
|
1577
|
+
});
|
|
1578
|
+
};
|
|
1579
|
+
setTableClient(entry[0], entry[1]);
|
|
1580
|
+
}
|
|
1581
|
+
return clients;
|
|
1582
|
+
};
|
|
1583
|
+
const buildOrmClient = (input) => {
|
|
1584
|
+
const { tableClients, sql, transaction } = input;
|
|
1585
|
+
return {
|
|
1586
|
+
...tableClients,
|
|
1587
|
+
$raw: sql,
|
|
1588
|
+
$transaction: transaction
|
|
1589
|
+
};
|
|
1590
|
+
};
|
|
1591
|
+
const buildTransactionClient = (tableClients, sql) => ({
|
|
1592
|
+
...tableClients,
|
|
1593
|
+
$raw: sql
|
|
1594
|
+
});
|
|
1595
|
+
const createOrm = (options) => {
|
|
1596
|
+
const sql = new Bun.SQL(options.url, { adapter: options.adapter });
|
|
1597
|
+
const tableRelationsMap = /* @__PURE__ */ new Map();
|
|
1598
|
+
return buildOrmClient({
|
|
1599
|
+
tableClients: buildTableClients({
|
|
1600
|
+
tables: options.tables,
|
|
1601
|
+
relations: options.relations,
|
|
1602
|
+
sql,
|
|
1603
|
+
adapter: options.adapter,
|
|
1604
|
+
tableRelationsMap,
|
|
1605
|
+
hooks: options.hooks
|
|
1606
|
+
}),
|
|
1607
|
+
sql,
|
|
1608
|
+
transaction: async (callback) => {
|
|
1609
|
+
return await sql.begin(async (txSql) => {
|
|
1610
|
+
return await callback(buildTransactionClient(buildTableClients({
|
|
1611
|
+
tables: options.tables,
|
|
1612
|
+
relations: options.relations,
|
|
1613
|
+
sql: txSql,
|
|
1614
|
+
adapter: options.adapter,
|
|
1615
|
+
tableRelationsMap,
|
|
1616
|
+
hooks: options.hooks
|
|
1617
|
+
}), txSql));
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
};
|
|
1622
|
+
//#endregion
|
|
1623
|
+
//#region src/lib/orm/table/index.ts
|
|
1624
|
+
const defineTable = (sqlName, columns) => {
|
|
1625
|
+
return {
|
|
1626
|
+
sqlName,
|
|
1627
|
+
columns
|
|
1628
|
+
};
|
|
1629
|
+
};
|
|
1630
|
+
//#endregion
|
|
1631
|
+
exports.boolean = boolean;
|
|
1632
|
+
exports.createOrm = createOrm;
|
|
1633
|
+
exports.date = date;
|
|
1634
|
+
exports.defineTable = defineTable;
|
|
1635
|
+
exports.enumType = enumType;
|
|
1636
|
+
exports.json = json;
|
|
1637
|
+
exports.jsonb = jsonb;
|
|
1638
|
+
exports.many = many;
|
|
1639
|
+
exports.number = number;
|
|
1640
|
+
exports.one = one;
|
|
1641
|
+
exports.string = string;
|
|
1642
|
+
exports.uuid = uuid;
|