@getstrata/core 0.5.42 → 0.5.43

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.
@@ -0,0 +1,1054 @@
1
+ // @bun
2
+ // ../../src/core/database/schema/columnDefinition.ts
3
+ class ColumnDefinition {
4
+ name;
5
+ kind;
6
+ length;
7
+ isNullable = false;
8
+ isPrimary = false;
9
+ isUnique = false;
10
+ autoIncrement = false;
11
+ defaultValue;
12
+ checkExpression;
13
+ foreignKey;
14
+ constructor(name, kind) {
15
+ this.name = name;
16
+ this.kind = kind;
17
+ }
18
+ nullable() {
19
+ this.isNullable = true;
20
+ return this;
21
+ }
22
+ notNullable() {
23
+ this.isNullable = false;
24
+ return this;
25
+ }
26
+ default(value) {
27
+ if (typeof value === "boolean") {
28
+ this.defaultValue = value ? "TRUE" : "FALSE";
29
+ return this;
30
+ }
31
+ if (typeof value === "number") {
32
+ this.defaultValue = String(value);
33
+ return this;
34
+ }
35
+ this.defaultValue = `'${value.replace(/'/g, "''")}'`;
36
+ return this;
37
+ }
38
+ defaultRaw(expression) {
39
+ this.defaultValue = expression;
40
+ return this;
41
+ }
42
+ unique() {
43
+ this.isUnique = true;
44
+ return this;
45
+ }
46
+ primary() {
47
+ this.isPrimary = true;
48
+ return this;
49
+ }
50
+ check(expression) {
51
+ this.checkExpression = expression;
52
+ return this;
53
+ }
54
+ }
55
+
56
+ class ForeignIdColumnDefinition extends ColumnDefinition {
57
+ constructor(name) {
58
+ super(name, "foreignId");
59
+ this.notNullable();
60
+ }
61
+ references(table, column = "id") {
62
+ this.foreignKey = {
63
+ referencesTable: table,
64
+ referencesColumn: column
65
+ };
66
+ return this;
67
+ }
68
+ constrained(table) {
69
+ const referencesTable = table ?? inferReferencedTable(this.name);
70
+ return this.references(referencesTable);
71
+ }
72
+ cascadeOnDelete() {
73
+ if (!this.foreignKey) {
74
+ throw new Error(`Foreign key is not defined for column ${this.name}`);
75
+ }
76
+ this.foreignKey.onDelete = "cascade";
77
+ return this;
78
+ }
79
+ nullOnDelete() {
80
+ if (!this.foreignKey) {
81
+ throw new Error(`Foreign key is not defined for column ${this.name}`);
82
+ }
83
+ this.foreignKey.onDelete = "set null";
84
+ return this;
85
+ }
86
+ }
87
+ function inferReferencedTable(columnName) {
88
+ if (!columnName.endsWith("_id")) {
89
+ throw new Error(`Cannot infer referenced table from column ${columnName}`);
90
+ }
91
+ return columnName.slice(0, -3);
92
+ }
93
+
94
+ // ../../src/core/database/schema/blueprint.ts
95
+ class Blueprint {
96
+ table;
97
+ action;
98
+ columns = [];
99
+ indexes = [];
100
+ droppedColumns = [];
101
+ droppedIndexes = [];
102
+ constructor(table, action) {
103
+ this.table = table;
104
+ this.action = action;
105
+ }
106
+ id(name = "id") {
107
+ const column = new ColumnDefinition(name, "id");
108
+ column.primary();
109
+ column.autoIncrement = true;
110
+ this.columns.push(column);
111
+ return column;
112
+ }
113
+ string(name, length) {
114
+ const column = new ColumnDefinition(name, "string");
115
+ column.length = length;
116
+ column.notNullable();
117
+ this.columns.push(column);
118
+ return column;
119
+ }
120
+ text(name) {
121
+ const column = new ColumnDefinition(name, "text");
122
+ column.notNullable();
123
+ this.columns.push(column);
124
+ return column;
125
+ }
126
+ boolean(name) {
127
+ const column = new ColumnDefinition(name, "boolean");
128
+ column.notNullable();
129
+ this.columns.push(column);
130
+ return column;
131
+ }
132
+ integer(name) {
133
+ const column = new ColumnDefinition(name, "integer");
134
+ column.notNullable();
135
+ this.columns.push(column);
136
+ return column;
137
+ }
138
+ bigInteger(name) {
139
+ const column = new ColumnDefinition(name, "bigInteger");
140
+ column.notNullable();
141
+ this.columns.push(column);
142
+ return column;
143
+ }
144
+ timestamp(name) {
145
+ const column = new ColumnDefinition(name, "timestamp");
146
+ column.notNullable();
147
+ this.columns.push(column);
148
+ return column;
149
+ }
150
+ json(name) {
151
+ const column = new ColumnDefinition(name, "json");
152
+ column.notNullable();
153
+ this.columns.push(column);
154
+ return column;
155
+ }
156
+ jsonb(name) {
157
+ const column = new ColumnDefinition(name, "jsonb");
158
+ column.notNullable();
159
+ this.columns.push(column);
160
+ return column;
161
+ }
162
+ foreignId(name) {
163
+ const column = new ForeignIdColumnDefinition(name);
164
+ this.columns.push(column);
165
+ return column;
166
+ }
167
+ timestamps() {
168
+ this.timestamp("created_at").defaultRaw("NOW()");
169
+ this.timestamp("updated_at").defaultRaw("NOW()");
170
+ }
171
+ softDeletes() {
172
+ this.timestamp("deleted_at").nullable();
173
+ }
174
+ dropColumn(name) {
175
+ this.droppedColumns.push(name);
176
+ }
177
+ dropSoftDeletes() {
178
+ this.dropColumn("deleted_at");
179
+ this.dropIndex(`idx_${this.table}_deleted_at`);
180
+ }
181
+ dropIndex(name) {
182
+ this.droppedIndexes.push(name);
183
+ }
184
+ unique(columns, name) {
185
+ this.indexes.push({
186
+ name,
187
+ columns: Array.isArray(columns) ? columns : [columns],
188
+ kind: "unique"
189
+ });
190
+ }
191
+ index(columns, options = {}) {
192
+ this.indexes.push({
193
+ name: options.name,
194
+ columns: Array.isArray(columns) ? columns : [columns],
195
+ kind: "index",
196
+ order: options.order
197
+ });
198
+ }
199
+ partialIndex(columns, where, nameOrOptions) {
200
+ const options = typeof nameOrOptions === "string" ? { name: nameOrOptions } : nameOrOptions ?? {};
201
+ this.indexes.push({
202
+ name: options.name,
203
+ columns: Array.isArray(columns) ? columns : [columns],
204
+ kind: options.unique ? "uniquePartial" : "partial",
205
+ where
206
+ });
207
+ }
208
+ fullText(columns, name) {
209
+ this.indexes.push({
210
+ name,
211
+ columns: Array.isArray(columns) ? columns : [columns],
212
+ kind: "fullText"
213
+ });
214
+ }
215
+ ginIndex(column, name) {
216
+ this.indexes.push({
217
+ name,
218
+ columns: [column],
219
+ kind: "gin"
220
+ });
221
+ }
222
+ }
223
+ // ../../src/core/database/schema/driver.ts
224
+ function normalizeConnectionName(connection) {
225
+ const normalized = connection.trim().toLowerCase();
226
+ if (normalized === "pgsql" || normalized === "postgres" || normalized === "postgresql") {
227
+ return "pgsql";
228
+ }
229
+ if (normalized === "mysql" || normalized === "mariadb") {
230
+ return "mysql";
231
+ }
232
+ if (normalized === "sqlite") {
233
+ return "sqlite";
234
+ }
235
+ throw new Error(`Unsupported DB_CONNECTION: ${connection}`);
236
+ }
237
+ function resolveDriverFromUrl(url) {
238
+ const normalized = url.trim().toLowerCase();
239
+ if (normalized.startsWith("postgres://") || normalized.startsWith("postgresql://") || normalized.startsWith("postgres:")) {
240
+ return "pgsql";
241
+ }
242
+ if (normalized.startsWith("mysql://") || normalized.startsWith("mysql:")) {
243
+ return "mysql";
244
+ }
245
+ if (normalized.startsWith("sqlite:")) {
246
+ return "sqlite";
247
+ }
248
+ return null;
249
+ }
250
+ function resolveDatabaseDriver(options = {}) {
251
+ const connection = options.connection ?? process.env.DB_CONNECTION;
252
+ if (connection) {
253
+ return normalizeConnectionName(connection);
254
+ }
255
+ const url = options.url ?? process.env.DATABASE_URL ?? "";
256
+ const fromUrl = resolveDriverFromUrl(url);
257
+ if (fromUrl) {
258
+ return fromUrl;
259
+ }
260
+ return "pgsql";
261
+ }
262
+ // ../../src/core/database/schema/errors.ts
263
+ class UnsupportedSchemaFeatureError extends Error {
264
+ constructor(feature, driver) {
265
+ super(`${feature} is not supported for the ${driver} driver`);
266
+ this.name = "UnsupportedSchemaFeatureError";
267
+ }
268
+ }
269
+ // ../../src/core/database/query.ts
270
+ function quoteIdentifier(identifier) {
271
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
272
+ throw new Error(`Invalid SQL identifier: ${identifier}`);
273
+ }
274
+ return `"${identifier}"`;
275
+ }
276
+ function qualifyColumn(tableName, column) {
277
+ return `${quoteIdentifier(tableName)}.${quoteIdentifier(column)}`;
278
+ }
279
+ function resolveQualifiedColumn(defaultTable, columnName) {
280
+ if (columnName.includes(".")) {
281
+ const [table, column] = columnName.split(".", 2);
282
+ if (!table || !column) {
283
+ throw new Error(`Invalid qualified column: ${columnName}`);
284
+ }
285
+ return qualifyColumn(table, column);
286
+ }
287
+ return qualifyColumn(defaultTable, columnName);
288
+ }
289
+ function parseQualifiedColumn(reference) {
290
+ const [table, column] = reference.split(".", 2);
291
+ if (!table || !column) {
292
+ throw new Error(`Join columns must be qualified as table.column: ${reference}`);
293
+ }
294
+ return { table, column };
295
+ }
296
+ function normalizeDirection(direction = "ASC") {
297
+ return direction.toUpperCase() === "DESC" ? "DESC" : "ASC";
298
+ }
299
+ function isQueryOperator(value) {
300
+ return value !== null && !Array.isArray(value) && !(value instanceof Date) && typeof value === "object";
301
+ }
302
+ function pushParam(values, value) {
303
+ values.push(value);
304
+ return `$${values.length}`;
305
+ }
306
+ function buildInClause(column, values, params) {
307
+ if (values.length === 0) {
308
+ return "1 = 0";
309
+ }
310
+ const placeholders = values.map((value) => pushParam(params, value)).join(", ");
311
+ return `${column} IN (${placeholders})`;
312
+ }
313
+ function buildOperatorClauses(column, operator, params) {
314
+ const clauses = [];
315
+ if (operator.isNull === true) {
316
+ clauses.push(`${column} IS NULL`);
317
+ }
318
+ if (operator.isNull === false) {
319
+ clauses.push(`${column} IS NOT NULL`);
320
+ }
321
+ if (operator.eq !== undefined) {
322
+ if (operator.eq === null) {
323
+ clauses.push(`${column} IS NULL`);
324
+ } else {
325
+ clauses.push(`${column} = ${pushParam(params, operator.eq)}`);
326
+ }
327
+ }
328
+ if (operator.in !== undefined) {
329
+ clauses.push(buildInClause(column, operator.in, params));
330
+ }
331
+ if (operator.gt !== undefined) {
332
+ clauses.push(`${column} > ${pushParam(params, operator.gt)}`);
333
+ }
334
+ if (operator.gte !== undefined) {
335
+ clauses.push(`${column} >= ${pushParam(params, operator.gte)}`);
336
+ }
337
+ if (operator.lt !== undefined) {
338
+ clauses.push(`${column} < ${pushParam(params, operator.lt)}`);
339
+ }
340
+ if (operator.lte !== undefined) {
341
+ clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
342
+ }
343
+ if (operator.ilike !== undefined) {
344
+ clauses.push(`${column} ILIKE ${pushParam(params, `%${operator.ilike}%`)}`);
345
+ }
346
+ if (operator.tsMatch !== undefined) {
347
+ clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
348
+ }
349
+ return clauses;
350
+ }
351
+ function appendWhereParts(tableName, where, params) {
352
+ const clauses = [];
353
+ for (const [columnName, filterValue] of Object.entries(where)) {
354
+ if (filterValue === undefined) {
355
+ continue;
356
+ }
357
+ const column = resolveQualifiedColumn(tableName, columnName);
358
+ if (Array.isArray(filterValue)) {
359
+ clauses.push(buildInClause(column, filterValue, params));
360
+ continue;
361
+ }
362
+ if (isQueryOperator(filterValue)) {
363
+ clauses.push(...buildOperatorClauses(column, filterValue, params));
364
+ continue;
365
+ }
366
+ if (filterValue === null) {
367
+ clauses.push(`${column} IS NULL`);
368
+ continue;
369
+ }
370
+ clauses.push(`${column} = ${pushParam(params, filterValue)}`);
371
+ }
372
+ return clauses.join(" AND ");
373
+ }
374
+ function buildWhereClause(tableName, where = {}) {
375
+ const params = [];
376
+ const body = appendWhereParts(tableName, where, params);
377
+ return {
378
+ clause: body.length > 0 ? ` WHERE ${body}` : "",
379
+ params
380
+ };
381
+ }
382
+ function buildWhereNodeClause(tableName, node, params) {
383
+ if ("where" in node) {
384
+ return appendWhereParts(tableName, node.where, params);
385
+ }
386
+ const grouped = buildWhereGroupClause(tableName, node.group, params);
387
+ if (!grouped) {
388
+ return "";
389
+ }
390
+ return grouped.includes(" OR ") ? `(${grouped})` : grouped;
391
+ }
392
+ function buildWhereGroupClause(tableName, nodes, params) {
393
+ let result = "";
394
+ for (const node of nodes) {
395
+ const part = buildWhereNodeClause(tableName, node, params);
396
+ if (!part) {
397
+ continue;
398
+ }
399
+ if (!result) {
400
+ result = part;
401
+ continue;
402
+ }
403
+ result = node.kind === "or" ? `${result} OR ${part}` : `${result} AND ${part}`;
404
+ }
405
+ if (!result) {
406
+ return "";
407
+ }
408
+ return result;
409
+ }
410
+ function buildAdvancedWhereClause(tableName, where = {}, whereNodes = [], params = []) {
411
+ const nodes = [];
412
+ if (Object.keys(where).length > 0) {
413
+ nodes.push({ kind: "and", where });
414
+ }
415
+ nodes.push(...whereNodes);
416
+ const combined = buildWhereGroupClause(tableName, nodes, params);
417
+ return {
418
+ clause: combined ? ` WHERE ${combined}` : "",
419
+ params
420
+ };
421
+ }
422
+ function resolveSoftDeleteColumn(table) {
423
+ if (!table.softDeletes) {
424
+ return null;
425
+ }
426
+ if (table.softDeletes === true) {
427
+ return "deleted_at";
428
+ }
429
+ return table.softDeletes.column ?? "deleted_at";
430
+ }
431
+ function appendSoftDeleteScope(table, options, clauses) {
432
+ const column = resolveSoftDeleteColumn(table);
433
+ if (!column) {
434
+ return;
435
+ }
436
+ const qualifiedColumn = qualifyColumn(table.name, column);
437
+ if (options.onlyTrashed) {
438
+ clauses.push(`${qualifiedColumn} IS NOT NULL`);
439
+ return;
440
+ }
441
+ if (!options.withTrashed) {
442
+ clauses.push(`${qualifiedColumn} IS NULL`);
443
+ }
444
+ }
445
+ function buildQueryWhereClause(table, options = {}, whereNodes = [], params = []) {
446
+ const { clause, params: whereParams } = buildAdvancedWhereClause(table.name, options.where ?? {}, whereNodes, params);
447
+ const softDeleteClauses = [];
448
+ appendSoftDeleteScope(table, options, softDeleteClauses);
449
+ if (softDeleteClauses.length === 0) {
450
+ return { clause, params: whereParams };
451
+ }
452
+ const base = clause.replace(/^ WHERE /, "");
453
+ const scope = softDeleteClauses.join(" AND ");
454
+ return {
455
+ clause: base ? ` WHERE (${base}) AND ${scope}` : ` WHERE ${scope}`,
456
+ params: whereParams
457
+ };
458
+ }
459
+ function isQueryOrder(value) {
460
+ return "column" in value;
461
+ }
462
+ function normalizeOrderBy(orderBy) {
463
+ if (!orderBy) {
464
+ return [];
465
+ }
466
+ if (Array.isArray(orderBy)) {
467
+ return orderBy;
468
+ }
469
+ if (isQueryOrder(orderBy)) {
470
+ return [orderBy];
471
+ }
472
+ return Object.entries(orderBy).map(([column, direction]) => ({
473
+ column,
474
+ direction
475
+ }));
476
+ }
477
+ function buildOrderByClause(tableName, orderBy) {
478
+ const parts = normalizeOrderBy(orderBy).map(({ column, direction }) => {
479
+ return `${resolveQualifiedColumn(tableName, column)} ${normalizeDirection(direction)}`;
480
+ });
481
+ return parts.length > 0 ? ` ORDER BY ${parts.join(", ")}` : "";
482
+ }
483
+ function buildGroupByClause(tableName, groupBy) {
484
+ if (!groupBy) {
485
+ return "";
486
+ }
487
+ const columns = (Array.isArray(groupBy) ? groupBy : [groupBy]).map((column) => String(column));
488
+ const parts = columns.map((column) => resolveQualifiedColumn(tableName, column));
489
+ return parts.length > 0 ? ` GROUP BY ${parts.join(", ")}` : "";
490
+ }
491
+ function buildHavingClause(tableName, having, params) {
492
+ if (!having) {
493
+ return "";
494
+ }
495
+ const body = appendWhereParts(tableName, having, params);
496
+ return body.length > 0 ? ` HAVING ${body}` : "";
497
+ }
498
+ function buildJoinClause(joins = []) {
499
+ return joins.map((join) => {
500
+ const joinType = join.type === "left" ? "LEFT JOIN" : "INNER JOIN";
501
+ const onClause = join.on.map(({ left, right }) => `${qualifyColumn(left.table, left.column)} = ${qualifyColumn(right.table, right.column)}`).join(" AND ");
502
+ return ` ${joinType} ${quoteIdentifier(join.table)} ON ${onClause}`;
503
+ }).join("");
504
+ }
505
+ function buildLimitClause(limit) {
506
+ if (limit === undefined) {
507
+ return "";
508
+ }
509
+ if (!Number.isInteger(limit) || limit <= 0) {
510
+ throw new Error("Query limit must be a positive integer.");
511
+ }
512
+ return ` LIMIT ${limit}`;
513
+ }
514
+ function buildOffsetClause(offset) {
515
+ if (offset === undefined) {
516
+ return "";
517
+ }
518
+ if (!Number.isInteger(offset) || offset < 0) {
519
+ throw new Error("Query offset must be a non-negative integer.");
520
+ }
521
+ return ` OFFSET ${offset}`;
522
+ }
523
+ function buildReturningColumns(table) {
524
+ return table.columns.map((column) => qualifyColumn(table.name, column)).join(", ");
525
+ }
526
+ function buildSelectList(table, select, params = []) {
527
+ if (!select || select.length === 0) {
528
+ return buildReturningColumns(table);
529
+ }
530
+ return select.map((item) => {
531
+ if (item.kind === "column") {
532
+ const column2 = qualifyColumn(item.table, item.column);
533
+ return item.as ? `${column2} AS ${quoteIdentifier(item.as)}` : column2;
534
+ }
535
+ if (item.kind === "literalText") {
536
+ return `${pushParam(params, item.value)}::text AS ${quoteIdentifier(item.as)}`;
537
+ }
538
+ const column = qualifyColumn(item.table, item.column);
539
+ const placeholder = pushParam(params, item.query);
540
+ return `ts_rank(${column}, plainto_tsquery('english', ${placeholder})) AS ${quoteIdentifier(item.as)}`;
541
+ }).join(", ");
542
+ }
543
+ function getDefinedColumnEntries(table, values, options = {}) {
544
+ const record = values;
545
+ const excluded = new Set(options.exclude ?? []);
546
+ return table.columns.flatMap((column) => {
547
+ if (excluded.has(column) || !Object.hasOwn(record, column)) {
548
+ return [];
549
+ }
550
+ const value = record[column];
551
+ if (value === undefined) {
552
+ return [];
553
+ }
554
+ return [[column, value]];
555
+ });
556
+ }
557
+ function buildSelectQuery(table, options = {}, whereNodes = []) {
558
+ const params = [];
559
+ const columns = buildSelectList(table, options.select, params);
560
+ const { clause } = buildQueryWhereClause(table, options, whereNodes, params);
561
+ const joins = buildJoinClause(options.joins);
562
+ const groupBy = buildGroupByClause(table.name, options.groupBy);
563
+ const havingClause = buildHavingClause(table.name, options.having, params);
564
+ const orderBy = buildOrderByClause(table.name, options.orderBy ?? table.defaultOrderBy);
565
+ const limit = buildLimitClause(options.limit);
566
+ const offset = buildOffsetClause(options.offset);
567
+ return {
568
+ text: `SELECT ${columns} FROM ${quoteIdentifier(table.name)}${joins}${clause}${groupBy}${havingClause}${orderBy}${limit}${offset}`,
569
+ params
570
+ };
571
+ }
572
+ function buildCountQuery(table, where = {}, options = {}, whereNodes = []) {
573
+ const params = [];
574
+ const { clause, params: whereParams } = buildQueryWhereClause(table, {
575
+ where,
576
+ withTrashed: options.withTrashed,
577
+ onlyTrashed: options.onlyTrashed
578
+ }, whereNodes);
579
+ params.push(...whereParams);
580
+ const joins = buildJoinClause(options.joins);
581
+ const groupBy = buildGroupByClause(table.name, options.groupBy);
582
+ return {
583
+ text: `SELECT COUNT(*) AS count FROM ${quoteIdentifier(table.name)}${joins}${clause}${groupBy}`,
584
+ params
585
+ };
586
+ }
587
+ function buildProjectionQuery(table, expression, alias, options = {}, whereNodes = []) {
588
+ assertSafeProjectionExpression(expression);
589
+ const params = [];
590
+ const { clause, params: whereParams } = buildQueryWhereClause(table, options, whereNodes);
591
+ params.push(...whereParams);
592
+ const joins = buildJoinClause(options.joins);
593
+ const groupBy = buildGroupByClause(table.name, options.groupBy);
594
+ const orderBy = buildOrderByClause(table.name, options.orderBy);
595
+ const limit = buildLimitClause(options.limit);
596
+ return {
597
+ text: `SELECT ${expression} AS ${quoteIdentifier(alias)} FROM ${quoteIdentifier(table.name)}${joins}${clause}${groupBy}${orderBy}${limit}`,
598
+ params
599
+ };
600
+ }
601
+ var SAFE_PROJECTION_EXPRESSION = /^(COUNT\(\*\)|(?:"[\w_]+"(?:\."[\w_]+")?)|(?:AVG|SUM|MIN|MAX)\((?:"[\w_]+"(?:\."[\w_]+")?)\))$/;
602
+ function assertSafeProjectionExpression(expression) {
603
+ if (!SAFE_PROJECTION_EXPRESSION.test(expression.trim())) {
604
+ throw new Error(`Unsafe projection expression: ${expression}`);
605
+ }
606
+ }
607
+ function buildGroupedCountQuery(table, column, where = {}, options = {}) {
608
+ const qualifiedColumn = qualifyColumn(table.name, column);
609
+ const { clause, params } = buildQueryWhereClause(table, {
610
+ where,
611
+ ...options
612
+ });
613
+ return {
614
+ text: `SELECT ${qualifiedColumn} AS ${quoteIdentifier("value")}, COUNT(*) AS ${quoteIdentifier("count")} FROM ${quoteIdentifier(table.name)}${clause} GROUP BY ${qualifiedColumn} ORDER BY ${qualifiedColumn} ASC`,
615
+ params
616
+ };
617
+ }
618
+ function buildInsertQuery(table, values) {
619
+ const entries = getDefinedColumnEntries(table, values);
620
+ if (entries.length === 0) {
621
+ throw new Error(`Cannot insert into ${table.name} without any column values.`);
622
+ }
623
+ const params = [];
624
+ const columns = entries.map(([column]) => quoteIdentifier(column)).join(", ");
625
+ const placeholders = entries.map(([, value]) => pushParam(params, value)).join(", ");
626
+ const returningColumns = buildReturningColumns(table);
627
+ return {
628
+ text: `INSERT INTO ${quoteIdentifier(table.name)} (${columns}) VALUES (${placeholders}) RETURNING ${returningColumns}`,
629
+ params
630
+ };
631
+ }
632
+ function buildUpdateQuery(table, id, changes) {
633
+ const entries = getDefinedColumnEntries(table, changes, {
634
+ exclude: [table.primaryKey]
635
+ });
636
+ if (entries.length === 0) {
637
+ throw new Error(`Cannot update ${table.name} without any changed column values.`);
638
+ }
639
+ const params = [];
640
+ const setClause = entries.map(([column, value]) => `${quoteIdentifier(column)} = ${pushParam(params, value)}`).join(", ");
641
+ const primaryKeyPlaceholder = pushParam(params, id);
642
+ const returningColumns = buildReturningColumns(table);
643
+ const scopeClauses = [];
644
+ appendSoftDeleteScope(table, {}, scopeClauses);
645
+ const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
646
+ return {
647
+ text: `UPDATE ${quoteIdentifier(table.name)} SET ${setClause} WHERE ${quoteIdentifier(table.primaryKey)} = ${primaryKeyPlaceholder}${scopeSuffix} RETURNING ${returningColumns}`,
648
+ params
649
+ };
650
+ }
651
+ function buildSoftDeleteByIdQuery(table, id, deletedAt) {
652
+ const deletedAtColumn = resolveSoftDeleteColumn(table);
653
+ if (!deletedAtColumn) {
654
+ throw new Error(`Table ${table.name} does not support soft deletes.`);
655
+ }
656
+ const returningColumns = buildReturningColumns(table);
657
+ const scopeClauses = [];
658
+ appendSoftDeleteScope(table, {}, scopeClauses);
659
+ const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
660
+ return {
661
+ text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = $1 WHERE ${quoteIdentifier(table.primaryKey)} = $2${scopeSuffix} RETURNING ${returningColumns}`,
662
+ params: [deletedAt, id]
663
+ };
664
+ }
665
+ function buildRestoreByIdQuery(table, id) {
666
+ const deletedAtColumn = resolveSoftDeleteColumn(table);
667
+ if (!deletedAtColumn) {
668
+ throw new Error(`Table ${table.name} does not support soft deletes.`);
669
+ }
670
+ const returningColumns = buildReturningColumns(table);
671
+ return {
672
+ text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = $1 WHERE ${quoteIdentifier(table.primaryKey)} = $2 AND ${qualifyColumn(table.name, deletedAtColumn)} IS NOT NULL RETURNING ${returningColumns}`,
673
+ params: [null, id]
674
+ };
675
+ }
676
+ function buildDeleteByIdQuery(table, id) {
677
+ return {
678
+ text: `DELETE FROM ${quoteIdentifier(table.name)} WHERE ${quoteIdentifier(table.primaryKey)} = $1 RETURNING ${quoteIdentifier(table.primaryKey)} AS ${quoteIdentifier("deleted_id")}`,
679
+ params: [id]
680
+ };
681
+ }
682
+
683
+ // ../../src/core/database/schema/grammars/grammar.ts
684
+ function compileColumnType(driver, column) {
685
+ switch (column.kind) {
686
+ case "id":
687
+ return compileIdType(driver);
688
+ case "string":
689
+ return compileStringType(driver, column.length);
690
+ case "text":
691
+ return compileTextType(driver);
692
+ case "boolean":
693
+ return compileBooleanType(driver);
694
+ case "integer":
695
+ case "foreignId":
696
+ return compileIntegerType(driver);
697
+ case "bigInteger":
698
+ return compileBigIntegerType(driver);
699
+ case "timestamp":
700
+ return compileTimestampType(driver);
701
+ case "json":
702
+ return compileJsonType(driver);
703
+ case "jsonb":
704
+ return compileJsonbType(driver);
705
+ default:
706
+ throw new Error(`Unsupported column kind: ${column.kind}`);
707
+ }
708
+ }
709
+ function compileIdType(driver) {
710
+ switch (driver) {
711
+ case "pgsql":
712
+ return "SERIAL";
713
+ case "mysql":
714
+ return "BIGINT UNSIGNED";
715
+ case "sqlite":
716
+ return "INTEGER";
717
+ }
718
+ }
719
+ function compileStringType(driver, length) {
720
+ switch (driver) {
721
+ case "pgsql":
722
+ return "TEXT";
723
+ case "mysql":
724
+ return length ? `VARCHAR(${length})` : "VARCHAR(255)";
725
+ case "sqlite":
726
+ return "TEXT";
727
+ }
728
+ }
729
+ function compileTextType(driver) {
730
+ switch (driver) {
731
+ case "pgsql":
732
+ case "sqlite":
733
+ return "TEXT";
734
+ case "mysql":
735
+ return "TEXT";
736
+ }
737
+ }
738
+ function compileBooleanType(driver) {
739
+ switch (driver) {
740
+ case "pgsql":
741
+ return "BOOLEAN";
742
+ case "mysql":
743
+ return "BOOLEAN";
744
+ case "sqlite":
745
+ return "INTEGER";
746
+ }
747
+ }
748
+ function compileIntegerType(driver) {
749
+ switch (driver) {
750
+ case "pgsql":
751
+ return "INTEGER";
752
+ case "mysql":
753
+ return "INT";
754
+ case "sqlite":
755
+ return "INTEGER";
756
+ }
757
+ }
758
+ function compileBigIntegerType(driver) {
759
+ switch (driver) {
760
+ case "pgsql":
761
+ return "BIGINT";
762
+ case "mysql":
763
+ return "BIGINT";
764
+ case "sqlite":
765
+ return "INTEGER";
766
+ }
767
+ }
768
+ function compileTimestampType(driver) {
769
+ switch (driver) {
770
+ case "pgsql":
771
+ return "TIMESTAMPTZ";
772
+ case "mysql":
773
+ return "TIMESTAMP";
774
+ case "sqlite":
775
+ return "TEXT";
776
+ }
777
+ }
778
+ function compileJsonType(driver) {
779
+ switch (driver) {
780
+ case "pgsql":
781
+ return "JSONB";
782
+ case "mysql":
783
+ return "JSON";
784
+ case "sqlite":
785
+ return "TEXT";
786
+ }
787
+ }
788
+ function compileJsonbType(driver) {
789
+ switch (driver) {
790
+ case "pgsql":
791
+ return "JSONB";
792
+ case "mysql":
793
+ return "JSON";
794
+ case "sqlite":
795
+ return "TEXT";
796
+ }
797
+ }
798
+
799
+ // ../../src/core/database/schema/grammars/compileStatements.ts
800
+ function compileCreateTable(driver, blueprint) {
801
+ const table = quoteIdentifier(blueprint.table);
802
+ const parts = blueprint.columns.map((column) => compileColumn(driver, column, "create"));
803
+ for (const index of blueprint.indexes) {
804
+ if (index.kind === "unique" && index.columns.length > 1) {
805
+ const columns = index.columns.map((column) => quoteIdentifier(column)).join(", ");
806
+ parts.push(`UNIQUE (${columns})`);
807
+ }
808
+ }
809
+ const statements = [`CREATE TABLE IF NOT EXISTS ${table} (
810
+ ${parts.join(`,
811
+ `)}
812
+ )`];
813
+ for (const index of blueprint.indexes) {
814
+ if (index.kind === "unique" && index.columns.length === 1) {
815
+ continue;
816
+ }
817
+ if (index.kind === "index") {
818
+ statements.push(compileIndex(driver, blueprint.table, index));
819
+ } else if (index.kind === "partial" || index.kind === "uniquePartial" || index.kind === "gin" || index.kind === "fullText") {
820
+ statements.push(...compileSpecialIndex(driver, blueprint.table, index));
821
+ }
822
+ }
823
+ return statements;
824
+ }
825
+ function compileAlterTable(driver, blueprint) {
826
+ const statements = [];
827
+ const table = quoteIdentifier(blueprint.table);
828
+ for (const column of blueprint.columns) {
829
+ const addPrefix = driver === "pgsql" ? "ADD COLUMN IF NOT EXISTS" : "ADD COLUMN";
830
+ statements.push(`ALTER TABLE ${table}
831
+ ${addPrefix} ${compileColumn(driver, column, "alter")}`);
832
+ }
833
+ for (const columnName of blueprint.droppedColumns) {
834
+ const dropPrefix = driver === "pgsql" ? "DROP COLUMN IF EXISTS" : "DROP COLUMN";
835
+ statements.push(`ALTER TABLE ${table} ${dropPrefix} ${quoteIdentifier(columnName)}`);
836
+ }
837
+ for (const indexName of blueprint.droppedIndexes) {
838
+ statements.push(`DROP INDEX IF EXISTS ${quoteIdentifier(indexName)}`);
839
+ }
840
+ for (const index of blueprint.indexes) {
841
+ if (index.kind === "index" || index.kind === "unique") {
842
+ statements.push(compileIndex(driver, blueprint.table, index));
843
+ } else {
844
+ statements.push(...compileSpecialIndex(driver, blueprint.table, index));
845
+ }
846
+ }
847
+ return statements;
848
+ }
849
+ function compileDropTable(driver, tableName) {
850
+ const cascade = driver === "pgsql" ? " CASCADE" : "";
851
+ return [`DROP TABLE IF EXISTS ${quoteIdentifier(tableName)}${cascade}`];
852
+ }
853
+ function compileColumn(driver, column, mode) {
854
+ const parts = [quoteIdentifier(column.name), compileColumnType(driver, column)];
855
+ if (column.autoIncrement && driver === "mysql") {
856
+ parts[1] = `${parts[1]} AUTO_INCREMENT`;
857
+ }
858
+ if (column.isPrimary && mode === "create") {
859
+ if (driver === "sqlite") {
860
+ parts.push("PRIMARY KEY AUTOINCREMENT");
861
+ } else {
862
+ parts.push("PRIMARY KEY");
863
+ }
864
+ } else if (!column.isNullable) {
865
+ parts.push("NOT NULL");
866
+ } else if (column.isNullable) {
867
+ parts.push("NULL");
868
+ }
869
+ if (column.defaultValue !== undefined) {
870
+ parts.push(`DEFAULT ${column.defaultValue}`);
871
+ }
872
+ if (column.isUnique) {
873
+ parts.push("UNIQUE");
874
+ }
875
+ if (column.checkExpression) {
876
+ parts.push(`CHECK (${column.checkExpression})`);
877
+ }
878
+ if (column.foreignKey) {
879
+ const { referencesTable, referencesColumn, onDelete } = column.foreignKey;
880
+ const reference = `${quoteIdentifier(referencesTable)}(${quoteIdentifier(referencesColumn)})`;
881
+ let clause = `REFERENCES ${reference}`;
882
+ if (onDelete === "cascade") {
883
+ clause += " ON DELETE CASCADE";
884
+ } else if (onDelete === "set null") {
885
+ clause += " ON DELETE SET NULL";
886
+ }
887
+ parts.push(clause);
888
+ }
889
+ return parts.join(" ");
890
+ }
891
+ function compileIndex(_driver, tableName, index) {
892
+ const indexName = index.name ?? defaultIndexName(tableName, index.columns, index.kind === "unique" ? "unique" : "index");
893
+ const columns = index.columns.map((column) => {
894
+ const quoted = quoteIdentifier(column);
895
+ if (index.order === "desc") {
896
+ return `${quoted} DESC`;
897
+ }
898
+ return quoted;
899
+ }).join(", ");
900
+ const unique = index.kind === "unique" ? "UNIQUE " : "";
901
+ return `CREATE ${unique}INDEX IF NOT EXISTS ${quoteIdentifier(indexName)} ON ${quoteIdentifier(tableName)}(${columns})`;
902
+ }
903
+ function compileSpecialIndex(driver, tableName, index) {
904
+ const indexName = index.name ?? defaultIndexName(tableName, index.columns, index.kind);
905
+ const columns = index.columns.map((column) => quoteIdentifier(column)).join(", ");
906
+ switch (index.kind) {
907
+ case "partial":
908
+ case "uniquePartial": {
909
+ if (driver !== "pgsql") {
910
+ throw new UnsupportedSchemaFeatureError("partialIndex()", driver);
911
+ }
912
+ const unique = index.kind === "uniquePartial" ? "UNIQUE " : "";
913
+ return [
914
+ `CREATE ${unique}INDEX IF NOT EXISTS ${quoteIdentifier(indexName)} ON ${quoteIdentifier(tableName)}(${columns}) WHERE ${index.where}`
915
+ ];
916
+ }
917
+ case "gin": {
918
+ if (driver !== "pgsql") {
919
+ throw new UnsupportedSchemaFeatureError("ginIndex()", driver);
920
+ }
921
+ return [
922
+ `CREATE INDEX IF NOT EXISTS ${quoteIdentifier(indexName)} ON ${quoteIdentifier(tableName)} USING GIN(${columns})`
923
+ ];
924
+ }
925
+ case "fullText": {
926
+ if (driver === "mysql") {
927
+ return [
928
+ `CREATE FULLTEXT INDEX ${quoteIdentifier(indexName)} ON ${quoteIdentifier(tableName)}(${columns})`
929
+ ];
930
+ }
931
+ if (driver === "pgsql") {
932
+ throw new UnsupportedSchemaFeatureError("fullText(); use ginIndex() with a tsvector column on PostgreSQL", driver);
933
+ }
934
+ throw new UnsupportedSchemaFeatureError("fullText()", driver);
935
+ }
936
+ default:
937
+ return [];
938
+ }
939
+ }
940
+ function defaultIndexName(tableName, columns, kind) {
941
+ return `idx_${tableName}_${columns.join("_")}_${kind}`;
942
+ }
943
+ function compileBlueprint(driver, blueprint) {
944
+ switch (blueprint.action) {
945
+ case "create":
946
+ return compileCreateTable(driver, blueprint);
947
+ case "alter":
948
+ return compileAlterTable(driver, blueprint);
949
+ case "drop":
950
+ return compileDropTable(driver, blueprint.table);
951
+ default:
952
+ throw new Error(`Unsupported blueprint action: ${blueprint.action}`);
953
+ }
954
+ }
955
+ // ../../src/core/database/schema/grammars/createGrammar.ts
956
+ function createGrammar(driver) {
957
+ return {
958
+ driver,
959
+ compile(blueprint) {
960
+ return compileBlueprint(driver, blueprint);
961
+ }
962
+ };
963
+ }
964
+
965
+ // ../../src/core/database/schema/grammars/mysqlGrammar.ts
966
+ var MySqlGrammar = createGrammar("mysql");
967
+
968
+ // ../../src/core/database/schema/grammars/postgresGrammar.ts
969
+ var PostgresGrammar = createGrammar("pgsql");
970
+
971
+ // ../../src/core/database/schema/grammars/sqliteGrammar.ts
972
+ var SqliteGrammar = createGrammar("sqlite");
973
+
974
+ // ../../src/core/database/schema/grammars/index.ts
975
+ function grammarForDriver(driver) {
976
+ switch (driver) {
977
+ case "pgsql":
978
+ return PostgresGrammar;
979
+ case "mysql":
980
+ return MySqlGrammar;
981
+ case "sqlite":
982
+ return SqliteGrammar;
983
+ default:
984
+ throw new Error(`Unsupported database driver: ${driver}`);
985
+ }
986
+ }
987
+ // ../../src/core/database/schema/schema.ts
988
+ class SchemaBuilder {
989
+ #driver;
990
+ #statements = [];
991
+ constructor(driver) {
992
+ this.#driver = driver;
993
+ }
994
+ create(table, callback) {
995
+ const blueprint = new Blueprint(table, "create");
996
+ callback(blueprint);
997
+ this.#statements.push(...grammarForDriver(this.#driver).compile(blueprint));
998
+ return this;
999
+ }
1000
+ table(table, callback) {
1001
+ const blueprint = new Blueprint(table, "alter");
1002
+ callback(blueprint);
1003
+ this.#statements.push(...grammarForDriver(this.#driver).compile(blueprint));
1004
+ return this;
1005
+ }
1006
+ drop(table) {
1007
+ const blueprint = new Blueprint(table, "drop");
1008
+ this.#statements.push(...grammarForDriver(this.#driver).compile(blueprint));
1009
+ return this;
1010
+ }
1011
+ toSql() {
1012
+ return [...this.#statements];
1013
+ }
1014
+ async execute(db) {
1015
+ for (const statement of this.#statements) {
1016
+ await db.unsafe(statement);
1017
+ }
1018
+ }
1019
+ }
1020
+
1021
+ class Schema {
1022
+ static builder(driver) {
1023
+ return new SchemaBuilder(driver ?? resolveDatabaseDriver());
1024
+ }
1025
+ static async run(db, driver, callback) {
1026
+ const schema = Schema.builder(driver);
1027
+ await callback(schema);
1028
+ await schema.execute(db);
1029
+ }
1030
+ }
1031
+ function createSchemaBuilder(db, driver) {
1032
+ const builder = Schema.builder(driver);
1033
+ return Object.assign(builder, {
1034
+ async commit() {
1035
+ await builder.execute(db);
1036
+ }
1037
+ });
1038
+ }
1039
+ export {
1040
+ resolveDatabaseDriver,
1041
+ inferReferencedTable,
1042
+ grammarForDriver,
1043
+ createSchemaBuilder,
1044
+ compileBlueprint,
1045
+ UnsupportedSchemaFeatureError,
1046
+ SqliteGrammar,
1047
+ SchemaBuilder,
1048
+ Schema,
1049
+ PostgresGrammar,
1050
+ MySqlGrammar,
1051
+ ForeignIdColumnDefinition,
1052
+ ColumnDefinition,
1053
+ Blueprint
1054
+ };