@holo-js/db 0.1.4 → 0.1.5

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +356 -337
  2. package/dist/index.mjs +1216 -925
  3. package/package.json +8 -9
package/dist/index.mjs CHANGED
@@ -110,7 +110,7 @@ function redactBindings(bindings, policy) {
110
110
  if (policy.redactBindingsInLogs) {
111
111
  return limited.map(() => "[REDACTED]");
112
112
  }
113
- return [...limited];
113
+ return limited;
114
114
  }
115
115
  function redactSql(sql, policy) {
116
116
  if (policy.debugSqlInLogs) {
@@ -278,13 +278,50 @@ function compareChunkValuesDescending(a, b) {
278
278
  return a < b ? 1 : -1;
279
279
  }
280
280
 
281
+ // src/query/pagination.ts
282
+ function assertPositiveInteger(value, kind, createError) {
283
+ if (!Number.isInteger(value) || value <= 0) {
284
+ throw createError(`${kind} must be a positive integer.`);
285
+ }
286
+ }
287
+ function normalizePaginationParameterName(value, fallback, createError) {
288
+ if (typeof value === "undefined") {
289
+ return fallback;
290
+ }
291
+ const trimmed = value.trim();
292
+ if (typeof value !== "string" || trimmed.length === 0) {
293
+ throw createError(
294
+ `${fallback === "cursor" ? "Cursor" : "Page"} parameter name must be a non-empty string.`
295
+ );
296
+ }
297
+ return trimmed;
298
+ }
299
+ function encodeOffsetCursor(offset) {
300
+ return Buffer.from(JSON.stringify({ offset }), "utf8").toString("base64url");
301
+ }
302
+ function decodeOffsetCursor(cursor, createError) {
303
+ if (cursor === null) {
304
+ return 0;
305
+ }
306
+ try {
307
+ const decoded = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
308
+ const offset = decoded.offset;
309
+ if (typeof offset !== "number" || !Number.isInteger(offset) || offset < 0) {
310
+ throw new Error("invalid offset");
311
+ }
312
+ return offset;
313
+ } catch {
314
+ throw createError("Cursor is malformed.");
315
+ }
316
+ }
317
+
281
318
  // src/query/paginator.ts
282
319
  function createPaginator(data, meta) {
283
320
  const result = {
284
321
  data,
285
322
  meta: {
286
323
  ...meta,
287
- pageName: normalizeParameterName(meta.pageName, "page")
324
+ pageName: normalizePaginationParameterName(meta.pageName, "page", (message) => new SecurityError(message))
288
325
  }
289
326
  };
290
327
  attachMethods(result, {
@@ -306,7 +343,7 @@ function createSimplePaginator(data, meta) {
306
343
  data,
307
344
  meta: {
308
345
  ...meta,
309
- pageName: normalizeParameterName(meta.pageName, "page")
346
+ pageName: normalizePaginationParameterName(meta.pageName, "page", (message) => new SecurityError(message))
310
347
  }
311
348
  };
312
349
  attachMethods(result, {
@@ -327,7 +364,7 @@ function createCursorPaginator(data, meta) {
327
364
  const result = {
328
365
  data,
329
366
  perPage: meta.perPage,
330
- cursorName: normalizeParameterName(meta.cursorName, "cursor"),
367
+ cursorName: normalizePaginationParameterName(meta.cursorName, "cursor", (message) => new SecurityError(message)),
331
368
  nextCursor: meta.nextCursor,
332
369
  prevCursor: meta.prevCursor
333
370
  };
@@ -347,15 +384,6 @@ function createCursorPaginator(data, meta) {
347
384
  });
348
385
  return result;
349
386
  }
350
- function normalizeParameterName(value, fallback) {
351
- if (typeof value === "undefined") {
352
- return fallback;
353
- }
354
- if (typeof value !== "string" || value.trim().length === 0) {
355
- throw new SecurityError(`${fallback === "cursor" ? "Cursor" : "Page"} parameter name must be a non-empty string.`);
356
- }
357
- return value;
358
- }
359
387
  function attachMethods(target, methods) {
360
388
  Object.defineProperties(
361
389
  target,
@@ -373,43 +401,6 @@ function attachMethods(target, methods) {
373
401
  );
374
402
  }
375
403
 
376
- // src/query/pagination.ts
377
- function assertPositiveInteger(value, kind, createError) {
378
- if (!Number.isInteger(value) || value <= 0) {
379
- throw createError(`${kind} must be a positive integer.`);
380
- }
381
- }
382
- function normalizePaginationParameterName(value, fallback, createError) {
383
- if (typeof value === "undefined") {
384
- return fallback;
385
- }
386
- const trimmed = value.trim();
387
- if (typeof value !== "string" || trimmed.length === 0) {
388
- throw createError(
389
- `${fallback === "cursor" ? "Cursor" : "Page"} parameter name must be a non-empty string.`
390
- );
391
- }
392
- return trimmed;
393
- }
394
- function encodeOffsetCursor(offset) {
395
- return Buffer.from(JSON.stringify({ offset }), "utf8").toString("base64url");
396
- }
397
- function decodeOffsetCursor(cursor, createError) {
398
- if (cursor === null) {
399
- return 0;
400
- }
401
- try {
402
- const decoded = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
403
- const offset = decoded.offset;
404
- if (typeof offset !== "number" || !Number.isInteger(offset) || offset < 0) {
405
- throw new Error("invalid offset");
406
- }
407
- return offset;
408
- } catch {
409
- throw createError("Cursor is malformed.");
410
- }
411
- }
412
-
413
404
  // src/query/ast.ts
414
405
  function createTableSource(table) {
415
406
  if (typeof table === "string") {
@@ -1687,16 +1678,6 @@ var SQLQueryCompiler = class {
1687
1678
  function createSqliteJsonExtractExpression(column2, pathLiteral) {
1688
1679
  return `json_extract(${column2}, ${pathLiteral})`;
1689
1680
  }
1690
- function compileSqliteJsonValuePredicate(extracted, predicate, bindings, createPlaceholder) {
1691
- bindings.push(predicate.value);
1692
- const operator = predicate.operator.toUpperCase();
1693
- const placeholder = createPlaceholder(bindings.length);
1694
- return `${extracted} ${operator} ${placeholder}`;
1695
- }
1696
- var SQLITE_JSON_HELPERS = Object.freeze({
1697
- compileValuePredicate: compileSqliteJsonValuePredicate,
1698
- createExtractExpression: createSqliteJsonExtractExpression
1699
- });
1700
1681
  var SQLITE_EMPTY_JSON_OBJECT = "json('{}')";
1701
1682
  var SQLiteQueryCompiler = class extends SQLQueryCompiler {
1702
1683
  compileLockClause(_lockMode) {
@@ -1705,16 +1686,12 @@ var SQLiteQueryCompiler = class extends SQLQueryCompiler {
1705
1686
  compileJsonPredicate(predicate, bindings) {
1706
1687
  const column2 = this.compileColumnReference(predicate.column);
1707
1688
  const pathLiteral = this.createJsonPathLiteral(predicate.path);
1689
+ const extracted = createSqliteJsonExtractExpression(column2, pathLiteral);
1708
1690
  if (predicate.jsonMode === "value") {
1709
- return SQLITE_JSON_HELPERS.compileValuePredicate(
1710
- SQLITE_JSON_HELPERS.createExtractExpression(column2, pathLiteral),
1711
- predicate,
1712
- bindings,
1713
- (index) => this.createPlaceholder(index)
1714
- );
1691
+ bindings.push(predicate.value);
1692
+ return `${extracted} ${predicate.operator.toUpperCase()} ${this.createPlaceholder(bindings.length)}`;
1715
1693
  }
1716
1694
  if (predicate.jsonMode === "contains") {
1717
- const extracted = SQLITE_JSON_HELPERS.createExtractExpression(column2, pathLiteral);
1718
1695
  if (predicate.value === null || ["string", "number", "boolean"].includes(typeof predicate.value)) {
1719
1696
  bindings.push(predicate.value);
1720
1697
  return `EXISTS (SELECT 1 FROM json_each(${extracted}) WHERE value = ${this.createPlaceholder(bindings.length)})`;
@@ -1723,7 +1700,7 @@ var SQLiteQueryCompiler = class extends SQLQueryCompiler {
1723
1700
  return `${extracted} = json(${this.createPlaceholder(bindings.length)})`;
1724
1701
  }
1725
1702
  bindings.push(predicate.value);
1726
- return `json_array_length(${SQLITE_JSON_HELPERS.createExtractExpression(column2, pathLiteral)}) ${predicate.operator.toUpperCase()} ${this.createPlaceholder(bindings.length)}`;
1703
+ return `json_array_length(${extracted}) ${predicate.operator.toUpperCase()} ${this.createPlaceholder(bindings.length)}`;
1727
1704
  }
1728
1705
  compileJsonUpdateOperations(column2, operations, bindings) {
1729
1706
  let expression = `COALESCE(${this.compileColumnReference(column2)}, ${SQLITE_EMPTY_JSON_OBJECT})`;
@@ -1878,23 +1855,6 @@ var MySQLQueryCompiler = class extends SQLQueryCompiler {
1878
1855
  const modifier = predicate.mode === "boolean" ? " IN BOOLEAN MODE" : " IN NATURAL LANGUAGE MODE";
1879
1856
  return `MATCH (${columns}) AGAINST (${this.createPlaceholder(bindings.length)}${modifier})`;
1880
1857
  }
1881
- compileDatePredicate(predicate, placeholder) {
1882
- const column2 = this.compileColumnReference(predicate.column);
1883
- switch (predicate.part) {
1884
- case "date":
1885
- return `DATE(${column2}) ${predicate.operator.toUpperCase()} ${placeholder}`;
1886
- case "time":
1887
- return `TIME(${column2}) ${predicate.operator.toUpperCase()} ${placeholder}`;
1888
- case "year":
1889
- return `EXTRACT(YEAR FROM ${column2}) ${predicate.operator.toUpperCase()} ${placeholder}`;
1890
- case "month":
1891
- return `EXTRACT(MONTH FROM ${column2}) ${predicate.operator.toUpperCase()} ${placeholder}`;
1892
- case "day":
1893
- return `EXTRACT(DAY FROM ${column2}) ${predicate.operator.toUpperCase()} ${placeholder}`;
1894
- default:
1895
- return super.compileDatePredicate(predicate, placeholder);
1896
- }
1897
- }
1898
1858
  };
1899
1859
 
1900
1860
  // src/schema/normalization.ts
@@ -3246,14 +3206,9 @@ function sanitizeIdentifierForGeneratedName(identifier) {
3246
3206
  }
3247
3207
 
3248
3208
  // src/schema/pluralize.ts
3209
+ import { pluralize } from "inflection";
3249
3210
  function pluralizeTableName(word) {
3250
- if (word.endsWith("y") && !/[aeiou]y$/i.test(word)) {
3251
- return `${word.slice(0, -1)}ies`;
3252
- }
3253
- if (/(?:[sxz]|ch|sh)$/i.test(word)) {
3254
- return `${word}es`;
3255
- }
3256
- return `${word}s`;
3211
+ return pluralize(word);
3257
3212
  }
3258
3213
  function inferConstrainedTableName(columnName) {
3259
3214
  if (columnName.endsWith("_id")) {
@@ -3559,7 +3514,11 @@ function defineTable(name, columns, options = {}) {
3559
3514
  }
3560
3515
 
3561
3516
  // src/schema/generated.ts
3562
- var generatedTables = /* @__PURE__ */ new Map();
3517
+ var generatedTables = (() => {
3518
+ const runtime = globalThis;
3519
+ runtime.__holoGeneratedTables__ ??= /* @__PURE__ */ new Map();
3520
+ return runtime.__holoGeneratedTables__;
3521
+ })();
3563
3522
  function registerGeneratedTables(tables) {
3564
3523
  for (const table of Object.values(tables)) {
3565
3524
  generatedTables.set(table.tableName, table);
@@ -3707,22 +3666,7 @@ function renderColumnBuilder(table, columnDefinition) {
3707
3666
  function renderIndex(index) {
3708
3667
  return `{ columns: ${JSON.stringify([...index.columns])}, unique: ${index.unique}${index.name ? `, name: ${JSON.stringify(index.name)}` : ""} }`;
3709
3668
  }
3710
- function renderGeneratedSchemaPlaceholder() {
3711
- return [
3712
- "/* eslint-disable @typescript-eslint/no-empty-object-type */",
3713
- "import { registerGeneratedTables } from '@holo-js/db'",
3714
- "",
3715
- "declare module '@holo-js/db' {",
3716
- " interface GeneratedSchemaTables {}",
3717
- "}",
3718
- "",
3719
- "export const tables = {} as const",
3720
- "",
3721
- "registerGeneratedTables(tables)",
3722
- ""
3723
- ].join("\n");
3724
- }
3725
- function renderGeneratedSchemaModule(tables) {
3669
+ function buildGeneratedSchemaModuleLines(tables, options) {
3726
3670
  const renderedTables = [...tables].sort((left, right) => left.tableName.localeCompare(right.tableName));
3727
3671
  const declarations = [
3728
3672
  "import { column, defineGeneratedTable, registerGeneratedTables } from '@holo-js/db'",
@@ -3741,24 +3685,146 @@ function renderGeneratedSchemaModule(tables) {
3741
3685
  declarations.push(`}${table.indexes.length > 0 ? `, { indexes: [${table.indexes.map(renderIndex).join(", ")}] }` : ""})`);
3742
3686
  declarations.push("");
3743
3687
  }
3744
- declarations.push("declare module '@holo-js/db' {");
3745
- declarations.push(" interface GeneratedSchemaTables {");
3746
- declarations.push(...interfaceLines);
3747
- declarations.push(" }");
3748
- declarations.push("}");
3749
- declarations.push("");
3750
- declarations.push(`export const tables = { ${exportedTableEntries.join(", ")} } as const`);
3688
+ if (options.withTypeAugmentation) {
3689
+ declarations.push("declare module '@holo-js/db' {");
3690
+ declarations.push(" interface GeneratedSchemaTables {");
3691
+ declarations.push(...interfaceLines);
3692
+ declarations.push(" }");
3693
+ declarations.push("}");
3694
+ declarations.push("");
3695
+ }
3696
+ declarations.push(
3697
+ options.withTypeAugmentation ? `export const tables = { ${exportedTableEntries.join(", ")} } as const` : `export const tables = Object.freeze({ ${exportedTableEntries.join(", ")} })`
3698
+ );
3751
3699
  declarations.push("");
3752
3700
  declarations.push("registerGeneratedTables(tables)");
3753
3701
  declarations.push("");
3754
- return declarations.join("\n");
3702
+ return declarations;
3703
+ }
3704
+ function renderGeneratedSchemaPlaceholder() {
3705
+ return [
3706
+ "/* eslint-disable @typescript-eslint/no-empty-object-type */",
3707
+ "import { registerGeneratedTables } from '@holo-js/db'",
3708
+ "",
3709
+ "declare module '@holo-js/db' {",
3710
+ " interface GeneratedSchemaTables {}",
3711
+ "}",
3712
+ "",
3713
+ "export const tables = {} as const",
3714
+ "",
3715
+ "registerGeneratedTables(tables)",
3716
+ ""
3717
+ ].join("\n");
3718
+ }
3719
+ function renderGeneratedSchemaModule(tables) {
3720
+ return buildGeneratedSchemaModuleLines(tables, { withTypeAugmentation: true }).join("\n");
3721
+ }
3722
+ function renderGeneratedSchemaRuntimeModule(tables) {
3723
+ return buildGeneratedSchemaModuleLines(tables, { withTypeAugmentation: false }).join("\n");
3724
+ }
3725
+
3726
+ // src/schema/foreignKeyBuilderState.ts
3727
+ var ForeignKeyBuilderState = class {
3728
+ constructor(columnName, referenceColumn = "id") {
3729
+ this.columnName = columnName;
3730
+ this.referenceColumn = referenceColumn;
3731
+ }
3732
+ referenceTable;
3733
+ referenceColumn;
3734
+ onDeleteAction;
3735
+ onUpdateAction;
3736
+ references(columnName) {
3737
+ this.referenceColumn = columnName;
3738
+ }
3739
+ on(table) {
3740
+ this.referenceTable = table;
3741
+ }
3742
+ constrained(table, columnName = "id") {
3743
+ this.referenceTable = table ?? inferConstrainedTableName(this.columnName);
3744
+ this.referenceColumn = columnName;
3745
+ }
3746
+ onDelete(action) {
3747
+ this.onDeleteAction = action;
3748
+ }
3749
+ onUpdate(action) {
3750
+ this.onUpdateAction = action;
3751
+ }
3752
+ toReference(defaultTable = "") {
3753
+ return {
3754
+ table: this.referenceTable ?? defaultTable,
3755
+ column: this.referenceColumn,
3756
+ onDelete: this.onDeleteAction,
3757
+ onUpdate: this.onUpdateAction
3758
+ };
3759
+ }
3760
+ applyToColumnBuilder(builder) {
3761
+ let next = builder.references(this.referenceColumn);
3762
+ if (this.referenceTable) {
3763
+ next = next.on(this.referenceTable);
3764
+ }
3765
+ if (this.onDeleteAction) {
3766
+ next = next.onDelete(this.onDeleteAction);
3767
+ }
3768
+ if (this.onUpdateAction) {
3769
+ next = next.onUpdate(this.onUpdateAction);
3770
+ }
3771
+ return next;
3772
+ }
3773
+ };
3774
+
3775
+ // src/schema/generatedNames.ts
3776
+ var DEFAULT_INDEX_NAME_LENGTH_POLICY = Object.freeze({
3777
+ maxBytes: 63,
3778
+ label: "portable PostgreSQL-compatible (NAMEDATALEN-1)"
3779
+ });
3780
+ function assertValidIndexName(indexName, policy = DEFAULT_INDEX_NAME_LENGTH_POLICY) {
3781
+ assertValidIdentifierSegment(indexName, "Index name");
3782
+ const byteLength = new TextEncoder().encode(indexName).length;
3783
+ if (byteLength > policy.maxBytes) {
3784
+ throw new SchemaError(
3785
+ `Index name "${indexName}" is ${byteLength} bytes long; ${policy.label} index names must be ${policy.maxBytes} bytes or fewer. Provide a shorter explicit index name.`
3786
+ );
3787
+ }
3788
+ }
3789
+ function assertUniqueResolvedIndexNames(tableName, indexes) {
3790
+ const names = /* @__PURE__ */ new Set();
3791
+ for (const index of indexes) {
3792
+ const indexName = resolveGeneratedIndexName(tableName, index);
3793
+ if (names.has(indexName)) {
3794
+ throw new SchemaError(
3795
+ `Index name "${indexName}" is used by multiple indexes on table "${tableName}". Provide explicit unique index names.`
3796
+ );
3797
+ }
3798
+ names.add(indexName);
3799
+ }
3800
+ }
3801
+ function resolveGeneratedIndexName(tableName, index) {
3802
+ const suffix = index.unique ? "unique" : "index";
3803
+ const indexName = index.name ?? createConventionalIndexName(tableName, index.columns, suffix);
3804
+ assertValidIndexName(indexName);
3805
+ return indexName;
3806
+ }
3807
+ function createConventionalIndexName(tableName, columns, suffix) {
3808
+ const columnsName = columns.map((column2) => sanitizeIdentifierForGeneratedName(column2)).join("_");
3809
+ return `${sanitizeIdentifierForGeneratedName(tableName)}_${columnsName}_${suffix}`;
3810
+ }
3811
+ function resolveConventionalIndexName(tableName, columns, suffix = "index") {
3812
+ const indexName = createConventionalIndexName(tableName, columns, suffix);
3813
+ assertValidIndexName(indexName);
3814
+ return indexName;
3815
+ }
3816
+ function resolveGeneratedForeignKeyName(tableName, columnName, constraintName) {
3817
+ const resolvedName = constraintName ?? `${sanitizeIdentifierForGeneratedName(tableName)}_${sanitizeIdentifierForGeneratedName(columnName)}_foreign`;
3818
+ assertValidIdentifierSegment(resolvedName, "Foreign key name");
3819
+ return resolvedName;
3755
3820
  }
3756
3821
 
3757
3822
  // src/schema/TableDefinitionBuilder.ts
3758
3823
  var TableCreateColumnBuilder = class {
3759
- constructor(root, builderRef) {
3824
+ constructor(root, builderRef, columnName) {
3760
3825
  this.root = root;
3761
3826
  this.builderRef = builderRef;
3827
+ this.columnName = columnName;
3762
3828
  }
3763
3829
  build() {
3764
3830
  return this.root.build();
@@ -3794,6 +3860,16 @@ var TableCreateColumnBuilder = class {
3794
3860
  this.builderRef.builder = this.builderRef.builder.unique();
3795
3861
  return this;
3796
3862
  }
3863
+ index(nameOrColumns, name) {
3864
+ if (typeof nameOrColumns !== "string" && nameOrColumns !== void 0) {
3865
+ return this.root.index(nameOrColumns, name);
3866
+ }
3867
+ this.root.index(
3868
+ [this.columnName],
3869
+ nameOrColumns ?? resolveConventionalIndexName(this.root.tableName, [this.columnName])
3870
+ );
3871
+ return this;
3872
+ }
3797
3873
  foreign(columnName) {
3798
3874
  return this.root.foreign(columnName);
3799
3875
  }
@@ -3866,9 +3942,6 @@ var TableCreateColumnBuilder = class {
3866
3942
  enum(columnName, values) {
3867
3943
  return this.root.enum(columnName, values);
3868
3944
  }
3869
- index(columns, name) {
3870
- return this.root.index(columns, name);
3871
- }
3872
3945
  timestamps() {
3873
3946
  return this.root.timestamps();
3874
3947
  }
@@ -3901,33 +3974,29 @@ var TableCreateColumnBuilder = class {
3901
3974
  }
3902
3975
  };
3903
3976
  var TableCreateForeignIdBuilder = class extends TableCreateColumnBuilder {
3977
+ foreignKeyState;
3904
3978
  constructor(root, builderRef, columnName) {
3905
- super(root, builderRef);
3906
- this.columnName = columnName;
3979
+ super(root, builderRef, columnName);
3980
+ this.foreignKeyState = new ForeignKeyBuilderState(columnName);
3907
3981
  }
3908
- referenceTable;
3909
- referenceColumn = "id";
3910
- onDeleteAction;
3911
- onUpdateAction;
3912
3982
  references(columnName) {
3913
- this.referenceColumn = columnName;
3983
+ this.foreignKeyState.references(columnName);
3914
3984
  return this.applyReference();
3915
3985
  }
3916
3986
  on(table) {
3917
- this.referenceTable = table;
3987
+ this.foreignKeyState.on(table);
3918
3988
  return this.applyReference();
3919
3989
  }
3920
3990
  constrained(table, columnName = "id") {
3921
- this.referenceTable = table ?? inferConstrainedTableName(this.columnName);
3922
- this.referenceColumn = columnName;
3991
+ this.foreignKeyState.constrained(table, columnName);
3923
3992
  return this.applyReference();
3924
3993
  }
3925
3994
  onDelete(action) {
3926
- this.onDeleteAction = action;
3995
+ this.foreignKeyState.onDelete(action);
3927
3996
  return this.applyReference();
3928
3997
  }
3929
3998
  onUpdate(action) {
3930
- this.onUpdateAction = action;
3999
+ this.foreignKeyState.onUpdate(action);
3931
4000
  return this.applyReference();
3932
4001
  }
3933
4002
  cascadeOnDelete() {
@@ -3955,17 +4024,7 @@ var TableCreateForeignIdBuilder = class extends TableCreateColumnBuilder {
3955
4024
  return this.onUpdate("no action");
3956
4025
  }
3957
4026
  applyReference() {
3958
- let builder = this.builderRef.builder.references(this.referenceColumn);
3959
- if (this.referenceTable) {
3960
- builder = builder.on(this.referenceTable);
3961
- }
3962
- if (this.onDeleteAction) {
3963
- builder = builder.onDelete(this.onDeleteAction);
3964
- }
3965
- if (this.onUpdateAction) {
3966
- builder = builder.onUpdate(this.onUpdateAction);
3967
- }
3968
- this.builderRef.builder = builder;
4027
+ this.builderRef.builder = this.foreignKeyState.applyToColumnBuilder(this.builderRef.builder);
3969
4028
  return this;
3970
4029
  }
3971
4030
  };
@@ -3973,31 +4032,27 @@ var TableCreateForeignKeyBuilder = class {
3973
4032
  constructor(root, builderRef, columnName) {
3974
4033
  this.root = root;
3975
4034
  this.builderRef = builderRef;
3976
- this.columnName = columnName;
4035
+ this.foreignKeyState = new ForeignKeyBuilderState(columnName);
3977
4036
  }
3978
- referenceTable;
3979
- referenceColumn = "id";
3980
- onDeleteAction;
3981
- onUpdateAction;
4037
+ foreignKeyState;
3982
4038
  references(columnName) {
3983
- this.referenceColumn = columnName;
4039
+ this.foreignKeyState.references(columnName);
3984
4040
  return this.applyReference();
3985
4041
  }
3986
4042
  on(table) {
3987
- this.referenceTable = table;
4043
+ this.foreignKeyState.on(table);
3988
4044
  return this.applyReference();
3989
4045
  }
3990
4046
  constrained(table, columnName = "id") {
3991
- this.referenceTable = table ?? inferConstrainedTableName(this.columnName);
3992
- this.referenceColumn = columnName;
4047
+ this.foreignKeyState.constrained(table, columnName);
3993
4048
  return this.applyReference();
3994
4049
  }
3995
4050
  onDelete(action) {
3996
- this.onDeleteAction = action;
4051
+ this.foreignKeyState.onDelete(action);
3997
4052
  return this.applyReference();
3998
4053
  }
3999
4054
  onUpdate(action) {
4000
- this.onUpdateAction = action;
4055
+ this.foreignKeyState.onUpdate(action);
4001
4056
  return this.applyReference();
4002
4057
  }
4003
4058
  cascadeOnDelete() {
@@ -4028,17 +4083,7 @@ var TableCreateForeignKeyBuilder = class {
4028
4083
  return this.root.build();
4029
4084
  }
4030
4085
  applyReference() {
4031
- let builder = this.builderRef.builder.references(this.referenceColumn);
4032
- if (this.referenceTable) {
4033
- builder = builder.on(this.referenceTable);
4034
- }
4035
- if (this.onDeleteAction) {
4036
- builder = builder.onDelete(this.onDeleteAction);
4037
- }
4038
- if (this.onUpdateAction) {
4039
- builder = builder.onUpdate(this.onUpdateAction);
4040
- }
4041
- this.builderRef.builder = builder;
4086
+ this.builderRef.builder = this.foreignKeyState.applyToColumnBuilder(this.builderRef.builder);
4042
4087
  return this;
4043
4088
  }
4044
4089
  };
@@ -4180,7 +4225,8 @@ var TableDefinitionBuilder = class {
4180
4225
  };
4181
4226
  return new TableCreateColumnBuilder(
4182
4227
  this,
4183
- builderRef
4228
+ builderRef,
4229
+ columnName
4184
4230
  );
4185
4231
  }
4186
4232
  foreignColumn(columnName, initialBuilder) {
@@ -4202,7 +4248,10 @@ var TableDefinitionBuilder = class {
4202
4248
  const idBuilder = nullable ? createIdBuilder().nullable() : createIdBuilder();
4203
4249
  this.column(typeColumn, typeBuilder);
4204
4250
  this.column(idColumn, idBuilder);
4205
- this.index([typeColumn, idColumn], indexName);
4251
+ this.index(
4252
+ [typeColumn, idColumn],
4253
+ indexName ?? resolveConventionalIndexName(this.tableName, [typeColumn, idColumn])
4254
+ );
4206
4255
  return this;
4207
4256
  }
4208
4257
  };
@@ -4211,36 +4260,32 @@ var TableDefinitionBuilder = class {
4211
4260
  var TableForeignKeyBuilder = class {
4212
4261
  constructor(operation) {
4213
4262
  this.operation = operation;
4263
+ this.foreignKeyState = new ForeignKeyBuilderState(operation.columnName, operation.reference.column);
4214
4264
  }
4265
+ foreignKeyState;
4215
4266
  references(columnName) {
4216
- this.operation.reference = {
4217
- ...this.operation.reference,
4218
- column: columnName
4219
- };
4267
+ this.foreignKeyState.references(columnName);
4268
+ this.applyReference();
4220
4269
  return this;
4221
4270
  }
4222
4271
  on(table) {
4223
- this.operation.reference = {
4224
- ...this.operation.reference,
4225
- table
4226
- };
4272
+ this.foreignKeyState.on(table);
4273
+ this.applyReference();
4227
4274
  return this;
4228
4275
  }
4229
4276
  constrained(table, columnName = "id") {
4230
- return this.on(table ?? inferConstrainedTableName(this.operation.columnName)).references(columnName);
4277
+ this.foreignKeyState.constrained(table, columnName);
4278
+ this.applyReference();
4279
+ return this;
4231
4280
  }
4232
4281
  onDelete(action) {
4233
- this.operation.reference = {
4234
- ...this.operation.reference,
4235
- onDelete: action
4236
- };
4282
+ this.foreignKeyState.onDelete(action);
4283
+ this.applyReference();
4237
4284
  return this;
4238
4285
  }
4239
4286
  onUpdate(action) {
4240
- this.operation.reference = {
4241
- ...this.operation.reference,
4242
- onUpdate: action
4243
- };
4287
+ this.foreignKeyState.onUpdate(action);
4288
+ this.applyReference();
4244
4289
  return this;
4245
4290
  }
4246
4291
  cascadeOnDelete() {
@@ -4267,6 +4312,9 @@ var TableForeignKeyBuilder = class {
4267
4312
  noActionOnUpdate() {
4268
4313
  return this.onUpdate("no action");
4269
4314
  }
4315
+ applyReference() {
4316
+ this.operation.reference = this.foreignKeyState.toReference();
4317
+ }
4270
4318
  };
4271
4319
  var TableColumnMutationBuilder = class {
4272
4320
  constructor(root, operation) {
@@ -4301,6 +4349,13 @@ var TableColumnMutationBuilder = class {
4301
4349
  this.operation.column = this.operation.column.unique();
4302
4350
  return this;
4303
4351
  }
4352
+ index(name) {
4353
+ this.root.index(
4354
+ [this.operation.columnName],
4355
+ name ?? resolveConventionalIndexName(this.root.table, [this.operation.columnName])
4356
+ );
4357
+ return this;
4358
+ }
4304
4359
  foreignId(columnName) {
4305
4360
  return this.root.foreignId(columnName);
4306
4361
  }
@@ -4347,31 +4402,28 @@ var TableForeignIdMutationBuilder = class extends TableColumnMutationBuilder {
4347
4402
  super(root, operation);
4348
4403
  this.operations = operations;
4349
4404
  this.columnName = columnName;
4405
+ this.foreignKeyState = new ForeignKeyBuilderState(columnName);
4350
4406
  }
4407
+ foreignKeyState;
4351
4408
  foreignOperation;
4352
- referenceTable;
4353
- referenceColumn = "id";
4354
- onDeleteAction;
4355
- onUpdateAction;
4356
4409
  references(columnName) {
4357
- this.referenceColumn = columnName;
4410
+ this.foreignKeyState.references(columnName);
4358
4411
  return this.applyForeignKey();
4359
4412
  }
4360
4413
  on(table) {
4361
- this.referenceTable = table;
4414
+ this.foreignKeyState.on(table);
4362
4415
  return this.applyForeignKey();
4363
4416
  }
4364
4417
  constrained(table, columnName = "id") {
4365
- this.referenceTable = table ?? inferConstrainedTableName(this.columnName);
4366
- this.referenceColumn = columnName;
4418
+ this.foreignKeyState.constrained(table, columnName);
4367
4419
  return this.applyForeignKey();
4368
4420
  }
4369
4421
  onDelete(action) {
4370
- this.onDeleteAction = action;
4422
+ this.foreignKeyState.onDelete(action);
4371
4423
  return this.applyForeignKey();
4372
4424
  }
4373
4425
  onUpdate(action) {
4374
- this.onUpdateAction = action;
4426
+ this.foreignKeyState.onUpdate(action);
4375
4427
  return this.applyForeignKey();
4376
4428
  }
4377
4429
  cascadeOnDelete() {
@@ -4400,12 +4452,7 @@ var TableForeignIdMutationBuilder = class extends TableColumnMutationBuilder {
4400
4452
  }
4401
4453
  applyForeignKey() {
4402
4454
  const operation = this.ensureForeignOperation();
4403
- operation.reference = {
4404
- table: this.referenceTable ?? "",
4405
- column: this.referenceColumn,
4406
- onDelete: this.onDeleteAction,
4407
- onUpdate: this.onUpdateAction
4408
- };
4455
+ operation.reference = this.foreignKeyState.toReference();
4409
4456
  return this;
4410
4457
  }
4411
4458
  ensureForeignOperation() {
@@ -4413,12 +4460,7 @@ var TableForeignIdMutationBuilder = class extends TableColumnMutationBuilder {
4413
4460
  this.foreignOperation = {
4414
4461
  kind: "createForeignKey",
4415
4462
  columnName: this.columnName,
4416
- reference: {
4417
- table: this.referenceTable ?? "",
4418
- column: this.referenceColumn,
4419
- onDelete: this.onDeleteAction,
4420
- onUpdate: this.onUpdateAction
4421
- }
4463
+ reference: this.foreignKeyState.toReference()
4422
4464
  };
4423
4465
  this.operations.push(this.foreignOperation);
4424
4466
  }
@@ -4586,7 +4628,10 @@ var TableMutationBuilder = class {
4586
4628
  const idColumn = `${name}_id`;
4587
4629
  this.columnMutation(typeColumn, () => nullable ? column.string().nullable() : column.string());
4588
4630
  this.columnMutation(idColumn, () => nullable ? createIdBuilder().nullable() : createIdBuilder());
4589
- this.index([typeColumn, idColumn], indexName);
4631
+ this.index(
4632
+ [typeColumn, idColumn],
4633
+ indexName ?? resolveConventionalIndexName(this.table, [typeColumn, idColumn])
4634
+ );
4590
4635
  return this;
4591
4636
  }
4592
4637
  };
@@ -4766,6 +4811,7 @@ var SQLSchemaCompiler = class {
4766
4811
  }
4767
4812
  compileCreateTable(table) {
4768
4813
  assertValidIdentifierPath(table.tableName, "Table name");
4814
+ assertUniqueResolvedIndexNames(table.tableName, table.indexes);
4769
4815
  const columnSql = Object.values(table.columns).map((column2) => this.compileColumn(column2));
4770
4816
  const sql = `CREATE TABLE IF NOT EXISTS ${this.compileIdentifierPath(table.tableName)} (${columnSql.join(", ")})`;
4771
4817
  const statements = [{
@@ -4857,7 +4903,7 @@ var SQLSchemaCompiler = class {
4857
4903
  }
4858
4904
  compileDropIndex(tableName, indexName) {
4859
4905
  assertValidIdentifierPath(tableName, "Table name");
4860
- assertValidIdentifierSegment(indexName, "Index name");
4906
+ assertValidIndexName(indexName);
4861
4907
  return {
4862
4908
  sql: `DROP INDEX IF EXISTS ${this.quoteIdentifier(indexName)}`,
4863
4909
  source: `schema:dropIndex:${tableName}:${indexName}`
@@ -4873,8 +4919,8 @@ var SQLSchemaCompiler = class {
4873
4919
  }
4874
4920
  compileRenameIndex(tableName, fromIndexName, toIndexName) {
4875
4921
  assertValidIdentifierPath(tableName, "Table name");
4876
- assertValidIdentifierSegment(fromIndexName, "Index name");
4877
- assertValidIdentifierSegment(toIndexName, "Index name");
4922
+ assertValidIndexName(fromIndexName);
4923
+ assertValidIndexName(toIndexName);
4878
4924
  return {
4879
4925
  sql: `ALTER INDEX ${this.quoteIdentifier(fromIndexName)} RENAME TO ${this.quoteIdentifier(toIndexName)}`,
4880
4926
  source: `schema:renameIndex:${tableName}:${fromIndexName}:${toIndexName}`
@@ -4964,14 +5010,10 @@ var SQLSchemaCompiler = class {
4964
5010
  return false;
4965
5011
  }
4966
5012
  resolveIndexName(tableName, index) {
4967
- const indexName = index.name ?? `${sanitizeIdentifierForGeneratedName(tableName)}_${index.columns.join("_")}_${index.unique ? "unique" : "index"}`;
4968
- assertValidIdentifierSegment(indexName, "Index name");
4969
- return indexName;
5013
+ return resolveGeneratedIndexName(tableName, index);
4970
5014
  }
4971
5015
  resolveForeignKeyName(tableName, columnName, constraintName) {
4972
- const resolvedName = constraintName ?? `${sanitizeIdentifierForGeneratedName(tableName)}_${columnName}_foreign`;
4973
- assertValidIdentifierSegment(resolvedName, "Foreign key name");
4974
- return resolvedName;
5016
+ return resolveGeneratedForeignKeyName(tableName, columnName, constraintName);
4975
5017
  }
4976
5018
  assertSupportedAlterColumnDefinition(column2) {
4977
5019
  if (column2.primaryKey || column2.unique || column2.references || column2.kind === "id") {
@@ -5130,6 +5172,41 @@ function resolveDialectColumnType(dialect, column2) {
5130
5172
  }
5131
5173
  return typeof entry === "function" ? entry(column2) : entry;
5132
5174
  }
5175
+ function resolveDialectComparisonColumnType(dialect, column2) {
5176
+ const compiledType = resolveDialectColumnType(dialect, column2);
5177
+ const normalized = compiledType.toLowerCase();
5178
+ if (dialect === "sqlite") {
5179
+ return normalized.split(/\s+/, 1)[0].toUpperCase();
5180
+ }
5181
+ if (dialect === "postgres") {
5182
+ if (normalized.startsWith("varchar")) {
5183
+ return "character varying";
5184
+ }
5185
+ if (normalized.startsWith("bigint")) {
5186
+ return "bigint";
5187
+ }
5188
+ return normalized;
5189
+ }
5190
+ if (normalized.startsWith("varchar")) {
5191
+ return "varchar";
5192
+ }
5193
+ if (normalized.startsWith("char")) {
5194
+ return "char";
5195
+ }
5196
+ if (normalized.startsWith("tinyint")) {
5197
+ return "tinyint";
5198
+ }
5199
+ if (normalized.startsWith("decimal")) {
5200
+ return "decimal";
5201
+ }
5202
+ if (normalized.startsWith("enum")) {
5203
+ return "enum";
5204
+ }
5205
+ if (normalized.startsWith("bigint")) {
5206
+ return "bigint";
5207
+ }
5208
+ return normalized;
5209
+ }
5133
5210
 
5134
5211
  // src/schema/SQLiteSchemaCompiler.ts
5135
5212
  var SQLiteSchemaCompiler = class extends SQLSchemaCompiler {
@@ -5211,6 +5288,8 @@ var MySQLSchemaCompiler = class extends SQLSchemaCompiler {
5211
5288
  };
5212
5289
  }
5213
5290
  compileRenameIndex(tableName, fromIndexName, toIndexName) {
5291
+ assertValidIndexName(fromIndexName);
5292
+ assertValidIndexName(toIndexName);
5214
5293
  return {
5215
5294
  sql: `ALTER TABLE ${this.compileIdentifierPath(tableName)} RENAME INDEX ${this.quoteIdentifier(fromIndexName)} TO ${this.quoteIdentifier(toIndexName)}`,
5216
5295
  source: `schema:renameIndex:${tableName}:${fromIndexName}:${toIndexName}`
@@ -5225,6 +5304,7 @@ var MySQLSchemaCompiler = class extends SQLSchemaCompiler {
5225
5304
  };
5226
5305
  }
5227
5306
  compileDropIndex(tableName, indexName) {
5307
+ assertValidIndexName(indexName);
5228
5308
  return {
5229
5309
  sql: `DROP INDEX ${this.quoteIdentifier(indexName)} ON ${this.compileIdentifierPath(tableName)}`,
5230
5310
  source: `schema:dropIndex:${tableName}:${indexName}`
@@ -5268,7 +5348,9 @@ var SchemaService = class {
5268
5348
  assertValidIdentifierPath(tableName, "Table name");
5269
5349
  const builder = new TableMutationBuilder(tableName);
5270
5350
  await callback(builder);
5271
- for (const operation of builder.getOperations()) {
5351
+ const operations = builder.getOperations();
5352
+ this.assertTableMutationIndexNames(tableName, operations);
5353
+ for (const operation of operations) {
5272
5354
  await this.executeTableMutation(tableName, operation);
5273
5355
  }
5274
5356
  }
@@ -5556,10 +5638,31 @@ var SchemaService = class {
5556
5638
  return column2.toDefinition({ name: columnName });
5557
5639
  }
5558
5640
  async createDefinedTable(table) {
5641
+ const statements = this.createCompiler().compile(createTableOperation(table));
5559
5642
  if (!this.connection.getSchemaRegistry().has(table.tableName)) {
5560
5643
  this.register(table);
5561
5644
  }
5562
- await this.execute(this.createCompiler().compile(createTableOperation(table)));
5645
+ await this.execute(statements);
5646
+ }
5647
+ assertTableMutationIndexNames(tableName, operations) {
5648
+ const indexes = [];
5649
+ for (const operation of operations) {
5650
+ switch (operation.kind) {
5651
+ case "createIndex":
5652
+ indexes.push(operation.index);
5653
+ this.resolveIndexName(tableName, operation.index);
5654
+ break;
5655
+ case "dropIndex":
5656
+ assertValidIndexName(operation.indexName);
5657
+ break;
5658
+ case "renameIndex":
5659
+ assertValidIndexName(operation.fromIndexName);
5660
+ assertValidIndexName(operation.toIndexName);
5661
+ indexes.push({ columns: [], name: operation.toIndexName, unique: false });
5662
+ break;
5663
+ }
5664
+ }
5665
+ assertUniqueResolvedIndexNames(tableName, indexes);
5563
5666
  }
5564
5667
  async executeTableMutation(tableName, operation) {
5565
5668
  switch (operation.kind) {
@@ -5757,10 +5860,10 @@ var SchemaService = class {
5757
5860
  return defineTable(table.tableName, nextColumns, { indexes: table.indexes });
5758
5861
  }
5759
5862
  resolveIndexName(tableName, index) {
5760
- return index.name ?? `${tableName.replaceAll(".", "_")}_${index.columns.join("_")}_${index.unique ? "unique" : "index"}`;
5863
+ return resolveGeneratedIndexName(tableName, index);
5761
5864
  }
5762
5865
  resolveForeignKeyName(tableName, columnName) {
5763
- return `${tableName.replaceAll(".", "_")}_${columnName}_foreign`;
5866
+ return resolveGeneratedForeignKeyName(tableName, columnName);
5764
5867
  }
5765
5868
  createForeignKeyConstraintStatement(enable) {
5766
5869
  if (this.isSqlite()) {
@@ -5969,117 +6072,15 @@ async function diffTable(schema, table) {
5969
6072
  }
5970
6073
  function lowerLogicalColumnType(column2, dialectName) {
5971
6074
  if (dialectName.startsWith("postgres")) {
5972
- return lowerPostgresLogicalColumnType(column2);
6075
+ return resolveDialectComparisonColumnType("postgres", column2);
5973
6076
  }
5974
6077
  if (dialectName.startsWith("mysql")) {
5975
- return lowerMySqlLogicalColumnType(column2);
5976
- }
5977
- switch (column2.kind) {
5978
- case "id":
5979
- case "integer":
5980
- case "bigInteger":
5981
- case "boolean":
5982
- return "INTEGER";
5983
- case "string":
5984
- case "uuid":
5985
- case "ulid":
5986
- case "snowflake":
5987
- case "date":
5988
- case "datetime":
5989
- case "timestamp":
5990
- case "text":
5991
- case "json":
5992
- case "enum":
5993
- return "TEXT";
5994
- case "real":
5995
- return "REAL";
5996
- case "decimal":
5997
- return "NUMERIC";
5998
- case "blob":
5999
- return "BLOB";
6000
- case "vector":
6001
- throw new SchemaError("SQLite schema diffing does not support logical vector columns.");
6002
- default:
6003
- throw new SchemaError(`Unsupported logical column kind "${String(column2.kind)}" for SQLite schema diffing.`);
6004
- }
6005
- }
6006
- function lowerPostgresLogicalColumnType(column2) {
6007
- switch (column2.kind) {
6008
- case "id":
6009
- case "bigInteger":
6010
- return "bigint";
6011
- case "integer":
6012
- return "integer";
6013
- case "boolean":
6014
- return "boolean";
6015
- case "string":
6016
- return "character varying";
6017
- case "text":
6018
- case "enum":
6019
- return "text";
6020
- case "uuid":
6021
- return "uuid";
6022
- case "ulid":
6023
- case "snowflake":
6024
- return "character varying";
6025
- case "date":
6026
- return "date";
6027
- case "datetime":
6028
- case "timestamp":
6029
- return "timestamp";
6030
- case "json":
6031
- return "jsonb";
6032
- case "real":
6033
- return "double precision";
6034
- case "decimal":
6035
- return "numeric";
6036
- case "blob":
6037
- return "bytea";
6038
- case "vector":
6039
- return `vector(${column2.vectorDimensions})`;
6040
- default:
6041
- throw new SchemaError(`Unsupported logical column kind "${String(column2.kind)}" for Postgres schema diffing.`);
6078
+ return resolveDialectComparisonColumnType("mysql", column2);
6042
6079
  }
6043
- }
6044
- function lowerMySqlLogicalColumnType(column2) {
6045
- switch (column2.kind) {
6046
- case "id":
6047
- case "bigInteger":
6048
- return "bigint";
6049
- case "integer":
6050
- return "int";
6051
- case "boolean":
6052
- return "tinyint";
6053
- case "string":
6054
- return "varchar";
6055
- case "text":
6056
- return "text";
6057
- case "uuid":
6058
- case "ulid":
6059
- return "char";
6060
- case "snowflake":
6061
- return "varchar";
6062
- case "date":
6063
- return "date";
6064
- case "datetime":
6065
- return "datetime";
6066
- case "timestamp":
6067
- return "timestamp";
6068
- case "enum":
6069
- return "enum";
6070
- case "json":
6071
- return "json";
6072
- case "real":
6073
- return "double";
6074
- case "decimal":
6075
- return "decimal";
6076
- case "blob":
6077
- return "blob";
6078
- case "vector":
6079
- throw new SchemaError("MySQL schema diffing does not support logical vector columns.");
6080
- default:
6081
- throw new SchemaError(`Unsupported logical column kind "${String(column2.kind)}" for MySQL schema diffing.`);
6080
+ if (dialectName.startsWith("sqlite")) {
6081
+ return resolveDialectComparisonColumnType("sqlite", column2);
6082
6082
  }
6083
+ return resolveDialectComparisonColumnType(dialectName, column2);
6083
6084
  }
6084
6085
  function normalizeExpectedIndex(table, index) {
6085
6086
  return {
@@ -6105,10 +6106,8 @@ function normalizeActualForeignKeyName(foreignKey) {
6105
6106
  // src/migrations/defineMigration.ts
6106
6107
  var MIGRATION_NAME_PATTERN = /^\d{4}_\d{2}_\d{2}_\d{6}_[a-z0-9_]+$/;
6107
6108
  function defineMigration(definition) {
6108
- if (definition.name && !MIGRATION_NAME_PATTERN.test(definition.name)) {
6109
- throw new ConfigurationError(
6110
- `Migration name "${definition.name}" must match YYYY_MM_DD_HHMMSS_description.`
6111
- );
6109
+ if (definition.name) {
6110
+ assertMigrationName(definition.name);
6112
6111
  }
6113
6112
  return Object.freeze(definition);
6114
6113
  }
@@ -6206,9 +6205,7 @@ var MigrationService = class {
6206
6205
  const executed = [];
6207
6206
  for (const migration of pending) {
6208
6207
  const log = this.createMigrationLog(migration.name, "up", nextBatch);
6209
- const startedAt = Date.now();
6210
- await this.connection.getLogger()?.onMigrationStart?.(log);
6211
- try {
6208
+ await this.runMigrationLifecycle(log, async () => {
6212
6209
  await this.connection.transaction(async (tx) => {
6213
6210
  const context = this.createContext(tx);
6214
6211
  await migration.up(context);
@@ -6218,18 +6215,7 @@ var MigrationService = class {
6218
6215
  migrated_at: (/* @__PURE__ */ new Date()).toISOString()
6219
6216
  });
6220
6217
  });
6221
- await this.connection.getLogger()?.onMigrationSuccess?.({
6222
- ...log,
6223
- durationMs: Date.now() - startedAt
6224
- });
6225
- } catch (error) {
6226
- await this.connection.getLogger()?.onMigrationError?.({
6227
- ...log,
6228
- durationMs: Date.now() - startedAt,
6229
- error
6230
- });
6231
- throw error;
6232
- }
6218
+ });
6233
6219
  executed.push(migration);
6234
6220
  }
6235
6221
  return executed;
@@ -6252,9 +6238,7 @@ var MigrationService = class {
6252
6238
  continue;
6253
6239
  }
6254
6240
  const log = this.createMigrationLog(record.name, "down", record.batch);
6255
- const startedAt = Date.now();
6256
- await this.connection.getLogger()?.onMigrationStart?.(log);
6257
- try {
6241
+ await this.runMigrationLifecycle(log, async () => {
6258
6242
  await this.connection.transaction(async (tx) => {
6259
6243
  const context = this.createContext(tx);
6260
6244
  if (migration.down) {
@@ -6262,18 +6246,7 @@ var MigrationService = class {
6262
6246
  }
6263
6247
  await new TableQueryBuilder(migrationsTable, tx).where("name", record.name).delete();
6264
6248
  });
6265
- await this.connection.getLogger()?.onMigrationSuccess?.({
6266
- ...log,
6267
- durationMs: Date.now() - startedAt
6268
- });
6269
- } catch (error) {
6270
- await this.connection.getLogger()?.onMigrationError?.({
6271
- ...log,
6272
- durationMs: Date.now() - startedAt,
6273
- error
6274
- });
6275
- throw error;
6276
- }
6249
+ });
6277
6250
  rolledBack.push(migration);
6278
6251
  }
6279
6252
  return rolledBack;
@@ -6312,6 +6285,25 @@ var MigrationService = class {
6312
6285
  schema: createSchemaService(connection)
6313
6286
  };
6314
6287
  }
6288
+ async runMigrationLifecycle(log, callback) {
6289
+ const startedAt = Date.now();
6290
+ await this.connection.getLogger()?.onMigrationStart?.(log);
6291
+ try {
6292
+ const result = await callback();
6293
+ await this.connection.getLogger()?.onMigrationSuccess?.({
6294
+ ...log,
6295
+ durationMs: Date.now() - startedAt
6296
+ });
6297
+ return result;
6298
+ } catch (error) {
6299
+ await this.connection.getLogger()?.onMigrationError?.({
6300
+ ...log,
6301
+ durationMs: Date.now() - startedAt,
6302
+ error
6303
+ });
6304
+ throw error;
6305
+ }
6306
+ }
6315
6307
  normalizeMigratedAt(value) {
6316
6308
  const normalized = value instanceof Date ? value : new Date(value);
6317
6309
  if (Number.isNaN(normalized.getTime())) {
@@ -6412,15 +6404,101 @@ function unsafeSql(sql, bindings = [], source) {
6412
6404
  };
6413
6405
  }
6414
6406
 
6407
+ // src/model/ModelRegistry.ts
6408
+ function resolveDefinition(reference) {
6409
+ return "definition" in reference ? reference.definition : reference;
6410
+ }
6411
+ function isMissingGeneratedSchemaModelError(error) {
6412
+ return error instanceof Error && error.message.includes("is not present in the generated schema registry");
6413
+ }
6414
+ function tryGetDefinitionTableName(definition) {
6415
+ try {
6416
+ return definition.table.tableName;
6417
+ } catch (error) {
6418
+ if (isMissingGeneratedSchemaModelError(error)) {
6419
+ return void 0;
6420
+ }
6421
+ throw error;
6422
+ }
6423
+ }
6424
+ function definitionsReferToSameModel(left, right) {
6425
+ if (left === right) {
6426
+ return true;
6427
+ }
6428
+ const leftTableName = tryGetDefinitionTableName(left);
6429
+ const rightTableName = tryGetDefinitionTableName(right);
6430
+ if (!leftTableName || !rightTableName) {
6431
+ return left.name === right.name && left.primaryKey === right.primaryKey && left.morphClass === right.morphClass;
6432
+ }
6433
+ return leftTableName === rightTableName && left.primaryKey === right.primaryKey && left.morphClass === right.morphClass;
6434
+ }
6435
+ function getGlobalModels() {
6436
+ const runtime = globalThis;
6437
+ runtime.__holoDbGlobalModels__ ??= /* @__PURE__ */ new Map();
6438
+ return runtime.__holoDbGlobalModels__;
6439
+ }
6440
+ var ModelRegistry = class {
6441
+ models = /* @__PURE__ */ new Map();
6442
+ register(reference) {
6443
+ const definition = resolveDefinition(reference);
6444
+ const existing = this.models.get(definition.name);
6445
+ if (existing && !definitionsReferToSameModel(existing, definition)) {
6446
+ throw new DatabaseError(`Model "${definition.name}" is already registered.`, "DUPLICATE_MODEL");
6447
+ }
6448
+ this.models.set(definition.name, definition);
6449
+ return definition;
6450
+ }
6451
+ has(name) {
6452
+ return this.models.has(name);
6453
+ }
6454
+ get(name) {
6455
+ return this.models.get(name);
6456
+ }
6457
+ list() {
6458
+ return [...this.models.values()];
6459
+ }
6460
+ clear() {
6461
+ this.models.clear();
6462
+ }
6463
+ };
6464
+ function createModelRegistry() {
6465
+ return new ModelRegistry();
6466
+ }
6467
+ function registerGlobalModel(reference) {
6468
+ const definition = resolveDefinition(reference);
6469
+ const globalModels = getGlobalModels();
6470
+ const existing = globalModels.get(definition.name);
6471
+ if (existing) {
6472
+ const existingDefinition = resolveDefinition(existing);
6473
+ if (!definitionsReferToSameModel(existingDefinition, definition)) {
6474
+ throw new DatabaseError(`Model "${definition.name}" is already registered globally.`, "DUPLICATE_MODEL");
6475
+ }
6476
+ return existing;
6477
+ }
6478
+ globalModels.set(definition.name, reference);
6479
+ return reference;
6480
+ }
6481
+ function getGlobalModel(name) {
6482
+ return getGlobalModels().get(name);
6483
+ }
6484
+ function resetGlobalModelRegistry() {
6485
+ getGlobalModels().clear();
6486
+ }
6487
+
6415
6488
  // src/model/morphRegistry.ts
6416
- var morphRegistry = /* @__PURE__ */ new Map();
6489
+ function getMorphRegistry() {
6490
+ const runtime = globalThis;
6491
+ runtime.__holoDbMorphRegistry__ ??= /* @__PURE__ */ new Map();
6492
+ return runtime.__holoDbMorphRegistry__;
6493
+ }
6417
6494
  function registerMorphModel(type, reference) {
6418
- morphRegistry.set(type, reference);
6495
+ getMorphRegistry().set(type, reference);
6419
6496
  }
6420
6497
  function resolveMorphModel(type) {
6421
- return morphRegistry.get(type);
6498
+ return getMorphRegistry().get(type);
6422
6499
  }
6423
6500
  function resolveMorphSelector(label) {
6501
+ const morphRegistry = getMorphRegistry();
6424
6502
  const exact = morphRegistry.get(label);
6425
6503
  if (exact) {
6426
6504
  return exact;
@@ -6440,10 +6518,10 @@ function resolveMorphSelector(label) {
6440
6518
  return void 0;
6441
6519
  }
6442
6520
  function listMorphModels() {
6443
- return [...morphRegistry.values()];
6521
+ return [...getMorphRegistry().values()];
6444
6522
  }
6445
6523
  function resetMorphRegistry() {
6446
- morphRegistry.clear();
6524
+ getMorphRegistry().clear();
6447
6525
  }
6448
6526
 
6449
6527
  // src/facade/DB.ts
@@ -6505,13 +6583,11 @@ function configureDB(manager) {
6505
6583
  }
6506
6584
  function resetDB() {
6507
6585
  DB.reset();
6586
+ resetGlobalModelRegistry();
6508
6587
  resetMorphRegistry();
6509
6588
  }
6510
6589
 
6511
- // src/model/Entity.ts
6512
- function isEntity(value) {
6513
- return value instanceof EntityBase;
6514
- }
6590
+ // src/model/entityRuntime.ts
6515
6591
  function valuesAreEqual(left, right) {
6516
6592
  if (Object.is(left, right)) {
6517
6593
  return true;
@@ -6534,15 +6610,76 @@ function valuesAreEqual(left, right) {
6534
6610
  return left.length === right.length && left.every((value, index) => valuesAreEqual(value, right[index]));
6535
6611
  }
6536
6612
  if (left && right && typeof left === "object" && typeof right === "object" && Object.getPrototypeOf(left) === Object.getPrototypeOf(right)) {
6537
- const leftEntries = Object.entries(left);
6538
- const rightEntries = Object.entries(right);
6539
- if (leftEntries.length !== rightEntries.length) {
6613
+ const leftRecord = left;
6614
+ const rightRecord = right;
6615
+ const leftKeys = Object.keys(leftRecord);
6616
+ const rightKeys = Object.keys(rightRecord);
6617
+ if (leftKeys.length !== rightKeys.length) {
6540
6618
  return false;
6541
6619
  }
6542
- return leftEntries.every(([key, value]) => valuesAreEqual(value, right[key]));
6620
+ if (leftKeys.some((key) => !Object.prototype.hasOwnProperty.call(rightRecord, key)) || rightKeys.some((key) => !Object.prototype.hasOwnProperty.call(leftRecord, key))) {
6621
+ return false;
6622
+ }
6623
+ return leftKeys.every((key) => valuesAreEqual(leftRecord[key], rightRecord[key]));
6543
6624
  }
6544
6625
  return false;
6545
6626
  }
6627
+ function initializeEntityModelProperties(entity, repo) {
6628
+ const columns = repo?.definition?.table?.columns;
6629
+ if (columns && typeof columns === "object") {
6630
+ for (const key of Object.keys(columns)) {
6631
+ if (key in entity) {
6632
+ continue;
6633
+ }
6634
+ Object.defineProperty(entity, key, {
6635
+ configurable: true,
6636
+ enumerable: true,
6637
+ get: () => entity.get(key),
6638
+ set: (value) => {
6639
+ entity.set(key, value);
6640
+ }
6641
+ });
6642
+ }
6643
+ }
6644
+ const relationNames = typeof repo?.getRelationNames === "function" ? repo.getRelationNames() : Object.keys(repo?.definition?.relations ?? {});
6645
+ for (const key of relationNames) {
6646
+ if (key in entity) {
6647
+ continue;
6648
+ }
6649
+ Object.defineProperty(entity, key, {
6650
+ configurable: true,
6651
+ enumerable: false,
6652
+ get: () => {
6653
+ if (entity.hasRelation(key)) {
6654
+ return entity.getRelation(key);
6655
+ }
6656
+ const relationMethod = (() => entity.relation(key));
6657
+ const loadRelation = () => {
6658
+ if (typeof repo?.resolveRelationProperty !== "function") {
6659
+ return Promise.resolve(void 0);
6660
+ }
6661
+ try {
6662
+ return Promise.resolve(repo.resolveRelationProperty(entity, key));
6663
+ } catch (error) {
6664
+ return Promise.reject(error);
6665
+ }
6666
+ };
6667
+ relationMethod.then = (onFulfilled, onRejected) => loadRelation().then(onFulfilled, onRejected);
6668
+ relationMethod.catch = (onRejected) => loadRelation().catch(onRejected);
6669
+ relationMethod.finally = (onFinally) => loadRelation().finally(onFinally);
6670
+ return relationMethod;
6671
+ },
6672
+ set: (value) => {
6673
+ entity.setRelation(key, value);
6674
+ }
6675
+ });
6676
+ }
6677
+ }
6678
+
6679
+ // src/model/Entity.ts
6680
+ function isEntity(value) {
6681
+ return value instanceof EntityBase;
6682
+ }
6546
6683
  var EntityBase = class _EntityBase {
6547
6684
  constructor(repository, attributes, exists = true) {
6548
6685
  this.repository = repository;
@@ -6551,7 +6688,7 @@ var EntityBase = class _EntityBase {
6551
6688
  this.changes = exists ? {} : { ...attributes };
6552
6689
  this.persisted = exists;
6553
6690
  this.relations = {};
6554
- this.initializeModelProperties();
6691
+ initializeEntityModelProperties(this, this.getRepositoryRuntime());
6555
6692
  }
6556
6693
  attributes;
6557
6694
  original;
@@ -6613,6 +6750,10 @@ var EntityBase = class _EntityBase {
6613
6750
  Object.assign(this.attributes, values);
6614
6751
  return this;
6615
6752
  }
6753
+ async update(values) {
6754
+ this.fill(values);
6755
+ return await this.save();
6756
+ }
6616
6757
  forceFill(values) {
6617
6758
  Object.assign(this.attributes, values);
6618
6759
  return this;
@@ -6744,6 +6885,75 @@ var EntityBase = class _EntityBase {
6744
6885
  Reflect.deleteProperty(this.relations, name);
6745
6886
  return this;
6746
6887
  }
6888
+ relation(name) {
6889
+ const repo = this.getRepositoryRuntime();
6890
+ const relationDef = typeof repo.getRelationDefinition === "function" ? repo.getRelationDefinition(name) : repo.definition.relations[name];
6891
+ if (!relationDef) {
6892
+ throw new Error(`Relation '${name}' is not defined on this model`);
6893
+ }
6894
+ if (relationDef.kind === "belongsTo") {
6895
+ return {
6896
+ associate: (related) => {
6897
+ this.associate(name, related);
6898
+ },
6899
+ dissociate: () => {
6900
+ this.dissociate(name);
6901
+ }
6902
+ };
6903
+ }
6904
+ if (relationDef.kind === "hasOne" || relationDef.kind === "hasOneOfMany" || relationDef.kind === "hasOneThrough" || relationDef.kind === "morphOne" || relationDef.kind === "morphOneOfMany") {
6905
+ return {
6906
+ create: async (values) => {
6907
+ return this.createRelated(name, values);
6908
+ },
6909
+ save: async (related) => {
6910
+ return this.saveRelated(name, related);
6911
+ }
6912
+ };
6913
+ }
6914
+ if (relationDef.kind === "hasMany" || relationDef.kind === "hasManyThrough" || relationDef.kind === "morphMany") {
6915
+ return {
6916
+ create: async (values) => {
6917
+ return this.createRelated(name, values);
6918
+ },
6919
+ createMany: async (values) => {
6920
+ return this.createManyRelated(name, values);
6921
+ },
6922
+ save: async (related) => {
6923
+ return this.saveRelated(name, related);
6924
+ },
6925
+ saveMany: async (related) => {
6926
+ return this.saveManyRelated(name, related);
6927
+ }
6928
+ };
6929
+ }
6930
+ if (relationDef.kind === "belongsToMany" || relationDef.kind === "morphToMany" || relationDef.kind === "morphedByMany") {
6931
+ return {
6932
+ attach: async (ids, attributes) => {
6933
+ await this.attach(name, ids, attributes ?? {});
6934
+ },
6935
+ detach: async (ids) => {
6936
+ return this.detach(name, ids);
6937
+ },
6938
+ sync: async (ids) => {
6939
+ return this.sync(name, ids);
6940
+ },
6941
+ toggle: async (ids) => {
6942
+ return this.toggle(name, ids);
6943
+ },
6944
+ updateExistingPivot: async (id, attributes) => {
6945
+ return this.updateExistingPivot(name, id, attributes);
6946
+ },
6947
+ create: async (values) => {
6948
+ return this.createRelated(name, values);
6949
+ },
6950
+ save: async (related) => {
6951
+ return this.saveRelated(name, related);
6952
+ }
6953
+ };
6954
+ }
6955
+ return {};
6956
+ }
6747
6957
  toJSON() {
6748
6958
  const repo = this.getRepositoryRuntime();
6749
6959
  if (typeof repo.serializeEntity === "function") {
@@ -6751,19 +6961,68 @@ var EntityBase = class _EntityBase {
6751
6961
  }
6752
6962
  return this.toAttributes();
6753
6963
  }
6754
- async save() {
6755
- const repo = this.getRepositoryRuntime();
6756
- if (typeof repo.saveEntity !== "function") {
6757
- throw new HydrationError("The bound repository cannot persist entities.");
6758
- }
6759
- const pendingChanges = this.persisted ? this.getDirty() : this.toAttributes();
6760
- const persisted = await repo.saveEntity(this);
6964
+ applyPersistedEntity(persisted, changes) {
6761
6965
  this.attributes = { ...persisted.toAttributes() };
6762
6966
  this.original = { ...persisted.toAttributes() };
6763
- this.changes = { ...pendingChanges };
6967
+ this.changes = { ...changes };
6764
6968
  this.persisted = true;
6969
+ }
6970
+ async persistEntity(methodName, errorMessage) {
6971
+ const repo = this.getRepositoryRuntime();
6972
+ const method = repo[methodName];
6973
+ if (typeof method !== "function") {
6974
+ throw new HydrationError(errorMessage);
6975
+ }
6976
+ const pendingChanges = this.persisted ? this.getDirty() : this.toAttributes();
6977
+ const persisted = await method.call(repo, this);
6978
+ this.applyPersistedEntity(persisted, pendingChanges);
6979
+ return this;
6980
+ }
6981
+ async deletePersistedEntity(methodName, errorMessage) {
6982
+ if (!this.persisted) {
6983
+ throw new HydrationError("Cannot delete an entity that has not been persisted yet.");
6984
+ }
6985
+ const repo = this.getRepositoryRuntime();
6986
+ const method = repo[methodName];
6987
+ if (typeof method !== "function") {
6988
+ throw new HydrationError(errorMessage);
6989
+ }
6990
+ await method.call(repo, this);
6991
+ this.persisted = typeof repo.shouldKeepEntityPersistedOnDelete === "function" ? repo.shouldKeepEntityPersistedOnDelete(this) : false;
6992
+ }
6993
+ async restoreEntity(methodName, errorMessage) {
6994
+ const repo = this.getRepositoryRuntime();
6995
+ const method = repo[methodName];
6996
+ if (typeof method !== "function") {
6997
+ throw new HydrationError(errorMessage);
6998
+ }
6999
+ const restored = await method.call(repo, this);
7000
+ this.applyPersistedEntity(restored, restored.getChanges());
7001
+ return this;
7002
+ }
7003
+ async forceDeletePersistedEntity(methodName, errorMessage) {
7004
+ if (!this.persisted) {
7005
+ throw new HydrationError("Cannot force-delete an entity that has not been persisted yet.");
7006
+ }
7007
+ const repo = this.getRepositoryRuntime();
7008
+ const method = repo[methodName];
7009
+ if (typeof method !== "function") {
7010
+ throw new HydrationError(errorMessage);
7011
+ }
7012
+ await method.call(repo, this);
7013
+ this.persisted = false;
7014
+ }
7015
+ async loadAggregateDefinitions(aggregates) {
7016
+ const repo = this.getRepositoryRuntime();
7017
+ if (typeof repo.loadRelationAggregates !== "function") {
7018
+ throw new HydrationError("The bound repository cannot load relation aggregates.");
7019
+ }
7020
+ await repo.loadRelationAggregates([this], aggregates);
6765
7021
  return this;
6766
7022
  }
7023
+ async save() {
7024
+ return this.persistEntity("saveEntity", "The bound repository cannot persist entities.");
7025
+ }
6767
7026
  async push() {
6768
7027
  const repo = this.getRepositoryRuntime();
6769
7028
  if (typeof repo.getRelationDefinition !== "function") {
@@ -6801,85 +7060,25 @@ var EntityBase = class _EntityBase {
6801
7060
  return this;
6802
7061
  }
6803
7062
  async saveQuietly() {
6804
- const repo = this.getRepositoryRuntime();
6805
- if (typeof repo.saveEntityQuietly !== "function") {
6806
- throw new HydrationError("The bound repository cannot persist entities quietly.");
6807
- }
6808
- const pendingChanges = this.persisted ? this.getDirty() : this.toAttributes();
6809
- const persisted = await repo.saveEntityQuietly(this);
6810
- this.attributes = { ...persisted.toAttributes() };
6811
- this.original = { ...persisted.toAttributes() };
6812
- this.changes = { ...pendingChanges };
6813
- this.persisted = true;
6814
- return this;
7063
+ return this.persistEntity("saveEntityQuietly", "The bound repository cannot persist entities quietly.");
6815
7064
  }
6816
7065
  async delete() {
6817
- if (!this.persisted) {
6818
- throw new HydrationError("Cannot delete an entity that has not been persisted yet.");
6819
- }
6820
- const repo = this.getRepositoryRuntime();
6821
- if (typeof repo.deleteEntity !== "function") {
6822
- throw new HydrationError("The bound repository cannot delete entities.");
6823
- }
6824
- await repo.deleteEntity(this);
6825
- this.persisted = typeof repo.shouldKeepEntityPersistedOnDelete === "function" ? repo.shouldKeepEntityPersistedOnDelete(this) : false;
7066
+ await this.deletePersistedEntity("deleteEntity", "The bound repository cannot delete entities.");
6826
7067
  }
6827
7068
  async deleteQuietly() {
6828
- if (!this.persisted) {
6829
- throw new HydrationError("Cannot delete an entity that has not been persisted yet.");
6830
- }
6831
- const repo = this.getRepositoryRuntime();
6832
- if (typeof repo.deleteEntityQuietly !== "function") {
6833
- throw new HydrationError("The bound repository cannot delete entities quietly.");
6834
- }
6835
- await repo.deleteEntityQuietly(this);
6836
- this.persisted = typeof repo.shouldKeepEntityPersistedOnDelete === "function" ? repo.shouldKeepEntityPersistedOnDelete(this) : false;
7069
+ await this.deletePersistedEntity("deleteEntityQuietly", "The bound repository cannot delete entities quietly.");
6837
7070
  }
6838
7071
  async restore() {
6839
- const repo = this.getRepositoryRuntime();
6840
- if (typeof repo.restoreEntity !== "function") {
6841
- throw new HydrationError("The bound repository cannot restore entities.");
6842
- }
6843
- const restored = await repo.restoreEntity(this);
6844
- this.attributes = { ...restored.toAttributes() };
6845
- this.original = { ...restored.toAttributes() };
6846
- this.changes = { ...restored.getChanges() };
6847
- this.persisted = true;
6848
- return this;
7072
+ return this.restoreEntity("restoreEntity", "The bound repository cannot restore entities.");
6849
7073
  }
6850
7074
  async restoreQuietly() {
6851
- const repo = this.getRepositoryRuntime();
6852
- if (typeof repo.restoreEntityQuietly !== "function") {
6853
- throw new HydrationError("The bound repository cannot restore entities quietly.");
6854
- }
6855
- const restored = await repo.restoreEntityQuietly(this);
6856
- this.attributes = { ...restored.toAttributes() };
6857
- this.original = { ...restored.toAttributes() };
6858
- this.changes = { ...restored.getChanges() };
6859
- this.persisted = true;
6860
- return this;
7075
+ return this.restoreEntity("restoreEntityQuietly", "The bound repository cannot restore entities quietly.");
6861
7076
  }
6862
7077
  async forceDelete() {
6863
- if (!this.persisted) {
6864
- throw new HydrationError("Cannot force-delete an entity that has not been persisted yet.");
6865
- }
6866
- const repo = this.getRepositoryRuntime();
6867
- if (typeof repo.forceDeleteEntity !== "function") {
6868
- throw new HydrationError("The bound repository cannot force-delete entities.");
6869
- }
6870
- await repo.forceDeleteEntity(this);
6871
- this.persisted = false;
7078
+ await this.forceDeletePersistedEntity("forceDeleteEntity", "The bound repository cannot force-delete entities.");
6872
7079
  }
6873
7080
  async forceDeleteQuietly() {
6874
- if (!this.persisted) {
6875
- throw new HydrationError("Cannot force-delete an entity that has not been persisted yet.");
6876
- }
6877
- const repo = this.getRepositoryRuntime();
6878
- if (typeof repo.forceDeleteEntityQuietly !== "function") {
6879
- throw new HydrationError("The bound repository cannot force-delete entities quietly.");
6880
- }
6881
- await repo.forceDeleteEntityQuietly(this);
6882
- this.persisted = false;
7081
+ await this.forceDeletePersistedEntity("forceDeleteEntityQuietly", "The bound repository cannot force-delete entities quietly.");
6883
7082
  }
6884
7083
  async fresh() {
6885
7084
  const repo = this.getRepositoryRuntime();
@@ -6936,52 +7135,22 @@ var EntityBase = class _EntityBase {
6936
7135
  return this;
6937
7136
  }
6938
7137
  async loadCount(...relations) {
6939
- const repo = this.getRepositoryRuntime();
6940
- if (typeof repo.loadRelationAggregates !== "function") {
6941
- throw new HydrationError("The bound repository cannot load relation aggregates.");
6942
- }
6943
- await repo.loadRelationAggregates([this], relations.map((relation) => ({ relation, kind: "count" })));
6944
- return this;
7138
+ return this.loadAggregateDefinitions(relations.map((relation) => ({ relation, kind: "count" })));
6945
7139
  }
6946
7140
  async loadExists(...relations) {
6947
- const repo = this.getRepositoryRuntime();
6948
- if (typeof repo.loadRelationAggregates !== "function") {
6949
- throw new HydrationError("The bound repository cannot load relation aggregates.");
6950
- }
6951
- await repo.loadRelationAggregates([this], relations.map((relation) => ({ relation, kind: "exists" })));
6952
- return this;
7141
+ return this.loadAggregateDefinitions(relations.map((relation) => ({ relation, kind: "exists" })));
6953
7142
  }
6954
7143
  async loadSum(relation, column2) {
6955
- const repo = this.getRepositoryRuntime();
6956
- if (typeof repo.loadRelationAggregates !== "function") {
6957
- throw new HydrationError("The bound repository cannot load relation aggregates.");
6958
- }
6959
- await repo.loadRelationAggregates([this], [{ relation, kind: "sum", column: column2 }]);
6960
- return this;
7144
+ return this.loadAggregateDefinitions([{ relation, kind: "sum", column: column2 }]);
6961
7145
  }
6962
7146
  async loadAvg(relation, column2) {
6963
- const repo = this.getRepositoryRuntime();
6964
- if (typeof repo.loadRelationAggregates !== "function") {
6965
- throw new HydrationError("The bound repository cannot load relation aggregates.");
6966
- }
6967
- await repo.loadRelationAggregates([this], [{ relation, kind: "avg", column: column2 }]);
6968
- return this;
7147
+ return this.loadAggregateDefinitions([{ relation, kind: "avg", column: column2 }]);
6969
7148
  }
6970
7149
  async loadMin(relation, column2) {
6971
- const repo = this.getRepositoryRuntime();
6972
- if (typeof repo.loadRelationAggregates !== "function") {
6973
- throw new HydrationError("The bound repository cannot load relation aggregates.");
6974
- }
6975
- await repo.loadRelationAggregates([this], [{ relation, kind: "min", column: column2 }]);
6976
- return this;
7150
+ return this.loadAggregateDefinitions([{ relation, kind: "min", column: column2 }]);
6977
7151
  }
6978
7152
  async loadMax(relation, column2) {
6979
- const repo = this.getRepositoryRuntime();
6980
- if (typeof repo.loadRelationAggregates !== "function") {
6981
- throw new HydrationError("The bound repository cannot load relation aggregates.");
6982
- }
6983
- await repo.loadRelationAggregates([this], [{ relation, kind: "max", column: column2 }]);
6984
- return this;
7153
+ return this.loadAggregateDefinitions([{ relation, kind: "max", column: column2 }]);
6985
7154
  }
6986
7155
  associate(relation, related) {
6987
7156
  const repo = this.getRepositoryRuntime();
@@ -7108,47 +7277,6 @@ var EntityBase = class _EntityBase {
7108
7277
  this.forgetRelation(relation);
7109
7278
  return result;
7110
7279
  }
7111
- initializeModelProperties() {
7112
- const repo = this.getRepositoryRuntime();
7113
- const columns = repo?.definition?.table?.columns;
7114
- if (columns && typeof columns === "object") {
7115
- for (const key of Object.keys(columns)) {
7116
- if (key in this) {
7117
- continue;
7118
- }
7119
- Object.defineProperty(this, key, {
7120
- configurable: true,
7121
- enumerable: true,
7122
- get: () => this.get(key),
7123
- set: (value) => {
7124
- this.set(key, value);
7125
- }
7126
- });
7127
- }
7128
- }
7129
- const relationNames = typeof repo?.getRelationNames === "function" ? repo.getRelationNames() : Object.keys(repo?.definition?.relations ?? {});
7130
- for (const key of relationNames) {
7131
- if (key in this) {
7132
- continue;
7133
- }
7134
- Object.defineProperty(this, key, {
7135
- configurable: true,
7136
- enumerable: false,
7137
- get: () => {
7138
- if (this.hasRelation(key)) {
7139
- return this.getRelation(key);
7140
- }
7141
- if (typeof repo?.resolveRelationProperty === "function") {
7142
- return repo.resolveRelationProperty(this, key);
7143
- }
7144
- return void 0;
7145
- },
7146
- set: (value) => {
7147
- this.setRelation(key, value);
7148
- }
7149
- });
7150
- }
7151
- }
7152
7280
  };
7153
7281
  var Entity = EntityBase;
7154
7282
 
@@ -7156,6 +7284,14 @@ var Entity = EntityBase;
7156
7284
  function createModelCollection(items) {
7157
7285
  const collection = [...items];
7158
7286
  const getRepository = (entity) => entity.getRepository();
7287
+ const loadRelationAggregates = async (aggregates) => {
7288
+ const first = collection[0];
7289
+ if (!first || aggregates.length === 0) {
7290
+ return collection;
7291
+ }
7292
+ await getRepository(first).loadRelationAggregates(collection, aggregates);
7293
+ return collection;
7294
+ };
7159
7295
  const methods = {
7160
7296
  modelKeys() {
7161
7297
  return collection.map((entity) => {
@@ -7171,6 +7307,9 @@ function createModelCollection(items) {
7171
7307
  const repo = getRepository(first);
7172
7308
  return repo.query().where(repo.definition.primaryKey, "in", this.modelKeys());
7173
7309
  },
7310
+ toJSON() {
7311
+ return collection.map((item) => item.toJSON());
7312
+ },
7174
7313
  async load(...relations) {
7175
7314
  const first = collection[0];
7176
7315
  if (!first || relations.length === 0) {
@@ -7202,58 +7341,22 @@ function createModelCollection(items) {
7202
7341
  return collection;
7203
7342
  },
7204
7343
  async loadCount(...relations) {
7205
- const first = collection[0];
7206
- if (!first || relations.length === 0) {
7207
- return collection;
7208
- }
7209
- const repo = getRepository(first);
7210
- await repo.loadRelationAggregates(collection, relations.map((relation) => ({ relation, kind: "count" })));
7211
- return collection;
7344
+ return loadRelationAggregates(relations.map((relation) => ({ relation, kind: "count" })));
7212
7345
  },
7213
7346
  async loadExists(...relations) {
7214
- const first = collection[0];
7215
- if (!first || relations.length === 0) {
7216
- return collection;
7217
- }
7218
- const repo = getRepository(first);
7219
- await repo.loadRelationAggregates(collection, relations.map((relation) => ({ relation, kind: "exists" })));
7220
- return collection;
7347
+ return loadRelationAggregates(relations.map((relation) => ({ relation, kind: "exists" })));
7221
7348
  },
7222
7349
  async loadSum(relation, column2) {
7223
- const first = collection[0];
7224
- if (!first || !relation) {
7225
- return collection;
7226
- }
7227
- const repo = getRepository(first);
7228
- await repo.loadRelationAggregates(collection, [{ relation, kind: "sum", column: column2 }]);
7229
- return collection;
7350
+ return loadRelationAggregates(relation ? [{ relation, kind: "sum", column: column2 }] : []);
7230
7351
  },
7231
7352
  async loadAvg(relation, column2) {
7232
- const first = collection[0];
7233
- if (!first || !relation) {
7234
- return collection;
7235
- }
7236
- const repo = getRepository(first);
7237
- await repo.loadRelationAggregates(collection, [{ relation, kind: "avg", column: column2 }]);
7238
- return collection;
7353
+ return loadRelationAggregates(relation ? [{ relation, kind: "avg", column: column2 }] : []);
7239
7354
  },
7240
7355
  async loadMin(relation, column2) {
7241
- const first = collection[0];
7242
- if (!first || !relation) {
7243
- return collection;
7244
- }
7245
- const repo = getRepository(first);
7246
- await repo.loadRelationAggregates(collection, [{ relation, kind: "min", column: column2 }]);
7247
- return collection;
7356
+ return loadRelationAggregates(relation ? [{ relation, kind: "min", column: column2 }] : []);
7248
7357
  },
7249
7358
  async loadMax(relation, column2) {
7250
- const first = collection[0];
7251
- if (!first || !relation) {
7252
- return collection;
7253
- }
7254
- const repo = getRepository(first);
7255
- await repo.loadRelationAggregates(collection, [{ relation, kind: "max", column: column2 }]);
7256
- return collection;
7359
+ return loadRelationAggregates(relation ? [{ relation, kind: "max", column: column2 }] : []);
7257
7360
  },
7258
7361
  async fresh() {
7259
7362
  const refreshed = await Promise.all(
@@ -7361,118 +7464,65 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
7361
7464
  getTableQueryBuilder() {
7362
7465
  return this.tableQuery;
7363
7466
  }
7467
+ collectNestedPredicates(callback) {
7468
+ const nested = new _ModelQueryBuilder(
7469
+ this.repository,
7470
+ new TableQueryBuilder(this.repository.definition.table, this.getConnection())
7471
+ );
7472
+ const callbackResult = callback(nested);
7473
+ const result = callbackResult instanceof _ModelQueryBuilder ? callbackResult : nested;
7474
+ return result.getTableQueryBuilder().getPlan().predicates;
7475
+ }
7476
+ replayNestedPredicates(query, predicates) {
7477
+ let next = query;
7478
+ for (const predicate of predicates) {
7479
+ next = new TableQueryBuilder(
7480
+ this.repository.definition.table,
7481
+ this.getConnection(),
7482
+ {
7483
+ ...next.getPlan(),
7484
+ predicates: Object.freeze([...next.getPlan().predicates, predicate])
7485
+ }
7486
+ );
7487
+ }
7488
+ return next;
7489
+ }
7364
7490
  from(table) {
7365
7491
  return this.clone(this.tableQuery.from(table));
7366
7492
  }
7367
7493
  where(columnOrCallback, operator, value) {
7368
7494
  if (typeof columnOrCallback === "function") {
7369
- const nested = new _ModelQueryBuilder(
7370
- this.repository,
7371
- new TableQueryBuilder(this.repository.definition.table, this.getConnection())
7372
- );
7373
- const callbackResult = columnOrCallback(nested);
7374
- const result = callbackResult instanceof _ModelQueryBuilder ? callbackResult : nested;
7375
- const predicates = result.getTableQueryBuilder().getPlan().predicates;
7495
+ const predicates = this.collectNestedPredicates(columnOrCallback);
7376
7496
  if (predicates.length === 0) {
7377
7497
  return this;
7378
7498
  }
7379
- return this.clone(this.tableQuery.where((query) => {
7380
- let next = query;
7381
- for (const predicate of predicates) {
7382
- next = new TableQueryBuilder(
7383
- this.repository.definition.table,
7384
- this.getConnection(),
7385
- {
7386
- ...next.getPlan(),
7387
- predicates: Object.freeze([...next.getPlan().predicates, predicate])
7388
- }
7389
- );
7390
- }
7391
- return next;
7392
- }));
7499
+ return this.clone(this.tableQuery.where((query) => this.replayNestedPredicates(query, predicates)));
7393
7500
  }
7394
7501
  return this.clone(this.tableQuery.where(columnOrCallback, operator, value));
7395
7502
  }
7396
7503
  orWhere(columnOrCallback, operator, value) {
7397
7504
  if (typeof columnOrCallback === "function") {
7398
- const nested = new _ModelQueryBuilder(
7399
- this.repository,
7400
- new TableQueryBuilder(this.repository.definition.table, this.getConnection())
7401
- );
7402
- const callbackResult = columnOrCallback(nested);
7403
- const result = callbackResult instanceof _ModelQueryBuilder ? callbackResult : nested;
7404
- const predicates = result.getTableQueryBuilder().getPlan().predicates;
7505
+ const predicates = this.collectNestedPredicates(columnOrCallback);
7405
7506
  if (predicates.length === 0) {
7406
7507
  return this;
7407
7508
  }
7408
- return this.clone(this.tableQuery.orWhere((query) => {
7409
- let next = query;
7410
- for (const predicate of predicates) {
7411
- next = new TableQueryBuilder(
7412
- this.repository.definition.table,
7413
- this.getConnection(),
7414
- {
7415
- ...next.getPlan(),
7416
- predicates: Object.freeze([...next.getPlan().predicates, predicate])
7417
- }
7418
- );
7419
- }
7420
- return next;
7421
- }));
7509
+ return this.clone(this.tableQuery.orWhere((query) => this.replayNestedPredicates(query, predicates)));
7422
7510
  }
7423
7511
  return this.clone(this.tableQuery.orWhere(columnOrCallback, operator, value));
7424
7512
  }
7425
7513
  whereNot(callback) {
7426
- const nested = new _ModelQueryBuilder(
7427
- this.repository,
7428
- new TableQueryBuilder(this.repository.definition.table, this.getConnection())
7429
- );
7430
- const callbackResult = callback(nested);
7431
- const result = callbackResult instanceof _ModelQueryBuilder ? callbackResult : nested;
7432
- const predicates = result.getTableQueryBuilder().getPlan().predicates;
7514
+ const predicates = this.collectNestedPredicates(callback);
7433
7515
  if (predicates.length === 0) {
7434
7516
  return this;
7435
7517
  }
7436
- return this.clone(this.tableQuery.whereNot((query) => {
7437
- let next = query;
7438
- for (const predicate of predicates) {
7439
- next = new TableQueryBuilder(
7440
- this.repository.definition.table,
7441
- this.getConnection(),
7442
- {
7443
- ...next.getPlan(),
7444
- predicates: Object.freeze([...next.getPlan().predicates, predicate])
7445
- }
7446
- );
7447
- }
7448
- return next;
7449
- }));
7518
+ return this.clone(this.tableQuery.whereNot((query) => this.replayNestedPredicates(query, predicates)));
7450
7519
  }
7451
7520
  orWhereNot(callback) {
7452
- const nested = new _ModelQueryBuilder(
7453
- this.repository,
7454
- new TableQueryBuilder(this.repository.definition.table, this.getConnection())
7455
- );
7456
- const callbackResult = callback(nested);
7457
- const result = callbackResult instanceof _ModelQueryBuilder ? callbackResult : nested;
7458
- const predicates = result.getTableQueryBuilder().getPlan().predicates;
7521
+ const predicates = this.collectNestedPredicates(callback);
7459
7522
  if (predicates.length === 0) {
7460
7523
  return this;
7461
7524
  }
7462
- return this.clone(this.tableQuery.orWhereNot((query) => {
7463
- let next = query;
7464
- for (const predicate of predicates) {
7465
- next = new TableQueryBuilder(
7466
- this.repository.definition.table,
7467
- this.getConnection(),
7468
- {
7469
- ...next.getPlan(),
7470
- predicates: Object.freeze([...next.getPlan().predicates, predicate])
7471
- }
7472
- );
7473
- }
7474
- return next;
7475
- }));
7525
+ return this.clone(this.tableQuery.orWhereNot((query) => this.replayNestedPredicates(query, predicates)));
7476
7526
  }
7477
7527
  whereExists(subquery) {
7478
7528
  return this.clone(this.tableQuery.whereExists(this.normalizeExistsSubquery(subquery)));
@@ -7846,10 +7896,16 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
7846
7896
  await this.repository.loadRelationAggregates(entities, this.aggregateLoads);
7847
7897
  return this.repository.createCollection(entities);
7848
7898
  }
7899
+ async getJson() {
7900
+ return (await this.get()).toJSON();
7901
+ }
7849
7902
  async first() {
7850
7903
  const [entity] = await this.limit(1).get();
7851
7904
  return entity;
7852
7905
  }
7906
+ async firstJson() {
7907
+ return (await this.first())?.toJSON();
7908
+ }
7853
7909
  async sole() {
7854
7910
  const entities = await this.limit(2).get();
7855
7911
  if (entities.length === 0) {
@@ -7860,6 +7916,9 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
7860
7916
  }
7861
7917
  return entities[0];
7862
7918
  }
7919
+ async soleJson() {
7920
+ return (await this.sole()).toJSON();
7921
+ }
7863
7922
  async paginate(perPage = 15, page = 1, options = {}) {
7864
7923
  assertPositiveInteger(perPage, "Per-page value", (message) => new HydrationError(message));
7865
7924
  assertPositiveInteger(page, "Page", (message) => new HydrationError(message));
@@ -7881,6 +7940,9 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
7881
7940
  hasMorePages: offset + data.length < total
7882
7941
  });
7883
7942
  }
7943
+ async paginateJson(perPage = 15, page = 1, options = {}) {
7944
+ return (await this.paginate(perPage, page, options)).toJSON();
7945
+ }
7884
7946
  async simplePaginate(perPage = 15, page = 1, options = {}) {
7885
7947
  assertPositiveInteger(perPage, "Per-page value", (message) => new HydrationError(message));
7886
7948
  assertPositiveInteger(page, "Page", (message) => new HydrationError(message));
@@ -7901,6 +7963,9 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
7901
7963
  hasMorePages
7902
7964
  });
7903
7965
  }
7966
+ async simplePaginateJson(perPage = 15, page = 1, options = {}) {
7967
+ return (await this.simplePaginate(perPage, page, options)).toJSON();
7968
+ }
7904
7969
  async cursorPaginate(perPage = 15, cursor = null, options = {}) {
7905
7970
  assertPositiveInteger(perPage, "Per-page value", (message) => new HydrationError(message));
7906
7971
  const cursorName = normalizePaginationParameterName(options.cursorName, "cursor", (message) => new HydrationError(message));
@@ -7917,6 +7982,9 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
7917
7982
  prevCursor: cursor
7918
7983
  });
7919
7984
  }
7985
+ async cursorPaginateJson(perPage = 15, cursor = null, options = {}) {
7986
+ return (await this.cursorPaginate(perPage, cursor, options)).toJSON();
7987
+ }
7920
7988
  async chunk(size, callback) {
7921
7989
  assertPositiveInteger(size, "Chunk size", (message) => new HydrationError(message));
7922
7990
  const entities = await this.getUnpaginatedEntities();
@@ -8062,6 +8130,9 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
8062
8130
  }
8063
8131
  return entity;
8064
8132
  }
8133
+ async findOrFailJson(value, column2) {
8134
+ return (await this.findOrFail(value, column2)).toJSON();
8135
+ }
8065
8136
  async update(values) {
8066
8137
  return this.tableQuery.update(this.repository.sanitizeWritePayload(values, "update"));
8067
8138
  }
@@ -8168,6 +8239,9 @@ var ModelQueryBuilder = class _ModelQueryBuilder {
8168
8239
  );
8169
8240
  }
8170
8241
  normalizeEagerLoads(first, second, rest = []) {
8242
+ if (Array.isArray(first)) {
8243
+ return first.map((relation) => ({ relation }));
8244
+ }
8171
8245
  if (typeof first === "string") {
8172
8246
  if (typeof second === "function") {
8173
8247
  return [{ relation: first, constraint: second }];
@@ -8432,6 +8506,13 @@ function resolveModelConnection(definition) {
8432
8506
  }
8433
8507
  return DB.connection(definition.connectionName);
8434
8508
  }
8509
+ function resolveActiveConnection(connection) {
8510
+ const active = connectionAsyncContext.getActive();
8511
+ if (active && active.connectionName === connection.getConnectionName() && active.connection.getContextId() === connection.getContextId()) {
8512
+ return active.connection;
8513
+ }
8514
+ return connection;
8515
+ }
8435
8516
  var ModelRepository = class _ModelRepository {
8436
8517
  constructor(definition, connection) {
8437
8518
  this.connection = connection;
@@ -8443,10 +8524,10 @@ var ModelRepository = class _ModelRepository {
8443
8524
  return new _ModelRepository(definition, connection ?? resolveModelConnection(definition));
8444
8525
  }
8445
8526
  getConnection() {
8446
- return this.connection;
8527
+ return resolveActiveConnection(this.connection);
8447
8528
  }
8448
8529
  getConnectionName() {
8449
- return this.connection.getConnectionName();
8530
+ return this.getConnection().getConnectionName();
8450
8531
  }
8451
8532
  getDeletedAtColumn() {
8452
8533
  return this.definition.deletedAtColumn;
@@ -8550,7 +8631,18 @@ var ModelRepository = class _ModelRepository {
8550
8631
  if (existing) {
8551
8632
  return existing;
8552
8633
  }
8553
- return this.create({ ...match, ...values });
8634
+ try {
8635
+ return await this.create({ ...match, ...values });
8636
+ } catch (error) {
8637
+ if (!isUniqueConstraintError(error)) {
8638
+ throw error;
8639
+ }
8640
+ const concurrent = await query.first();
8641
+ if (concurrent) {
8642
+ return concurrent;
8643
+ }
8644
+ throw error;
8645
+ }
8554
8646
  }
8555
8647
  make(values = {}) {
8556
8648
  return new Entity(this, this.sanitizeWritePayload(this.applyPendingAttributes(values), "create"), false);
@@ -9229,10 +9321,11 @@ var ModelRepository = class _ModelRepository {
9229
9321
  });
9230
9322
  }
9231
9323
  async runWriteUnit(callback) {
9232
- if (this.connection.getScope().kind !== "root") {
9324
+ const connection = this.getConnection();
9325
+ if (connection.getScope().kind !== "root") {
9233
9326
  return callback();
9234
9327
  }
9235
- return this.connection.transaction(async () => callback());
9328
+ return connection.transaction(async () => callback());
9236
9329
  }
9237
9330
  async forceDeleteEntityQuietly(entity) {
9238
9331
  return withoutModelEvents(() => this.forceDeleteEntity(entity));
@@ -9266,7 +9359,7 @@ var ModelRepository = class _ModelRepository {
9266
9359
  if (entries.length === 0) {
9267
9360
  return;
9268
9361
  }
9269
- await this.connection.transaction(async (tx) => {
9362
+ await this.getConnection().transaction(async (tx) => {
9270
9363
  const currentRows = await this.getPivotRows(context, tx, entries.map((entry) => entry.id));
9271
9364
  const currentMap = this.indexPivotRows(currentRows, this.getPivotRelatedIdColumn(context.relation));
9272
9365
  for (const entry of entries) {
@@ -9283,7 +9376,7 @@ var ModelRepository = class _ModelRepository {
9283
9376
  }
9284
9377
  async detachRelation(entity, relationName, ids) {
9285
9378
  const context = this.getPivotMutationContext(entity, relationName);
9286
- return this.connection.transaction(async (tx) => {
9379
+ return this.getConnection().transaction(async (tx) => {
9287
9380
  if (typeof ids === "undefined" || ids === null) {
9288
9381
  return this.deletePivotRows(context, tx);
9289
9382
  }
@@ -9300,7 +9393,7 @@ var ModelRepository = class _ModelRepository {
9300
9393
  this.assertValidPivotEntries(relationName, entries, context.relation);
9301
9394
  const desiredMap = new Map(entries.map((entry) => [String(entry.id), entry]));
9302
9395
  const result = { attached: [], detached: [], updated: [] };
9303
- await this.connection.transaction(async (tx) => {
9396
+ await this.getConnection().transaction(async (tx) => {
9304
9397
  const currentRows = await this.getPivotRows(context, tx);
9305
9398
  const currentMap = this.indexPivotRows(currentRows, this.getPivotRelatedIdColumn(context.relation));
9306
9399
  for (const entry of entries) {
@@ -9329,10 +9422,10 @@ var ModelRepository = class _ModelRepository {
9329
9422
  const context = this.getPivotMutationContext(entity, relationName);
9330
9423
  this.assertValidPivotAttributes(relationName, attributes, context.relation);
9331
9424
  if (Object.keys(attributes).length === 0) return 0;
9332
- const [existing] = await this.getPivotRows(context, this.connection, [id]);
9425
+ const [existing] = await this.getPivotRows(context, this.getConnection(), [id]);
9333
9426
  if (!existing) return 0;
9334
9427
  if (!this.pivotAttributesChanged(existing, attributes, context.relation)) return 0;
9335
- await this.updatePivotRow(context, this.connection, id, attributes);
9428
+ await this.updatePivotRow(context, this.getConnection(), id, attributes);
9336
9429
  return 1;
9337
9430
  }
9338
9431
  async toggleRelation(entity, relationName, ids) {
@@ -9343,7 +9436,7 @@ var ModelRepository = class _ModelRepository {
9343
9436
  if (entries.length === 0) {
9344
9437
  return result;
9345
9438
  }
9346
- await this.connection.transaction(async (tx) => {
9439
+ await this.getConnection().transaction(async (tx) => {
9347
9440
  const currentRows = await this.getPivotRows(context, tx, entries.map((entry) => entry.id));
9348
9441
  const currentMap = this.indexPivotRows(currentRows, this.getPivotRelatedIdColumn(context.relation));
9349
9442
  for (const entry of entries) {
@@ -9404,7 +9497,7 @@ var ModelRepository = class _ModelRepository {
9404
9497
  createQuery(applySoftDeletes, applyGlobalScopes, applyDefaultRelations, excludedGlobalScopes = /* @__PURE__ */ new Set()) {
9405
9498
  let query = new ModelQueryBuilder(
9406
9499
  this,
9407
- new TableQueryBuilder(this.definition.table, this.connection)
9500
+ new TableQueryBuilder(this.definition.table, this.getConnection())
9408
9501
  );
9409
9502
  const deletedAtColumn = this.getDeletedAtColumn();
9410
9503
  if (applySoftDeletes && deletedAtColumn) {
@@ -9554,7 +9647,7 @@ var ModelRepository = class _ModelRepository {
9554
9647
  }
9555
9648
  return;
9556
9649
  }
9557
- const pivotRows = await this.createBelongsToManyPivotQuery(relation, this.connection).where(relation.foreignPivotKey, "in", parentKeys).get();
9650
+ const pivotRows = await this.createBelongsToManyPivotQuery(relation, this.getConnection()).where(relation.foreignPivotKey, "in", parentKeys).get();
9558
9651
  if (pivotRows.length === 0) {
9559
9652
  for (const entity of entities) {
9560
9653
  entity.setRelation(relationName, []);
@@ -9731,17 +9824,17 @@ var ModelRepository = class _ModelRepository {
9731
9824
  }
9732
9825
  case "belongsToMany": {
9733
9826
  const related = this.resolveRelatedRepository(relation.related);
9734
- const pivotSubquery = this.createBelongsToManyPivotQuery(relation, this.connection).select(relation.relatedPivotKey).whereColumn(relation.foreignPivotKey, "=", this.qualifyParentColumn(relation.parentKey));
9827
+ const pivotSubquery = this.createBelongsToManyPivotQuery(relation, this.getConnection()).select(relation.relatedPivotKey).whereColumn(relation.foreignPivotKey, "=", this.qualifyParentColumn(relation.parentKey));
9735
9828
  return [this.applyRelationConstraint(relation, related, constraint).getTableQueryBuilder().whereInSub(relation.relatedKey, pivotSubquery)];
9736
9829
  }
9737
9830
  case "morphToMany": {
9738
9831
  const related = this.resolveRelatedRepository(relation.related);
9739
- const pivotSubquery = this.createMorphToManyPivotQuery(relation, this.connection).select(relation.foreignPivotKey).where(relation.morphTypeColumn, this.getMorphTypeValue()).whereColumn(relation.morphIdColumn, "=", this.qualifyParentColumn(relation.parentKey));
9832
+ const pivotSubquery = this.createMorphToManyPivotQuery(relation, this.getConnection()).select(relation.foreignPivotKey).where(relation.morphTypeColumn, this.getMorphTypeValue()).whereColumn(relation.morphIdColumn, "=", this.qualifyParentColumn(relation.parentKey));
9740
9833
  return [this.applyRelationConstraint(relation, related, constraint).getTableQueryBuilder().whereInSub(relation.relatedKey, pivotSubquery)];
9741
9834
  }
9742
9835
  case "morphedByMany": {
9743
9836
  const related = this.resolveRelatedRepository(relation.related);
9744
- const pivotSubquery = this.createMorphedByManyPivotQuery(relation, related.definition.morphClass, this.connection).select(relation.morphIdColumn).whereColumn(relation.foreignPivotKey, "=", this.qualifyParentColumn(relation.parentKey));
9837
+ const pivotSubquery = this.createMorphedByManyPivotQuery(relation, related.definition.morphClass, this.getConnection()).select(relation.morphIdColumn).whereColumn(relation.foreignPivotKey, "=", this.qualifyParentColumn(relation.parentKey));
9745
9838
  return [this.applyRelationConstraint(relation, related, constraint).getTableQueryBuilder().whereInSub(relation.relatedKey, pivotSubquery)];
9746
9839
  }
9747
9840
  }
@@ -10305,7 +10398,7 @@ var ModelRepository = class _ModelRepository {
10305
10398
  if (parentKeys.length === 0) {
10306
10399
  return /* @__PURE__ */ new Set();
10307
10400
  }
10308
- const pivotRows = await this.createBelongsToManyPivotQuery(relation, this.connection).where(relation.foreignPivotKey, "in", parentKeys).get();
10401
+ const pivotRows = await this.createBelongsToManyPivotQuery(relation, this.getConnection()).where(relation.foreignPivotKey, "in", parentKeys).get();
10309
10402
  const relatedIds = [...new Set(
10310
10403
  pivotRows.map((row) => row[relation.relatedPivotKey]).filter((value) => value !== null && typeof value !== "undefined")
10311
10404
  )];
@@ -10338,7 +10431,7 @@ var ModelRepository = class _ModelRepository {
10338
10431
  if (parentKeys.length === 0) {
10339
10432
  return /* @__PURE__ */ new Map();
10340
10433
  }
10341
- const pivotRows = await this.createBelongsToManyPivotQuery(relation, this.connection).where(relation.foreignPivotKey, "in", parentKeys).get();
10434
+ const pivotRows = await this.createBelongsToManyPivotQuery(relation, this.getConnection()).where(relation.foreignPivotKey, "in", parentKeys).get();
10342
10435
  const relatedIds = [...new Set(
10343
10436
  pivotRows.map((row) => row[relation.relatedPivotKey]).filter((value) => value !== null && typeof value !== "undefined")
10344
10437
  )];
@@ -10381,7 +10474,7 @@ var ModelRepository = class _ModelRepository {
10381
10474
  if (parentKeys.length === 0) {
10382
10475
  return /* @__PURE__ */ new Map();
10383
10476
  }
10384
- const pivotRows = await this.createBelongsToManyPivotQuery(relation, this.connection).where(relation.foreignPivotKey, "in", parentKeys).get();
10477
+ const pivotRows = await this.createBelongsToManyPivotQuery(relation, this.getConnection()).where(relation.foreignPivotKey, "in", parentKeys).get();
10385
10478
  const relatedIds = [...new Set(
10386
10479
  pivotRows.map((row) => row[relation.relatedPivotKey]).filter((value) => value !== null && typeof value !== "undefined")
10387
10480
  )];
@@ -10414,7 +10507,7 @@ var ModelRepository = class _ModelRepository {
10414
10507
  if (parentKeys.length === 0) {
10415
10508
  return /* @__PURE__ */ new Map();
10416
10509
  }
10417
- const pivotRows = await this.createMorphToManyPivotQuery(relation, this.connection).where(relation.morphTypeColumn, this.getMorphTypeValue()).where(relation.morphIdColumn, "in", parentKeys).get();
10510
+ const pivotRows = await this.createMorphToManyPivotQuery(relation, this.getConnection()).where(relation.morphTypeColumn, this.getMorphTypeValue()).where(relation.morphIdColumn, "in", parentKeys).get();
10418
10511
  const relatedIds = [...new Set(
10419
10512
  pivotRows.map((row) => row[relation.foreignPivotKey]).filter((value) => value !== null && typeof value !== "undefined")
10420
10513
  )];
@@ -10448,7 +10541,7 @@ var ModelRepository = class _ModelRepository {
10448
10541
  return /* @__PURE__ */ new Map();
10449
10542
  }
10450
10543
  const related = this.resolveRelatedRepository(relation.related);
10451
- const pivotRows = await this.createMorphedByManyPivotQuery(relation, related.definition.morphClass, this.connection).where(relation.foreignPivotKey, "in", parentKeys).get();
10544
+ const pivotRows = await this.createMorphedByManyPivotQuery(relation, related.definition.morphClass, this.getConnection()).where(relation.foreignPivotKey, "in", parentKeys).get();
10452
10545
  const relatedIds = [...new Set(
10453
10546
  pivotRows.map((row) => row[relation.morphIdColumn]).filter((value) => value !== null && typeof value !== "undefined")
10454
10547
  )];
@@ -10943,7 +11036,7 @@ var ModelRepository = class _ModelRepository {
10943
11036
  return normalizeDialectWriteValue(this.getSchemaDialectName(), column2, value);
10944
11037
  }
10945
11038
  getSchemaDialectName() {
10946
- return this.connection.getDriver();
11039
+ return this.getConnection().getDriver();
10947
11040
  }
10948
11041
  applyCastGet(cast, value) {
10949
11042
  const builtInCast = this.parseBuiltInCast(cast);
@@ -11093,37 +11186,25 @@ var ModelRepository = class _ModelRepository {
11093
11186
  return parameter.replaceAll(/[YmdHis]/g, (token) => parts[token]);
11094
11187
  }
11095
11188
  };
11096
-
11097
- // src/model/ModelRegistry.ts
11098
- function resolveDefinition(reference) {
11099
- return "definition" in reference ? reference.definition : reference;
11189
+ function getErrorCode(error) {
11190
+ return error && typeof error === "object" && "code" in error ? error.code : void 0;
11100
11191
  }
11101
- var ModelRegistry = class {
11102
- models = /* @__PURE__ */ new Map();
11103
- register(reference) {
11104
- const definition = resolveDefinition(reference);
11105
- const existing = this.models.get(definition.name);
11106
- if (existing && existing !== definition) {
11107
- throw new DatabaseError(`Model "${definition.name}" is already registered.`, "DUPLICATE_MODEL");
11192
+ function getErrorCause(error) {
11193
+ return error && typeof error === "object" && "cause" in error ? error.cause : void 0;
11194
+ }
11195
+ function isUniqueConstraintError(error) {
11196
+ let current = error;
11197
+ while (current) {
11198
+ const code = getErrorCode(current);
11199
+ if (code === "23505" || code === 1062 || code === "1062" || code === "ER_DUP_ENTRY" || code === "SQLITE_CONSTRAINT" || code === "SQLITE_CONSTRAINT_UNIQUE" || code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
11200
+ return true;
11108
11201
  }
11109
- this.models.set(definition.name, definition);
11110
- return definition;
11111
- }
11112
- has(name) {
11113
- return this.models.has(name);
11114
- }
11115
- get(name) {
11116
- return this.models.get(name);
11117
- }
11118
- list() {
11119
- return [...this.models.values()];
11120
- }
11121
- clear() {
11122
- this.models.clear();
11202
+ if (current instanceof Error && /unique constraint|duplicate entry|unique constraint failed/i.test(current.message)) {
11203
+ return true;
11204
+ }
11205
+ current = getErrorCause(current);
11123
11206
  }
11124
- };
11125
- function createModelRegistry() {
11126
- return new ModelRegistry();
11207
+ return false;
11127
11208
  }
11128
11209
 
11129
11210
  // src/core/QueryScheduler.ts
@@ -11152,22 +11233,26 @@ var QueryScheduler = class {
11152
11233
  this.concurrentState = {
11153
11234
  active: 0,
11154
11235
  queued: 0,
11155
- limit: concurrencyLimit
11236
+ limit: concurrencyLimit,
11237
+ waiters: []
11156
11238
  };
11157
11239
  this.serializedState = {
11158
11240
  active: 0,
11159
11241
  queued: 0,
11160
- limit: 1
11242
+ limit: 1,
11243
+ waiters: []
11161
11244
  };
11162
11245
  this.workerState = {
11163
11246
  active: 0,
11164
11247
  queued: 0,
11165
- limit: concurrencyLimit
11248
+ limit: concurrencyLimit,
11249
+ waiters: []
11166
11250
  };
11167
11251
  }
11168
11252
  async schedule(options, callback) {
11169
11253
  const schedulingMode = this.preview(options);
11170
11254
  const state = this.resolveState(schedulingMode);
11255
+ let slotReserved = false;
11171
11256
  if (state.active >= state.limit) {
11172
11257
  if (state.queued >= this.queueLimit) {
11173
11258
  throw new DatabaseError(
@@ -11175,20 +11260,12 @@ var QueryScheduler = class {
11175
11260
  "QUERY_SCHEDULER_BACKPRESSURE"
11176
11261
  );
11177
11262
  }
11178
- await new Promise((resolve) => {
11179
- state.queued += 1;
11180
- const poll = () => {
11181
- if (state.active < state.limit) {
11182
- state.queued -= 1;
11183
- resolve();
11184
- return;
11185
- }
11186
- queueMicrotask(poll);
11187
- };
11188
- queueMicrotask(poll);
11189
- });
11263
+ await this.waitForSlot(state);
11264
+ slotReserved = true;
11265
+ }
11266
+ if (!slotReserved) {
11267
+ state.active += 1;
11190
11268
  }
11191
- state.active += 1;
11192
11269
  try {
11193
11270
  return {
11194
11271
  result: await callback(schedulingMode),
@@ -11196,6 +11273,7 @@ var QueryScheduler = class {
11196
11273
  };
11197
11274
  } finally {
11198
11275
  state.active -= 1;
11276
+ this.wakeNext(state);
11199
11277
  }
11200
11278
  }
11201
11279
  preview(options) {
@@ -11222,6 +11300,22 @@ var QueryScheduler = class {
11222
11300
  }
11223
11301
  return this.serializedState;
11224
11302
  }
11303
+ waitForSlot(state) {
11304
+ state.queued += 1;
11305
+ return new Promise((resolve) => {
11306
+ state.waiters.push(() => {
11307
+ state.queued -= 1;
11308
+ state.active += 1;
11309
+ resolve();
11310
+ });
11311
+ });
11312
+ }
11313
+ wakeNext(state) {
11314
+ if (state.active >= state.limit) {
11315
+ return;
11316
+ }
11317
+ state.waiters.shift()?.();
11318
+ }
11225
11319
  };
11226
11320
  function createQueryScheduler(options) {
11227
11321
  return new QueryScheduler(options);
@@ -11251,7 +11345,9 @@ function createCapabilities(overrides = {}) {
11251
11345
  }
11252
11346
 
11253
11347
  // src/core/DatabaseContext.ts
11348
+ var nextDatabaseContextId = 0;
11254
11349
  var DatabaseContext = class _DatabaseContext {
11350
+ _contextId;
11255
11351
  _connectionName;
11256
11352
  _schemaName;
11257
11353
  _adapter;
@@ -11271,6 +11367,7 @@ var DatabaseContext = class _DatabaseContext {
11271
11367
  constructor(options) {
11272
11368
  if (!options.adapter) throw new ConfigurationError("DatabaseContext requires an adapter.");
11273
11369
  if (!options.dialect) throw new ConfigurationError("DatabaseContext requires a dialect.");
11370
+ this._contextId = "contextId" in options ? options.contextId : nextDatabaseContextId += 1;
11274
11371
  this._connectionName = options.connectionName || "default";
11275
11372
  this._schemaName = "schemaName" in options ? options.schemaName : void 0;
11276
11373
  this._adapter = options.adapter;
@@ -11286,6 +11383,7 @@ var DatabaseContext = class _DatabaseContext {
11286
11383
  this._modelRegistry = ("modelRegistry" in options ? options.modelRegistry : void 0) ?? createModelRegistry();
11287
11384
  this._runtime = "runtime" in options ? options.runtime : {
11288
11385
  savepointCounter: 0,
11386
+ rootTransactionTail: Promise.resolve(),
11289
11387
  scheduler: createQueryScheduler({
11290
11388
  connectionName: this._connectionName,
11291
11389
  supportsConcurrentQueries: this._dialect.capabilities.concurrentQueries,
@@ -11316,6 +11414,9 @@ var DatabaseContext = class _DatabaseContext {
11316
11414
  getConnectionName() {
11317
11415
  return this._connectionName;
11318
11416
  }
11417
+ getContextId() {
11418
+ return this._contextId;
11419
+ }
11319
11420
  getSchemaName() {
11320
11421
  return this._schemaName;
11321
11422
  }
@@ -11438,40 +11539,72 @@ var DatabaseContext = class _DatabaseContext {
11438
11539
  }
11439
11540
  async _runRootTransaction(callback, options) {
11440
11541
  const runWithinScope = this._adapter.runWithTransactionScope?.bind(this._adapter) ?? (async (runner) => runner());
11441
- return runWithinScope(async () => {
11442
- const entry = {
11443
- scope: "transaction",
11444
- depth: 1
11445
- };
11542
+ const entry = {
11543
+ scope: "transaction",
11544
+ depth: 1
11545
+ };
11546
+ let tx;
11547
+ let committed = false;
11548
+ const runTransaction = async () => {
11446
11549
  await this._logger?.onTransactionStart?.(entry);
11447
- await this._callTransactionHook("begin", () => this._adapter.beginTransaction(options), options);
11448
- const tx = this._createChildContext(
11449
- { kind: "transaction", depth: 1 },
11450
- this._createTransactionCallbackState()
11451
- );
11452
- let committed = false;
11453
11550
  try {
11454
- const result = await this._runTransactionCallback(tx, callback);
11455
- await this._callTransactionHook("commit", () => this._adapter.commit(options), options);
11456
- committed = true;
11457
- await tx._flushTransactionCallbacks("afterCommit");
11551
+ const result = await runWithinScope(async () => {
11552
+ await this._callTransactionHook("begin", () => this._adapter.beginTransaction(options), options);
11553
+ tx = this._createChildContext(
11554
+ { kind: "transaction", depth: 1 },
11555
+ this._createTransactionCallbackState()
11556
+ );
11557
+ try {
11558
+ const value = await this._runTransactionCallback(tx, callback);
11559
+ await this._callTransactionHook("commit", () => this._adapter.commit(options), options);
11560
+ committed = true;
11561
+ return value;
11562
+ } catch (error) {
11563
+ if (committed) {
11564
+ throw error;
11565
+ }
11566
+ try {
11567
+ await this._callTransactionHook("rollback", () => this._adapter.rollback(options), options);
11568
+ } catch (rollbackError) {
11569
+ await this._logger?.onTransactionRollback?.({ ...entry, error: rollbackError });
11570
+ throw rollbackError;
11571
+ }
11572
+ throw error;
11573
+ }
11574
+ });
11575
+ await tx?._flushTransactionCallbacks("afterCommit");
11458
11576
  await this._logger?.onTransactionCommit?.(entry);
11459
11577
  return result;
11460
11578
  } catch (error) {
11461
11579
  if (committed) {
11462
11580
  throw error;
11463
11581
  }
11464
- try {
11465
- await this._callTransactionHook("rollback", () => this._adapter.rollback(options), options);
11466
- } catch (rollbackError) {
11467
- await this._logger?.onTransactionRollback?.({ ...entry, error: rollbackError });
11468
- throw rollbackError;
11469
- }
11470
- await tx._flushTransactionCallbacks("afterRollback");
11582
+ await tx?._flushTransactionCallbacks("afterRollback");
11471
11583
  await this._logger?.onTransactionRollback?.({ ...entry, error });
11472
11584
  throw error;
11473
11585
  }
11474
- });
11586
+ };
11587
+ if (this._adapter.runWithTransactionScope) {
11588
+ return runTransaction();
11589
+ }
11590
+ return this._runSerializedRootTransaction(runTransaction);
11591
+ }
11592
+ async _runSerializedRootTransaction(callback) {
11593
+ const previous = this._runtime.rootTransactionTail;
11594
+ let release;
11595
+ const current = previous.then(() => new Promise((resolve) => {
11596
+ release = resolve;
11597
+ }));
11598
+ this._runtime.rootTransactionTail = current;
11599
+ await previous;
11600
+ try {
11601
+ return await callback();
11602
+ } finally {
11603
+ release();
11604
+ if (this._runtime.rootTransactionTail === current) {
11605
+ this._runtime.rootTransactionTail = Promise.resolve();
11606
+ }
11607
+ }
11475
11608
  }
11476
11609
  async _runNestedTransaction(callback, options) {
11477
11610
  if (!this._dialect.capabilities.savepoints) {
@@ -11538,6 +11671,7 @@ var DatabaseContext = class _DatabaseContext {
11538
11671
  }
11539
11672
  _createChildContext(scope, transactionCallbacks) {
11540
11673
  return new _DatabaseContext({
11674
+ contextId: this._contextId,
11541
11675
  adapter: this._adapter,
11542
11676
  dialect: this._dialect,
11543
11677
  driver: this._driver,
@@ -11828,6 +11962,15 @@ function createConnectionManager(options) {
11828
11962
  return new ConnectionManager(options);
11829
11963
  }
11830
11964
 
11965
+ // src/drivers/savepoints.ts
11966
+ var SAVEPOINT_NAME_PATTERN = /^[A-Z_]\w*$/i;
11967
+ function normalizeSavepointName(name) {
11968
+ if (!SAVEPOINT_NAME_PATTERN.test(name)) {
11969
+ throw new TransactionError(`Invalid savepoint name "${name}".`);
11970
+ }
11971
+ return name;
11972
+ }
11973
+
11831
11974
  // src/drivers/index.ts
11832
11975
  function isModuleNotFoundError(error) {
11833
11976
  return !!error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND";
@@ -11953,22 +12096,13 @@ var SQLiteAdapter = class extends LazyDriverAdapter {
11953
12096
  return module.createSQLiteAdapter(this.options);
11954
12097
  }
11955
12098
  async createSavepoint(name, options) {
11956
- if (!/^[A-Z_]\w*$/i.test(name)) {
11957
- throw new TransactionError(`Invalid savepoint name "${name}".`);
11958
- }
11959
- await super.createSavepoint(name, options);
12099
+ await super.createSavepoint(normalizeSavepointName(name), options);
11960
12100
  }
11961
12101
  async rollbackToSavepoint(name, options) {
11962
- if (!/^[A-Z_]\w*$/i.test(name)) {
11963
- throw new TransactionError(`Invalid savepoint name "${name}".`);
11964
- }
11965
- await super.rollbackToSavepoint(name, options);
12102
+ await super.rollbackToSavepoint(normalizeSavepointName(name), options);
11966
12103
  }
11967
12104
  async releaseSavepoint(name, options) {
11968
- if (!/^[A-Z_]\w*$/i.test(name)) {
11969
- throw new TransactionError(`Invalid savepoint name "${name}".`);
11970
- }
11971
- await super.releaseSavepoint(name, options);
12105
+ await super.releaseSavepoint(normalizeSavepointName(name), options);
11972
12106
  }
11973
12107
  };
11974
12108
  function createSQLiteAdapter(options = {}) {
@@ -11991,32 +12125,14 @@ var PostgresAdapter = class extends LazyDriverAdapter {
11991
12125
  const createPostgresAdapter2 = module.createPostgresAdapter;
11992
12126
  return createPostgresAdapter2(this.options);
11993
12127
  }
11994
- releaseScopedTransaction(state) {
11995
- if (state.released) {
11996
- return;
11997
- }
11998
- if (state.leased) {
11999
- state.client.release?.();
12000
- }
12001
- state.released = true;
12002
- }
12003
12128
  async createSavepoint(name, options) {
12004
- if (!/^[A-Z_]\w*$/i.test(name)) {
12005
- throw new TransactionError(`Invalid savepoint name "${name}".`);
12006
- }
12007
- await super.createSavepoint(name, options);
12129
+ await super.createSavepoint(normalizeSavepointName(name), options);
12008
12130
  }
12009
12131
  async rollbackToSavepoint(name, options) {
12010
- if (!/^[A-Z_]\w*$/i.test(name)) {
12011
- throw new TransactionError(`Invalid savepoint name "${name}".`);
12012
- }
12013
- await super.rollbackToSavepoint(name, options);
12132
+ await super.rollbackToSavepoint(normalizeSavepointName(name), options);
12014
12133
  }
12015
12134
  async releaseSavepoint(name, options) {
12016
- if (!/^[A-Z_]\w*$/i.test(name)) {
12017
- throw new TransactionError(`Invalid savepoint name "${name}".`);
12018
- }
12019
- await super.releaseSavepoint(name, options);
12135
+ await super.releaseSavepoint(normalizeSavepointName(name), options);
12020
12136
  }
12021
12137
  };
12022
12138
  function createPostgresAdapter(options = {}) {
@@ -12039,32 +12155,14 @@ var MySQLAdapter = class extends LazyDriverAdapter {
12039
12155
  const createMySQLAdapter2 = module.createMySQLAdapter;
12040
12156
  return createMySQLAdapter2(this.options);
12041
12157
  }
12042
- releaseScopedTransaction(state) {
12043
- if (state.released) {
12044
- return;
12045
- }
12046
- if (state.leased) {
12047
- state.client.release?.();
12048
- }
12049
- state.released = true;
12050
- }
12051
12158
  async createSavepoint(name, options) {
12052
- if (!/^[A-Z_]\w*$/i.test(name)) {
12053
- throw new TransactionError(`Invalid savepoint name "${name}".`);
12054
- }
12055
- await super.createSavepoint(name, options);
12159
+ await super.createSavepoint(normalizeSavepointName(name), options);
12056
12160
  }
12057
12161
  async rollbackToSavepoint(name, options) {
12058
- if (!/^[A-Z_]\w*$/i.test(name)) {
12059
- throw new TransactionError(`Invalid savepoint name "${name}".`);
12060
- }
12061
- await super.rollbackToSavepoint(name, options);
12162
+ await super.rollbackToSavepoint(normalizeSavepointName(name), options);
12062
12163
  }
12063
12164
  async releaseSavepoint(name, options) {
12064
- if (!/^[A-Z_]\w*$/i.test(name)) {
12065
- throw new TransactionError(`Invalid savepoint name "${name}".`);
12066
- }
12067
- await super.releaseSavepoint(name, options);
12165
+ await super.releaseSavepoint(normalizeSavepointName(name), options);
12068
12166
  }
12069
12167
  };
12070
12168
  function createMySQLAdapter(options = {}) {
@@ -12075,7 +12173,6 @@ function createMySQLAdapter(options = {}) {
12075
12173
  var DEFAULT_RUNTIME_CONNECTION = Object.freeze({
12076
12174
  driver: "sqlite",
12077
12175
  url: "./data/database.sqlite",
12078
- schema: "public",
12079
12176
  logging: false
12080
12177
  });
12081
12178
  function asRecord(value) {
@@ -12087,6 +12184,9 @@ function normalizeConnectionInput(input) {
12087
12184
  }
12088
12185
  return input ?? {};
12089
12186
  }
12187
+ function hasRuntimeConnectionEntries(entries) {
12188
+ return entries.length > 0;
12189
+ }
12090
12190
  function inferDatabaseDriver(value) {
12091
12191
  if (!value) return void 0;
12092
12192
  const normalized = value.trim().toLowerCase();
@@ -12159,31 +12259,6 @@ function resolveConnectionConfig(name, input) {
12159
12259
  };
12160
12260
  return createRuntimeConnectionOptions(driver, connection, logging, schemaName, name);
12161
12261
  }
12162
- function mergeConnectionGroups(...groups) {
12163
- const merged = {};
12164
- for (const group of groups) {
12165
- if (!group?.connections) {
12166
- continue;
12167
- }
12168
- Object.assign(merged, group.connections);
12169
- }
12170
- return merged;
12171
- }
12172
- function resolveConfiguredDefaultConnection(...groups) {
12173
- for (const group of groups) {
12174
- const configured = group?.defaultConnection;
12175
- if (configured) {
12176
- return configured;
12177
- }
12178
- }
12179
- return void 0;
12180
- }
12181
- function resolveImplicitDefaultConnectionName(connectionNames) {
12182
- if (connectionNames.includes("default")) {
12183
- return "default";
12184
- }
12185
- return connectionNames[0] ?? "default";
12186
- }
12187
12262
  function isSupportedDatabaseDriver(value) {
12188
12263
  return value === "sqlite" || value === "postgres" || value === "mysql";
12189
12264
  }
@@ -12340,9 +12415,9 @@ function createRuntimeConnectionOptions(driver, connection, dbLogging, schemaNam
12340
12415
  }
12341
12416
  function resolveRuntimeConnectionManagerOptions(config) {
12342
12417
  const topLevelDb = asRecord(config.db);
12343
- const mergedConnections = mergeConnectionGroups(topLevelDb);
12344
- const connectionNames = Object.keys(mergedConnections);
12345
- if (connectionNames.length === 0) {
12418
+ const connections = topLevelDb?.connections ?? {};
12419
+ const connectionEntries = Object.entries(connections);
12420
+ if (!hasRuntimeConnectionEntries(connectionEntries)) {
12346
12421
  return createConnectionManager({
12347
12422
  defaultConnection: "default",
12348
12423
  connections: {
@@ -12350,15 +12425,15 @@ function resolveRuntimeConnectionManagerOptions(config) {
12350
12425
  }
12351
12426
  });
12352
12427
  }
12353
- const configuredDefault = resolveConfiguredDefaultConnection(topLevelDb);
12354
- const defaultConnection = configuredDefault ?? resolveImplicitDefaultConnectionName(connectionNames);
12355
- const connectionEntries = Object.entries(mergedConnections).map(([name, input]) => [
12428
+ const configuredDefault = topLevelDb?.defaultConnection;
12429
+ const defaultConnection = configuredDefault ? configuredDefault : Object.hasOwn(connections, "default") ? "default" : connectionEntries[0][0];
12430
+ const resolvedConnections = connectionEntries.map(([name, input]) => [
12356
12431
  name,
12357
12432
  resolveConnectionConfig(name, input)
12358
12433
  ]);
12359
12434
  return createConnectionManager({
12360
12435
  defaultConnection,
12361
- connections: Object.fromEntries(connectionEntries)
12436
+ connections: Object.fromEntries(resolvedConnections)
12362
12437
  });
12363
12438
  }
12364
12439
 
@@ -12431,12 +12506,12 @@ function generateMigrationTemplate(name, options = {}) {
12431
12506
  };
12432
12507
  }
12433
12508
  function renderMigrationTemplate(options) {
12509
+ return wrapMigrationTemplate(renderMigrationBody(options));
12510
+ }
12511
+ function renderMigrationBody(options) {
12434
12512
  switch (options.kind) {
12435
12513
  case "create_table":
12436
12514
  return [
12437
- "import { defineMigration, type MigrationContext } from '@holo-js/db'",
12438
- "",
12439
- "export default defineMigration({",
12440
12515
  " async up({ schema }: MigrationContext) {",
12441
12516
  ` await schema.createTable('${options.tableName}', (table) => {`,
12442
12517
  " table.id()",
@@ -12445,15 +12520,10 @@ function renderMigrationTemplate(options) {
12445
12520
  " },",
12446
12521
  " async down({ schema }: MigrationContext) {",
12447
12522
  ` await schema.dropTable('${options.tableName}')`,
12448
- " },",
12449
- "})",
12450
- ""
12451
- ].join("\n");
12523
+ " },"
12524
+ ];
12452
12525
  case "alter_table":
12453
12526
  return [
12454
- "import { defineMigration, type MigrationContext } from '@holo-js/db'",
12455
- "",
12456
- "export default defineMigration({",
12457
12527
  " async up({ schema }: MigrationContext) {",
12458
12528
  ` await schema.table('${options.tableName}', (table) => {`,
12459
12529
  " void table",
@@ -12463,29 +12533,19 @@ function renderMigrationTemplate(options) {
12463
12533
  ` await schema.table('${options.tableName}', (table) => {`,
12464
12534
  " void table",
12465
12535
  " })",
12466
- " },",
12467
- "})",
12468
- ""
12469
- ].join("\n");
12536
+ " },"
12537
+ ];
12470
12538
  case "drop_table":
12471
12539
  return [
12472
- "import { defineMigration, type MigrationContext } from '@holo-js/db'",
12473
- "",
12474
- "export default defineMigration({",
12475
12540
  " async up({ schema }: MigrationContext) {",
12476
12541
  ` await schema.dropTable('${options.tableName}')`,
12477
12542
  " },",
12478
12543
  " async down() {",
12479
12544
  ` throw new Error('Recreate "${options.tableName}" manually in this migration if rollback support is required.')`,
12480
- " },",
12481
- "})",
12482
- ""
12483
- ].join("\n");
12545
+ " },"
12546
+ ];
12484
12547
  case "blank":
12485
12548
  return [
12486
- "import { defineMigration, type MigrationContext } from '@holo-js/db'",
12487
- "",
12488
- "export default defineMigration({",
12489
12549
  " async up({ schema, db }: MigrationContext) {",
12490
12550
  " void schema",
12491
12551
  " void db",
@@ -12493,12 +12553,20 @@ function renderMigrationTemplate(options) {
12493
12553
  " async down({ schema, db }: MigrationContext) {",
12494
12554
  " void schema",
12495
12555
  " void db",
12496
- " },",
12497
- "})",
12498
- ""
12499
- ].join("\n");
12556
+ " },"
12557
+ ];
12500
12558
  }
12501
12559
  }
12560
+ function wrapMigrationTemplate(body) {
12561
+ return [
12562
+ "import { defineMigration, type MigrationContext } from '@holo-js/db'",
12563
+ "",
12564
+ "export default defineMigration({",
12565
+ ...body,
12566
+ "})",
12567
+ ""
12568
+ ].join("\n");
12569
+ }
12502
12570
 
12503
12571
  // src/seeders/defineSeeder.ts
12504
12572
  function defineSeeder(seeder) {
@@ -13197,12 +13265,14 @@ function validateUniqueIdConfig(table, _modelName, config) {
13197
13265
  }
13198
13266
  }
13199
13267
 
13200
- // src/model/defineModel.ts
13268
+ // src/model/defineModelHelpers.ts
13269
+ import { singularize } from "inflection";
13201
13270
  function buildModelTable(tableName, builder) {
13202
13271
  return builder(new TableDefinitionBuilder(tableName)).build();
13203
13272
  }
13204
13273
  function inferModelName(tableName) {
13205
- const singular = tableName.endsWith("s") ? tableName.slice(0, -1) : tableName;
13274
+ const unqualifiedName = tableName.split(".").at(-1) ?? tableName;
13275
+ const singular = singularize(unqualifiedName);
13206
13276
  return singular.split(/[_-]/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
13207
13277
  }
13208
13278
  function inferPrimaryKey(table) {
@@ -13212,6 +13282,25 @@ function inferPrimaryKey(table) {
13212
13282
  }
13213
13283
  return primaryKey.name;
13214
13284
  }
13285
+ function getRuntimeSchemaTable(tableName, connectionName) {
13286
+ try {
13287
+ return DB.getManager().connection(connectionName).getSchemaRegistry().get(tableName);
13288
+ } catch (error) {
13289
+ if (error instanceof ConfigurationError && error.code === "CONFIGURATION_ERROR" && error.message.includes("ConnectionManager") && error.message.includes("not configured")) {
13290
+ return void 0;
13291
+ }
13292
+ throw error;
13293
+ }
13294
+ }
13295
+ function resolveGeneratedModelTable(tableName, connectionName) {
13296
+ const table = getGeneratedTableDefinition(tableName) ?? getRuntimeSchemaTable(tableName, connectionName);
13297
+ if (!table) {
13298
+ throw new SchemaError(
13299
+ `Model "${tableName}" is not present in the generated schema registry. Run "holo migrate" to refresh the internal generated schema metadata.`
13300
+ );
13301
+ }
13302
+ return table;
13303
+ }
13215
13304
  function resolveDeletedAtColumn(table, options) {
13216
13305
  if (!options.softDeletes) {
13217
13306
  return void 0;
@@ -13247,6 +13336,45 @@ function validateTouches(modelName, relations, touches) {
13247
13336
  }
13248
13337
  return Object.freeze([...touches]);
13249
13338
  }
13339
+
13340
+ // src/model/defineModel.ts
13341
+ var HOLO_MODEL_REFERENCE_REGISTRY = /* @__PURE__ */ Symbol.for("holo-js.db.model-reference-registry");
13342
+ function createSharedModelDefinitionFields(inferredName, relations, touches, options) {
13343
+ return {
13344
+ kind: "model",
13345
+ name: inferredName,
13346
+ connectionName: options.connectionName,
13347
+ morphClass: options.morphClass ?? inferredName,
13348
+ with: Object.freeze([...options.with ?? []]),
13349
+ pendingAttributes: Object.freeze({ ...options.pendingAttributes ?? {} }),
13350
+ preventLazyLoading: options.preventLazyLoading ?? false,
13351
+ preventAccessingMissingAttributes: options.preventAccessingMissingAttributes ?? false,
13352
+ automaticEagerLoading: options.automaticEagerLoading ?? false,
13353
+ timestamps: options.timestamps ?? true,
13354
+ fillable: Object.freeze([...options.fillable ?? []]),
13355
+ hasExplicitFillable: typeof options.fillable !== "undefined",
13356
+ guarded: Object.freeze([...options.guarded ?? []]),
13357
+ scopes: options.scopes ?? {},
13358
+ globalScopes: { ...options.globalScopes ?? {} },
13359
+ relations,
13360
+ casts: { ...options.casts ?? {} },
13361
+ accessors: { ...options.accessors ?? {} },
13362
+ mutators: { ...options.mutators ?? {} },
13363
+ hidden: Object.freeze([...options.hidden ?? []]),
13364
+ visible: Object.freeze([...options.visible ?? []]),
13365
+ appended: Object.freeze([...options.appended ?? []]),
13366
+ serializeDate: options.serializeDate,
13367
+ collection: options.collection,
13368
+ prunable: options.prunable,
13369
+ massPrunable: options.massPrunable ?? false,
13370
+ touches,
13371
+ traits: Object.freeze([...options.traits ?? []]),
13372
+ replicationExcludes: Object.freeze([...options.replicationExcludes ?? []]),
13373
+ softDeletes: options.softDeletes ?? false,
13374
+ events: normalizeEventHandlers(options.events),
13375
+ observers: Object.freeze([...options.observers ?? []])
13376
+ };
13377
+ }
13250
13378
  function defineModel(tableOrName, builderOrOptions, options) {
13251
13379
  if (typeof tableOrName !== "string") {
13252
13380
  return defineModelFromResolvedTable(tableOrName, builderOrOptions ?? {});
@@ -13257,13 +13385,73 @@ function defineModel(tableOrName, builderOrOptions, options) {
13257
13385
  options ?? {}
13258
13386
  );
13259
13387
  }
13260
- const table = getGeneratedTableDefinition(tableOrName);
13261
- if (!table) {
13262
- throw new SchemaError(
13263
- `Model "${tableOrName}" is not present in the generated schema registry. Import your generated schema module and run "holo migrate" to refresh it.`
13388
+ return defineModelFromGeneratedTableName(
13389
+ tableOrName,
13390
+ builderOrOptions ?? {}
13391
+ );
13392
+ }
13393
+ function defineModelFromGeneratedTableName(tableName, options = {}) {
13394
+ const inferredName = options.name ?? inferModelName(tableName);
13395
+ const relations = { ...options.relations ?? {} };
13396
+ const touches = validateTouches(inferredName, relations, options.touches ?? []);
13397
+ const resolveTableDefinition = () => resolveGeneratedModelTable(tableName, options.connectionName);
13398
+ const resolvePrimaryKeyFromTable = (table) => options.primaryKey ?? inferPrimaryKey(table);
13399
+ const resolveUniqueIdFromTable = (table) => {
13400
+ return resolveUniqueIdConfig(
13401
+ options.traits,
13402
+ resolvePrimaryKeyFromTable(table),
13403
+ options.uniqueIds,
13404
+ options.newUniqueId
13264
13405
  );
13265
- }
13266
- return defineModelFromResolvedTable(table, builderOrOptions ?? {});
13406
+ };
13407
+ const resolveTable = () => {
13408
+ const table = resolveTableDefinition();
13409
+ validateUniqueIdConfig(table, inferredName, resolveUniqueIdFromTable(table));
13410
+ return table;
13411
+ };
13412
+ const resolvePrimaryKey = () => resolvePrimaryKeyFromTable(resolveTable());
13413
+ const resolveCreatedAtColumn = () => {
13414
+ const timestamps = options.timestamps ?? true;
13415
+ return timestamps ? resolveTimestampColumn(resolveTable(), options.createdAtColumn, "created_at") : void 0;
13416
+ };
13417
+ const resolveUpdatedAtColumn = () => {
13418
+ const timestamps = options.timestamps ?? true;
13419
+ return timestamps ? resolveTimestampColumn(resolveTable(), options.updatedAtColumn, "updated_at") : void 0;
13420
+ };
13421
+ const resolveDeletedAt = () => resolveDeletedAtColumn(resolveTable(), options);
13422
+ const resolveUniqueId = () => {
13423
+ return resolveUniqueIdFromTable(resolveTable());
13424
+ };
13425
+ const definition = {
13426
+ ...createSharedModelDefinitionFields(inferredName, relations, touches, options)
13427
+ };
13428
+ Object.defineProperties(definition, {
13429
+ table: {
13430
+ enumerable: true,
13431
+ get: resolveTable
13432
+ },
13433
+ primaryKey: {
13434
+ enumerable: true,
13435
+ get: resolvePrimaryKey
13436
+ },
13437
+ createdAtColumn: {
13438
+ enumerable: true,
13439
+ get: resolveCreatedAtColumn
13440
+ },
13441
+ updatedAtColumn: {
13442
+ enumerable: true,
13443
+ get: resolveUpdatedAtColumn
13444
+ },
13445
+ deletedAtColumn: {
13446
+ enumerable: true,
13447
+ get: resolveDeletedAt
13448
+ },
13449
+ uniqueIdConfig: {
13450
+ enumerable: true,
13451
+ get: resolveUniqueId
13452
+ }
13453
+ });
13454
+ return createStaticModelApi(Object.freeze(definition));
13267
13455
  }
13268
13456
  function defineModelFromResolvedTable(table, options = {}) {
13269
13457
  const deletedAtColumn = resolveDeletedAtColumn(table, options);
@@ -13282,45 +13470,17 @@ function defineModelFromResolvedTable(table, options = {}) {
13282
13470
  const relations = { ...options.relations ?? {} };
13283
13471
  const touches = validateTouches(inferredName, relations, options.touches ?? []);
13284
13472
  const definition = Object.freeze({
13285
- kind: "model",
13473
+ ...createSharedModelDefinitionFields(inferredName, relations, touches, options),
13286
13474
  table,
13287
- name: inferredName,
13288
13475
  primaryKey,
13289
- connectionName: options.connectionName,
13290
- morphClass: options.morphClass ?? inferredName,
13291
- with: Object.freeze([...options.with ?? []]),
13292
- pendingAttributes: Object.freeze({ ...options.pendingAttributes ?? {} }),
13293
- preventLazyLoading: options.preventLazyLoading ?? false,
13294
- preventAccessingMissingAttributes: options.preventAccessingMissingAttributes ?? false,
13295
- automaticEagerLoading: options.automaticEagerLoading ?? false,
13296
- timestamps,
13297
13476
  createdAtColumn,
13298
13477
  updatedAtColumn,
13299
- fillable: Object.freeze([...options.fillable ?? []]),
13300
- hasExplicitFillable: typeof options.fillable !== "undefined",
13301
- guarded: Object.freeze([...options.guarded ?? []]),
13302
- scopes: options.scopes ?? {},
13303
- globalScopes: { ...options.globalScopes ?? {} },
13304
- relations,
13305
- casts: { ...options.casts ?? {} },
13306
- accessors: { ...options.accessors ?? {} },
13307
- mutators: { ...options.mutators ?? {} },
13308
- hidden: Object.freeze([...options.hidden ?? []]),
13309
- visible: Object.freeze([...options.visible ?? []]),
13310
- appended: Object.freeze([...options.appended ?? []]),
13311
- serializeDate: options.serializeDate,
13312
- collection: options.collection,
13313
- prunable: options.prunable,
13314
- massPrunable: options.massPrunable ?? false,
13315
- touches,
13316
- traits: Object.freeze([...options.traits ?? []]),
13317
13478
  uniqueIdConfig,
13318
- replicationExcludes: Object.freeze([...options.replicationExcludes ?? []]),
13319
- softDeletes: options.softDeletes ?? false,
13320
- deletedAtColumn,
13321
- events: normalizeEventHandlers(options.events),
13322
- observers: Object.freeze([...options.observers ?? []])
13479
+ deletedAtColumn
13323
13480
  });
13481
+ return createStaticModelApi(definition);
13482
+ }
13483
+ function createStaticModelApi(definition) {
13324
13484
  const model = {
13325
13485
  definition,
13326
13486
  query() {
@@ -13338,8 +13498,8 @@ function defineModelFromResolvedTable(table, options = {}) {
13338
13498
  newQueryWithoutRelationships() {
13339
13499
  return this.getRepository().newQueryWithoutRelationships();
13340
13500
  },
13341
- from(table2) {
13342
- return this.query().from(table2);
13501
+ from(table) {
13502
+ return this.query().from(table);
13343
13503
  },
13344
13504
  debug() {
13345
13505
  return this.query().debug();
@@ -13467,17 +13627,17 @@ function defineModelFromResolvedTable(table, options = {}) {
13467
13627
  whereNone(columns, operator, value) {
13468
13628
  return this.query().whereNone(columns, operator, value);
13469
13629
  },
13470
- join(table2, leftColumn, operator, rightColumn) {
13471
- return this.query().join(table2, leftColumn, operator, rightColumn);
13630
+ join(table, leftColumn, operator, rightColumn) {
13631
+ return this.query().join(table, leftColumn, operator, rightColumn);
13472
13632
  },
13473
- leftJoin(table2, leftColumn, operator, rightColumn) {
13474
- return this.query().leftJoin(table2, leftColumn, operator, rightColumn);
13633
+ leftJoin(table, leftColumn, operator, rightColumn) {
13634
+ return this.query().leftJoin(table, leftColumn, operator, rightColumn);
13475
13635
  },
13476
- rightJoin(table2, leftColumn, operator, rightColumn) {
13477
- return this.query().rightJoin(table2, leftColumn, operator, rightColumn);
13636
+ rightJoin(table, leftColumn, operator, rightColumn) {
13637
+ return this.query().rightJoin(table, leftColumn, operator, rightColumn);
13478
13638
  },
13479
- crossJoin(table2) {
13480
- return this.query().crossJoin(table2);
13639
+ crossJoin(table) {
13640
+ return this.query().crossJoin(table);
13481
13641
  },
13482
13642
  joinSub(query, alias, leftColumn, operator, rightColumn) {
13483
13643
  return this.query().joinSub(query, alias, leftColumn, operator, rightColumn);
@@ -13548,11 +13708,11 @@ function defineModelFromResolvedTable(table, options = {}) {
13548
13708
  orWhereJsonLength(columnPath, operator, value) {
13549
13709
  return this.query().orWhereJsonLength(columnPath, operator, value);
13550
13710
  },
13551
- whereFullText(columns, value, options2 = {}) {
13552
- return this.query().whereFullText(columns, value, options2);
13711
+ whereFullText(columns, value, options = {}) {
13712
+ return this.query().whereFullText(columns, value, options);
13553
13713
  },
13554
- orWhereFullText(columns, value, options2 = {}) {
13555
- return this.query().orWhereFullText(columns, value, options2);
13714
+ orWhereFullText(columns, value, options = {}) {
13715
+ return this.query().orWhereFullText(columns, value, options);
13556
13716
  },
13557
13717
  whereVectorSimilarTo(column2, vector, minSimilarity = 0) {
13558
13718
  return this.query().whereVectorSimilarTo(column2, vector, minSimilarity);
@@ -13560,6 +13720,9 @@ function defineModelFromResolvedTable(table, options = {}) {
13560
13720
  orWhereVectorSimilarTo(column2, vector, minSimilarity = 0) {
13561
13721
  return this.query().orWhereVectorSimilarTo(column2, vector, minSimilarity);
13562
13722
  },
13723
+ orderBy(column2, direction = "asc") {
13724
+ return this.query().orderBy(column2, direction);
13725
+ },
13563
13726
  latest(column2) {
13564
13727
  return this.query().latest(column2);
13565
13728
  },
@@ -13585,7 +13748,10 @@ function defineModelFromResolvedTable(table, options = {}) {
13585
13748
  return this.query().sharedLock();
13586
13749
  },
13587
13750
  with(first, second, ...rest) {
13588
- if (typeof first !== "string") {
13751
+ if (Array.isArray(first)) {
13752
+ return this.query().with(first);
13753
+ }
13754
+ if (typeof first === "object") {
13589
13755
  return this.query().with(first);
13590
13756
  }
13591
13757
  if (typeof second === "function") {
@@ -13671,32 +13837,53 @@ function defineModelFromResolvedTable(table, options = {}) {
13671
13837
  findOrFail(value) {
13672
13838
  return this.getRepository().findOrFail(value);
13673
13839
  },
13840
+ findOrFailJson(value) {
13841
+ return this.query().findOrFailJson(value);
13842
+ },
13674
13843
  first() {
13675
13844
  return this.getRepository().first();
13676
13845
  },
13846
+ firstJson() {
13847
+ return this.query().firstJson();
13848
+ },
13677
13849
  firstOrFail() {
13678
13850
  return this.getRepository().firstOrFail();
13679
13851
  },
13680
13852
  sole() {
13681
13853
  return this.getRepository().sole();
13682
13854
  },
13855
+ soleJson() {
13856
+ return this.query().soleJson();
13857
+ },
13683
13858
  firstWhere(column2, operator, value) {
13684
13859
  return this.getRepository().firstWhere(column2, operator, value);
13685
13860
  },
13686
13861
  get() {
13687
13862
  return this.getRepository().get();
13688
13863
  },
13864
+ getJson() {
13865
+ return this.query().getJson();
13866
+ },
13689
13867
  all() {
13690
13868
  return this.getRepository().all();
13691
13869
  },
13692
- paginate(perPage = 15, page = 1, options2 = {}) {
13693
- return this.query().paginate(perPage, page, options2);
13870
+ paginate(perPage = 15, page = 1, options = {}) {
13871
+ return this.query().paginate(perPage, page, options);
13872
+ },
13873
+ paginateJson(perPage = 15, page = 1, options = {}) {
13874
+ return this.query().paginateJson(perPage, page, options);
13875
+ },
13876
+ simplePaginate(perPage = 15, page = 1, options = {}) {
13877
+ return this.query().simplePaginate(perPage, page, options);
13878
+ },
13879
+ simplePaginateJson(perPage = 15, page = 1, options = {}) {
13880
+ return this.query().simplePaginateJson(perPage, page, options);
13694
13881
  },
13695
- simplePaginate(perPage = 15, page = 1, options2 = {}) {
13696
- return this.query().simplePaginate(perPage, page, options2);
13882
+ cursorPaginate(perPage = 15, cursor = null, options = {}) {
13883
+ return this.query().cursorPaginate(perPage, cursor, options);
13697
13884
  },
13698
- cursorPaginate(perPage = 15, cursor = null, options2 = {}) {
13699
- return this.query().cursorPaginate(perPage, cursor, options2);
13885
+ cursorPaginateJson(perPage = 15, cursor = null, options = {}) {
13886
+ return this.query().cursorPaginateJson(perPage, cursor, options);
13700
13887
  },
13701
13888
  chunk(size, callback) {
13702
13889
  return this.query().chunk(size, callback);
@@ -13824,9 +14011,94 @@ function defineModelFromResolvedTable(table, options = {}) {
13824
14011
  const scoped = scope;
13825
14012
  model[name] = (...args) => scoped(model.query(), ...args);
13826
14013
  }
14014
+ registerGlobalModel(model);
13827
14015
  registerMorphModel(definition.morphClass, model);
14016
+ getHoloModelReferenceRegistry().add(model);
13828
14017
  return Object.freeze(model);
13829
14018
  }
14019
+ function getHoloModelReferenceRegistry() {
14020
+ const registryGlobal = globalThis;
14021
+ const existing = registryGlobal[HOLO_MODEL_REFERENCE_REGISTRY];
14022
+ if (existing) {
14023
+ return existing;
14024
+ }
14025
+ const registry = /* @__PURE__ */ new WeakSet();
14026
+ Object.defineProperty(globalThis, HOLO_MODEL_REFERENCE_REGISTRY, {
14027
+ value: registry
14028
+ });
14029
+ return registry;
14030
+ }
14031
+
14032
+ // src/model/serialize.ts
14033
+ function isSerializableModel(value) {
14034
+ return Boolean(value && typeof value === "object" && typeof value.toJSON === "function");
14035
+ }
14036
+ function serializeModels(value) {
14037
+ if (value instanceof Date || value === null || typeof value !== "object") {
14038
+ return value;
14039
+ }
14040
+ if (isSerializableModel(value)) {
14041
+ return value.toJSON();
14042
+ }
14043
+ if (Array.isArray(value)) {
14044
+ return value.map((item) => serializeModels(item));
14045
+ }
14046
+ return Object.fromEntries(
14047
+ Object.entries(value).map(([key, entry]) => [key, serializeModels(entry)])
14048
+ );
14049
+ }
14050
+
14051
+ // src/model/slug.ts
14052
+ var reservedSlugs = /* @__PURE__ */ new Map();
14053
+ var slugReservationQueues = /* @__PURE__ */ new Map();
14054
+ function slugify(value, separator, fallback) {
14055
+ const normalized = value.toLowerCase().trim().replace(/[^a-z0-9]+/g, separator).replace(new RegExp(`^${escapeRegExp(separator)}+|${escapeRegExp(separator)}+$`, "g"), "");
14056
+ return normalized || fallback;
14057
+ }
14058
+ function escapeRegExp(value) {
14059
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14060
+ }
14061
+ async function reserveNextSlug(key, callback) {
14062
+ const previous = slugReservationQueues.get(key) ?? Promise.resolve();
14063
+ let release;
14064
+ const current = previous.then(() => new Promise((resolve) => {
14065
+ release = resolve;
14066
+ }));
14067
+ slugReservationQueues.set(key, current);
14068
+ await previous;
14069
+ try {
14070
+ return await callback();
14071
+ } finally {
14072
+ release();
14073
+ if (slugReservationQueues.get(key) === current) {
14074
+ slugReservationQueues.delete(key);
14075
+ }
14076
+ }
14077
+ }
14078
+ async function uniqueSlug(model, value, options = {}) {
14079
+ const column2 = options.column ?? "slug";
14080
+ const separator = options.separator ?? "-";
14081
+ const fallback = options.fallback ?? "entry";
14082
+ const base = slugify(value, separator, fallback);
14083
+ const key = `${model.definition.connectionName ?? "default"}:${model.definition.name}:${column2}:${base}`;
14084
+ return await reserveNextSlug(key, async () => {
14085
+ const query = model.whereLike(column2, `${base}%`).when(typeof options.ignore !== "undefined", (builder) => builder.where(model.definition.primaryKey, "!=", options.ignore));
14086
+ const existing = await query.pluck(column2);
14087
+ const reserved = reservedSlugs.get(key) ?? /* @__PURE__ */ new Set();
14088
+ const taken = new Set(reserved);
14089
+ for (const slug of existing) {
14090
+ taken.add(slug);
14091
+ }
14092
+ for (let index = 1; ; index += 1) {
14093
+ const candidate = index === 1 ? base : `${base}${separator}${index}`;
14094
+ if (!taken.has(candidate)) {
14095
+ reserved.add(candidate);
14096
+ reservedSlugs.set(key, reserved);
14097
+ return candidate;
14098
+ }
14099
+ }
14100
+ });
14101
+ }
13830
14102
 
13831
14103
  // src/model/relations.ts
13832
14104
  function defaultMorphTypeColumn(name) {
@@ -13835,6 +14107,21 @@ function defaultMorphTypeColumn(name) {
13835
14107
  function defaultMorphIdColumn(name) {
13836
14108
  return `${name}_id`;
13837
14109
  }
14110
+ function resolveRegisteredModel(name) {
14111
+ const model = getGlobalModel(name);
14112
+ if (!model) {
14113
+ throw new RelationError(
14114
+ `Relation target "${name}" is not registered. Import the related model once during boot or use your framework's model discovery.`
14115
+ );
14116
+ }
14117
+ return model;
14118
+ }
14119
+ function normalizeRelatedResolver(related) {
14120
+ if (typeof related === "string") {
14121
+ return () => resolveRegisteredModel(related);
14122
+ }
14123
+ return related;
14124
+ }
13838
14125
  function decoratePivotRelation(relation) {
13839
14126
  const clone = (overrides) => {
13840
14127
  return decoratePivotRelation(Object.freeze({
@@ -13891,7 +14178,7 @@ function belongsTo(related, foreignKeyOrOptions, ownerKey = "id") {
13891
14178
  const resolvedOwnerKey = typeof foreignKeyOrOptions === "string" ? ownerKey : foreignKeyOrOptions.ownerKey ?? "id";
13892
14179
  return Object.freeze({
13893
14180
  kind: "belongsTo",
13894
- related,
14181
+ related: normalizeRelatedResolver(related),
13895
14182
  foreignKey,
13896
14183
  ownerKey: resolvedOwnerKey
13897
14184
  });
@@ -13901,7 +14188,7 @@ function hasMany(related, foreignKeyOrOptions, localKey = "id") {
13901
14188
  const resolvedLocalKey = typeof foreignKeyOrOptions === "string" ? localKey : foreignKeyOrOptions.localKey ?? "id";
13902
14189
  return Object.freeze({
13903
14190
  kind: "hasMany",
13904
- related,
14191
+ related: normalizeRelatedResolver(related),
13905
14192
  foreignKey,
13906
14193
  localKey: resolvedLocalKey
13907
14194
  });
@@ -13911,7 +14198,7 @@ function hasOne(related, foreignKeyOrOptions, localKey = "id") {
13911
14198
  const resolvedLocalKey = typeof foreignKeyOrOptions === "string" ? localKey : foreignKeyOrOptions.localKey ?? "id";
13912
14199
  return Object.freeze({
13913
14200
  kind: "hasOne",
13914
- related,
14201
+ related: normalizeRelatedResolver(related),
13915
14202
  foreignKey,
13916
14203
  localKey: resolvedLocalKey
13917
14204
  });
@@ -13986,7 +14273,7 @@ function belongsToMany(related, pivotTableOrOptions, foreignPivotKey, relatedPiv
13986
14273
  const resolvedRelatedKey = typeof pivotTableOrOptions === "object" && "pivotTable" in pivotTableOrOptions ? pivotTableOrOptions.relatedKey ?? "id" : relatedKey;
13987
14274
  return decoratePivotRelation(Object.freeze({
13988
14275
  kind: "belongsToMany",
13989
- related,
14276
+ related: normalizeRelatedResolver(related),
13990
14277
  pivotTable,
13991
14278
  foreignPivotKey: resolvedForeignPivotKey,
13992
14279
  relatedPivotKey: resolvedRelatedPivotKey,
@@ -14073,7 +14360,7 @@ function hasManyThrough(related, through, firstKey, secondKey, localKey = "id",
14073
14360
  var DEFAULT_HOLO_PROJECT_PATHS = Object.freeze({
14074
14361
  models: "server/models",
14075
14362
  migrations: "server/db/migrations",
14076
- generatedSchema: "server/db/schema.generated.ts",
14363
+ generatedSchema: ".holo-js/generated/schema.generated.ts",
14077
14364
  seeders: "server/db/seeders",
14078
14365
  observers: "server/db/observers",
14079
14366
  factories: "server/db/factories",
@@ -14250,8 +14537,10 @@ export {
14250
14537
  renameTableOperation,
14251
14538
  renderGeneratedSchemaModule,
14252
14539
  renderGeneratedSchemaPlaceholder,
14540
+ renderGeneratedSchemaRuntimeModule,
14253
14541
  resetDB,
14254
14542
  resetDatabaseQueryCacheBridge,
14543
+ resetGlobalModelRegistry,
14255
14544
  resetMorphRegistry,
14256
14545
  resolveDialectColumnType,
14257
14546
  resolveDialectIdStrategyType,
@@ -14259,6 +14548,8 @@ export {
14259
14548
  resolveMorphModel,
14260
14549
  resolveRuntimeConnectionManagerOptions,
14261
14550
  scopeRelation,
14551
+ serializeModels,
14552
+ uniqueSlug,
14262
14553
  unsafeSql,
14263
14554
  validateQueryPlan,
14264
14555
  withLimit,