@vibeorm/runtime 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/adapter-kit/index.d.ts +6 -3
  2. package/dist/adapter-kit/index.d.ts.map +1 -1
  3. package/dist/adapter-kit/index.js +178 -19
  4. package/dist/adapter-kit/index.js.map +8 -5
  5. package/dist/adapter-kit/nested-options.d.ts +22 -0
  6. package/dist/adapter-kit/nested-options.d.ts.map +1 -0
  7. package/dist/adapter-kit/savepoint-gate.d.ts +31 -0
  8. package/dist/adapter-kit/savepoint-gate.d.ts.map +1 -1
  9. package/dist/adapter-kit/session.d.ts +9 -0
  10. package/dist/adapter-kit/session.d.ts.map +1 -0
  11. package/dist/adapter-kit/sqlite-session.d.ts +8 -0
  12. package/dist/adapter-kit/sqlite-session.d.ts.map +1 -0
  13. package/dist/adapter.d.ts +34 -27
  14. package/dist/adapter.d.ts.map +1 -1
  15. package/dist/bulk-upsert.d.ts +5 -3
  16. package/dist/bulk-upsert.d.ts.map +1 -1
  17. package/dist/client-types.d.ts +14 -0
  18. package/dist/client-types.d.ts.map +1 -0
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/codecs.d.ts +5 -0
  21. package/dist/codecs.d.ts.map +1 -1
  22. package/dist/database-module.d.ts +27 -0
  23. package/dist/database-module.d.ts.map +1 -0
  24. package/dist/diagnostics/plan.d.ts +3 -1
  25. package/dist/diagnostics/plan.d.ts.map +1 -1
  26. package/dist/diagnostics/preview.d.ts +3 -1
  27. package/dist/diagnostics/preview.d.ts.map +1 -1
  28. package/dist/diagnostics/statement-diagnostics.d.ts +10 -6
  29. package/dist/diagnostics/statement-diagnostics.d.ts.map +1 -1
  30. package/dist/index.d.ts +4 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +529 -296
  33. package/dist/index.js.map +20 -18
  34. package/dist/module-context.d.ts +20 -0
  35. package/dist/module-context.d.ts.map +1 -0
  36. package/dist/nested-writes.d.ts.map +1 -1
  37. package/dist/policy.d.ts.map +1 -1
  38. package/dist/query-builder.d.ts.map +1 -1
  39. package/dist/read-only.d.ts.map +1 -1
  40. package/dist/scoped.d.ts.map +1 -1
  41. package/dist/sql-access.d.ts +18 -2
  42. package/dist/sql-access.d.ts.map +1 -1
  43. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -50,22 +50,30 @@ function createSqlModel(params) {
50
50
  return Object.freeze({ table: params.alias === undefined ? table : sql`${table} AS ${sql.ident({ parts: [params.alias] })}`, fields: Object.freeze(fields) });
51
51
  }
52
52
  // packages/runtime/src/adapter.ts
53
+ import { VibeError as VibeError2 } from "@vibeorm/schema";
53
54
  function toSqlExecutor(params) {
54
55
  const { adapter } = params;
55
- return async (queryParams) => {
56
- const result = await adapter.executeUnsafe({
57
- text: queryParams.text,
58
- values: queryParams.values
59
- });
56
+ const execute = async (queryParams) => {
57
+ const result = await adapter.executeUnsafe(queryParams);
60
58
  return result.rows;
61
59
  };
60
+ const withSession = async ({ run }) => {
61
+ if (adapter.withSession === undefined)
62
+ throw new VibeError2({
63
+ code: "VIBE_UNSUPPORTED_CAPABILITY",
64
+ message: "This adapter cannot reserve a migration session. Supply an adapter with session support or an explicitly owned SqlExecutor.",
65
+ meta: { capability: "session", provider: adapter.provider }
66
+ });
67
+ return adapter.withSession({ run });
68
+ };
69
+ return Object.assign(execute, { withSession });
62
70
  }
63
71
  // packages/runtime/src/db-now.ts
64
- import { VibeError as VibeError2 } from "@vibeorm/schema";
72
+ import { VibeError as VibeError3 } from "@vibeorm/schema";
65
73
  function dbNow(options) {
66
74
  const offset = options?.plusSeconds ?? 0;
67
75
  if (typeof offset !== "number" || !Number.isFinite(offset)) {
68
- throw new VibeError2({
76
+ throw new VibeError3({
69
77
  code: "VIBE_VALIDATION",
70
78
  message: `dbNow plusSeconds must be a finite number of seconds (fractional is fine), got ${String(offset)}`,
71
79
  meta: { plusSeconds: offset }
@@ -84,10 +92,10 @@ function isDbNow(value) {
84
92
  return candidate.__vibeDbNow === true && typeof candidate.offsetSeconds === "number" && Number.isFinite(candidate.offsetSeconds);
85
93
  }
86
94
  // packages/runtime/src/advisory-lock.ts
87
- import { VibeError as VibeError3, unsupportedCapability } from "@vibeorm/schema";
95
+ import { VibeError as VibeError4, unsupportedCapability } from "@vibeorm/schema";
88
96
  import { ADVISORY_KEY_BOUNDS } from "@vibeorm/sql";
89
97
  function validationError(params) {
90
- return new VibeError3({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
98
+ return new VibeError4({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
91
99
  }
92
100
  function readInt32(params) {
93
101
  const { value, member, method } = params;
@@ -192,7 +200,7 @@ function statementFor(params) {
192
200
  };
193
201
  }
194
202
  // packages/runtime/src/model-meta.ts
195
- import { VibeError as VibeError4, enumStorageOf, isEnumType as isEnumType2, validateComputedConfig } from "@vibeorm/schema";
203
+ import { VibeError as VibeError5, enumStorageOf, isEnumType as isEnumType2, validateComputedConfig } from "@vibeorm/schema";
196
204
  import { enumMembershipCodec } from "@vibeorm/sql";
197
205
  function toClientName(params) {
198
206
  const { name } = params;
@@ -321,7 +329,7 @@ function buildModelMeta(params) {
321
329
  for (const name of model.primaryKey.fields) {
322
330
  const field = fieldByName.get(name);
323
331
  if (field === undefined) {
324
- throw new VibeError4({
332
+ throw new VibeError5({
325
333
  code: "VIBE_SCHEMA_INVALID",
326
334
  message: `model "${model.name}" declares composite primary-key field "${name}", which does not exist`,
327
335
  meta: { model: model.name, field: name }
@@ -427,7 +435,7 @@ function getModelMeta(params) {
427
435
  const { meta, model } = params;
428
436
  const found = meta.modelByName.get(model);
429
437
  if (found === undefined) {
430
- throw new VibeError4({
438
+ throw new VibeError5({
431
439
  code: "VIBE_VALIDATION",
432
440
  message: `unknown model "${model}"`,
433
441
  meta: { model, known: [...new Set(meta.models.map((entry) => entry.name))] }
@@ -440,20 +448,20 @@ function getFieldMeta(params) {
440
448
  const found = model.fieldByName.get(field);
441
449
  if (found === undefined) {
442
450
  if (model.computed !== null && model.computed.has(field)) {
443
- throw new VibeError4({
451
+ throw new VibeError5({
444
452
  code: "VIBE_VALIDATION",
445
453
  message: `"${field}" on model "${model.name}" is a computed field \u2014 computed fields appear in results (select/omit) only, not in ${context}`,
446
454
  meta: { model: model.name, field, context, computed: true }
447
455
  });
448
456
  }
449
457
  if (model.relationByName.has(field)) {
450
- throw new VibeError4({
458
+ throw new VibeError5({
451
459
  code: "VIBE_VALIDATION",
452
460
  message: `"${field}" on model "${model.name}" is a relation \u2014 not valid in ${context} here (relation writes use nested-write verbs, relation reads use select/include)`,
453
461
  meta: { model: model.name, field, context }
454
462
  });
455
463
  }
456
- throw new VibeError4({
464
+ throw new VibeError5({
457
465
  code: "VIBE_VALIDATION",
458
466
  message: `unknown field "${field}" on model "${model.name}" in ${context}`,
459
467
  meta: { model: model.name, field, context }
@@ -463,7 +471,7 @@ function getFieldMeta(params) {
463
471
  }
464
472
  // packages/runtime/src/codecs.ts
465
473
  import { JSON_TRANSPORT, projectionOutputName, parsePgArrayLiteral, toPgArrayLiteral } from "@vibeorm/sql";
466
- import { VibeError as VibeError5, isEnumType as isEnumType3 } from "@vibeorm/schema";
474
+ import { VibeError as VibeError6, isEnumType as isEnumType3 } from "@vibeorm/schema";
467
475
  import { DIALECT_CODECS, decodeIsNoop } from "@vibeorm/sql";
468
476
  import {
469
477
  DIALECT_CODECS as DIALECT_CODECS2,
@@ -512,10 +520,29 @@ function decodeValue(params) {
512
520
  return codec.decode(value);
513
521
  }
514
522
  var DECIMAL_LITERAL = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
523
+ function validateAtomicOperand(params) {
524
+ const { field, value } = params;
525
+ if (field.scalarType === "BigInt") {
526
+ if (typeof value === "bigint" || typeof value === "number" && Number.isSafeInteger(value))
527
+ return value;
528
+ } else if (field.scalarType === "Decimal") {
529
+ if (typeof value === "string" && DECIMAL_LITERAL.test(value.trim()))
530
+ return value.trim();
531
+ if (typeof value === "number" && Number.isFinite(value))
532
+ return value;
533
+ } else if (typeof value === "number" && Number.isFinite(value)) {
534
+ return value;
535
+ }
536
+ throw new VibeError6({
537
+ code: "VIBE_VALIDATION",
538
+ message: `Invalid arithmetic operand for ${field.scalarType} field "${field.name}"`,
539
+ meta: { field: field.name, value }
540
+ });
541
+ }
515
542
  function toRealNumber(params) {
516
543
  const { field, value } = params;
517
544
  const refuse = () => {
518
- throw new VibeError5({
545
+ throw new VibeError6({
519
546
  code: "VIBE_VALIDATION",
520
547
  message: `"${String(value)}" is not a numeric value for the Decimal field "${field.name}"`,
521
548
  meta: { field: field.name, value }
@@ -755,7 +782,7 @@ function positionalMaterializer(params) {
755
782
  const output = projectionOutputName({ column });
756
783
  const entry = plan.byColumn.get(output);
757
784
  if (entry === undefined)
758
- throw new VibeError5({ code: "VIBE_INTERNAL", message: "positional projection has no schema field", meta: { model: model.name, column: output } });
785
+ throw new VibeError6({ code: "VIBE_INTERNAL", message: "positional projection has no schema field", meta: { model: model.name, column: output } });
759
786
  return entry;
760
787
  });
761
788
  const width = slots.length;
@@ -763,7 +790,7 @@ function positionalMaterializer(params) {
763
790
  const chunked = width > chunkSize;
764
791
  const count = Math.ceil(width / chunkSize);
765
792
  const invalid = () => {
766
- throw new VibeError5({ code: "VIBE_INTERNAL", message: "positional relation payload does not match its SQL projection", meta: { model: model.name, expectedColumns: width } });
793
+ throw new VibeError6({ code: "VIBE_INTERNAL", message: "positional relation payload does not match its SQL projection", meta: { model: model.name, expectedColumns: width } });
767
794
  };
768
795
  if (width === 0 || new Set(slots.map((slot) => slot.name)).size !== width)
769
796
  return invalid();
@@ -805,7 +832,7 @@ function decodeAggregateValue(params) {
805
832
  return field === null ? value : decodeValue({ dialect, field, value });
806
833
  }
807
834
  // packages/runtime/src/lateral-projection.ts
808
- import { VibeError as VibeError6, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
835
+ import { VibeError as VibeError7, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
809
836
  function isPlainObject(value) {
810
837
  return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
811
838
  }
@@ -822,7 +849,7 @@ function lateralProjection(params) {
822
849
  const columns = names.map((name) => {
823
850
  const field = model.fieldByName.get(name);
824
851
  if (field === undefined)
825
- throw new VibeError6({ code: "VIBE_INTERNAL", message: "lateral projection references an unknown field", meta: { model: model.name, field: name } });
852
+ throw new VibeError7({ code: "VIBE_INTERNAL", message: "lateral projection references an unknown field", meta: { model: model.name, field: name } });
826
853
  return Object.freeze(applyLateralTransform({ model, column: { table: LATERAL_CHILD_ALIAS, name: field.columnName } }));
827
854
  });
828
855
  const descriptor = Object.freeze({ columns: Object.freeze(columns), hiddenOrder: Object.freeze(hiddenOrder) });
@@ -871,7 +898,7 @@ function applyLateralTransform(params) {
871
898
 
872
899
  // packages/runtime/src/query-builder.ts
873
900
  import { isSqlFragment, mapConflictColumns } from "@vibeorm/sql";
874
- import { VibeError as VibeError10, unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
901
+ import { VibeError as VibeError11, unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
875
902
  import {
876
903
  SQL_DEFAULT_CELL,
877
904
  containsPattern,
@@ -885,9 +912,9 @@ import {
885
912
  import { describeRowLock } from "@vibeorm/sql";
886
913
 
887
914
  // packages/runtime/src/bulk-upsert.ts
888
- import { VibeError as VibeError7, unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
915
+ import { VibeError as VibeError8, unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
889
916
  function validationError2(params) {
890
- return new VibeError7({
917
+ return new VibeError8({
891
918
  code: "VIBE_VALIDATION",
892
919
  message: params.message,
893
920
  ...params.meta === undefined ? {} : { meta: params.meta }
@@ -1069,8 +1096,13 @@ function conflictKeyOf(params) {
1069
1096
  function assertNoDuplicateConflictKeys(params) {
1070
1097
  const { model, compiled, targetColumns } = params;
1071
1098
  const firstIndexByKey = new Map;
1099
+ const signature = [...targetColumns].sort((left, right) => left < right ? -1 : 1).join("\x00");
1100
+ const nullsNotDistinct = [...model.compoundUniques.values(), ...model.singleIndexUniques].some((group) => group.nullsNotDistinct && signatureOf({ fields: group.fields }) === signature);
1072
1101
  for (let index = 0;index < compiled.length; index += 1) {
1073
- const key = conflictKeyOf({ row: compiled[index], columns: targetColumns });
1102
+ const row = compiled[index];
1103
+ if (!nullsNotDistinct && targetColumns.some((column) => row[column] === null))
1104
+ continue;
1105
+ const key = conflictKeyOf({ row, columns: targetColumns });
1074
1106
  const first = firstIndexByKey.get(key);
1075
1107
  if (first !== undefined) {
1076
1108
  throw validationError2({
@@ -1206,7 +1238,7 @@ function assertConflictKeysAreComparable(params) {
1206
1238
  }
1207
1239
 
1208
1240
  // packages/runtime/src/keyset.ts
1209
- import { VibeError as VibeError8 } from "@vibeorm/schema";
1241
+ import { VibeError as VibeError9 } from "@vibeorm/schema";
1210
1242
  var KEYSET_TOKEN_VERSION = 1;
1211
1243
  var KEYSET_TOKEN_PREFIX = "vk1.";
1212
1244
  var KEYSET_MAX_PAGE_SIZE = 1e4;
@@ -1216,10 +1248,10 @@ var DATETIME_NEEDS_PRECISION_ASSERTION = Object.freeze({
1216
1248
  sqlite: false
1217
1249
  });
1218
1250
  function keysetValidation(params) {
1219
- return new VibeError8({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
1251
+ return new VibeError9({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
1220
1252
  }
1221
1253
  function keysetUnsupported(params) {
1222
- return new VibeError8({
1254
+ return new VibeError9({
1223
1255
  code: "VIBE_UNSUPPORTED_CAPABILITY",
1224
1256
  message: params.message,
1225
1257
  meta: params.meta
@@ -1602,9 +1634,9 @@ function decodeKeysetToken(params) {
1602
1634
  }
1603
1635
 
1604
1636
  // packages/runtime/src/relation-plan.ts
1605
- import { VibeError as VibeError9, implicitJoinTable } from "@vibeorm/schema";
1637
+ import { VibeError as VibeError10, implicitJoinTable } from "@vibeorm/schema";
1606
1638
  function validationError3(params) {
1607
- return new VibeError9({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
1639
+ return new VibeError10({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
1608
1640
  }
1609
1641
  function isPlainObject2(value) {
1610
1642
  return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
@@ -1629,7 +1661,7 @@ function keyFields(params) {
1629
1661
  return names.map((name) => {
1630
1662
  const field = model.fieldByName.get(name);
1631
1663
  if (field === undefined) {
1632
- throw new VibeError9({
1664
+ throw new VibeError10({
1633
1665
  code: "VIBE_SCHEMA_INVALID",
1634
1666
  message: `relation "${relation.name}" on ${relation.model} names unknown ${what} field "${name}"`,
1635
1667
  meta: { model: relation.model, relation: relation.name }
@@ -1641,7 +1673,7 @@ function keyFields(params) {
1641
1673
  function singleKeyPair(params) {
1642
1674
  const { link, what } = params;
1643
1675
  if (link.parentKeys.length !== 1 || link.childKeys.length !== 1) {
1644
- throw new VibeError9({
1676
+ throw new VibeError10({
1645
1677
  code: "VIBE_UNSUPPORTED_CAPABILITY",
1646
1678
  message: `${what} on relation "${link.relation.name}" of ${link.relation.model} needs a single-column relation key \u2014 this relation links ${link.parentKeys.length} columns; use an explicit junction model`,
1647
1679
  meta: { model: link.relation.model, relation: link.relation.name, arity: link.parentKeys.length }
@@ -1685,7 +1717,7 @@ function resolveRelationLinkUncached(params) {
1685
1717
  const target = getModelMeta({ meta, model: relation.targetModel });
1686
1718
  if (relation.type === "manyToMany") {
1687
1719
  if (relation.model === relation.targetModel) {
1688
- throw new VibeError9({
1720
+ throw new VibeError10({
1689
1721
  code: "VIBE_SCHEMA_INVALID",
1690
1722
  message: `relation "${relation.name}" on ${relation.model} is an implicit self many-to-many: both sides own the ` + `same join-table column, so this field and its inverse would read the same rows. Model each direction ` + `with an explicit junction model.`,
1691
1723
  meta: { model: relation.model, relation: relation.name, targetModel: relation.targetModel }
@@ -1727,7 +1759,7 @@ function resolveRelationLinkUncached(params) {
1727
1759
  }
1728
1760
  const reverse = findReverseRelation({ relation, target });
1729
1761
  if (reverse === null || reverse.fields === undefined || reverse.fields.length === 0) {
1730
- throw new VibeError9({
1762
+ throw new VibeError10({
1731
1763
  code: "VIBE_SCHEMA_INVALID",
1732
1764
  message: `relation "${relation.name}" on ${relation.model} has no foreign-key counterpart on ${relation.targetModel}`,
1733
1765
  meta: { model: relation.model, relation: relation.name, target: relation.targetModel }
@@ -1752,7 +1784,7 @@ function pairedKeys(params) {
1752
1784
  const parentKeys = keyFields({ model, names: parentNames, relation, what: "fields" });
1753
1785
  const childKeys = keyFields({ model: target, names: childNames, relation, what: "references" });
1754
1786
  if (parentKeys.length !== childKeys.length) {
1755
- throw new VibeError9({
1787
+ throw new VibeError10({
1756
1788
  code: "VIBE_SCHEMA_INVALID",
1757
1789
  message: `relation "${relation.name}" on ${relation.model} links ${parentKeys.length} field(s) to ${childKeys.length} reference(s) \u2014 a foreign key must pair every column`,
1758
1790
  meta: { model: relation.model, relation: relation.name }
@@ -2102,7 +2134,7 @@ var AGG_FN_BY_KEY = {
2102
2134
  _max: "max"
2103
2135
  };
2104
2136
  function validationError4(params) {
2105
- return new VibeError10({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
2137
+ return new VibeError11({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
2106
2138
  }
2107
2139
  function isPlainObject3(value) {
2108
2140
  return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
@@ -2667,6 +2699,12 @@ function compileOperatorFilter(params) {
2667
2699
  });
2668
2700
  }
2669
2701
  for (const item of operand) {
2702
+ if (item === null) {
2703
+ throw validationError4({
2704
+ message: `"${operator}" on ${model.name}.${field.name} cannot contain null; use an explicit null comparison`,
2705
+ meta: { model: model.name, field: field.name, operator }
2706
+ });
2707
+ }
2670
2708
  if (isDbNow(item)) {
2671
2709
  throw validationError4({
2672
2710
  message: `dbNow() cannot ride an "${operator}" list on ${model.name}.${field.name} \u2014 compare with lt/lte/gt/gte/equals instead`,
@@ -3496,21 +3534,17 @@ function orderedColumns(params) {
3496
3534
  return model.fields.map((field) => field.columnName).filter((column) => (column in values));
3497
3535
  }
3498
3536
  function compileAtomicOperation(params) {
3499
- const { model, field, operator, operand } = params;
3537
+ const { model, entry, operator, operand } = params;
3538
+ const field = entry.field;
3500
3539
  if (field.scalarType === null || !NUMERIC_TYPES.includes(field.scalarType)) {
3501
3540
  throw validationError4({
3502
3541
  message: `"${operator}" needs a numeric field, but ${model.name}.${field.name} is ${String(field.enumName ?? field.scalarType)}`,
3503
3542
  meta: { model: model.name, field: field.name, operator }
3504
3543
  });
3505
3544
  }
3506
- const numeric = typeof operand === "bigint" ? Number(operand) : operand;
3507
- if (typeof numeric !== "number" || !Number.isFinite(numeric)) {
3508
- throw validationError4({
3509
- message: `"${operator}" on ${model.name}.${field.name} expects a number`,
3510
- meta: { model: model.name, field: field.name, operator, operand }
3511
- });
3512
- }
3513
- return { __op: operator, value: numeric };
3545
+ const numeric = validateAtomicOperand({ field, value: operand });
3546
+ const encoded = encodeWith(entry, numeric);
3547
+ return { __op: operator, value: encoded, ...field.scalarType === "Decimal" ? { numericType: "Decimal" } : {} };
3514
3548
  }
3515
3549
  function compileUpdateSet(params) {
3516
3550
  const { dialect, model, data } = params;
@@ -3565,7 +3599,7 @@ function compileUpdateSet(params) {
3565
3599
  meta: { model: model.name, field: field.name, operator }
3566
3600
  });
3567
3601
  }
3568
- set[field.columnName] = compileAtomicOperation({ model, field, operator: atomic, operand });
3602
+ set[field.columnName] = compileAtomicOperation({ model, entry, operator: atomic, operand });
3569
3603
  continue;
3570
3604
  }
3571
3605
  set[field.columnName] = encodeWith(entry, value);
@@ -5283,7 +5317,7 @@ function buildQuery(params) {
5283
5317
  const targetFields = conflictTargetGroup({ model, where }).fields;
5284
5318
  for (const field of targetFields) {
5285
5319
  if (!(field.columnName in row.values)) {
5286
- throw new VibeError10({
5320
+ throw new VibeError11({
5287
5321
  code: "VIBE_INTERNAL",
5288
5322
  message: `upsert on ${model.name}: create data does not bind conflict-target column "${field.columnName}" \u2014 this shape must run the find-then-write fallback flow`,
5289
5323
  meta: { model: model.name, column: field.columnName }
@@ -5294,7 +5328,7 @@ function buildQuery(params) {
5294
5328
  for (const [key, value] of Object.entries(where)) {
5295
5329
  if (value === undefined || nativeTargetNames.has(key))
5296
5330
  continue;
5297
- throw new VibeError10({
5331
+ throw new VibeError11({
5298
5332
  code: "VIBE_INTERNAL",
5299
5333
  message: `upsert on ${model.name}: where field "${key}" is not part of the conflict target \u2014 guarded upserts must run the find-then-write fallback flow`,
5300
5334
  meta: { model: model.name, field: key }
@@ -5346,8 +5380,95 @@ function buildQuery(params) {
5346
5380
  // packages/runtime/src/diagnostics/preview.ts
5347
5381
  import { getSqlDialect, renderStatement } from "@vibeorm/sql";
5348
5382
 
5383
+ // packages/runtime/src/extensions.ts
5384
+ import { VibeError as VibeError12, extensionManifestEntry } from "@vibeorm/schema";
5385
+ function assertExtensionManifest(params) {
5386
+ const { manifest } = params;
5387
+ const instances = params.instances ?? [];
5388
+ const live = new Map(instances.map((extension) => [extension.name, extensionManifestEntry({ extension })]));
5389
+ const problems = [];
5390
+ for (const baked of manifest) {
5391
+ const current = live.get(baked.name);
5392
+ if (current === undefined) {
5393
+ problems.push(`"${baked.name}" was baked into the client but not passed to VibeClient`);
5394
+ continue;
5395
+ }
5396
+ if (current.version !== baked.version) {
5397
+ problems.push(`"${baked.name}" version changed (generated ${baked.version}, live ${current.version})`);
5398
+ } else if (current.optionsHash !== baked.optionsHash) {
5399
+ problems.push(`"${baked.name}" options changed since generate`);
5400
+ }
5401
+ live.delete(baked.name);
5402
+ }
5403
+ for (const name of live.keys()) {
5404
+ problems.push(`"${name}" is configured but missing from the generated client`);
5405
+ }
5406
+ if (problems.length > 0) {
5407
+ throw new VibeError12({
5408
+ code: "VIBE_STALE_CLIENT",
5409
+ message: `extension list changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
5410
+ meta: { manifest, problems }
5411
+ });
5412
+ }
5413
+ }
5414
+ function buildExtensionOperatorMap(params) {
5415
+ const { instances, dialect, provider } = params;
5416
+ const map = new Map;
5417
+ const owner = new Map;
5418
+ for (const extension of instances) {
5419
+ for (const operator of extension.sql?.whereOperators ?? []) {
5420
+ const existing = owner.get(operator.name);
5421
+ if (existing !== undefined) {
5422
+ throw new VibeError12({
5423
+ code: "VIBE_CONFIG",
5424
+ message: `where operator "${operator.name}" is claimed by both "${existing}" and "${extension.name}"`,
5425
+ meta: { operator: operator.name }
5426
+ });
5427
+ }
5428
+ owner.set(operator.name, extension.name);
5429
+ map.set(operator.name, ({ column, operand, model, field }) => {
5430
+ const compiled = operator.compile({
5431
+ column,
5432
+ operand,
5433
+ dialect,
5434
+ provider,
5435
+ options: extension.options
5436
+ });
5437
+ if ("error" in compiled) {
5438
+ throw new VibeError12({
5439
+ code: "VIBE_VALIDATION",
5440
+ message: `"${operator.name}" on ${model}.${field}: ${compiled.error}`,
5441
+ meta: { model, field, operator: operator.name, extension: extension.name }
5442
+ });
5443
+ }
5444
+ return { kind: "fragment", fragment: compiled.fragment };
5445
+ });
5446
+ }
5447
+ }
5448
+ return map.size === 0 ? null : map;
5449
+ }
5450
+ function buildConnectHooks(params) {
5451
+ const { instances, dialect, provider } = params;
5452
+ const hooks = [];
5453
+ for (const extension of instances) {
5454
+ const onConnect = extension.runtime?.onConnect;
5455
+ if (onConnect === undefined)
5456
+ continue;
5457
+ hooks.push(({ execute }) => onConnect({
5458
+ execute: (execParams) => execute({
5459
+ text: execParams.text,
5460
+ ...execParams.values === undefined ? {} : { values: execParams.values }
5461
+ }),
5462
+ dialect,
5463
+ provider,
5464
+ options: extension.options
5465
+ }));
5466
+ }
5467
+ return hooks;
5468
+ }
5469
+
5349
5470
  // packages/runtime/src/relation-key.ts
5350
- import { VibeError as VibeError11 } from "@vibeorm/schema";
5471
+ import { VibeError as VibeError13 } from "@vibeorm/schema";
5351
5472
  function relationValueKey(params) {
5352
5473
  const { value } = params;
5353
5474
  if (value === null || value === undefined)
@@ -5380,7 +5501,7 @@ function relationTupleKey(params) {
5380
5501
  if (value === null || value === undefined)
5381
5502
  return null;
5382
5503
  if (typeof value === "object" && !(value instanceof Date) && !(value instanceof Uint8Array)) {
5383
- throw new VibeError11({
5504
+ throw new VibeError13({
5384
5505
  code: "VIBE_UNSUPPORTED_CAPABILITY",
5385
5506
  message: "a relation key of this type cannot be compared faithfully in memory \u2014 relation links support String, Int, BigInt, Boolean, DateTime and Bytes keys",
5386
5507
  meta: { received: Object.prototype.toString.call(value) }
@@ -5406,7 +5527,7 @@ function assertWriteScope(params) {
5406
5527
  }
5407
5528
 
5408
5529
  // packages/runtime/src/nested-writes.ts
5409
- import { VibeError as VibeError12 } from "@vibeorm/schema";
5530
+ import { VibeError as VibeError14 } from "@vibeorm/schema";
5410
5531
  function parentTupleOf(params) {
5411
5532
  const { link, row } = params;
5412
5533
  return link.parentKeys.map((field) => row[field.name]);
@@ -5453,10 +5574,10 @@ function detachAssignment(params) {
5453
5574
  return out;
5454
5575
  }
5455
5576
  function validationError5(params) {
5456
- return new VibeError12({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
5577
+ return new VibeError14({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
5457
5578
  }
5458
5579
  function notFound(params) {
5459
- return new VibeError12({ code: "VIBE_NOT_FOUND", message: params.message, meta: params.meta });
5580
+ return new VibeError14({ code: "VIBE_NOT_FOUND", message: params.message, meta: params.meta });
5460
5581
  }
5461
5582
  function isPlainObject4(value) {
5462
5583
  return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
@@ -5601,7 +5722,7 @@ function chunkKeyList(params) {
5601
5722
  const { dialect, keys, perKey, reserved } = params;
5602
5723
  if (keys.length === 0)
5603
5724
  return [];
5604
- if (dialect.inArrayStrategy !== "expand")
5725
+ if (params.expanded !== true && dialect.inArrayStrategy !== "expand")
5605
5726
  return [keys];
5606
5727
  const size = Math.max(1, Math.floor((dialect.paramBudget - reserved) / perKey));
5607
5728
  const chunks = [];
@@ -5638,27 +5759,30 @@ async function deleteJoinRows(params) {
5638
5759
  const join = link.join;
5639
5760
  const pair = singleKeyPair({ link, what: "implicit many-to-many writes" });
5640
5761
  const parentValue = encodeValue({ dialect: dialect.name, field: pair.parent, value: parentKey });
5641
- await run.statement({
5642
- statement: {
5643
- kind: "delete",
5644
- table: { name: join.tableName },
5645
- where: {
5646
- kind: "and",
5647
- operands: [
5648
- { kind: "cmp", op: "eq", column: { name: join.parentColumn }, value: parentValue },
5649
- ...childKeys === undefined ? [] : [
5650
- {
5651
- kind: "inArray",
5652
- column: { name: join.childColumn },
5653
- values: childKeys.map((key) => encodeValue({ dialect: dialect.name, field: pair.child, value: key }))
5654
- }
5762
+ const chunks = childKeys === undefined ? [undefined] : chunkKeyList({ dialect, keys: childKeys, perKey: 1, reserved: 1 });
5763
+ for (const chunk of chunks) {
5764
+ await run.statement({
5765
+ statement: {
5766
+ kind: "delete",
5767
+ table: { name: join.tableName },
5768
+ where: {
5769
+ kind: "and",
5770
+ operands: [
5771
+ { kind: "cmp", op: "eq", column: { name: join.parentColumn }, value: parentValue },
5772
+ ...chunk === undefined ? [] : [
5773
+ {
5774
+ kind: "inArray",
5775
+ column: { name: join.childColumn },
5776
+ values: chunk.map((key) => encodeValue({ dialect: dialect.name, field: pair.child, value: key }))
5777
+ }
5778
+ ]
5655
5779
  ]
5656
- ]
5657
- }
5658
- },
5659
- model: link.relation.model,
5660
- method: "nested.disconnect"
5661
- });
5780
+ }
5781
+ },
5782
+ model: link.relation.model,
5783
+ method: "nested.disconnect"
5784
+ });
5785
+ }
5662
5786
  }
5663
5787
  async function assertM2MMembers(params) {
5664
5788
  const { run, ctx, link, parentKey, childKeys, target, verb, relation } = params;
@@ -5666,10 +5790,10 @@ async function assertM2MMembers(params) {
5666
5790
  return;
5667
5791
  const members = await loadMemberKeys({ run, ctx, link, parentKey, childKeys });
5668
5792
  const pair = singleKeyPair({ link, what: "implicit many-to-many writes" });
5669
- const connected = new Set(members.map((member) => String(member)));
5793
+ const connected = new Set(members.map((member) => relationValueKey({ value: decodeValue({ dialect: ctx.dialect.name, field: pair.child, value: member }) })));
5670
5794
  for (const childKey of childKeys) {
5671
- const childValue = encodeValue({ dialect: ctx.dialect.name, field: pair.child, value: childKey });
5672
- if (connected.has(String(childValue)))
5795
+ const childValue = decodeValue({ dialect: ctx.dialect.name, field: pair.child, value: childKey });
5796
+ if (connected.has(relationValueKey({ value: childValue })))
5673
5797
  continue;
5674
5798
  throw notFound({
5675
5799
  message: `nested "${verb}" on ${link.relation.model}.${relation}: ${target} is not connected`,
@@ -5760,7 +5884,7 @@ function isFilterableUniqueWhere(params) {
5760
5884
  return isUniqueWhere({ model: target, where });
5761
5885
  }
5762
5886
  async function connectChildrenByFk(params) {
5763
- const { run, op, items, fk, verb } = params;
5887
+ const { dialect, run, op, items, fk, verb } = params;
5764
5888
  const target = op.link.target;
5765
5889
  if (items.length === 0)
5766
5890
  return;
@@ -5771,11 +5895,18 @@ async function connectChildrenByFk(params) {
5771
5895
  }
5772
5896
  return;
5773
5897
  }
5774
- const affected = await run.affected({
5775
- model: target.name,
5776
- method: "updateMany",
5777
- args: { where: { OR: items.map((item) => ({ ...item })) }, data: fk }
5778
- });
5898
+ let perKey = 1;
5899
+ for (const item of items)
5900
+ perKey = Math.max(perKey, Object.values(item).filter((value) => value !== undefined && value !== null).length);
5901
+ const reserved = Object.values(fk).filter((value) => value !== undefined).length + target.fields.filter((field) => field.isUpdatedAt && fk[field.name] === undefined).length;
5902
+ let affected = 0;
5903
+ for (const chunk of chunkKeyList({ dialect, keys: items, perKey, reserved, expanded: true })) {
5904
+ affected += await run.affected({
5905
+ model: target.name,
5906
+ method: "updateMany",
5907
+ args: { where: { OR: chunk.map((item) => ({ ...item })) }, data: fk }
5908
+ });
5909
+ }
5779
5910
  if (affected >= items.length)
5780
5911
  return;
5781
5912
  for (const item of items) {
@@ -5845,7 +5976,7 @@ async function applyChildCreateOps(params) {
5845
5976
  continue;
5846
5977
  }
5847
5978
  if (verb === "connect") {
5848
- await connectChildrenByFk({ run, op, items, fk, verb });
5979
+ await connectChildrenByFk({ dialect: ctx.dialect, run, op, items, fk, verb });
5849
5980
  continue;
5850
5981
  }
5851
5982
  for (const item of items) {
@@ -6082,7 +6213,8 @@ async function loadMemberKeys(params) {
6082
6213
  const values = childKeys.map((childKey) => encodeValue({ dialect: ctx.dialect.name, field: pair.child, value: childKey }));
6083
6214
  const found = [];
6084
6215
  for (const chunk of chunkKeyList({ dialect: ctx.dialect, keys: values, perKey: 1, reserved: 1 })) {
6085
- found.push(...await scan(chunk));
6216
+ for (const member of await scan(chunk))
6217
+ found.push(member);
6086
6218
  }
6087
6219
  return found;
6088
6220
  }
@@ -6128,7 +6260,7 @@ async function applyChildUpdateOps(params) {
6128
6260
  method: "updateMany",
6129
6261
  args: { where: fk, data: detach }
6130
6262
  });
6131
- await connectChildrenByFk({ run, op, items, fk, verb });
6263
+ await connectChildrenByFk({ dialect: ctx.dialect, run, op, items, fk, verb });
6132
6264
  }
6133
6265
  break;
6134
6266
  }
@@ -6578,7 +6710,9 @@ function previewOperation(params) {
6578
6710
  const { schema, model, method, args, relationStrategy, clientOptions } = params;
6579
6711
  const includeValues = params.includeParameterValues === true;
6580
6712
  const dialect = getSqlDialect({ dialect: params.dialect });
6581
- const meta = buildRuntimeMeta({ schema });
6713
+ const baseMeta = buildRuntimeMeta({ schema });
6714
+ const operatorMap = buildExtensionOperatorMap({ instances: params.extensions ?? [], dialect: params.dialect, provider: schema.datasource.provider });
6715
+ const meta = operatorMap === null ? baseMeta : { ...baseMeta, extensionOperators: operatorMap };
6582
6716
  const modelMeta = getModelMeta({ meta, model });
6583
6717
  const writesNested = (method === "create" || method === "update" || method === "upsert") && (hasNestedWrites({ model: modelMeta, data: args?.data }) || hasNestedWrites({ model: modelMeta, data: args?.create }) || hasNestedWrites({ model: modelMeta, data: args?.update }));
6584
6718
  const effectiveArgs = args === undefined || !writesNested ? args : {
@@ -6660,7 +6794,7 @@ function previewOperation(params) {
6660
6794
  };
6661
6795
  }
6662
6796
  // packages/runtime/src/diagnostics/plan.ts
6663
- import { VibeError as VibeError13, unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
6797
+ import { VibeError as VibeError15, unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
6664
6798
  import { NATIVE_RLS, getSqlDialect as getSqlDialect2, renderPostgresExplain, renderPostgresWorkload, renderStatement as renderStatement2 } from "@vibeorm/sql";
6665
6799
  var PLAN_DEFAULTS = Object.freeze({ maxNodes: 200, maxConditionLength: 2000 });
6666
6800
  var CONDITION_KEYS = [
@@ -6679,7 +6813,7 @@ var CONDITION_KEYS = [
6679
6813
  "Output"
6680
6814
  ];
6681
6815
  function planError(params) {
6682
- return new VibeError13({ code: "VIBE_ADAPTER", message: params.message, meta: {} });
6816
+ return new VibeError15({ code: "VIBE_ADAPTER", message: params.message, meta: {} });
6683
6817
  }
6684
6818
  function asRecord(value) {
6685
6819
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
@@ -6712,14 +6846,14 @@ function parsePostgresPlan(params) {
6712
6846
  const maxNodes = params.maxNodes ?? PLAN_DEFAULTS.maxNodes;
6713
6847
  const includeLiterals = params.includeLiterals === true;
6714
6848
  if (!Number.isInteger(maxNodes) || maxNodes < 1) {
6715
- throw new VibeError13({ code: "VIBE_CONFIG", message: `maxNodes must be a positive integer, got ${String(maxNodes)}`, meta: { maxNodes } });
6849
+ throw new VibeError15({ code: "VIBE_CONFIG", message: `maxNodes must be a positive integer, got ${String(maxNodes)}`, meta: { maxNodes } });
6716
6850
  }
6717
6851
  let parsed = payload;
6718
6852
  if (typeof payload === "string") {
6719
6853
  try {
6720
6854
  parsed = JSON.parse(payload);
6721
6855
  } catch {
6722
- throw new VibeError13({
6856
+ throw new VibeError15({
6723
6857
  code: "VIBE_ADAPTER",
6724
6858
  message: "EXPLAIN returned malformed JSON",
6725
6859
  meta: { reason: "malformed-plan-json", mode }
@@ -6787,7 +6921,7 @@ function parsePostgresPlan(params) {
6787
6921
  var WRITE_LEADERS = ["INSERT", "UPDATE", "DELETE", "MERGE", "TRUNCATE", "CREATE", "DROP", "ALTER", "GRANT", "REVOKE", "COPY", "CALL", "DO"];
6788
6922
  var SIDE_EFFECT_CALLS = /\bpg_(?:try_)?advisory_(?:xact_)?lock(?:_shared)?\s*\(/i;
6789
6923
  function refuse(params) {
6790
- return new VibeError13({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta ?? {} });
6924
+ return new VibeError15({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta ?? {} });
6791
6925
  }
6792
6926
  function executableReadRefusal(params) {
6793
6927
  const normalized = normalizeSql({ text: params.text });
@@ -6917,7 +7051,7 @@ async function explainOnSession(params) {
6917
7051
  const context = await readPlanContext({ executor, dialect, rlsContextKeys: contextKeys });
6918
7052
  if (!context.representative) {
6919
7053
  if (params.requireRepresentativeContext === true) {
6920
- throw new VibeError13({
7054
+ throw new VibeError15({
6921
7055
  code: "VIBE_RLS_CONTEXT",
6922
7056
  message: `refusing to report a plan taken under a non-representative identity: ${context.reason ?? "role bypasses row policies"}`,
6923
7057
  meta: { currentUser: context.currentUser, bypassRls: context.bypassRls, superuser: context.superuser }
@@ -6960,7 +7094,9 @@ async function explainOperation(params) {
6960
7094
  const { schema, model, method, args } = params;
6961
7095
  const dialect = requirePostgres({ dialect: params.dialect });
6962
7096
  const mode = params.mode ?? "estimate";
6963
- const meta = buildRuntimeMeta({ schema });
7097
+ const baseMeta = buildRuntimeMeta({ schema });
7098
+ const operatorMap = buildExtensionOperatorMap({ instances: params.extensions ?? [], dialect: params.dialect, provider: schema.datasource.provider });
7099
+ const meta = operatorMap === null ? baseMeta : { ...baseMeta, extensionOperators: operatorMap };
6964
7100
  const plan = buildQuery({
6965
7101
  meta,
6966
7102
  dialect,
@@ -6991,18 +7127,19 @@ async function explainOperation(params) {
6991
7127
  });
6992
7128
  }
6993
7129
  // packages/runtime/src/diagnostics/statement-diagnostics.ts
6994
- import { VibeError as VibeError14 } from "@vibeorm/schema";
7130
+ import { VibeError as VibeError16 } from "@vibeorm/schema";
6995
7131
  import { renderPostgresWorkload as renderPostgresWorkload2 } from "@vibeorm/sql";
6996
7132
  var STATEMENT_DIAGNOSTICS_DEFAULTS = Object.freeze({
6997
7133
  slowerThanMs: 0,
6998
7134
  explain: "off",
6999
7135
  explainTimeoutMs: 5000,
7136
+ maxOutstandingExplains: 4,
7000
7137
  analyzeWarnBelowMs: 1,
7001
7138
  envVariable: "VIBEORM_DIAGNOSTICS"
7002
7139
  });
7003
7140
  var EXPLAIN_MODES = ["off", "estimate", "analyze"];
7004
7141
  function configError(params) {
7005
- return new VibeError14({ code: "VIBE_CONFIG", message: params.message, meta: params.meta ?? {} });
7142
+ return new VibeError16({ code: "VIBE_CONFIG", message: params.message, meta: params.meta ?? {} });
7006
7143
  }
7007
7144
  function isExplainMode(value) {
7008
7145
  return typeof value === "string" && EXPLAIN_MODES.includes(value);
@@ -7128,7 +7265,7 @@ function summarizePlan(params) {
7128
7265
  };
7129
7266
  }
7130
7267
  function errorCode(error) {
7131
- return error instanceof VibeError14 ? error.code : "VIBE_INTERNAL";
7268
+ return error instanceof VibeError16 ? error.code : "VIBE_INTERNAL";
7132
7269
  }
7133
7270
  function createStatementDiagnostics(params) {
7134
7271
  const { config, adapter, dialect } = params;
@@ -7215,6 +7352,10 @@ function createStatementDiagnostics(params) {
7215
7352
  report({ ...base, explainSkipped: "not-a-read" });
7216
7353
  return;
7217
7354
  }
7355
+ if (outstanding.size >= STATEMENT_DIAGNOSTICS_DEFAULTS.maxOutstandingExplains) {
7356
+ report({ ...base, explainSkipped: "overload" });
7357
+ return;
7358
+ }
7218
7359
  schedule(statement, config.explain, base);
7219
7360
  },
7220
7361
  flush: async () => {
@@ -7346,7 +7487,7 @@ async function readWorkloadStatistics(params) {
7346
7487
  };
7347
7488
  }
7348
7489
  // packages/runtime/src/diagnostics/insight.ts
7349
- import { VibeError as VibeError15 } from "@vibeorm/schema";
7490
+ import { VibeError as VibeError17 } from "@vibeorm/schema";
7350
7491
  var DIAGNOSTIC_THRESHOLDS = Object.freeze({
7351
7492
  repeatThreshold: 5,
7352
7493
  minRepeatThreshold: 2,
@@ -7364,7 +7505,7 @@ function bounded(params) {
7364
7505
  if (value === undefined)
7365
7506
  return fallback;
7366
7507
  if (!Number.isInteger(value) || value < min || value > max) {
7367
- throw new VibeError15({
7508
+ throw new VibeError17({
7368
7509
  code: "VIBE_CONFIG",
7369
7510
  message: `${name} must be an integer between ${min} and ${max}, got ${String(value)}`,
7370
7511
  meta: { [name]: value }
@@ -7587,11 +7728,11 @@ function recommendFromPlan(params) {
7587
7728
  return findings.slice(0, maxFindings);
7588
7729
  }
7589
7730
  // packages/runtime/src/relation-loader.ts
7590
- import { VibeError as VibeError17 } from "@vibeorm/schema";
7731
+ import { VibeError as VibeError19 } from "@vibeorm/schema";
7591
7732
  import { renderStatement as renderStatement3 } from "@vibeorm/sql";
7592
7733
 
7593
7734
  // packages/runtime/src/computed.ts
7594
- import { VibeError as VibeError16 } from "@vibeorm/schema";
7735
+ import { VibeError as VibeError18 } from "@vibeorm/schema";
7595
7736
  function sameNeeds(a, b) {
7596
7737
  if (a.length !== b.length)
7597
7738
  return false;
@@ -7627,7 +7768,7 @@ function assertComputedManifest(params) {
7627
7768
  problems.push(`"${key}" is configured but missing from the generated client`);
7628
7769
  }
7629
7770
  if (problems.length > 0) {
7630
- throw new VibeError16({
7771
+ throw new VibeError18({
7631
7772
  code: "VIBE_STALE_CLIENT",
7632
7773
  message: `computed-field config changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
7633
7774
  meta: { problems }
@@ -7655,7 +7796,7 @@ function resolveRelationConcurrency(params) {
7655
7796
  if (concurrency === undefined)
7656
7797
  return 1;
7657
7798
  if (!Number.isInteger(concurrency) || concurrency < 1) {
7658
- throw new VibeError17({
7799
+ throw new VibeError19({
7659
7800
  code: "VIBE_CONFIG",
7660
7801
  message: `relationLoading.concurrency must be a positive integer \u2014 got ${String(concurrency)}`,
7661
7802
  meta: { concurrency }
@@ -7840,7 +7981,7 @@ function chunkKeys(params) {
7840
7981
  const rendered = renderStatement3({ dialect: ctx.dialect, statement: build(keys.slice(0, 1)) });
7841
7982
  fixed = rendered.values.length - (expands ? arity : 1);
7842
7983
  if (fixed + arity > budget) {
7843
- throw new VibeError17({
7984
+ throw new VibeError19({
7844
7985
  code: "VIBE_UNSUPPORTED_CAPABILITY",
7845
7986
  message: `relation "${relation}" needs ${fixed + arity} bound parameters for its own predicate, more than ${ctx.dialect.name}'s ${budget}-parameter statement budget \u2014 narrow the nested filter`,
7846
7987
  meta: { relation, dialect: ctx.dialect.name, required: fixed + arity, budget }
@@ -7970,7 +8111,7 @@ async function loadManyToMany(params) {
7970
8111
  const link = node.link;
7971
8112
  const join = link.join;
7972
8113
  if (join === null) {
7973
- throw new VibeError17({
8114
+ throw new VibeError19({
7974
8115
  code: "VIBE_INTERNAL",
7975
8116
  message: `manyToMany relation "${node.name}" resolved without join-table wiring`
7976
8117
  });
@@ -8267,7 +8408,7 @@ async function attachLateralRelations(params) {
8267
8408
  try {
8268
8409
  parsed2 = JSON.parse(raw);
8269
8410
  } catch (cause) {
8270
- throw new VibeError17({
8411
+ throw new VibeError19({
8271
8412
  code: "VIBE_INTERNAL",
8272
8413
  message: `relation column "${column}" returned text that is not valid JSON`,
8273
8414
  meta: { relation: node.name },
@@ -8277,7 +8418,7 @@ async function attachLateralRelations(params) {
8277
8418
  }
8278
8419
  if (isList) {
8279
8420
  if (!Array.isArray(parsed2)) {
8280
- throw new VibeError17({ code: "VIBE_INTERNAL", message: "to-many relation payload must be an array", meta: { relation: node.name } });
8421
+ throw new VibeError19({ code: "VIBE_INTERNAL", message: "to-many relation payload must be an array", meta: { relation: node.name } });
8281
8422
  }
8282
8423
  const children = parsed2.map(decodePosition);
8283
8424
  for (const child of children) {
@@ -8318,7 +8459,7 @@ async function attachLateralRelations(params) {
8318
8459
  const raw = row[column];
8319
8460
  const count = typeof raw === "number" || typeof raw === "bigint" || typeof raw === "string" && /^\d+$/.test(raw) ? Number(raw) : NaN;
8320
8461
  if (!Number.isSafeInteger(count) || count < 0) {
8321
- throw new VibeError17({ code: "VIBE_INTERNAL", message: "relation count payload must be a non-negative safe integer", meta: { relation: selection.counts[index]?.name } });
8462
+ throw new VibeError19({ code: "VIBE_INTERNAL", message: "relation count payload must be a non-negative safe integer", meta: { relation: selection.counts[index]?.name } });
8322
8463
  }
8323
8464
  counts[selection.counts[index].name] = count;
8324
8465
  delete row[column];
@@ -8326,97 +8467,12 @@ async function attachLateralRelations(params) {
8326
8467
  row._count = counts;
8327
8468
  }
8328
8469
  }
8329
- // packages/runtime/src/extensions.ts
8330
- import { VibeError as VibeError18, extensionManifestEntry } from "@vibeorm/schema";
8331
- function assertExtensionManifest(params) {
8332
- const { manifest } = params;
8333
- const instances = params.instances ?? [];
8334
- const live = new Map(instances.map((extension) => [extension.name, extensionManifestEntry({ extension })]));
8335
- const problems = [];
8336
- for (const baked of manifest) {
8337
- const current = live.get(baked.name);
8338
- if (current === undefined) {
8339
- problems.push(`"${baked.name}" was baked into the client but not passed to VibeClient`);
8340
- continue;
8341
- }
8342
- if (current.version !== baked.version) {
8343
- problems.push(`"${baked.name}" version changed (generated ${baked.version}, live ${current.version})`);
8344
- } else if (current.optionsHash !== baked.optionsHash) {
8345
- problems.push(`"${baked.name}" options changed since generate`);
8346
- }
8347
- live.delete(baked.name);
8348
- }
8349
- for (const name of live.keys()) {
8350
- problems.push(`"${name}" is configured but missing from the generated client`);
8351
- }
8352
- if (problems.length > 0) {
8353
- throw new VibeError18({
8354
- code: "VIBE_STALE_CLIENT",
8355
- message: `extension list changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
8356
- meta: { manifest, problems }
8357
- });
8358
- }
8359
- }
8360
- function buildExtensionOperatorMap(params) {
8361
- const { instances, dialect, provider } = params;
8362
- const map = new Map;
8363
- const owner = new Map;
8364
- for (const extension of instances) {
8365
- for (const operator of extension.sql?.whereOperators ?? []) {
8366
- const existing = owner.get(operator.name);
8367
- if (existing !== undefined) {
8368
- throw new VibeError18({
8369
- code: "VIBE_CONFIG",
8370
- message: `where operator "${operator.name}" is claimed by both "${existing}" and "${extension.name}"`,
8371
- meta: { operator: operator.name }
8372
- });
8373
- }
8374
- owner.set(operator.name, extension.name);
8375
- map.set(operator.name, ({ column, operand, model, field }) => {
8376
- const compiled = operator.compile({
8377
- column,
8378
- operand,
8379
- dialect,
8380
- provider,
8381
- options: extension.options
8382
- });
8383
- if ("error" in compiled) {
8384
- throw new VibeError18({
8385
- code: "VIBE_VALIDATION",
8386
- message: `"${operator.name}" on ${model}.${field}: ${compiled.error}`,
8387
- meta: { model, field, operator: operator.name, extension: extension.name }
8388
- });
8389
- }
8390
- return { kind: "fragment", fragment: compiled.fragment };
8391
- });
8392
- }
8393
- }
8394
- return map.size === 0 ? null : map;
8395
- }
8396
- function buildConnectHooks(params) {
8397
- const { instances, dialect, provider } = params;
8398
- const hooks = [];
8399
- for (const extension of instances) {
8400
- const onConnect = extension.runtime?.onConnect;
8401
- if (onConnect === undefined)
8402
- continue;
8403
- hooks.push(({ execute }) => onConnect({
8404
- execute: (execParams) => execute({
8405
- text: execParams.text,
8406
- ...execParams.values === undefined ? {} : { values: execParams.values }
8407
- }),
8408
- dialect,
8409
- provider,
8410
- options: extension.options
8411
- }));
8412
- }
8413
- return hooks;
8414
- }
8415
8470
  // packages/runtime/src/views.ts
8416
- import { VibeError as VibeError20 } from "@vibeorm/schema";
8471
+ import { VibeError as VibeError21 } from "@vibeorm/schema";
8417
8472
 
8418
8473
  // packages/runtime/src/sql-access.ts
8419
- import { VibeError as VibeError19 } from "@vibeorm/schema";
8474
+ import { Buffer as Buffer2 } from "buffer";
8475
+ import { VibeError as VibeError20 } from "@vibeorm/schema";
8420
8476
  import { isSqlFragment as isSqlFragment2 } from "@vibeorm/sql";
8421
8477
  function containsSqlFragment(value, seen = new Set) {
8422
8478
  if (isSqlFragment2(value))
@@ -8424,42 +8480,92 @@ function containsSqlFragment(value, seen = new Set) {
8424
8480
  if (typeof value !== "object" || value === null || seen.has(value))
8425
8481
  return false;
8426
8482
  seen.add(value);
8427
- if (Array.isArray(value))
8428
- return value.some((entry) => containsSqlFragment(entry, seen));
8429
- return Object.values(value).some((entry) => containsSqlFragment(entry, seen));
8483
+ const keys = new Set;
8484
+ for (let object = value;object !== null && object !== Object.prototype; object = Object.getPrototypeOf(object)) {
8485
+ for (const key of Object.getOwnPropertyNames(object)) {
8486
+ if (keys.has(key) || key === "constructor")
8487
+ continue;
8488
+ keys.add(key);
8489
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
8490
+ if (descriptor === undefined)
8491
+ continue;
8492
+ const child = "value" in descriptor ? descriptor.value : object === value && descriptor.enumerable ? value[key] : undefined;
8493
+ if (containsSqlFragment(child, seen))
8494
+ return true;
8495
+ }
8496
+ }
8497
+ return false;
8430
8498
  }
8431
8499
  function restrictedSqlArgs(params) {
8500
+ const refuse2 = () => params.refusal?.() ?? new VibeError20({ code: "VIBE_VALIDATION", message: `SQL fragments are unavailable through ${params.path}; use the unrestricted client`, meta: { path: params.path } });
8501
+ if (containsSqlFragment(params.value))
8502
+ throw refuse2();
8432
8503
  const seen = new Map;
8433
8504
  const copy = (value) => {
8434
8505
  if (isSqlFragment2(value))
8435
- throw new VibeError19({ code: "VIBE_VALIDATION", message: `SQL fragments are unavailable through ${params.path}; use the unrestricted client`, meta: { path: params.path } });
8506
+ throw refuse2();
8436
8507
  if (typeof value !== "object" || value === null)
8437
8508
  return value;
8438
8509
  if (seen.has(value))
8439
8510
  return seen.get(value);
8511
+ let out;
8440
8512
  if (Array.isArray(value)) {
8441
- const out2 = [];
8442
- seen.set(value, out2);
8513
+ const array = [];
8514
+ out = array;
8515
+ seen.set(value, out);
8443
8516
  for (const item of value)
8444
- out2.push(copy(item));
8445
- return out2;
8517
+ array.push(copy(item));
8518
+ } else if (value instanceof Date) {
8519
+ out = new Date(value.getTime());
8520
+ } else if (value instanceof Uint8Array) {
8521
+ out = Buffer2.isBuffer(value) ? Buffer2.from(value) : new Uint8Array(value);
8522
+ } else {
8523
+ const record = Object.create(null);
8524
+ out = record;
8525
+ seen.set(value, out);
8526
+ for (const [key, child] of Object.entries(value)) {
8527
+ if (key !== "toJSON" || typeof child !== "function")
8528
+ record[key] = copy(child);
8529
+ }
8446
8530
  }
8447
- if (value instanceof Date)
8448
- return new Date(value.getTime());
8449
- if (value instanceof Uint8Array)
8450
- return new Uint8Array(value);
8451
- const out = Object.create(null);
8452
8531
  seen.set(value, out);
8453
- for (const [key, child] of Object.entries(value))
8454
- out[key] = copy(child);
8532
+ const toJSON = value.toJSON;
8533
+ const nativeJson = value instanceof Date && toJSON === Date.prototype.toJSON || Buffer2.isBuffer(value) && toJSON === Buffer2.prototype.toJSON;
8534
+ if (typeof toJSON === "function" && !nativeJson) {
8535
+ Object.defineProperty(out, "toJSON", { enumerable: false, value: (key) => {
8536
+ const result = toJSON.call(value, key);
8537
+ return result === value ? out : restrictedSqlArgs({ ...params, value: result });
8538
+ } });
8539
+ }
8455
8540
  return out;
8456
8541
  };
8457
8542
  return copy(params.value);
8458
8543
  }
8544
+ var conflictSchemas = new WeakMap;
8545
+ function registerRestrictedSqlSchema(params) {
8546
+ conflictSchemas.set(params.meta, new Map(params.schema.models.map((model) => [
8547
+ model.name,
8548
+ model.indexes.flatMap((index) => index.kind === "unique" && index.where !== undefined ? [{ fields: index.fields.map((field) => field.name), where: index.where }] : [])
8549
+ ])));
8550
+ }
8551
+ function restrictConflictPredicates(params) {
8552
+ for (const key of ["onConflict", "skipDuplicates"]) {
8553
+ const option = params.args[key];
8554
+ if (typeof option !== "object" || option === null)
8555
+ continue;
8556
+ const spec = option;
8557
+ if (spec.where === undefined)
8558
+ continue;
8559
+ const target = spec.target;
8560
+ const declared = conflictSchemas.get(params.meta)?.get(params.model.name)?.some((index) => typeof spec.where === "string" && index.where === spec.where && Array.isArray(target) && target.length === index.fields.length && index.fields.every((field, position) => field === target[position]));
8561
+ if (declared !== true)
8562
+ throw new VibeError20({ code: "VIBE_VALIDATION", message: `${params.path}: ${key}.where must match a declared unique index and its target fields; free-form SQL requires the unrestricted client`, meta: { model: params.model.name, path: params.path } });
8563
+ }
8564
+ }
8459
8565
 
8460
8566
  // packages/runtime/src/views.ts
8461
8567
  function validationError6(params) {
8462
- return new VibeError20({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
8568
+ return new VibeError21({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
8463
8569
  }
8464
8570
 
8465
8571
  class ViewResult {
@@ -8813,7 +8919,7 @@ function createView(params) {
8813
8919
  for (const [clientName, mask] of resolved) {
8814
8920
  const delegate = models[clientName];
8815
8921
  if (delegate === undefined) {
8816
- throw new VibeError20({
8922
+ throw new VibeError21({
8817
8923
  code: "VIBE_INTERNAL",
8818
8924
  message: `view "${name}": no delegate for model "${mask.model.name}"`,
8819
8925
  meta: { view: name, model: mask.model.name }
@@ -8829,9 +8935,9 @@ function attachDefineView(params) {
8829
8935
  Object.defineProperty(client, "$defineView", { value, enumerable: true });
8830
8936
  }
8831
8937
  // packages/runtime/src/validators.ts
8832
- import { VibeError as VibeError21 } from "@vibeorm/schema";
8938
+ import { VibeError as VibeError22 } from "@vibeorm/schema";
8833
8939
  function validationFailed(params) {
8834
- return new VibeError21({
8940
+ return new VibeError22({
8835
8941
  code: "VIBE_VALIDATION",
8836
8942
  message: `validation failed for ${params.model}.${params.field}: ${params.issues[0]?.message ?? "invalid value"}`,
8837
8943
  meta: { model: params.model, field: params.field, issues: params.issues }
@@ -8840,7 +8946,7 @@ function validationFailed(params) {
8840
8946
  function validateValue(params) {
8841
8947
  const result = params.schema["~standard"].validate(params.value);
8842
8948
  if (result instanceof Promise) {
8843
- throw new VibeError21({
8949
+ throw new VibeError22({
8844
8950
  code: "VIBE_VALIDATION",
8845
8951
  message: `async validator on ${params.model}.${params.field} \u2014 write validators must be synchronous (validate before calling the client for async rules)`,
8846
8952
  meta: { model: params.model, field: params.field }
@@ -8963,7 +9069,29 @@ function deferPolicyOperation(params) {
8963
9069
  }
8964
9070
 
8965
9071
  // packages/runtime/src/policy.ts
8966
- import { VibeError as VibeError22 } from "@vibeorm/schema";
9072
+ import { VibeError as VibeError24 } from "@vibeorm/schema";
9073
+
9074
+ // packages/runtime/src/module-context.ts
9075
+ import { VibeError as VibeError23 } from "@vibeorm/schema";
9076
+ var binders = new WeakMap;
9077
+ function registerModuleBinder(params) {
9078
+ binders.set(params.client, params.bind);
9079
+ }
9080
+ function inheritReadOnlyModuleBinder(params) {
9081
+ const bind = binders.get(params.from);
9082
+ if (bind !== undefined)
9083
+ binders.set(params.to, (artifact) => bind(artifact).$readOnly());
9084
+ }
9085
+ function bindModuleContext(params) {
9086
+ const bind = binders.get(params.db);
9087
+ if (bind === undefined)
9088
+ throw new VibeError23({
9089
+ code: "VIBE_CONFIG",
9090
+ message: `Module ${params.artifact.manifest.id} needs an unrestricted handle from the same VibeORM runtime installation; scoped and foreign handles cannot attach package models`,
9091
+ meta: { reason: "module_handle", moduleId: params.artifact.manifest.id, protocol: params.artifact.manifest.protocol }
9092
+ });
9093
+ return bind(params.artifact);
9094
+ }
8967
9095
 
8968
9096
  // packages/runtime/src/read-only.ts
8969
9097
  import { ReadOnlyClientError } from "@vibeorm/schema";
@@ -9050,7 +9178,7 @@ function classifiedMount(params) {
9050
9178
  return typeof value === "function" ? (...args) => {
9051
9179
  if (containsSqlFragment(args))
9052
9180
  return Promise.reject(readOnlyRefusal({ path: "extension", reason: "raw" }));
9053
- return value(...restrictedSqlArgs({ value: args, path: "$readOnly extension" }));
9181
+ return value(...restrictedSqlArgs({ value: args, path: "$readOnly extension", refusal: () => readOnlyRefusal({ path: "extension", reason: "raw" }) }));
9054
9182
  } : undefined;
9055
9183
  if (typeof value !== "object" || value === null)
9056
9184
  return;
@@ -9062,7 +9190,7 @@ function classifiedMount(params) {
9062
9190
  namespace[member] = (...args) => {
9063
9191
  if (containsSqlFragment(args))
9064
9192
  return Promise.reject(readOnlyRefusal({ path: member, reason: "raw" }));
9065
- return fn(...restrictedSqlArgs({ value: args, path: "$readOnly extension" }));
9193
+ return fn(...restrictedSqlArgs({ value: args, path: "$readOnly extension", refusal: () => readOnlyRefusal({ path: member, reason: "raw" }) }));
9066
9194
  };
9067
9195
  }
9068
9196
  return Object.freeze(namespace);
@@ -9077,7 +9205,7 @@ function readOnlyDelegate(params) {
9077
9205
  delegate[method] = (...args) => {
9078
9206
  if (containsSqlFragment(args))
9079
9207
  return Promise.reject(readOnlyRefusal({ path: `${model.clientName}.${method}`, reason: "raw" }));
9080
- const operation = fn(...args);
9208
+ const operation = fn(...restrictedSqlArgs({ value: args, path: "$readOnly", refusal: () => readOnlyRefusal({ path: `${model.clientName}.${method}`, reason: "raw" }) }));
9081
9209
  params.operations.restrictSql?.({ value: operation });
9082
9210
  return operation;
9083
9211
  };
@@ -9196,6 +9324,7 @@ function readOnlyOver(params) {
9196
9324
  if (existing !== undefined)
9197
9325
  return existing;
9198
9326
  const built = buildReadOnly(params);
9327
+ inheritReadOnlyModuleBinder({ from: params.client, to: built });
9199
9328
  facades.set(params.client, built);
9200
9329
  return built;
9201
9330
  }
@@ -9226,7 +9355,7 @@ function inheritReadOnly(params) {
9226
9355
 
9227
9356
  // packages/runtime/src/policy.ts
9228
9357
  function policyError(message, meta) {
9229
- return new VibeError22({ code: "VIBE_VALIDATION", message, meta: meta ?? {} });
9358
+ return new VibeError24({ code: "VIBE_VALIDATION", message, meta: meta ?? {} });
9230
9359
  }
9231
9360
  function policiedModels(meta) {
9232
9361
  return meta.models.filter((model) => model.policy !== undefined);
@@ -9551,7 +9680,11 @@ function scopedDelegate(params) {
9551
9680
  const flat = uniqueToFilter({ model, where: scoped.where ?? {} });
9552
9681
  return { ...scoped, where: mergeWhere({ where: flat, field: policy.field, value }) };
9553
9682
  };
9554
- const scopeWrite = (args) => scopeSelection({ meta, model, args, context });
9683
+ const scopeWrite = (input) => {
9684
+ const args = restrictedSqlArgs({ value: input, path: "$withPolicy" });
9685
+ restrictConflictPredicates({ meta, model, args, path: "$withPolicy" });
9686
+ return scopeSelection({ meta, model, args, context });
9687
+ };
9555
9688
  const writeData = (data, phase) => forceWriteData({ meta, model, data: data ?? {}, context, phase });
9556
9689
  const pkClientKey = () => {
9557
9690
  for (const [key, group] of model.compoundUniques) {
@@ -9572,7 +9705,7 @@ function scopedDelegate(params) {
9572
9705
  const pkWhere = pk.length === 1 ? { [pk[0]]: target[pk[0]] } : { [pkClientKey()]: Object.fromEntries(pk.map((name) => [name, target[name]])) };
9573
9706
  return write(raw, pkWhere);
9574
9707
  };
9575
- const notFound2 = (method) => new VibeError22({ code: "VIBE_NOT_FOUND", message: `${method} on ${model.name}: no row matches the unique where inside the bound policy context`, meta: { model: model.name, method } });
9708
+ const notFound2 = (method) => new VibeError24({ code: "VIBE_NOT_FOUND", message: `${method} on ${model.name}: no row matches the unique where inside the bound policy context`, meta: { model: model.name, method } });
9576
9709
  return {
9577
9710
  findMany: (args) => base.findMany(scopeFind(args)),
9578
9711
  findFirst: (args) => base.findFirst(scopeFind(args)),
@@ -9581,7 +9714,7 @@ function scopedDelegate(params) {
9581
9714
  findUniqueOrThrow: (args) => base.findFirstOrThrow(scopeUnique(args)),
9582
9715
  create: (args) => deferred(() => base.create({ ...scopeWrite(args), data: writeData(args.data, "create") })),
9583
9716
  createMany: (args) => deferred(() => base.createMany({
9584
- ...args,
9717
+ ...scopeWrite(args),
9585
9718
  data: (args.data ?? []).map((row) => writeData(row, "create"))
9586
9719
  })),
9587
9720
  createManyAndReturn: (args) => deferred(() => base.createManyAndReturn({
@@ -9606,7 +9739,7 @@ function scopedDelegate(params) {
9606
9739
  })
9607
9740
  })),
9608
9741
  upsertMany: () => {
9609
- throw new VibeError22({
9742
+ throw new VibeError24({
9610
9743
  code: "VIBE_UNSUPPORTED_CAPABILITY",
9611
9744
  message: "upsertMany is not available on a policy-bound client: its conflict target is not policy-checked yet",
9612
9745
  meta: { model: model.name }
@@ -9615,7 +9748,7 @@ function scopedDelegate(params) {
9615
9748
  updateManyAndReturn: (args) => deferred(() => {
9616
9749
  const run = base.updateManyAndReturn;
9617
9750
  if (run === undefined) {
9618
- throw new VibeError22({
9751
+ throw new VibeError24({
9619
9752
  code: "VIBE_UNSUPPORTED_CAPABILITY",
9620
9753
  message: "updateManyAndReturn is not available on this delegate",
9621
9754
  meta: { model: model.name }
@@ -9664,7 +9797,10 @@ function scopedDelegate(params) {
9664
9797
  function globalDelegate(params) {
9665
9798
  const { meta, model, base, context } = params;
9666
9799
  const readArgs = (args) => scopeArgs({ meta, model, args: args ?? {}, context });
9667
- const sel = (args) => {
9800
+ const sel = (input) => {
9801
+ const args = params.policyBound === false ? input : restrictedSqlArgs({ value: input, path: "$withPolicy" });
9802
+ if (params.policyBound !== false)
9803
+ restrictConflictPredicates({ meta, model, args, path: "$withPolicy" });
9668
9804
  const scoped = scopeSelection({ meta, model, args, context });
9669
9805
  return scoped.where === undefined ? scoped : { ...scoped, where: scopeWhere({ meta, model, where: scoped.where, context }) };
9670
9806
  };
@@ -9679,14 +9815,14 @@ function globalDelegate(params) {
9679
9815
  aggregate: (args) => base.aggregate(readArgs(args)),
9680
9816
  groupBy: (args) => base.groupBy(readArgs(args)),
9681
9817
  create: (args) => deferred(() => base.create({ ...sel(args), data: payload(args.data, "create") })),
9682
- createMany: (args) => deferred(() => base.createMany({ ...args, data: (args.data ?? []).map((r) => payload(r, "create")) })),
9818
+ createMany: (args) => deferred(() => base.createMany({ ...sel(args), data: (args.data ?? []).map((r) => payload(r, "create")) })),
9683
9819
  createManyAndReturn: (args) => deferred(() => base.createManyAndReturn({ ...sel(args), data: (args.data ?? []).map((r) => payload(r, "create")) })),
9684
9820
  update: (args) => deferred(() => base.update({ ...sel(args), data: payload(args.data, "update") })),
9685
9821
  updateMany: (args) => deferred(() => base.updateMany({ ...sel(args), data: payload(args.data, "update") })),
9686
9822
  updateManyAndReturn: (args) => deferred(() => {
9687
9823
  const run = base.updateManyAndReturn;
9688
9824
  if (run === undefined) {
9689
- throw new VibeError22({
9825
+ throw new VibeError24({
9690
9826
  code: "VIBE_UNSUPPORTED_CAPABILITY",
9691
9827
  message: "updateManyAndReturn is not available on this delegate"
9692
9828
  });
@@ -9697,7 +9833,7 @@ function globalDelegate(params) {
9697
9833
  upsertMany: (args) => deferred(() => {
9698
9834
  if (params.policyBound === false)
9699
9835
  return base.upsertMany(args);
9700
- throw new VibeError22({
9836
+ throw new VibeError24({
9701
9837
  code: "VIBE_UNSUPPORTED_CAPABILITY",
9702
9838
  message: "upsertMany is not available on a policy-bound client: its conflict target is not policy-checked yet"
9703
9839
  });
@@ -9764,13 +9900,26 @@ function attachPolicy(params) {
9764
9900
  });
9765
9901
  }
9766
9902
  }
9767
- const buildScoped = (context) => {
9768
- for (const model of policied) {
9769
- const key = model.policy?.context;
9770
- if (context[key] === undefined || context[key] === null) {
9771
- throw policyError(`$withPolicy: missing context value "${key}" (required by ${model.name})`, { context: key });
9772
- }
9903
+ const buildScoped = (input) => {
9904
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
9905
+ throw policyError("$withPolicy: context must contain own scalar identity values");
9773
9906
  }
9907
+ const snapshot = Object.create(null);
9908
+ for (const model of policied) {
9909
+ const policy = model.policy;
9910
+ const key = policy.context;
9911
+ const descriptor = Object.getOwnPropertyDescriptor(Object.hasOwn(snapshot, key) ? snapshot : input, key);
9912
+ if (descriptor === undefined || !("value" in descriptor) || descriptor.value === undefined || descriptor.value === null) {
9913
+ throw policyError(`$withPolicy: missing context value "${key}" (required as an own data property by ${model.name})`, { context: key });
9914
+ }
9915
+ const value = descriptor.value;
9916
+ const field = model.fieldByName.get(policy.field);
9917
+ const valid = field !== undefined && !field.isList && (field.scalarType === "String" ? typeof value === "string" : field.scalarType === "Int" ? typeof value === "number" && Number.isSafeInteger(value) : field.scalarType === "BigInt" ? typeof value === "bigint" : field.scalarType === "Float" || field.scalarType === "Decimal" ? typeof value === "number" && Number.isFinite(value) : field.enumName !== null && typeof value === "string" && meta.enums.get(field.enumName)?.includes(value) === true);
9918
+ if (!valid)
9919
+ throw policyError(`$withPolicy: context "${key}" must match the scalar type of ${model.name}.${policy.field}`, { context: key, model: model.name, field: policy.field });
9920
+ snapshot[key] = value;
9921
+ }
9922
+ const context = Object.freeze(snapshot);
9774
9923
  const scoped = Object.create(client);
9775
9924
  const scopedModels = { ...client.$models };
9776
9925
  for (const model of meta.models) {
@@ -9855,7 +10004,7 @@ function attachPolicy(params) {
9855
10004
  });
9856
10005
  }
9857
10006
  // packages/runtime/src/scoped.ts
9858
- import { ScopedQueryError, VibeError as VibeError23 } from "@vibeorm/schema";
10007
+ import { ScopedQueryError, VibeError as VibeError25 } from "@vibeorm/schema";
9859
10008
  function refusal(params) {
9860
10009
  return new ScopedQueryError(params);
9861
10010
  }
@@ -10588,6 +10737,7 @@ function rewriteScopedCall(params) {
10588
10737
  const scoped = entry.kind !== "none";
10589
10738
  const root = `${model.clientName}.${method}`;
10590
10739
  const args = restrictedSqlArgs({ value: params.args ?? {}, path: "$scoped" });
10740
+ restrictConflictPredicates({ meta, model, args, path: "$scoped" });
10591
10741
  const ownPred = () => requirePred({ ctx, model, path: root });
10592
10742
  const walkOwnWhere = (where, path) => isPlainObject8(where) ? walkWhere({ ctx, model, where, path }) : where;
10593
10743
  if (FIND_LIKE.includes(method)) {
@@ -10810,7 +10960,7 @@ function attachScoped(params) {
10810
10960
  Object.defineProperty(client, "$scoped", {
10811
10961
  value: (config) => {
10812
10962
  if (!isPlainObject8(config) || !isPlainObject8(config.models)) {
10813
- throw new VibeError23({
10963
+ throw new VibeError25({
10814
10964
  code: "VIBE_VALIDATION",
10815
10965
  message: "$scoped takes { scope: string | number | bigint | null, models: { <Model>: \u2026 } }"
10816
10966
  });
@@ -10826,7 +10976,7 @@ function attachScoped(params) {
10826
10976
  }
10827
10977
  const scope = config.scope;
10828
10978
  if (scope !== null && typeof scope !== "string" && typeof scope !== "number" && typeof scope !== "bigint") {
10829
- throw new VibeError23({
10979
+ throw new VibeError25({
10830
10980
  code: "VIBE_VALIDATION",
10831
10981
  message: `$scoped: scope must be a string, number, bigint or null \u2014 got ${typeof scope}`
10832
10982
  });
@@ -10842,7 +10992,7 @@ function attachScoped(params) {
10842
10992
  });
10843
10993
  }
10844
10994
  // packages/runtime/src/rls-readiness.ts
10845
- import { VibeError as VibeError24, assertValidSchemaIR, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
10995
+ import { VibeError as VibeError26, assertValidSchemaIR, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
10846
10996
  import { NATIVE_RLS as NATIVE_RLS2, getSqlDialect as getSqlDialect4, renderRlsPolicy, renderRlsRole, NATIVE_RLS_PROBE_POLICY, renderRlsReadinessQuery, renderRlsProbeTable } from "@vibeorm/sql";
10847
10997
  import { RLS_READINESS_SQL, NATIVE_RLS_PROBE_POLICY as NATIVE_RLS_PROBE_POLICY2 } from "@vibeorm/sql";
10848
10998
  function textOf(params) {
@@ -10866,7 +11016,7 @@ function intOf(params) {
10866
11016
  return Number.parseInt(value, 10);
10867
11017
  }
10868
11018
  function unreadableCatalog(params) {
10869
- return new VibeError24({
11019
+ return new VibeError26({
10870
11020
  code: "VIBE_ADAPTER",
10871
11021
  message: `native RLS readiness could not read the "${params.key}" result of the folded catalog read: ${params.detail}. ` + "The inspection is refused rather than treated as an empty result set.",
10872
11022
  meta: { result: params.key }
@@ -10925,7 +11075,7 @@ function requireNativeRls(params) {
10925
11075
  throw unsupportedCapability7({ dialect: dialect.name, feature: "nativeRowLevelSecurity" });
10926
11076
  }
10927
11077
  if (params.schema.rls === undefined) {
10928
- throw new VibeError24({
11078
+ throw new VibeError26({
10929
11079
  code: "VIBE_CONFIG",
10930
11080
  message: "Native row-level security readiness requires a schema that declares native RLS"
10931
11081
  });
@@ -11338,7 +11488,7 @@ async function inspectNativeRls(params) {
11338
11488
  function requireEmbeddedRole(params) {
11339
11489
  const { role } = params;
11340
11490
  if (typeof role !== "string" || role.length === 0 || role.includes("\x00")) {
11341
- throw new VibeError24({
11491
+ throw new VibeError26({
11342
11492
  code: "VIBE_CONFIG",
11343
11493
  message: "PGlite native row-level security requires a pre-existing restricted runtime role (nativeRls.runtimeRole) \u2014 its default embedded session is privileged and bypasses every policy",
11344
11494
  meta: { provider: "pglite" }
@@ -11350,7 +11500,7 @@ function assertNativeRlsReady(params) {
11350
11500
  const { report } = params;
11351
11501
  if (report.ready)
11352
11502
  return;
11353
- throw new VibeError24({
11503
+ throw new VibeError26({
11354
11504
  code: "VIBE_CONFIG",
11355
11505
  message: `Native row-level security is not ready on this database: ${report.problems.map((problem) => `${problem.name} (${problem.status})`).join(", ")}`,
11356
11506
  meta: {
@@ -11374,9 +11524,13 @@ function formatNativeRlsReport(params) {
11374
11524
  }
11375
11525
  return lines;
11376
11526
  }
11527
+ // packages/runtime/src/database-module.ts
11528
+ import { createHash } from "crypto";
11529
+ import { VibeError as VibeError31, assertValidSchemaIR as assertValidSchemaIR3, providerToDialect } from "@vibeorm/schema";
11530
+
11377
11531
  // packages/runtime/src/client.ts
11378
11532
  import { compileSql } from "@vibeorm/sql";
11379
- import { RlsContextError as RlsContextError2, VibeError as VibeError28, assertValidSchemaIR as assertValidSchemaIR2, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
11533
+ import { RlsContextError as RlsContextError2, VibeError as VibeError30, assertValidSchemaIR as assertValidSchemaIR2, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
11380
11534
  import { ADVISORY_LOCK_RESULT_COLUMN, ParamCollector, getSqlDialect as getSqlDialect5, renderRlsContext, renderRlsPolicy as renderRlsPolicy2, renderRlsRole as renderRlsRole2, renderStatement as renderStatement5 } from "@vibeorm/sql";
11381
11535
 
11382
11536
  // packages/runtime/src/render-cache.ts
@@ -11887,11 +12041,11 @@ function renderStatementCached(params) {
11887
12041
  }
11888
12042
 
11889
12043
  // packages/runtime/src/strict-args.ts
11890
- import { VibeError as VibeError25 } from "@vibeorm/schema";
12044
+ import { VibeError as VibeError27 } from "@vibeorm/schema";
11891
12045
  var SKIP = Symbol.for("vibeorm.skip");
11892
12046
  function explicitUndefined(params) {
11893
12047
  const { model, method, path } = params;
11894
- return new VibeError25({
12048
+ return new VibeError27({
11895
12049
  code: "VIBE_VALIDATION",
11896
12050
  message: `${model}.${method}(): explicit undefined at ${path} \u2014 an undefined value is refused ` + `because it would silently drop the property (widening a filter or losing a write). ` + `Omit the property, or pass SKIP to omit it deliberately; ` + `VibeClient({ strictArguments: false }) restores the legacy behaviour.`,
11897
12051
  meta: { model, method, path }
@@ -12068,10 +12222,10 @@ function bindRlsContext(params) {
12068
12222
  }
12069
12223
 
12070
12224
  // packages/runtime/src/telemetry/recorder.ts
12071
- import { VibeError as VibeError27 } from "@vibeorm/schema";
12225
+ import { VibeError as VibeError29 } from "@vibeorm/schema";
12072
12226
 
12073
12227
  // packages/runtime/src/telemetry/config.ts
12074
- import { VibeError as VibeError26 } from "@vibeorm/schema";
12228
+ import { VibeError as VibeError28 } from "@vibeorm/schema";
12075
12229
  var TELEMETRY_DEFAULTS = Object.freeze({
12076
12230
  captureSql: false,
12077
12231
  captureParameterValues: false,
@@ -12086,7 +12240,7 @@ var TELEMETRY_DEFAULTS = Object.freeze({
12086
12240
  latencyBucketsMs: Object.freeze([1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000])
12087
12241
  });
12088
12242
  function configError2(params) {
12089
- return new VibeError26({
12243
+ return new VibeError28({
12090
12244
  code: "VIBE_CONFIG",
12091
12245
  message: `telemetry: ${params.message}`,
12092
12246
  ...params.meta === undefined ? {} : { meta: params.meta }
@@ -12174,7 +12328,7 @@ function deriveStage(params) {
12174
12328
  return "decode";
12175
12329
  }
12176
12330
  function errorCodeOf(error) {
12177
- return error instanceof VibeError27 ? error.code : undefined;
12331
+ return error instanceof VibeError29 ? error.code : undefined;
12178
12332
  }
12179
12333
  function newHistogram(bounds) {
12180
12334
  return { counts: new Array(bounds.length + 1).fill(0), sum: 0, count: 0 };
@@ -12557,7 +12711,7 @@ async function instrumentStatement(params) {
12557
12711
  // packages/runtime/src/client.ts
12558
12712
  function notFound2(params) {
12559
12713
  const { model, method } = params;
12560
- return new VibeError28({
12714
+ return new VibeError30({
12561
12715
  code: "VIBE_NOT_FOUND",
12562
12716
  message: `no ${model} record matched the where clause of ${method}()`,
12563
12717
  meta: { model, method }
@@ -12565,7 +12719,7 @@ function notFound2(params) {
12565
12719
  }
12566
12720
  function transactionLifecycleRefusal(params) {
12567
12721
  const { method } = params;
12568
- return new VibeError28({
12722
+ return new VibeError30({
12569
12723
  code: "VIBE_VALIDATION",
12570
12724
  message: `${method}() is not available on a transaction client \u2014 the connection is owned by the open transaction; call it on the client that started the transaction`,
12571
12725
  meta: { method, inTransaction: true }
@@ -12573,7 +12727,7 @@ function transactionLifecycleRefusal(params) {
12573
12727
  }
12574
12728
  function viewIsReadOnly(params) {
12575
12729
  const { model, method } = params;
12576
- return new VibeError28({
12730
+ return new VibeError30({
12577
12731
  code: "VIBE_VALIDATION",
12578
12732
  message: `${model} is a view \u2014 ${method}() is not available on views (they are read-only); write to the underlying table instead`,
12579
12733
  meta: { model, method }
@@ -12656,6 +12810,7 @@ function assembleAggregates(params) {
12656
12810
  }
12657
12811
  return into;
12658
12812
  }
12813
+ var moduleMetadata = new WeakMap;
12659
12814
  function engineStateError(params) {
12660
12815
  const { engine } = params;
12661
12816
  for (let frame = engine.lifetime;frame !== undefined; frame = frame.parent) {
@@ -12679,7 +12834,7 @@ async function engineTransaction(params) {
12679
12834
  const support = engine.adapter.budgets?.transactionDeadline;
12680
12835
  const requested = options.deadline.enforcement ?? "between-statements";
12681
12836
  if (support === undefined || support === "unsupported" || requested === "cancel-running-statements" && support !== "cancel-running-statements") {
12682
- throw new VibeError28({
12837
+ throw new VibeError30({
12683
12838
  code: "VIBE_UNSUPPORTED_CAPABILITY",
12684
12839
  message: "the adapter does not declare support for the requested transaction deadline",
12685
12840
  meta: { provider: engine.adapter.provider, feature: "transactionDeadline", requested, supported: support ?? "undeclared" }
@@ -12854,8 +13009,6 @@ class QueryPromise extends Promise {
12854
13009
  const state = engineStateError({ engine: this.#target });
12855
13010
  if (state !== undefined)
12856
13011
  return state;
12857
- if (engine.rlsSchema === undefined && this.#target.rlsSchema === undefined)
12858
- return;
12859
13012
  if (this.#target.rootIdentity !== engine.rootIdentity || this.#target.lifetime !== engine.lifetime || !sameRecord(this.#target.context?.settings ?? {}, engine.context?.settings ?? {}))
12860
13013
  return new RlsContextError2({ reason: "conflicting" });
12861
13014
  return;
@@ -12870,7 +13023,7 @@ class QueryPromise extends Promise {
12870
13023
  }
12871
13024
  #claim() {
12872
13025
  if (this.#started) {
12873
- throw new VibeError28({
13026
+ throw new VibeError30({
12874
13027
  code: "VIBE_VALIDATION",
12875
13028
  message: "$transaction([...]) needs operations that have not dispatched \u2014 pass delegate calls in the same tick they are created (this one was awaited or created earlier and already executed outside the transaction)"
12876
13029
  });
@@ -12879,6 +13032,7 @@ class QueryPromise extends Promise {
12879
13032
  const scope = this.#scope;
12880
13033
  return {
12881
13034
  start: (target) => {
13035
+ target = { ...target, meta: this.#target.meta, renderCache: this.#target.renderCache, options: this.#target.options };
12882
13036
  if (this.#target.readOnlySql === true)
12883
13037
  target = { ...target, readOnlySql: true };
12884
13038
  if (scope !== undefined && target.operation !== undefined) {
@@ -12983,19 +13137,18 @@ async function runAffected(params) {
12983
13137
  }
12984
13138
  var LOCKABLE_METHODS = Object.freeze(["findMany", "findFirst", "findUnique"]);
12985
13139
  function plan(params) {
12986
- const { engine, model, method, args } = params;
12987
- if (engine.readOnlySql === true && containsSqlFragment(args))
12988
- throw readOnlyRefusal({ path: `${model}.${method}`, reason: "raw" });
13140
+ const { engine, model, method } = params;
13141
+ const args = engine.readOnlySql === true ? restrictedSqlArgs({ value: params.args, path: "$readOnly", refusal: () => readOnlyRefusal({ path: `${model}.${method}`, reason: "raw" }) }) : params.args;
12989
13142
  if (args !== undefined && args["lock"] !== undefined) {
12990
13143
  if (engine.inTransaction !== true) {
12991
- throw new VibeError28({
13144
+ throw new VibeError30({
12992
13145
  code: "VIBE_VALIDATION",
12993
13146
  message: `lock on ${model}.${method}() requires a transaction \u2014 call it on the client $transaction hands its callback (outside one, the lock would release at statement end)`,
12994
13147
  meta: { model, method }
12995
13148
  });
12996
13149
  }
12997
13150
  if (!LOCKABLE_METHODS.includes(method)) {
12998
- throw new VibeError28({
13151
+ throw new VibeError30({
12999
13152
  code: "VIBE_VALIDATION",
13000
13153
  message: `lock on ${model}.${method}() is not supported \u2014 only ${LOCKABLE_METHODS.join(", ")} take a lock. ` + `Lock the rows with a find inside the transaction, then write them`,
13001
13154
  meta: { model, method }
@@ -13022,7 +13175,7 @@ function readAcquiredVerdict(params) {
13022
13175
  return true;
13023
13176
  if (value === false || value === 0 || value === "f" || value === "false" || value === "0")
13024
13177
  return false;
13025
- throw new VibeError28({
13178
+ throw new VibeError30({
13026
13179
  code: "VIBE_INTERNAL",
13027
13180
  message: `internal: ${params.method}() expected a boolean "${ADVISORY_LOCK_RESULT_COLUMN}" column, got ${String(value)}`,
13028
13181
  meta: { method: params.method, value: String(value) }
@@ -13070,6 +13223,10 @@ async function insertedKeyWhere(params) {
13070
13223
  hint: "this model's key is assigned by the database and cannot be read back on this dialect"
13071
13224
  });
13072
13225
  }
13226
+ const insertId = params.insertId;
13227
+ if (typeof insertId === "number" && Number.isSafeInteger(insertId) || typeof insertId === "string" && /^-?\d+$/.test(insertId)) {
13228
+ return { [keyField.name]: decodeValue({ dialect: engine.dialect.name, field: keyField, value: insertId }) };
13229
+ }
13073
13230
  const result = await runStatement({
13074
13231
  engine,
13075
13232
  kind: "rows",
@@ -13082,7 +13239,7 @@ async function insertedKeyWhere(params) {
13082
13239
  });
13083
13240
  const id = result.rows[0]?.id;
13084
13241
  if (id === undefined || id === null) {
13085
- throw new VibeError28({
13242
+ throw new VibeError30({
13086
13243
  code: "VIBE_INTERNAL",
13087
13244
  message: `could not read back the generated key for ${source.model.name}`,
13088
13245
  meta: { model: source.model.name }
@@ -13120,7 +13277,7 @@ async function runSingleMutation(params) {
13120
13277
  values: bindValues({ adapter: target.adapter, values: rendered.values })
13121
13278
  })
13122
13279
  });
13123
- return result.affectedRows;
13280
+ return result;
13124
13281
  };
13125
13282
  if (readBeforeWrite) {
13126
13283
  const where = source.refetchWhere;
@@ -13131,7 +13288,7 @@ async function runSingleMutation(params) {
13131
13288
  const before = await refetch({ engine: txEngine, source, where, lock: true });
13132
13289
  if (before === null)
13133
13290
  throw notFound2({ model: source.model.name, method: source.method });
13134
- const affected = await executeWrite(txEngine);
13291
+ const { affectedRows: affected } = await executeWrite(txEngine);
13135
13292
  if (affected === 0)
13136
13293
  throw notFound2({ model: source.model.name, method: source.method });
13137
13294
  return before;
@@ -13166,7 +13323,7 @@ async function runSingleMutation(params) {
13166
13323
  if (source.method === "update") {
13167
13324
  return await engine.adapter.transaction(async (txAdapter) => {
13168
13325
  const txEngine = { ...engine, adapter: txAdapter, inTransaction: true };
13169
- const affected2 = await executeWrite(txEngine);
13326
+ const { affectedRows: affected2 } = await executeWrite(txEngine);
13170
13327
  if (affected2 === 0)
13171
13328
  throw notFound2({ model: source.model.name, method: source.method });
13172
13329
  const after2 = await refetch({ engine: txEngine, source, where: knownWhere });
@@ -13175,7 +13332,7 @@ async function runSingleMutation(params) {
13175
13332
  return after2;
13176
13333
  });
13177
13334
  }
13178
- const affected = await executeWrite(engine);
13335
+ const { affectedRows: affected } = await executeWrite(engine);
13179
13336
  if (affected === 0 && params.allowZeroRows === true)
13180
13337
  return null;
13181
13338
  const after = await refetch({ engine, source, where: knownWhere });
@@ -13185,10 +13342,10 @@ async function runSingleMutation(params) {
13185
13342
  }
13186
13343
  return await engine.adapter.transaction(async (txAdapter) => {
13187
13344
  const txEngine = { ...engine, adapter: txAdapter, inTransaction: true };
13188
- const affected = await executeWrite(txEngine);
13345
+ const { affectedRows: affected, insertId } = await executeWrite(txEngine);
13189
13346
  if (affected === 0 && params.allowZeroRows === true)
13190
13347
  return null;
13191
- const where = await insertedKeyWhere({ engine: txEngine, source });
13348
+ const where = await insertedKeyWhere({ engine: txEngine, source, insertId });
13192
13349
  const after = await refetch({ engine: txEngine, source, where });
13193
13350
  if (after === null)
13194
13351
  throw notFound2({ model: source.model.name, method: source.method });
@@ -13410,7 +13567,7 @@ function createDelegate(params) {
13410
13567
  const skipping = args.onConflict !== undefined;
13411
13568
  if (hasNestedWrites({ model: modelMeta, data: args.data })) {
13412
13569
  if (skipping) {
13413
- throw new VibeError28({
13570
+ throw new VibeError30({
13414
13571
  code: "VIBE_VALIDATION",
13415
13572
  message: `onConflict cannot combine with nested writes on ${modelMeta.name}.create() \u2014 insert the parent row alone, then connect relations`,
13416
13573
  meta: { model: modelMeta.name }
@@ -13511,7 +13668,7 @@ function createDelegate(params) {
13511
13668
  const upsert = async (target, args) => {
13512
13669
  const modelMeta = getModelMeta({ meta: target.meta, model });
13513
13670
  if (hasNestedWrites({ model: modelMeta, data: args.create }) || hasNestedWrites({ model: modelMeta, data: args.update })) {
13514
- throw new VibeError28({
13671
+ throw new VibeError30({
13515
13672
  code: "VIBE_VALIDATION",
13516
13673
  message: `nested writes are not supported inside upsert arms on ${modelMeta.name} \u2014 use create()/update() with nested data`,
13517
13674
  meta: { model: modelMeta.name }
@@ -13519,7 +13676,7 @@ function createDelegate(params) {
13519
13676
  }
13520
13677
  if (!upsertUsesNativeStatement({ model: modelMeta, args })) {
13521
13678
  if (args.onConflict !== undefined) {
13522
- throw new VibeError28({
13679
+ throw new VibeError30({
13523
13680
  code: "VIBE_VALIDATION",
13524
13681
  message: `onConflict on ${modelMeta.name}.upsert() needs the native single-statement path \u2014 this shape (a guard field beyond the conflict target, or create data not binding the where's key) runs the find-then-write fallback, which cannot honor an explicit conflict target`,
13525
13682
  meta: { model: modelMeta.name }
@@ -13726,7 +13883,7 @@ function buildClient(params) {
13726
13883
  text: statement.text,
13727
13884
  values: statement.values,
13728
13885
  rowCount: (value) => method.startsWith("$query") ? value.rows.length : value.affectedRows,
13729
- run: () => statement.values.length > 0 ? runBoundAffected({ adapter: target.adapter, text: statement.text, values: statement.values }) : target.adapter.executeUnsafe(statement)
13886
+ run: () => statement.values.length > 0 ? target.adapter.executeRawBound !== undefined ? target.adapter.executeRawBound(statement) : runBoundAffected({ adapter: target.adapter, text: statement.text, values: statement.values }) : target.adapter.executeUnsafe(statement)
13730
13887
  });
13731
13888
  decodeRows2?.(result.rows);
13732
13889
  return result;
@@ -13797,7 +13954,7 @@ function buildClient(params) {
13797
13954
  }
13798
13955
  for (const operation of operations) {
13799
13956
  if (!isQueryPromise(operation)) {
13800
- claimError = new VibeError28({
13957
+ claimError = new VibeError30({
13801
13958
  code: "VIBE_VALIDATION",
13802
13959
  message: "$transaction([...]) takes query promises from model delegate calls \u2014 a foreign promise is already running and cannot join the transaction"
13803
13960
  });
@@ -13862,7 +14019,7 @@ function buildClient(params) {
13862
14019
  $models: models,
13863
14020
  $on: (event, listener) => {
13864
14021
  if (event !== "query") {
13865
- throw new VibeError28({
14022
+ throw new VibeError30({
13866
14023
  code: "VIBE_VALIDATION",
13867
14024
  message: `unknown client event "${String(event)}" \u2014 supported: "query"`,
13868
14025
  meta: { event }
@@ -13877,7 +14034,7 @@ function buildClient(params) {
13877
14034
  $querySql: async ({ query, decoder }) => {
13878
14035
  assertEngineActive({ engine });
13879
14036
  if (decoder === null || typeof decoder !== "object" || typeof decoder.decode !== "function")
13880
- throw new VibeError28({ code: "VIBE_VALIDATION", message: "$querySql requires an explicit result decoder" });
14037
+ throw new VibeError30({ code: "VIBE_VALIDATION", message: "$querySql requires an explicit result decoder" });
13881
14038
  const statement = compileSql({ dialect: engine.dialect, query });
13882
14039
  let output = [];
13883
14040
  await runRaw("$querySql", statement, (rows) => {
@@ -13899,6 +14056,8 @@ function buildClient(params) {
13899
14056
  $tryAdvisoryLock: (options) => runAdvisoryLock({ method: "$tryAdvisoryLock", options }),
13900
14057
  $connect: async () => {
13901
14058
  assertEngineActive({ engine, requireContext: false });
14059
+ if (engine.borrowedModule === true)
14060
+ throw new VibeError30({ code: "VIBE_VALIDATION", message: "A package module borrows its adapter; the application owns connection lifecycle", meta: { reason: "module_borrowed_lifecycle", method: "$connect" } });
13902
14061
  if (engine.inTransaction === true)
13903
14062
  throw transactionLifecycleRefusal({ method: "$connect" });
13904
14063
  if (engine.rlsSchema !== undefined) {
@@ -13919,6 +14078,8 @@ function buildClient(params) {
13919
14078
  },
13920
14079
  $disconnect: async () => {
13921
14080
  assertEngineActive({ engine, requireContext: false });
14081
+ if (engine.borrowedModule === true)
14082
+ throw new VibeError30({ code: "VIBE_VALIDATION", message: "A package module borrows its adapter; the application owns connection lifecycle", meta: { reason: "module_borrowed_lifecycle", method: "$disconnect" } });
13922
14083
  if (engine.inTransaction === true)
13923
14084
  throw transactionLifecycleRefusal({ method: "$disconnect" });
13924
14085
  await engine.diagnostics?.flush();
@@ -13954,7 +14115,7 @@ function buildClient(params) {
13954
14115
  });
13955
14116
  } else {
13956
14117
  const refuseLegacy = () => {
13957
- throw new VibeError28({ code: "VIBE_CONFIG", message: "Native row-level security cannot be combined with application policy or tenant scoping" });
14118
+ throw new VibeError30({ code: "VIBE_CONFIG", message: "Native row-level security cannot be combined with application policy or tenant scoping" });
13958
14119
  };
13959
14120
  Object.defineProperties(client, {
13960
14121
  $withContext: { enumerable: true, value: (context) => {
@@ -13980,6 +14141,25 @@ function buildClient(params) {
13980
14141
  Object.freeze(models);
13981
14142
  Object.freeze(client);
13982
14143
  }
14144
+ registerModuleBinder({ client, bind: ({ schema, manifest }) => {
14145
+ assertEngineActive({ engine, requireContext: false });
14146
+ if (engine.rlsSchema !== undefined || engine.meta.models.some((model) => model.policy !== undefined))
14147
+ throw new VibeError30({ code: "VIBE_CONFIG", message: `Module ${manifest.id} cannot attach models to a policy or native-RLS context`, meta: { reason: "module_restricted_context", moduleId: manifest.id } });
14148
+ if (manifest.dialect !== engine.dialect.name)
14149
+ throw new VibeError30({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Module ${manifest.id} requires ${manifest.dialect}, received ${engine.dialect.name}`, meta: { reason: "module_dialect", moduleId: manifest.id } });
14150
+ const previous = engine.moduleManifests.get(manifest.id);
14151
+ if (previous !== undefined && (previous.schemaChecksum !== manifest.schemaChecksum || previous.revision !== manifest.revision || previous.contract !== manifest.contract))
14152
+ throw new VibeError30({ code: "VIBE_CONFIG", message: `Module ${manifest.id} was already bound with a different manifest; use compatible package artifacts`, meta: { reason: "module_manifest_conflict", moduleId: manifest.id, revision: manifest.revision } });
14153
+ let meta = moduleMetadata.get(schema);
14154
+ if (meta === undefined) {
14155
+ meta = buildRuntimeMeta({ schema });
14156
+ registerRestrictedSqlSchema({ meta, schema });
14157
+ moduleMetadata.set(schema, meta);
14158
+ }
14159
+ engine.moduleManifests.set(manifest.id, manifest);
14160
+ const { validate: _validate, ...options } = engine.options;
14161
+ return buildClient({ engine: { ...engine, meta, options, borrowedModule: true, extensionMounts: [], connectHooks: [], renderCache: new StatementRenderCache } });
14162
+ } });
13983
14163
  return client;
13984
14164
  }
13985
14165
  function makeMountContext(params) {
@@ -14024,7 +14204,7 @@ function makeMountContext(params) {
14024
14204
  compileWhere: ({ where, model: modelName, qualify }) => {
14025
14205
  const target = modelName === undefined ? model : getModelMeta({ meta: engine.meta, model: modelName });
14026
14206
  if (target === null) {
14027
- throw new VibeError28({
14207
+ throw new VibeError30({
14028
14208
  code: "VIBE_INTERNAL",
14029
14209
  message: `extension "${extension}" compiled a where with no model in scope`,
14030
14210
  meta: { extension }
@@ -14053,24 +14233,24 @@ function createMountSurface(params) {
14053
14233
  for (const key of path) {
14054
14234
  receiver = current;
14055
14235
  if (current === null || typeof current !== "object" && typeof current !== "function")
14056
- throw new VibeError28({ code: "VIBE_CONFIG", message: "Extension mount changed its method shape" });
14236
+ throw new VibeError30({ code: "VIBE_CONFIG", message: "Extension mount changed its method shape" });
14057
14237
  current = Reflect.get(current, key);
14058
14238
  }
14059
14239
  if (typeof current !== "function")
14060
- throw new VibeError28({ code: "VIBE_CONFIG", message: "Extension mount changed its method shape" });
14240
+ throw new VibeError30({ code: "VIBE_CONFIG", message: "Extension mount changed its method shape" });
14061
14241
  return Reflect.apply(current, receiver, args);
14062
14242
  } });
14063
14243
  }
14064
14244
  if (value === null || typeof value !== "object")
14065
14245
  return value;
14066
14246
  if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
14067
- throw new VibeError28({ code: "VIBE_CONFIG", message: "Native RLS extension mounts must expose plain objects and methods" });
14247
+ throw new VibeError30({ code: "VIBE_CONFIG", message: "Native RLS extension mounts must expose plain objects and methods" });
14068
14248
  }
14069
14249
  const result = {};
14070
14250
  for (const key of Reflect.ownKeys(value)) {
14071
14251
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
14072
14252
  if (!("value" in descriptor))
14073
- throw new VibeError28({ code: "VIBE_CONFIG", message: "Native RLS extension mount accessors are unsupported" });
14253
+ throw new VibeError30({ code: "VIBE_CONFIG", message: "Native RLS extension mount accessors are unsupported" });
14074
14254
  Object.defineProperty(result, key, { value: wrap(descriptor.value, [...path, key]), enumerable: descriptor.enumerable });
14075
14255
  }
14076
14256
  return Object.freeze(result);
@@ -14087,7 +14267,7 @@ function attachExtensionMounts(params) {
14087
14267
  const model = getModelMeta({ meta: engine.meta, model: mount.model ?? "" });
14088
14268
  const delegate = models[model.name];
14089
14269
  if (delegate === undefined || mount.property in delegate) {
14090
- throw new VibeError28({
14270
+ throw new VibeError30({
14091
14271
  code: "VIBE_CONFIG",
14092
14272
  message: `extension "${mount.extension}" cannot mount "${mount.property}" on ${model.name} \u2014 the name is taken`,
14093
14273
  meta: { extension: mount.extension, model: model.name, property: mount.property }
@@ -14098,7 +14278,7 @@ function attachExtensionMounts(params) {
14098
14278
  continue;
14099
14279
  }
14100
14280
  if (!mount.property.startsWith("$") || mount.property in client) {
14101
- throw new VibeError28({
14281
+ throw new VibeError30({
14102
14282
  code: "VIBE_CONFIG",
14103
14283
  message: `extension "${mount.extension}" cannot mount client property "${mount.property}" \u2014 root mounts are $-prefixed and must not collide`,
14104
14284
  meta: { extension: mount.extension, property: mount.property }
@@ -14133,9 +14313,9 @@ function createClient(params) {
14133
14313
  const role = options?.nativeRls?.runtimeRole;
14134
14314
  if (adapter.provider === "pglite") {
14135
14315
  if (typeof role !== "string" || role.length === 0 || role.includes("\x00"))
14136
- throw new VibeError28({ code: "VIBE_CONFIG", message: "PGlite native row-level security requires a pre-existing restricted runtimeRole; embedded binding is not a boundary against the host process" });
14316
+ throw new VibeError30({ code: "VIBE_CONFIG", message: "PGlite native row-level security requires a pre-existing restricted runtimeRole; embedded binding is not a boundary against the host process" });
14137
14317
  } else if (role !== undefined) {
14138
- throw new VibeError28({ code: "VIBE_CONFIG", message: "Server native row-level security must use the runtime login role, not a runtimeRole switch" });
14318
+ throw new VibeError30({ code: "VIBE_CONFIG", message: "Server native row-level security must use the runtime login role, not a runtimeRole switch" });
14139
14319
  }
14140
14320
  }
14141
14321
  if (extensions?.manifest !== undefined) {
@@ -14145,7 +14325,7 @@ function createClient(params) {
14145
14325
  assertComputedManifest({ manifest: computed.manifest, specs: computed.specs });
14146
14326
  }
14147
14327
  if (computed === undefined && options !== undefined && options["computed"] !== undefined) {
14148
- throw new VibeError28({
14328
+ throw new VibeError30({
14149
14329
  code: "VIBE_CONFIG",
14150
14330
  message: "this client was generated without computed fields \u2014 declare them in vibeorm.config.ts and re-run vibeorm generate",
14151
14331
  meta: {}
@@ -14159,9 +14339,11 @@ function createClient(params) {
14159
14339
  ...computed?.specs === undefined ? {} : { computed: computed.specs }
14160
14340
  });
14161
14341
  const meta = operatorMap === null ? baseMeta : { ...baseMeta, extensionOperators: operatorMap };
14342
+ registerRestrictedSqlSchema({ meta, schema });
14162
14343
  return buildClient({
14163
14344
  engine: {
14164
14345
  rootIdentity: Object.freeze({}),
14346
+ moduleManifests: new Map,
14165
14347
  ...schema.rls === undefined ? {} : { rlsSchema: schema },
14166
14348
  meta,
14167
14349
  adapter,
@@ -14176,10 +14358,60 @@ function createClient(params) {
14176
14358
  }
14177
14359
  });
14178
14360
  }
14361
+
14362
+ // packages/runtime/src/database-module.ts
14363
+ function canonicalValue(params) {
14364
+ const { value } = params;
14365
+ if (Array.isArray(value))
14366
+ return value.map((entry) => canonicalValue({ value: entry }));
14367
+ if (value === null || typeof value !== "object")
14368
+ return value;
14369
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entry]) => [key, canonicalValue({ value: entry })]));
14370
+ }
14371
+ function freezeData(params) {
14372
+ if (params.value === null || typeof params.value !== "object" || Object.isFrozen(params.value))
14373
+ return;
14374
+ for (const value of Object.values(params.value))
14375
+ freezeData({ value });
14376
+ Object.freeze(params.value);
14377
+ }
14378
+ function defineDatabaseModule(params) {
14379
+ for (const key of ["id", "contract", "revision"]) {
14380
+ if (typeof params[key] !== "string" || params[key].trim().length === 0 || params[key].includes("\x00"))
14381
+ throw new VibeError31({ code: "VIBE_CONFIG", message: `Database module ${key} must be a non-empty identifier`, meta: { reason: "module_manifest", key } });
14382
+ }
14383
+ assertValidSchemaIR3({ schema: params.schema });
14384
+ if (params.schema.rls !== undefined || params.schema.extensions.length > 0 || params.schema.models.some((model) => model.policy !== undefined))
14385
+ throw new VibeError31({
14386
+ code: "VIBE_UNSUPPORTED_CAPABILITY",
14387
+ message: `Module ${params.id} cannot declare extensions, policy or native RLS in this module protocol`,
14388
+ meta: { reason: "module_schema_feature", moduleId: params.id }
14389
+ });
14390
+ const serialized = JSON.stringify(canonicalValue({ value: params.schema }));
14391
+ const schema = JSON.parse(serialized);
14392
+ freezeData({ value: schema });
14393
+ const manifest = Object.freeze({
14394
+ id: params.id,
14395
+ contract: params.contract,
14396
+ revision: params.revision,
14397
+ protocol: 1,
14398
+ dialect: providerToDialect({ provider: schema.datasource.provider }),
14399
+ schemaChecksum: createHash("sha256").update(serialized).digest("hex")
14400
+ });
14401
+ if (params.manifest !== undefined && Object.keys(manifest).some((key) => params.manifest[key] !== manifest[key]))
14402
+ throw new VibeError31({
14403
+ code: "VIBE_STALE_CLIENT",
14404
+ message: `Module ${params.id} has incompatible or modified generated metadata; regenerate with a compatible runtime`,
14405
+ meta: { reason: "module_artifact", moduleId: params.id, revision: params.revision, protocol: params.manifest.protocol }
14406
+ });
14407
+ const artifact = Object.freeze({ schema, manifest });
14408
+ const bind = ({ db }) => bindModuleContext({ db, artifact });
14409
+ return Object.freeze({ ...artifact, bind, create: ({ adapter }) => bind({ db: createClient({ schema, adapter }) }) });
14410
+ }
14179
14411
  // packages/runtime/src/keyset-iterator.ts
14180
- import { VibeError as VibeError29 } from "@vibeorm/schema";
14412
+ import { VibeError as VibeError32 } from "@vibeorm/schema";
14181
14413
  function validation(params) {
14182
- return new VibeError29({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
14414
+ return new VibeError32({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
14183
14415
  }
14184
14416
  function projectionFor(params) {
14185
14417
  const { plan: plan2, select, omit } = params;
@@ -14256,7 +14488,7 @@ function iterateKeyset(options) {
14256
14488
  });
14257
14489
  if (advanced === false) {
14258
14490
  stop();
14259
- throw new VibeError29({
14491
+ throw new VibeError32({
14260
14492
  code: "VIBE_DATA_INVALID",
14261
14493
  message: `keyset traversal of "${options.model}" is not advancing: the engine returned a row that does not sort after the previous position. ` + "The ordering is not total, or a cursor value lost precision \u2014 a DateTime column finer than milliseconds asserted as millisecond-exact does this.",
14262
14494
  meta: { model: options.model, order: plan2.signature, reason: "keyset-position-not-advancing" }
@@ -14329,7 +14561,7 @@ function iterateKeyset(options) {
14329
14561
  return iterator;
14330
14562
  }
14331
14563
  // packages/runtime/src/telemetry/collector.ts
14332
- import { VibeError as VibeError30 } from "@vibeorm/schema";
14564
+ import { VibeError as VibeError33 } from "@vibeorm/schema";
14333
14565
  function createTelemetryCollector(params) {
14334
14566
  const throwOn = new Set(params?.throwOn ?? []);
14335
14567
  const operations = [];
@@ -14339,7 +14571,7 @@ function createTelemetryCollector(params) {
14339
14571
  let shutdowns = 0;
14340
14572
  const hostile = (phase) => {
14341
14573
  if (throwOn.has(phase)) {
14342
- throw new VibeError30({
14574
+ throw new VibeError33({
14343
14575
  code: "VIBE_INTERNAL",
14344
14576
  message: `telemetry collector: deliberate ${phase} failure`,
14345
14577
  meta: { reason: "telemetry-collector-deliberate-failure", phase }
@@ -14395,7 +14627,7 @@ function createTelemetryCollector(params) {
14395
14627
  operationFor: (lookup) => {
14396
14628
  const matches = operations.filter((event) => event.method === lookup.method && (lookup.model === undefined || event.model === lookup.model));
14397
14629
  if (matches.length > 1) {
14398
- throw new VibeError30({
14630
+ throw new VibeError33({
14399
14631
  code: "VIBE_VALIDATION",
14400
14632
  message: `telemetry collector: ${matches.length} operations match ${lookup.model ?? "*"}.${lookup.method}`,
14401
14633
  meta: {
@@ -14460,6 +14692,7 @@ export {
14460
14692
  encodeValue,
14461
14693
  encodeKeysetToken,
14462
14694
  delegateMethodAccess,
14695
+ defineDatabaseModule,
14463
14696
  decodeValue,
14464
14697
  decodeRows,
14465
14698
  decodeRow,
@@ -14521,4 +14754,4 @@ export {
14521
14754
  CLIENT_MEMBER_ACCESS
14522
14755
  };
14523
14756
 
14524
- //# debugId=66C6E11BD1A77F7364756E2164756E21
14757
+ //# debugId=22841846B558EACA64756E2164756E21