@getstrata/core 0.5.46 → 0.5.48

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.
@@ -1,1388 +1 @@
1
- // @bun
2
- // ../../src/core/events/eventBus.ts
3
- class EventBus {
4
- constructor() {}
5
- listeners = new Map;
6
- listen(event, listener) {
7
- const handlers = this.listeners.get(event) ?? new Set;
8
- handlers.add(listener);
9
- this.listeners.set(event, handlers);
10
- return () => {
11
- handlers.delete(listener);
12
- if (handlers.size === 0) {
13
- this.listeners.delete(event);
14
- }
15
- };
16
- }
17
- async dispatch(event, payload) {
18
- const handlers = this.listeners.get(event);
19
- if (!handlers || handlers.size === 0) {
20
- return;
21
- }
22
- for (const handler of handlers) {
23
- await handler(payload);
24
- }
25
- }
26
- }
27
- var eventBus = new EventBus;
28
-
29
- // ../../src/core/events/index.ts
30
- function modelEventName(tableName, action) {
31
- return `${tableName}.${action}`;
32
- }
33
-
34
- // ../../src/core/pagination/index.ts
35
- function buildPaginationMeta(input) {
36
- const lastPage = Math.max(1, Math.ceil(input.total / input.perPage));
37
- return {
38
- page: input.page,
39
- per_page: input.perPage,
40
- total: input.total,
41
- last_page: lastPage
42
- };
43
- }
44
-
45
- // ../../src/core/errors/http.ts
46
- class HttpError extends Error {
47
- status;
48
- details;
49
- constructor(status, message, details) {
50
- super(message);
51
- this.name = new.target.name;
52
- this.status = status;
53
- this.details = details;
54
- }
55
- }
56
-
57
- class BadRequestError extends HttpError {
58
- constructor(message = "Bad Request", details) {
59
- super(400, message, details);
60
- }
61
- }
62
-
63
- class NotFoundError extends HttpError {
64
- constructor(message = "Not Found", details) {
65
- super(404, message, details);
66
- }
67
- }
68
-
69
- class ConflictError extends HttpError {
70
- constructor(message = "Conflict", details) {
71
- super(409, message, details);
72
- }
73
- }
74
-
75
- class UnprocessableEntityError extends HttpError {
76
- constructor(message = "Unprocessable Entity", details) {
77
- super(422, message, details);
78
- }
79
- }
80
-
81
- class ValidationError extends HttpError {
82
- constructor(message = "Validation failed", details) {
83
- super(422, message, details);
84
- }
85
- }
86
-
87
- class ForbiddenError extends HttpError {
88
- constructor(message = "Forbidden", details) {
89
- super(403, message, details);
90
- }
91
- }
92
-
93
- class UnauthorizedError extends HttpError {
94
- constructor(message = "Unauthorized", details) {
95
- super(401, message, details);
96
- }
97
- }
98
-
99
- class PayloadTooLargeError extends HttpError {
100
- constructor(message = "Payload Too Large", details) {
101
- super(413, message, details);
102
- }
103
- }
104
-
105
- class PreconditionFailedError extends HttpError {
106
- constructor(message = "Precondition Failed", details) {
107
- super(412, message, details);
108
- }
109
- }
110
-
111
- // ../../src/core/database/errors.ts
112
- function isPostgresError(error) {
113
- return typeof error === "object" && error !== null && (("errno" in error) || ("code" in error));
114
- }
115
- function getPostgresSqlState(error) {
116
- if (typeof error.errno === "string" && /^\d{5}$/.test(error.errno)) {
117
- return error.errno;
118
- }
119
- if (typeof error.errno === "number") {
120
- return String(error.errno).padStart(5, "0");
121
- }
122
- if (typeof error.code === "string" && /^\d{5}$/.test(error.code)) {
123
- return error.code;
124
- }
125
- return;
126
- }
127
- function mapDatabaseError(error) {
128
- if (error instanceof HttpError) {
129
- return error;
130
- }
131
- if (!isPostgresError(error)) {
132
- const message = error instanceof Error ? error.message : "Database operation failed.";
133
- return new BadRequestError(message);
134
- }
135
- const sqlState = getPostgresSqlState(error);
136
- switch (sqlState) {
137
- case "23505":
138
- return new ConflictError(error.detail ?? "A record with these values already exists.", {
139
- constraint: error.constraint
140
- });
141
- case "23503":
142
- return new UnprocessableEntityError(error.detail ?? "Referenced record does not exist.", {
143
- constraint: error.constraint
144
- });
145
- case "23502":
146
- return new BadRequestError(error.detail ?? "Required field is missing.", {
147
- constraint: error.constraint
148
- });
149
- case "23514":
150
- return new BadRequestError(error.detail ?? "Value violates a database constraint.", {
151
- constraint: error.constraint
152
- });
153
- default:
154
- return new BadRequestError(error.message ?? "Database operation failed.", {
155
- code: error.code,
156
- sqlState
157
- });
158
- }
159
- }
160
- async function withDatabaseErrorHandling(operation) {
161
- try {
162
- return await operation();
163
- } catch (error) {
164
- throw mapDatabaseError(error);
165
- }
166
- }
167
-
168
- // ../../src/core/database/query.ts
169
- function quoteIdentifier(identifier) {
170
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
171
- throw new Error(`Invalid SQL identifier: ${identifier}`);
172
- }
173
- return `"${identifier}"`;
174
- }
175
- function qualifyColumn(tableName, column) {
176
- return `${quoteIdentifier(tableName)}.${quoteIdentifier(column)}`;
177
- }
178
- function resolveQualifiedColumn(defaultTable, columnName) {
179
- if (columnName.includes(".")) {
180
- const [table, column] = columnName.split(".", 2);
181
- if (!table || !column) {
182
- throw new Error(`Invalid qualified column: ${columnName}`);
183
- }
184
- return qualifyColumn(table, column);
185
- }
186
- return qualifyColumn(defaultTable, columnName);
187
- }
188
- function parseQualifiedColumn(reference) {
189
- const [table, column] = reference.split(".", 2);
190
- if (!table || !column) {
191
- throw new Error(`Join columns must be qualified as table.column: ${reference}`);
192
- }
193
- return { table, column };
194
- }
195
- function normalizeDirection(direction = "ASC") {
196
- return direction.toUpperCase() === "DESC" ? "DESC" : "ASC";
197
- }
198
- function isQueryOperator(value) {
199
- return value !== null && !Array.isArray(value) && !(value instanceof Date) && typeof value === "object";
200
- }
201
- function pushParam(values, value) {
202
- values.push(value);
203
- return `$${values.length}`;
204
- }
205
- function buildInClause(column, values, params) {
206
- if (values.length === 0) {
207
- return "1 = 0";
208
- }
209
- const placeholders = values.map((value) => pushParam(params, value)).join(", ");
210
- return `${column} IN (${placeholders})`;
211
- }
212
- function buildOperatorClauses(column, operator, params) {
213
- const clauses = [];
214
- if (operator.isNull === true) {
215
- clauses.push(`${column} IS NULL`);
216
- }
217
- if (operator.isNull === false) {
218
- clauses.push(`${column} IS NOT NULL`);
219
- }
220
- if (operator.eq !== undefined) {
221
- if (operator.eq === null) {
222
- clauses.push(`${column} IS NULL`);
223
- } else {
224
- clauses.push(`${column} = ${pushParam(params, operator.eq)}`);
225
- }
226
- }
227
- if (operator.in !== undefined) {
228
- clauses.push(buildInClause(column, operator.in, params));
229
- }
230
- if (operator.gt !== undefined) {
231
- clauses.push(`${column} > ${pushParam(params, operator.gt)}`);
232
- }
233
- if (operator.gte !== undefined) {
234
- clauses.push(`${column} >= ${pushParam(params, operator.gte)}`);
235
- }
236
- if (operator.lt !== undefined) {
237
- clauses.push(`${column} < ${pushParam(params, operator.lt)}`);
238
- }
239
- if (operator.lte !== undefined) {
240
- clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
241
- }
242
- if (operator.ilike !== undefined) {
243
- clauses.push(`${column} ILIKE ${pushParam(params, `%${operator.ilike}%`)}`);
244
- }
245
- if (operator.tsMatch !== undefined) {
246
- clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
247
- }
248
- return clauses;
249
- }
250
- function appendWhereParts(tableName, where, params) {
251
- const clauses = [];
252
- for (const [columnName, filterValue] of Object.entries(where)) {
253
- if (filterValue === undefined) {
254
- continue;
255
- }
256
- const column = resolveQualifiedColumn(tableName, columnName);
257
- if (Array.isArray(filterValue)) {
258
- clauses.push(buildInClause(column, filterValue, params));
259
- continue;
260
- }
261
- if (isQueryOperator(filterValue)) {
262
- clauses.push(...buildOperatorClauses(column, filterValue, params));
263
- continue;
264
- }
265
- if (filterValue === null) {
266
- clauses.push(`${column} IS NULL`);
267
- continue;
268
- }
269
- clauses.push(`${column} = ${pushParam(params, filterValue)}`);
270
- }
271
- return clauses.join(" AND ");
272
- }
273
- function buildWhereClause(tableName, where = {}) {
274
- const params = [];
275
- const body = appendWhereParts(tableName, where, params);
276
- return {
277
- clause: body.length > 0 ? ` WHERE ${body}` : "",
278
- params
279
- };
280
- }
281
- function buildWhereNodeClause(tableName, node, params) {
282
- if ("where" in node) {
283
- return appendWhereParts(tableName, node.where, params);
284
- }
285
- const grouped = buildWhereGroupClause(tableName, node.group, params);
286
- if (!grouped) {
287
- return "";
288
- }
289
- return grouped.includes(" OR ") ? `(${grouped})` : grouped;
290
- }
291
- function buildWhereGroupClause(tableName, nodes, params) {
292
- let result = "";
293
- for (const node of nodes) {
294
- const part = buildWhereNodeClause(tableName, node, params);
295
- if (!part) {
296
- continue;
297
- }
298
- if (!result) {
299
- result = part;
300
- continue;
301
- }
302
- result = node.kind === "or" ? `${result} OR ${part}` : `${result} AND ${part}`;
303
- }
304
- if (!result) {
305
- return "";
306
- }
307
- return result;
308
- }
309
- function buildAdvancedWhereClause(tableName, where = {}, whereNodes = [], params = []) {
310
- const nodes = [];
311
- if (Object.keys(where).length > 0) {
312
- nodes.push({ kind: "and", where });
313
- }
314
- nodes.push(...whereNodes);
315
- const combined = buildWhereGroupClause(tableName, nodes, params);
316
- return {
317
- clause: combined ? ` WHERE ${combined}` : "",
318
- params
319
- };
320
- }
321
- function resolveSoftDeleteColumn(table) {
322
- if (!table.softDeletes) {
323
- return null;
324
- }
325
- if (table.softDeletes === true) {
326
- return "deleted_at";
327
- }
328
- return table.softDeletes.column ?? "deleted_at";
329
- }
330
- function appendSoftDeleteScope(table, options, clauses) {
331
- const column = resolveSoftDeleteColumn(table);
332
- if (!column) {
333
- return;
334
- }
335
- const qualifiedColumn = qualifyColumn(table.name, column);
336
- if (options.onlyTrashed) {
337
- clauses.push(`${qualifiedColumn} IS NOT NULL`);
338
- return;
339
- }
340
- if (!options.withTrashed) {
341
- clauses.push(`${qualifiedColumn} IS NULL`);
342
- }
343
- }
344
- function buildQueryWhereClause(table, options = {}, whereNodes = [], params = []) {
345
- const { clause, params: whereParams } = buildAdvancedWhereClause(table.name, options.where ?? {}, whereNodes, params);
346
- const softDeleteClauses = [];
347
- appendSoftDeleteScope(table, options, softDeleteClauses);
348
- if (softDeleteClauses.length === 0) {
349
- return { clause, params: whereParams };
350
- }
351
- const base = clause.replace(/^ WHERE /, "");
352
- const scope = softDeleteClauses.join(" AND ");
353
- return {
354
- clause: base ? ` WHERE (${base}) AND ${scope}` : ` WHERE ${scope}`,
355
- params: whereParams
356
- };
357
- }
358
- function isQueryOrder(value) {
359
- return "column" in value;
360
- }
361
- function normalizeOrderBy(orderBy) {
362
- if (!orderBy) {
363
- return [];
364
- }
365
- if (Array.isArray(orderBy)) {
366
- return orderBy;
367
- }
368
- if (isQueryOrder(orderBy)) {
369
- return [orderBy];
370
- }
371
- return Object.entries(orderBy).map(([column, direction]) => ({
372
- column,
373
- direction
374
- }));
375
- }
376
- function buildOrderByClause(tableName, orderBy) {
377
- const parts = normalizeOrderBy(orderBy).map(({ column, direction }) => {
378
- return `${resolveQualifiedColumn(tableName, column)} ${normalizeDirection(direction)}`;
379
- });
380
- return parts.length > 0 ? ` ORDER BY ${parts.join(", ")}` : "";
381
- }
382
- function buildGroupByClause(tableName, groupBy) {
383
- if (!groupBy) {
384
- return "";
385
- }
386
- const columns = (Array.isArray(groupBy) ? groupBy : [groupBy]).map((column) => String(column));
387
- const parts = columns.map((column) => resolveQualifiedColumn(tableName, column));
388
- return parts.length > 0 ? ` GROUP BY ${parts.join(", ")}` : "";
389
- }
390
- function buildHavingClause(tableName, having, params) {
391
- if (!having) {
392
- return "";
393
- }
394
- const body = appendWhereParts(tableName, having, params);
395
- return body.length > 0 ? ` HAVING ${body}` : "";
396
- }
397
- function buildJoinClause(joins = []) {
398
- return joins.map((join) => {
399
- const joinType = join.type === "left" ? "LEFT JOIN" : "INNER JOIN";
400
- const onClause = join.on.map(({ left, right }) => `${qualifyColumn(left.table, left.column)} = ${qualifyColumn(right.table, right.column)}`).join(" AND ");
401
- return ` ${joinType} ${quoteIdentifier(join.table)} ON ${onClause}`;
402
- }).join("");
403
- }
404
- function buildLimitClause(limit) {
405
- if (limit === undefined) {
406
- return "";
407
- }
408
- if (!Number.isInteger(limit) || limit <= 0) {
409
- throw new Error("Query limit must be a positive integer.");
410
- }
411
- return ` LIMIT ${limit}`;
412
- }
413
- function buildOffsetClause(offset) {
414
- if (offset === undefined) {
415
- return "";
416
- }
417
- if (!Number.isInteger(offset) || offset < 0) {
418
- throw new Error("Query offset must be a non-negative integer.");
419
- }
420
- return ` OFFSET ${offset}`;
421
- }
422
- function buildReturningColumns(table) {
423
- return table.columns.map((column) => qualifyColumn(table.name, column)).join(", ");
424
- }
425
- function buildSelectList(table, select, params = []) {
426
- if (!select || select.length === 0) {
427
- return buildReturningColumns(table);
428
- }
429
- return select.map((item) => {
430
- if (item.kind === "column") {
431
- const column2 = qualifyColumn(item.table, item.column);
432
- return item.as ? `${column2} AS ${quoteIdentifier(item.as)}` : column2;
433
- }
434
- if (item.kind === "literalText") {
435
- return `${pushParam(params, item.value)}::text AS ${quoteIdentifier(item.as)}`;
436
- }
437
- const column = qualifyColumn(item.table, item.column);
438
- const placeholder = pushParam(params, item.query);
439
- return `ts_rank(${column}, plainto_tsquery('english', ${placeholder})) AS ${quoteIdentifier(item.as)}`;
440
- }).join(", ");
441
- }
442
- function getDefinedColumnEntries(table, values, options = {}) {
443
- const record = values;
444
- const excluded = new Set(options.exclude ?? []);
445
- return table.columns.flatMap((column) => {
446
- if (excluded.has(column) || !Object.hasOwn(record, column)) {
447
- return [];
448
- }
449
- const value = record[column];
450
- if (value === undefined) {
451
- return [];
452
- }
453
- return [[column, value]];
454
- });
455
- }
456
- function buildSelectQuery(table, options = {}, whereNodes = []) {
457
- const params = [];
458
- const columns = buildSelectList(table, options.select, params);
459
- const { clause } = buildQueryWhereClause(table, options, whereNodes, params);
460
- const joins = buildJoinClause(options.joins);
461
- const groupBy = buildGroupByClause(table.name, options.groupBy);
462
- const havingClause = buildHavingClause(table.name, options.having, params);
463
- const orderBy = buildOrderByClause(table.name, options.orderBy ?? table.defaultOrderBy);
464
- const limit = buildLimitClause(options.limit);
465
- const offset = buildOffsetClause(options.offset);
466
- return {
467
- text: `SELECT ${columns} FROM ${quoteIdentifier(table.name)}${joins}${clause}${groupBy}${havingClause}${orderBy}${limit}${offset}`,
468
- params
469
- };
470
- }
471
- function buildCountQuery(table, where = {}, options = {}, whereNodes = []) {
472
- const params = [];
473
- const { clause, params: whereParams } = buildQueryWhereClause(table, {
474
- where,
475
- withTrashed: options.withTrashed,
476
- onlyTrashed: options.onlyTrashed
477
- }, whereNodes);
478
- params.push(...whereParams);
479
- const joins = buildJoinClause(options.joins);
480
- const groupBy = buildGroupByClause(table.name, options.groupBy);
481
- return {
482
- text: `SELECT COUNT(*) AS count FROM ${quoteIdentifier(table.name)}${joins}${clause}${groupBy}`,
483
- params
484
- };
485
- }
486
- function buildProjectionQuery(table, expression, alias, options = {}, whereNodes = []) {
487
- assertSafeProjectionExpression(expression);
488
- const params = [];
489
- const { clause, params: whereParams } = buildQueryWhereClause(table, options, whereNodes);
490
- params.push(...whereParams);
491
- const joins = buildJoinClause(options.joins);
492
- const groupBy = buildGroupByClause(table.name, options.groupBy);
493
- const orderBy = buildOrderByClause(table.name, options.orderBy);
494
- const limit = buildLimitClause(options.limit);
495
- return {
496
- text: `SELECT ${expression} AS ${quoteIdentifier(alias)} FROM ${quoteIdentifier(table.name)}${joins}${clause}${groupBy}${orderBy}${limit}`,
497
- params
498
- };
499
- }
500
- var SAFE_PROJECTION_EXPRESSION = /^(COUNT\(\*\)|(?:"[\w_]+"(?:\."[\w_]+")?)|(?:AVG|SUM|MIN|MAX)\((?:"[\w_]+"(?:\."[\w_]+")?)\))$/;
501
- function assertSafeProjectionExpression(expression) {
502
- if (!SAFE_PROJECTION_EXPRESSION.test(expression.trim())) {
503
- throw new Error(`Unsafe projection expression: ${expression}`);
504
- }
505
- }
506
- function buildGroupedCountQuery(table, column, where = {}, options = {}) {
507
- const qualifiedColumn = qualifyColumn(table.name, column);
508
- const { clause, params } = buildQueryWhereClause(table, {
509
- where,
510
- ...options
511
- });
512
- return {
513
- text: `SELECT ${qualifiedColumn} AS ${quoteIdentifier("value")}, COUNT(*) AS ${quoteIdentifier("count")} FROM ${quoteIdentifier(table.name)}${clause} GROUP BY ${qualifiedColumn} ORDER BY ${qualifiedColumn} ASC`,
514
- params
515
- };
516
- }
517
- function buildInsertQuery(table, values) {
518
- const entries = getDefinedColumnEntries(table, values);
519
- if (entries.length === 0) {
520
- throw new Error(`Cannot insert into ${table.name} without any column values.`);
521
- }
522
- const params = [];
523
- const columns = entries.map(([column]) => quoteIdentifier(column)).join(", ");
524
- const placeholders = entries.map(([, value]) => pushParam(params, value)).join(", ");
525
- const returningColumns = buildReturningColumns(table);
526
- return {
527
- text: `INSERT INTO ${quoteIdentifier(table.name)} (${columns}) VALUES (${placeholders}) RETURNING ${returningColumns}`,
528
- params
529
- };
530
- }
531
- function buildUpdateQuery(table, id, changes) {
532
- const entries = getDefinedColumnEntries(table, changes, {
533
- exclude: [table.primaryKey]
534
- });
535
- if (entries.length === 0) {
536
- throw new Error(`Cannot update ${table.name} without any changed column values.`);
537
- }
538
- const params = [];
539
- const setClause = entries.map(([column, value]) => `${quoteIdentifier(column)} = ${pushParam(params, value)}`).join(", ");
540
- const primaryKeyPlaceholder = pushParam(params, id);
541
- const returningColumns = buildReturningColumns(table);
542
- const scopeClauses = [];
543
- appendSoftDeleteScope(table, {}, scopeClauses);
544
- const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
545
- return {
546
- text: `UPDATE ${quoteIdentifier(table.name)} SET ${setClause} WHERE ${quoteIdentifier(table.primaryKey)} = ${primaryKeyPlaceholder}${scopeSuffix} RETURNING ${returningColumns}`,
547
- params
548
- };
549
- }
550
- function buildSoftDeleteByIdQuery(table, id, deletedAt) {
551
- const deletedAtColumn = resolveSoftDeleteColumn(table);
552
- if (!deletedAtColumn) {
553
- throw new Error(`Table ${table.name} does not support soft deletes.`);
554
- }
555
- const returningColumns = buildReturningColumns(table);
556
- const scopeClauses = [];
557
- appendSoftDeleteScope(table, {}, scopeClauses);
558
- const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
559
- return {
560
- text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = $1 WHERE ${quoteIdentifier(table.primaryKey)} = $2${scopeSuffix} RETURNING ${returningColumns}`,
561
- params: [deletedAt, id]
562
- };
563
- }
564
- function buildRestoreByIdQuery(table, id) {
565
- const deletedAtColumn = resolveSoftDeleteColumn(table);
566
- if (!deletedAtColumn) {
567
- throw new Error(`Table ${table.name} does not support soft deletes.`);
568
- }
569
- const returningColumns = buildReturningColumns(table);
570
- return {
571
- text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = $1 WHERE ${quoteIdentifier(table.primaryKey)} = $2 AND ${qualifyColumn(table.name, deletedAtColumn)} IS NOT NULL RETURNING ${returningColumns}`,
572
- params: [null, id]
573
- };
574
- }
575
- function buildDeleteByIdQuery(table, id) {
576
- return {
577
- text: `DELETE FROM ${quoteIdentifier(table.name)} WHERE ${quoteIdentifier(table.primaryKey)} = $1 RETURNING ${quoteIdentifier(table.primaryKey)} AS ${quoteIdentifier("deleted_id")}`,
578
- params: [id]
579
- };
580
- }
581
-
582
- // ../../src/core/database/relationships.ts
583
- function hasMany(definition) {
584
- return {
585
- type: "hasMany",
586
- ...definition
587
- };
588
- }
589
- function hasOne(definition) {
590
- return {
591
- type: "hasOne",
592
- ...definition
593
- };
594
- }
595
- function belongsTo(definition) {
596
- return {
597
- type: "belongsTo",
598
- ...definition
599
- };
600
- }
601
- function belongsToMany(definition) {
602
- return {
603
- type: "belongsToMany",
604
- ...definition
605
- };
606
- }
607
- function indexHasManyRelation(parents, children, relation) {
608
- const groups = new Map;
609
- for (const parent of parents) {
610
- groups.set(parent[relation.localKey], []);
611
- }
612
- for (const child of children) {
613
- const key = child[relation.foreignKey];
614
- const group = groups.get(key);
615
- if (!group) {
616
- continue;
617
- }
618
- group.push(child);
619
- }
620
- return groups;
621
- }
622
- function indexHasOneRelation(parents, children, relation) {
623
- const grouped = indexHasManyRelation(parents, children, relation);
624
- const result = new Map;
625
- for (const parent of parents) {
626
- const matches = grouped.get(parent[relation.localKey]) ?? [];
627
- result.set(parent[relation.localKey], matches[0]);
628
- }
629
- return result;
630
- }
631
- function indexBelongsToRelation(children, parents, relation) {
632
- const parentsById = new Map;
633
- for (const parent of parents) {
634
- parentsById.set(parent[relation.ownerKey], parent);
635
- }
636
- const result = new Map;
637
- for (const child of children) {
638
- const foreignKey = child[relation.foreignKey];
639
- const parent = parentsById.get(foreignKey);
640
- if (parent) {
641
- result.set(foreignKey, parent);
642
- }
643
- }
644
- return result;
645
- }
646
- function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
647
- const relatedById = new Map;
648
- for (const related of relatedRows) {
649
- relatedById.set(related[relation.relatedKey], related);
650
- }
651
- const groups = new Map;
652
- for (const parent of parents) {
653
- groups.set(parent[relation.parentKey], []);
654
- }
655
- for (const pivot of pivotRows) {
656
- const parentId = pivot[relation.foreignPivotKey];
657
- const relatedId = pivot[relation.relatedPivotKey];
658
- const group = groups.get(parentId);
659
- const related = relatedById.get(relatedId);
660
- if (!group || !related) {
661
- continue;
662
- }
663
- group.push(related);
664
- }
665
- return groups;
666
- }
667
- function morphMany(definition) {
668
- return {
669
- type: "morphMany",
670
- ...definition
671
- };
672
- }
673
- function morphOne(definition) {
674
- return {
675
- type: "morphOne",
676
- ...definition
677
- };
678
- }
679
- function morphTo(definition) {
680
- return {
681
- type: "morphTo",
682
- ...definition
683
- };
684
- }
685
- function indexMorphManyRelation(parents, children, relation) {
686
- const groups = new Map;
687
- for (const parent of parents) {
688
- groups.set(parent[relation.localKey], []);
689
- }
690
- for (const child of children) {
691
- if (child[relation.morphTypeKey] !== relation.morphType) {
692
- continue;
693
- }
694
- const key = child[relation.morphIdKey];
695
- const group = groups.get(key);
696
- if (!group) {
697
- continue;
698
- }
699
- group.push(child);
700
- }
701
- return groups;
702
- }
703
- function indexMorphOneRelation(parents, children, relation) {
704
- const grouped = indexMorphManyRelation(parents, children, relation);
705
- const result = new Map;
706
- for (const parent of parents) {
707
- const matches = grouped.get(parent[relation.localKey]) ?? [];
708
- result.set(parent[relation.localKey], matches[0]);
709
- }
710
- return result;
711
- }
712
- function indexMorphToRelation(children, parentsByType, relation) {
713
- const result = new Map;
714
- for (const child of children) {
715
- const morphType = String(child[relation.morphTypeKey]);
716
- const parents = parentsByType.get(morphType);
717
- if (!parents) {
718
- continue;
719
- }
720
- const parent = parents.get(child[relation.morphIdKey]);
721
- if (parent) {
722
- result.set(child[relation.morphIdKey], parent);
723
- }
724
- }
725
- return result;
726
- }
727
-
728
- // ../../src/core/database/boundConnection.ts
729
- var boundConnectionHolder = {
730
- connection: null
731
- };
732
- function bindDatabaseConnection(connection) {
733
- boundConnectionHolder.connection = connection;
734
- }
735
- function getBoundDatabaseConnection() {
736
- return boundConnectionHolder.connection;
737
- }
738
- function resetBoundDatabaseConnection() {
739
- boundConnectionHolder.connection = null;
740
- }
741
-
742
- // ../../src/core/runtime/asyncContextStore.ts
743
- import { AsyncLocalStorage } from "async_hooks";
744
- function createAsyncContextStore(key) {
745
- const symbol = Symbol.for(key);
746
- const globalRecord = globalThis;
747
- const existing = globalRecord[symbol];
748
- if (existing) {
749
- return existing;
750
- }
751
- const store = new AsyncLocalStorage;
752
- globalRecord[symbol] = store;
753
- return store;
754
- }
755
-
756
- // ../../src/core/database/connectionContext.ts
757
- var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
758
- function runWithDatabaseConnection(connection, callback) {
759
- return activeConnection.run(connection, callback);
760
- }
761
- function getActiveDatabaseConnection(fallback) {
762
- return activeConnection.getStore() ?? fallback;
763
- }
764
- function hasActiveDatabaseConnection() {
765
- return activeConnection.getStore() !== undefined;
766
- }
767
-
768
- // ../../src/core/database/queryProxy.ts
769
- var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
770
- function createDatabaseQueryProxy(pool) {
771
- function resolveDatabase() {
772
- return getActiveDatabaseConnection(pool);
773
- }
774
- function resolveDatabaseForProperty(property) {
775
- if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
776
- return pool;
777
- }
778
- return resolveDatabase();
779
- }
780
- return new Proxy(function database() {}, {
781
- apply(_target, _thisArg, args) {
782
- return resolveDatabase()(...args);
783
- },
784
- get(_target, property) {
785
- const connection = resolveDatabaseForProperty(property);
786
- const value = connection[property];
787
- return typeof value === "function" ? value.bind(connection) : value;
788
- }
789
- });
790
- }
791
-
792
- // ../../src/core/database/defaultConnection.ts
793
- var defaultPool = {
794
- connection: null
795
- };
796
- var defaultQuery = {
797
- connection: null
798
- };
799
- function registerDefaultDatabasePool(connection) {
800
- defaultPool.connection = connection;
801
- defaultQuery.connection = createDatabaseQueryProxy(connection);
802
- }
803
- function getDefaultDatabasePool() {
804
- if (!defaultPool.connection) {
805
- throw new Error("Default database pool is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
806
- }
807
- return defaultPool.connection;
808
- }
809
- function getDefaultDatabaseQuery() {
810
- if (!defaultQuery.connection) {
811
- throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
812
- }
813
- return defaultQuery.connection;
814
- }
815
-
816
- // ../../src/core/database/repositoryConnection.ts
817
- function resolveRepositoryConnection() {
818
- return getBoundDatabaseConnection() ?? getDefaultDatabaseQuery();
819
- }
820
- var repositoryConnection = new Proxy(function repositoryConnection2() {}, {
821
- apply(_target, _thisArg, args) {
822
- return resolveRepositoryConnection()(...args);
823
- },
824
- get(_target, property) {
825
- const connection = resolveRepositoryConnection();
826
- const value = connection[property];
827
- return typeof value === "function" ? value.bind(connection) : value;
828
- }
829
- });
830
-
831
- // ../../src/core/database/whereBuilder.ts
832
- class WhereBuilder {
833
- nodes = [];
834
- where(where) {
835
- this.nodes.push({ kind: "and", where });
836
- return this;
837
- }
838
- orWhere(where) {
839
- this.nodes.push({ kind: "or", where });
840
- return this;
841
- }
842
- whereGroup(fn) {
843
- const nested = new WhereBuilder;
844
- fn(nested);
845
- if (nested.nodes.length > 0) {
846
- this.nodes.push({ kind: "and", group: nested.nodes });
847
- }
848
- return this;
849
- }
850
- orWhereGroup(fn) {
851
- const nested = new WhereBuilder;
852
- fn(nested);
853
- if (nested.nodes.length > 0) {
854
- this.nodes.push({ kind: "or", group: nested.nodes });
855
- }
856
- return this;
857
- }
858
- }
859
-
860
- // ../../src/core/database/repositoryQuery.ts
861
- class RepositoryQuery {
862
- repository;
863
- whereClause;
864
- queryOptions;
865
- eagerLoads = [];
866
- whereNodes = [];
867
- constructor(repository, whereClause = {}, queryOptions = {}) {
868
- this.repository = repository;
869
- this.whereClause = whereClause;
870
- this.queryOptions = queryOptions;
871
- }
872
- where(input) {
873
- if (typeof input === "function") {
874
- const builder = new WhereBuilder;
875
- input(builder);
876
- this.whereNodes.push(...builder.nodes);
877
- return this;
878
- }
879
- this.whereClause = { ...this.whereClause, ...input };
880
- return this;
881
- }
882
- orWhere(input) {
883
- if (typeof input === "function") {
884
- const builder = new WhereBuilder;
885
- input(builder);
886
- if (builder.nodes.length > 0) {
887
- this.whereNodes.push({ kind: "or", group: builder.nodes });
888
- }
889
- return this;
890
- }
891
- this.whereNodes.push({ kind: "or", where: input });
892
- return this;
893
- }
894
- orderBy(orderBy) {
895
- this.queryOptions = { ...this.queryOptions, orderBy };
896
- return this;
897
- }
898
- limit(limit) {
899
- this.queryOptions = { ...this.queryOptions, limit };
900
- return this;
901
- }
902
- offset(offset) {
903
- this.queryOptions = { ...this.queryOptions, offset };
904
- return this;
905
- }
906
- join(left, right) {
907
- return this.addJoin("inner", left, right);
908
- }
909
- leftJoin(left, right) {
910
- return this.addJoin("left", left, right);
911
- }
912
- groupBy(groupBy) {
913
- this.queryOptions = { ...this.queryOptions, groupBy };
914
- return this;
915
- }
916
- having(having) {
917
- this.queryOptions = { ...this.queryOptions, having };
918
- return this;
919
- }
920
- withHasMany(as, relation, childRepository, options = {}) {
921
- this.eagerLoads.push({
922
- kind: "hasMany",
923
- as,
924
- relation,
925
- repository: childRepository,
926
- options
927
- });
928
- return this;
929
- }
930
- withBelongsTo(as, relation, parentRepository, options = {}) {
931
- this.eagerLoads.push({
932
- kind: "belongsTo",
933
- as,
934
- relation,
935
- repository: parentRepository,
936
- options
937
- });
938
- return this;
939
- }
940
- withMorphMany(as, relation, childRepository, options = {}) {
941
- this.eagerLoads.push({
942
- kind: "morphMany",
943
- as,
944
- relation,
945
- repository: childRepository,
946
- options
947
- });
948
- return this;
949
- }
950
- withMorphOne(as, relation, childRepository, options = {}) {
951
- this.eagerLoads.push({
952
- kind: "morphOne",
953
- as,
954
- relation,
955
- repository: childRepository,
956
- options
957
- });
958
- return this;
959
- }
960
- withMorphTo(as, relation, repositoriesByType, options = {}) {
961
- this.eagerLoads.push({
962
- kind: "morphTo",
963
- as,
964
- relation,
965
- repository: this.repository,
966
- morphRepositories: repositoriesByType,
967
- options
968
- });
969
- return this;
970
- }
971
- async get() {
972
- const rows = await this.repository.findAll(this.buildOptions());
973
- return await this.attach(rows);
974
- }
975
- async first() {
976
- const rows = await this.get();
977
- return rows[0] ?? null;
978
- }
979
- async paginate(options) {
980
- return await this.repository.paginate({
981
- ...this.buildOptions(),
982
- page: options.page,
983
- perPage: options.perPage
984
- });
985
- }
986
- buildOptions() {
987
- return {
988
- ...this.queryOptions,
989
- where: this.whereClause,
990
- whereNodes: this.whereNodes
991
- };
992
- }
993
- addJoin(type, left, right) {
994
- const leftRef = parseQualifiedColumn(left);
995
- const rightRef = parseQualifiedColumn(right);
996
- const table = type === "inner" ? rightRef.table : rightRef.table;
997
- const joins = this.queryOptions.joins ?? [];
998
- const existing = joins.find((join) => join.table === table && join.type === type);
999
- if (existing) {
1000
- existing.on.push({ left: leftRef, right: rightRef });
1001
- return this;
1002
- }
1003
- this.queryOptions = {
1004
- ...this.queryOptions,
1005
- joins: [
1006
- ...joins,
1007
- {
1008
- type,
1009
- table,
1010
- on: [{ left: leftRef, right: rightRef }]
1011
- }
1012
- ]
1013
- };
1014
- return this;
1015
- }
1016
- async attach(rows) {
1017
- if (rows.length === 0 || this.eagerLoads.length === 0) {
1018
- return rows.map((row) => ({ ...row }));
1019
- }
1020
- let result = rows.map((row) => ({ ...row }));
1021
- for (const load of this.eagerLoads) {
1022
- if (load.kind === "hasMany") {
1023
- const relation2 = load.relation;
1024
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation2, load.options);
1025
- result = result.map((row) => ({
1026
- ...row,
1027
- [load.as]: grouped2.get(row[relation2.localKey]) ?? []
1028
- }));
1029
- continue;
1030
- }
1031
- if (load.kind === "morphMany") {
1032
- const relation2 = load.relation;
1033
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
1034
- result = result.map((row) => ({
1035
- ...row,
1036
- [load.as]: grouped2.get(row[relation2.localKey]) ?? []
1037
- }));
1038
- continue;
1039
- }
1040
- if (load.kind === "morphOne") {
1041
- const relation2 = load.relation;
1042
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
1043
- result = result.map((row) => ({
1044
- ...row,
1045
- [load.as]: grouped2.get(row[relation2.localKey])
1046
- }));
1047
- continue;
1048
- }
1049
- if (load.kind === "morphTo") {
1050
- const relation2 = load.relation;
1051
- const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
1052
- result = result.map((row) => ({
1053
- ...row,
1054
- [load.as]: grouped2.get(row[relation2.morphIdKey])
1055
- }));
1056
- continue;
1057
- }
1058
- const relation = load.relation;
1059
- const grouped = await this.repository.loadBelongsToForParents(rows, relation, load.repository, load.options);
1060
- result = result.map((row) => ({
1061
- ...row,
1062
- [load.as]: grouped.get(row[relation.foreignKey])
1063
- }));
1064
- }
1065
- return result;
1066
- }
1067
- }
1068
-
1069
- // ../../src/core/database/baseRepository.ts
1070
- class BaseRepository {
1071
- table;
1072
- connection;
1073
- constructor(table, connection = repositoryConnection) {
1074
- this.table = table;
1075
- this.connection = connection;
1076
- }
1077
- async findAll(options = {}) {
1078
- return await withDatabaseErrorHandling(async () => {
1079
- const { whereNodes, ...queryOptions } = options;
1080
- const { text, params } = buildSelectQuery(this.table, queryOptions, whereNodes ?? []);
1081
- return await this.connection.unsafe(text, params);
1082
- });
1083
- }
1084
- async paginate(options) {
1085
- const { whereNodes, where = {}, page, perPage, ...queryOptions } = options;
1086
- const total = await this.countWhere(where, {
1087
- withTrashed: options.withTrashed,
1088
- onlyTrashed: options.onlyTrashed,
1089
- joins: options.joins,
1090
- groupBy: options.groupBy
1091
- }, whereNodes);
1092
- const offset = (page - 1) * perPage;
1093
- const data = await this.findAll({
1094
- ...queryOptions,
1095
- where,
1096
- whereNodes,
1097
- limit: perPage,
1098
- offset
1099
- });
1100
- return {
1101
- data,
1102
- meta: buildPaginationMeta({ page, perPage, total })
1103
- };
1104
- }
1105
- async chunk(count, callback, options = {}) {
1106
- if (!Number.isInteger(count) || count <= 0) {
1107
- throw new Error("Chunk size must be a positive integer.");
1108
- }
1109
- let offset = 0;
1110
- while (true) {
1111
- const rows = await this.findAll({
1112
- ...options,
1113
- limit: count,
1114
- offset
1115
- });
1116
- if (rows.length === 0) {
1117
- return;
1118
- }
1119
- const shouldContinue = await callback(rows);
1120
- if (shouldContinue === false || rows.length < count) {
1121
- return;
1122
- }
1123
- offset += count;
1124
- }
1125
- }
1126
- async cursorPaginate(options) {
1127
- const {
1128
- perPage,
1129
- cursor,
1130
- cursorColumn = this.table.primaryKey,
1131
- direction = "asc",
1132
- where = {},
1133
- whereNodes,
1134
- ...queryOptions
1135
- } = options;
1136
- if (!Number.isInteger(perPage) || perPage <= 0) {
1137
- throw new Error("Cursor page size must be a positive integer.");
1138
- }
1139
- const cursorWhere = { ...where };
1140
- if (cursor !== undefined) {
1141
- cursorWhere[cursorColumn] = direction === "asc" ? { gt: cursor } : { lt: cursor };
1142
- }
1143
- const rows = await this.findAll({
1144
- ...queryOptions,
1145
- where: cursorWhere,
1146
- whereNodes,
1147
- orderBy: { [cursorColumn]: direction },
1148
- limit: perPage + 1
1149
- });
1150
- const hasMore = rows.length > perPage;
1151
- const data = hasMore ? rows.slice(0, perPage) : rows;
1152
- const nextCursor = hasMore ? data[data.length - 1]?.[cursorColumn] ?? null : null;
1153
- const prevCursor = cursor ?? null;
1154
- return {
1155
- data,
1156
- meta: {
1157
- per_page: perPage,
1158
- next_cursor: nextCursor,
1159
- prev_cursor: prevCursor,
1160
- has_more: hasMore
1161
- }
1162
- };
1163
- }
1164
- async findById(id) {
1165
- return await this.firstOrNull({
1166
- [this.table.primaryKey]: id
1167
- });
1168
- }
1169
- async findByIdOrThrow(id, errorFactory) {
1170
- const record = await this.findById(id);
1171
- if (record) {
1172
- return record;
1173
- }
1174
- throw errorFactory?.(id) ?? new Error(`${this.table.name} ${String(id)} was not found.`);
1175
- }
1176
- async findByIds(ids) {
1177
- const uniqueIds = [...new Set(ids)];
1178
- if (uniqueIds.length === 0) {
1179
- return [];
1180
- }
1181
- return await this.findWhere({
1182
- [this.table.primaryKey]: uniqueIds
1183
- });
1184
- }
1185
- async firstOrNull(where, options = {}) {
1186
- const [record] = await this.findAll({ ...options, where, limit: 1 });
1187
- return record ?? null;
1188
- }
1189
- async create(values) {
1190
- return await withDatabaseErrorHandling(async () => {
1191
- const { text, params } = buildInsertQuery(this.table, values);
1192
- const [record] = await this.connection.unsafe(text, params);
1193
- if (!record) {
1194
- throw new Error(`Insert into ${this.table.name} did not return a record.`);
1195
- }
1196
- const entity = record;
1197
- await eventBus.dispatch(modelEventName(this.table.name, "created"), entity);
1198
- return entity;
1199
- });
1200
- }
1201
- async updateById(id, changes) {
1202
- return await withDatabaseErrorHandling(async () => {
1203
- const { text, params } = buildUpdateQuery(this.table, id, changes);
1204
- const [record] = await this.connection.unsafe(text, params);
1205
- const entity = record ?? null;
1206
- if (entity) {
1207
- await eventBus.dispatch(modelEventName(this.table.name, "updated"), entity);
1208
- }
1209
- return entity;
1210
- });
1211
- }
1212
- async updateByIdOrThrow(id, changes, errorFactory) {
1213
- const record = await this.updateById(id, changes);
1214
- if (record) {
1215
- return record;
1216
- }
1217
- throw errorFactory?.(id) ?? new Error(`${this.table.name} ${String(id)} was not found.`);
1218
- }
1219
- async deleteById(id) {
1220
- if (resolveSoftDeleteColumn(this.table)) {
1221
- return await this.softDeleteById(id);
1222
- }
1223
- return await this.forceDeleteById(id);
1224
- }
1225
- async softDeleteById(id) {
1226
- return await withDatabaseErrorHandling(async () => {
1227
- const { text, params } = buildSoftDeleteByIdQuery(this.table, id, new Date);
1228
- const [record] = await this.connection.unsafe(text, params);
1229
- if (!record) {
1230
- return false;
1231
- }
1232
- await eventBus.dispatch(modelEventName(this.table.name, "deleted"), record);
1233
- return true;
1234
- });
1235
- }
1236
- async forceDeleteById(id) {
1237
- return await withDatabaseErrorHandling(async () => {
1238
- const { text, params } = buildDeleteByIdQuery(this.table, id);
1239
- const [row] = await this.connection.unsafe(text, params);
1240
- if (!row) {
1241
- return false;
1242
- }
1243
- await eventBus.dispatch(modelEventName(this.table.name, "force-deleted"), {
1244
- id
1245
- });
1246
- return true;
1247
- });
1248
- }
1249
- async restoreById(id) {
1250
- return await withDatabaseErrorHandling(async () => {
1251
- const { text, params } = buildRestoreByIdQuery(this.table, id);
1252
- const [record] = await this.connection.unsafe(text, params);
1253
- if (!record) {
1254
- return null;
1255
- }
1256
- const entity = record;
1257
- await eventBus.dispatch(modelEventName(this.table.name, "restored"), entity);
1258
- return entity;
1259
- });
1260
- }
1261
- withConnection(connection) {
1262
- const clone = Object.create(Object.getPrototypeOf(this));
1263
- Object.assign(clone, this);
1264
- clone.connection = connection;
1265
- return clone;
1266
- }
1267
- getConnection() {
1268
- return this.connection;
1269
- }
1270
- getTable() {
1271
- return this.table;
1272
- }
1273
- query(where = {}) {
1274
- return new RepositoryQuery(this, where);
1275
- }
1276
- async findWhere(where, options = {}) {
1277
- return await this.findAll({ ...options, where });
1278
- }
1279
- async countWhere(where = {}, options = {}, whereNodes = []) {
1280
- const { text, params } = buildCountQuery(this.table, where, options, whereNodes);
1281
- const [row] = await this.connection.unsafe(text, params);
1282
- return Number(row?.count ?? 0);
1283
- }
1284
- async averageColumn(column, where = {}) {
1285
- const qualifiedColumn = qualifyColumn(this.table.name, column);
1286
- return await this.averageExpression(`AVG(${qualifiedColumn})`, "value", where);
1287
- }
1288
- async averageExpression(expression, alias, where = {}) {
1289
- const { text, params } = buildProjectionQuery(this.table, expression, alias, { where });
1290
- const [row] = await this.connection.unsafe(text, params);
1291
- return Math.round(Number(row?.[alias] ?? 0));
1292
- }
1293
- async pluckNumberValues(expression, alias, options = {}) {
1294
- const { text, params } = buildProjectionQuery(this.table, expression, alias, options);
1295
- const rows = await this.connection.unsafe(text, params);
1296
- return rows.flatMap((row) => {
1297
- const value = row[alias];
1298
- return value === null || value === undefined ? [] : [Number(value)];
1299
- });
1300
- }
1301
- async countGroupedBy(column, where = {}) {
1302
- const { text, params } = buildGroupedCountQuery(this.table, column, where);
1303
- const rows = await this.connection.unsafe(text, params);
1304
- return rows.map(({ value, count }) => ({
1305
- value,
1306
- count: Number(count)
1307
- }));
1308
- }
1309
- async findByHasManyRelation(relation, parentId, options = {}) {
1310
- return await this.findWhere({
1311
- [relation.foreignKey]: parentId
1312
- }, options);
1313
- }
1314
- async loadHasManyForParents(parents, relation, options = {}) {
1315
- if (parents.length === 0) {
1316
- return indexHasManyRelation(parents, [], relation);
1317
- }
1318
- const parentIds = [...new Set(parents.map((parent) => parent[relation.localKey]))];
1319
- const children = await this.findWhere({
1320
- [relation.foreignKey]: parentIds
1321
- }, options);
1322
- return indexHasManyRelation(parents, children, relation);
1323
- }
1324
- async loadBelongsToForParents(children, relation, parentRepository, options = {}) {
1325
- if (children.length === 0) {
1326
- return new Map;
1327
- }
1328
- const ownerIds = [...new Set(children.map((child) => child[relation.foreignKey]))];
1329
- const parents = await parentRepository.withConnection(this.connection).findWhere({
1330
- [relation.ownerKey]: ownerIds
1331
- }, options);
1332
- return indexBelongsToRelation(children, parents, relation);
1333
- }
1334
- async loadMorphManyForParents(parents, relation, options = {}) {
1335
- if (parents.length === 0) {
1336
- return indexMorphManyRelation(parents, [], relation);
1337
- }
1338
- const parentIds = [...new Set(parents.map((parent) => parent[relation.localKey]))];
1339
- const children = await this.findWhere({
1340
- [relation.morphTypeKey]: relation.morphType,
1341
- [relation.morphIdKey]: parentIds
1342
- }, options);
1343
- return indexMorphManyRelation(parents, children, relation);
1344
- }
1345
- async loadMorphOneForParents(parents, relation, options = {}) {
1346
- const grouped = await this.loadMorphManyForParents(parents, relation, options);
1347
- const result = new Map;
1348
- for (const parent of parents) {
1349
- const matches = grouped.get(parent[relation.localKey]) ?? [];
1350
- result.set(parent[relation.localKey], matches[0]);
1351
- }
1352
- return result;
1353
- }
1354
- async loadMorphToForChildren(children, relation, repositoriesByType, options = {}) {
1355
- if (children.length === 0) {
1356
- return new Map;
1357
- }
1358
- const idsByType = new Map;
1359
- for (const child of children) {
1360
- const morphType = String(child[relation.morphTypeKey]);
1361
- const morphId = child[relation.morphIdKey];
1362
- const ids = idsByType.get(morphType) ?? new Set;
1363
- ids.add(morphId);
1364
- idsByType.set(morphType, ids);
1365
- }
1366
- const parentsByType = new Map;
1367
- for (const [morphType, ids] of idsByType) {
1368
- const repository = repositoriesByType.get(morphType);
1369
- if (!repository) {
1370
- continue;
1371
- }
1372
- const ownerKey = repository.getTable().primaryKey;
1373
- const parents = await repository.withConnection(this.connection).findWhere({
1374
- [ownerKey]: [...ids]
1375
- }, options);
1376
- const indexed = new Map;
1377
- for (const parent of parents) {
1378
- indexed.set(parent[ownerKey], parent);
1379
- }
1380
- parentsByType.set(morphType, indexed);
1381
- }
1382
- return indexMorphToRelation(children, parentsByType, relation);
1383
- }
1384
- }
1385
- var baseRepository_default = BaseRepository;
1386
- export {
1387
- BaseRepository
1388
- };
1
+ export * from "../../index.js";