@voltro/database 0.86.0 → 0.88.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 (172) hide show
  1. package/CHANGELOG.md +5484 -0
  2. package/THIRD-PARTY-NOTICES.md +2 -226
  3. package/dist/actorSnapshot.js +1 -2
  4. package/dist/arrayCodec.js +1 -2
  5. package/dist/branchExecute.js +1 -2
  6. package/dist/branching.js +1 -2
  7. package/dist/bulkInsertLimits.js +16 -12
  8. package/dist/cdcOffsetsTables.js +1 -2
  9. package/dist/changeEvent.js +1 -2
  10. package/dist/changeIdentity.js +1 -2
  11. package/dist/changeListenerCeiling.js +1 -2
  12. package/dist/columnReference.js +1 -2
  13. package/dist/columns.js +1 -2
  14. package/dist/committedChanges.js +1 -2
  15. package/dist/connectionIdentity.js +1 -2
  16. package/dist/coreTables.js +1 -2
  17. package/dist/coreTablesRegistry.js +1 -2
  18. package/dist/dataTransfers.js +3 -3
  19. package/dist/dbCause.js +1 -2
  20. package/dist/dbMetrics.js +1 -2
  21. package/dist/decimalComparison.js +1 -2
  22. package/dist/derivedRowId.js +1 -2
  23. package/dist/dialect.js +1 -2
  24. package/dist/enumRenames.js +1 -2
  25. package/dist/expiresMixin.js +1 -2
  26. package/dist/fieldEncryption.js +65 -43
  27. package/dist/frameworkTables.js +16 -4
  28. package/dist/ginIndex.js +1 -2
  29. package/dist/hybridSearch.js +1 -2
  30. package/dist/idGenerator.js +5 -6
  31. package/dist/identifierLimits.js +1 -2
  32. package/dist/index.d.ts +153 -22
  33. package/dist/index.js +78 -78
  34. package/dist/indexAudit.js +45 -44
  35. package/dist/indexStorage.js +1 -2
  36. package/dist/joinCompiler.js +1 -2
  37. package/dist/jsonEagerCompiler.js +1 -2
  38. package/dist/jsonPatch.js +1 -2
  39. package/dist/jsonPatchAssignments.js +1 -2
  40. package/dist/jsonPath.js +1 -2
  41. package/dist/localFirstMixin.js +1 -2
  42. package/dist/migrate.js +52 -33
  43. package/dist/migration.js +1 -2
  44. package/dist/migrations/appliedAtValue.js +1 -2
  45. package/dist/migrations/applier.js +400 -386
  46. package/dist/migrations/boot.js +2 -3
  47. package/dist/migrations/branchRehearsal.js +1 -2
  48. package/dist/migrations/columnEquivalence.js +1 -2
  49. package/dist/migrations/ddlError.js +1 -2
  50. package/dist/migrations/ddlStatements.js +8 -0
  51. package/dist/migrations/declaredSchema.js +2 -3
  52. package/dist/migrations/deployLedger.js +1 -2
  53. package/dist/migrations/fileBased.js +1 -2
  54. package/dist/migrations/fileContext.js +1 -2
  55. package/dist/migrations/fileDiscovery.js +146 -133
  56. package/dist/migrations/fileSkipProbes.js +1 -2
  57. package/dist/migrations/fingerprint.js +1 -2
  58. package/dist/migrations/fkCycles.js +1 -2
  59. package/dist/migrations/frameworkLiveTables.js +1 -2
  60. package/dist/migrations/idempotentDdl.js +1 -2
  61. package/dist/migrations/indexPredicate.js +1 -2
  62. package/dist/migrations/introspect.js +188 -187
  63. package/dist/migrations/ledgerRows.js +1 -2
  64. package/dist/migrations/lock.js +1 -2
  65. package/dist/migrations/migrationTableExists.js +1 -2
  66. package/dist/migrations/mssqlColumnDependencies.js +1 -2
  67. package/dist/migrations/mysqlEngine.js +1 -2
  68. package/dist/migrations/namespaceCharset.js +1 -2
  69. package/dist/migrations/planner.js +25 -16
  70. package/dist/migrations/postgresEnumCheck.js +5 -3
  71. package/dist/migrations/reactiveTriggerDrift.js +1 -2
  72. package/dist/migrations/reactiveTriggerName.js +1 -2
  73. package/dist/migrations/renameMssqlCheck.js +1 -2
  74. package/dist/migrations/restoreSnapshots.js +1 -2
  75. package/dist/migrations/resumeLedger.js +1 -2
  76. package/dist/migrations/rollingDeploy.js +1 -2
  77. package/dist/migrations/schemaOwner.js +1 -2
  78. package/dist/migrations/schemaOwnerTable.js +1 -2
  79. package/dist/migrations/softDropSnapshot.js +1 -2
  80. package/dist/migrations/sqlLimit.js +1 -2
  81. package/dist/migrations/sqliteCatalogName.js +12 -0
  82. package/dist/migrations/sqliteConstraintIdentity.js +1 -2
  83. package/dist/migrations/sqliteDecimalBoot.js +1 -2
  84. package/dist/migrations/sqliteDecimalPublication.js +1 -2
  85. package/dist/migrations/sqliteDecimalReview.js +1 -2
  86. package/dist/migrations/sqliteDecimalReviewStore.js +1 -2
  87. package/dist/migrations/sqliteDecimalReviewWitness.js +1 -2
  88. package/dist/migrations/sqliteDecimalSource.js +1 -2
  89. package/dist/migrations/sqliteDecimalStage.js +1 -2
  90. package/dist/migrations/sqliteForeignKeys.js +1 -2
  91. package/dist/migrations/sqliteMigrationBatch.js +1 -2
  92. package/dist/migrations/sqliteRetainedSource.js +1 -2
  93. package/dist/migrations/sqliteRowIdentity.js +1 -2
  94. package/dist/migrations/sqliteSchemaEpoch.js +1 -2
  95. package/dist/migrations/sqliteSnapshotGuard.js +1 -2
  96. package/dist/migrations/sqliteTableContents.js +1 -2
  97. package/dist/migrations/sqliteTableCopy.js +1 -2
  98. package/dist/migrations/sqliteTableSchema.js +1 -2
  99. package/dist/migrations/sqliteWitnessProtection.js +1 -2
  100. package/dist/migrations/squash.js +1 -2
  101. package/dist/migrations/storeCharset.js +1 -2
  102. package/dist/migrations/transactionCause.js +1 -2
  103. package/dist/migrations/undeclaredCharset.js +1 -2
  104. package/dist/migrations/uniqueActiveLowering.js +1 -2
  105. package/dist/mixins.js +1 -2
  106. package/dist/mssqlDecimalDistinct.js +1 -2
  107. package/dist/mssqlJsonValueCheck.js +1 -2
  108. package/dist/mssqlTextStorage.js +1 -2
  109. package/dist/namespace.js +1 -2
  110. package/dist/optimisticVersion.js +1 -2
  111. package/dist/pendingAttribution.js +1 -2
  112. package/dist/pluginRef.js +1 -2
  113. package/dist/predicate.js +28 -22
  114. package/dist/predicateCoercion.js +1 -2
  115. package/dist/projectedColumn.js +1 -2
  116. package/dist/queryBuilder.js +7 -2
  117. package/dist/rawSql.js +1 -2
  118. package/dist/relations.js +1 -2
  119. package/dist/relationsRegistry.js +1 -2
  120. package/dist/replaceInProgress.js +1 -2
  121. package/dist/requiredColumns.js +1 -2
  122. package/dist/residency.js +1 -2
  123. package/dist/retention.js +1 -2
  124. package/dist/rowDecoder.js +62 -13
  125. package/dist/rowKey.js +1 -2
  126. package/dist/rowSchema.js +1 -2
  127. package/dist/scopeKey.js +1 -2
  128. package/dist/seed.js +1 -2
  129. package/dist/snowflake.js +1 -2
  130. package/dist/sql.d.ts +2746 -3748
  131. package/dist/sql.js +15 -14
  132. package/dist/sqlCompiler.js +89 -73
  133. package/dist/sqlError.js +1 -2
  134. package/dist/sqlMutation.js +54 -0
  135. package/dist/sqlPoolCapacity.js +1 -2
  136. package/dist/sqliteDecimal.js +1 -2
  137. package/dist/sqliteDecimalAggregateSql.js +1 -2
  138. package/dist/sqliteDecimalColumnSql.js +1 -2
  139. package/dist/sqliteDecimalConversion.js +1 -2
  140. package/dist/sqliteDecimalCte.js +1 -2
  141. package/dist/sqliteDecimalEncodeSql.js +1 -2
  142. package/dist/sqliteDecimalOutputs.js +1 -2
  143. package/dist/sqliteDecimalPredicate.js +1 -2
  144. package/dist/sqliteDecimalQuery.js +1 -2
  145. package/dist/sqliteDecimalQueryPlan.js +1 -2
  146. package/dist/sqliteDecimalResultSql.js +1 -2
  147. package/dist/sqliteDecimalRowQuery.js +1 -2
  148. package/dist/sqliteDecimalSetQuery.js +1 -2
  149. package/dist/sqliteDecimalSource.js +1 -2
  150. package/dist/sqliteDecimalStorage.js +1 -2
  151. package/dist/sqliteDecimalSubquery.js +1 -2
  152. package/dist/sqliteDecimalWindowQuery.js +1 -2
  153. package/dist/sqliteValueCoercion.js +1 -2
  154. package/dist/stampId.js +1 -2
  155. package/dist/storeErrors.js +1 -2
  156. package/dist/storeRefusal.js +1 -2
  157. package/dist/stream.js +1 -2
  158. package/dist/subjectGraph.js +1 -2
  159. package/dist/table.js +1 -2
  160. package/dist/tableRegistry.js +1 -2
  161. package/dist/tenantSelection.js +1 -2
  162. package/dist/transactionOutcome.js +5 -3
  163. package/dist/transientContention.js +1 -2
  164. package/dist/typeId.js +50 -0
  165. package/dist/typedStore.js +2 -3
  166. package/dist/upsertUpdate.js +1 -2
  167. package/dist/vectorEmbedding.js +1 -2
  168. package/dist/view.js +1 -2
  169. package/dist/wire.js +1 -2
  170. package/dist/writeAttribution.js +1 -2
  171. package/dist/writeRecorder.js +22 -22
  172. package/package.json +3 -4
@@ -1,19 +1,23 @@
1
- //#region src/fieldEncryption.ts
2
- var e = "enc:v1:", t = "enc:v2:", n = (e, t, n) => {
1
+ // src/fieldEncryption.ts
2
+ var e = /^[a-z0-9][a-z0-9-]{0,15}$/, t = (t) => {
3
+ if (!c(t)) return;
4
+ let i = t.startsWith(r), a = t.slice(i ? r.length : n.length).split(":")[i ? 4 : 3];
5
+ return a !== void 0 && e.test(a) ? a : void 0;
6
+ }, n = "enc:v1:", r = "enc:v2:", i = (e, t, n) => {
3
7
  let r = `${e}:${t}`, i = n == null || n === "" ? void 0 : String(n);
4
8
  return i === void 0 ? { column: r } : {
5
9
  column: r,
6
10
  row: `${r}:${i}`
7
11
  };
8
- }, r = (e) => typeof e == "string" && e.startsWith("enc:v2:"), i = class extends Error {
12
+ }, a = (e) => typeof e == "string" && e.startsWith("enc:v2:"), o = class extends Error {
9
13
  _tag = "FieldDecryptionError";
10
14
  table;
11
15
  column;
12
16
  reason;
13
17
  constructor(e) {
14
- super(`column "${e.table}.${e.column}" is .encrypted() but could not be decrypted (${e.reason}) — the stored value was most likely encrypted under a different key`), this.name = "FieldDecryptionError", this.table = e.table, this.column = e.column, this.reason = e.reason;
18
+ super(`column "${e.table}.${e.column}" is .encrypted() but could not be decrypted (${e.reason}) — the stored value was sealed under a key this process does not hold, or it is not where it was sealed: a value opens only at the table, column and row it was written for (a rename needs .renamedFrom(); an id changed outside the store leaves it bound to the old id)`), this.name = "FieldDecryptionError", this.table = e.table, this.column = e.column, this.reason = e.reason;
15
19
  }
16
- }, a = (e) => typeof e == "object" && !!e && e._tag === "FieldDecryptionError", o = (e) => typeof e == "string" && (e.startsWith("enc:v1:") || e.startsWith("enc:v2:")), s = (e, t, n) => e.encrypt(JSON.stringify(t), n), c = /* @__PURE__ */ new Set([
20
+ }, s = (e) => typeof e == "object" && !!e && e._tag === "FieldDecryptionError", c = (e) => typeof e == "string" && (e.startsWith("enc:v1:") || e.startsWith("enc:v2:")), l = (e, t, n) => e.encrypt(JSON.stringify(t), n), u = /* @__PURE__ */ new Set([
17
21
  "text",
18
22
  "id",
19
23
  "reference",
@@ -21,78 +25,96 @@ var e = "enc:v1:", t = "enc:v2:", n = (e, t, n) => {
21
25
  "uuid",
22
26
  "email",
23
27
  "url"
24
- ]), l = (e, t, n, r) => {
28
+ ]), d = (e, t, n, r) => {
25
29
  let i = e.decrypt(t, r), a;
26
30
  try {
27
31
  a = JSON.parse(i);
28
32
  } catch {
29
33
  return i;
30
34
  }
31
- return typeof a == "string" ? a : n === void 0 || c.has(n) ? i : a;
32
- }, u = (e, t, n, r) => {
35
+ return typeof a == "string" ? a : n === void 0 || u.has(n) ? i : a;
36
+ }, f = (e, t, n, r) => {
33
37
  let i = e.decrypt(t, r);
34
38
  try {
35
- return typeof JSON.parse(i) == "string" ? !1 : n === void 0 || c.has(n) ? !0 : void 0;
39
+ return typeof JSON.parse(i) == "string" ? !1 : n === void 0 || u.has(n) ? !0 : void 0;
36
40
  } catch {
37
41
  return !0;
38
42
  }
39
- }, d = (e, t) => e.fields[t]?.type, f = (e) => {
43
+ }, p = (e, t) => e.fields[t]?.type, m = (e) => {
40
44
  let t = [];
41
45
  for (let [n, r] of Object.entries(e.fields)) r.encrypted === !0 && t.push(n);
42
46
  return t;
43
- }, p = "id", m = (e, t, n, r) => {
47
+ }, h = "id", g = (e, t, n) => {
48
+ let r = e.tableName ?? "?", a = e.previousTableName, o = e.fields[t]?.renamedFrom, s = a === void 0 ? [r] : [r, a], c = o === void 0 ? [t] : [t, o];
49
+ return s.flatMap((e) => c.map((t) => i(e, t, n)));
50
+ }, _ = (e, t, n, r) => {
51
+ let i;
52
+ for (let a of r) try {
53
+ return d(e, t, n, a);
54
+ } catch (e) {
55
+ i ??= e;
56
+ }
57
+ throw i;
58
+ }, v = (e, t, n, r) => {
44
59
  if (!t) return e;
45
- let i = f(t);
46
- return i.length === 0 ? e : g(e, i, t.tableName ?? "?", n, r);
47
- }, h = (e, t, n) => {
60
+ let i = m(t);
61
+ return i.length === 0 ? e : x(e, i, t.tableName ?? "?", n, r);
62
+ }, y = (e, t, n) => {
63
+ if (t === void 0 || n == null) return;
64
+ let r = e[h];
65
+ if (r === void 0 || String(r) === String(n)) return;
66
+ let i = m(t);
67
+ if (i.length === 0) return;
68
+ let a = t.tableName ?? "?";
69
+ throw Error(`${a}: an update may not change the id of a row with .encrypted() columns (${i.join(", ")}) — each sealed value is bound to the id it was written under, so ${String(n)} → ${String(r)} would leave every one of them unreadable. Write the row under the new id (insert it, then delete the old one); the store seals its values there.`);
70
+ }, b = (e, t, n) => {
48
71
  if (t === void 0 || e.length === 0) return e;
49
- let r = f(t);
72
+ let r = m(t);
50
73
  if (r.length === 0) return e;
51
74
  let i = t.tableName ?? "?";
52
- return e.map((e) => g(e, r, i, n));
53
- }, g = (e, t, r, i, a) => {
54
- let c = a ?? e[p], l = { ...e };
75
+ return e.map((e) => x(e, r, i, n));
76
+ }, x = (e, t, n, r, a) => {
77
+ let o = a ?? e[h], s = { ...e };
55
78
  for (let e of t) {
56
- let t = l[e];
57
- if (!(t == null || o(t))) {
58
- if (!i) throw Error(`column "${r}.${e}" is .encrypted() but no field cipher is registered — set VOLTRO_FIELD_ENCRYPTION_KEY (\`voltro dev\` mints one; \`voltro serve\` refuses to boot without it); in a test, pass \`fieldEncryptionKey\` to makeTestContext`);
59
- l[e] = s(i, t, n(r, e, c));
79
+ let t = s[e];
80
+ if (!(t == null || c(t))) {
81
+ if (!r) throw Error(`column "${n}.${e}" is .encrypted() but no field cipher is registered — set VOLTRO_FIELD_ENCRYPTION_KEY (\`voltro dev\` mints one; \`voltro serve\` refuses to boot without it); in a test, pass \`fieldEncryptionKey\` to makeTestContext`);
82
+ s[e] = l(r, t, i(n, e, o));
60
83
  }
61
84
  }
62
- return l;
63
- }, _ = (e, t, r, a) => {
85
+ return s;
86
+ }, S = (e, t, n, r) => {
64
87
  if (!t) return e;
65
- let s = f(t);
66
- if (s.length === 0 || !r) return e;
67
- let c = a?.onError ?? "throw", u = t.tableName ?? "?";
88
+ let i = m(t);
89
+ if (i.length === 0 || !n) return e;
90
+ let a = r?.onError ?? "throw", s = t.tableName ?? "?";
68
91
  return e.map((e) => {
69
- let f;
70
- for (let m of s) {
71
- let s = e[m];
72
- if (o(s)) {
73
- f ??= { ...e };
92
+ let l;
93
+ for (let u of i) {
94
+ let i = e[u];
95
+ if (c(i)) {
96
+ l ??= { ...e };
74
97
  try {
75
- f[m] = l(r, s, d(t, m), n(u, m, a?.rowId ?? e[p]));
98
+ l[u] = _(n, i, p(t, u), g(t, u, r?.rowId ?? e[h]));
76
99
  } catch (e) {
77
100
  let t = String(e?.message ?? e);
78
- if (c === "null") {
79
- f[m] = null, a?.warn?.({
80
- table: u,
81
- column: m,
101
+ if (a === "null") {
102
+ l[u] = null, r?.warn?.({
103
+ table: s,
104
+ column: u,
82
105
  reason: t
83
106
  });
84
107
  continue;
85
108
  }
86
- throw new i({
87
- table: u,
88
- column: m,
109
+ throw new o({
110
+ table: s,
111
+ column: u,
89
112
  reason: t
90
113
  });
91
114
  }
92
115
  }
93
116
  }
94
- return f ?? e;
117
+ return l ?? e;
95
118
  });
96
119
  };
97
- //#endregion
98
- export { t as BOUND_ENCRYPTED_PREFIX, e as ENCRYPTED_PREFIX, i as FieldDecryptionError, l as decodeFieldValue, _ as decryptFieldsOnRead, s as encodeFieldValue, m as encryptFieldsForWrite, h as encryptFieldsForWriteBatch, f as encryptedColumnsOf, n as fieldBinding, r as isBoundCiphertext, o as isEncrypted, a as isFieldDecryptionError, u as isRawEncoding };
120
+ export { r as BOUND_ENCRYPTED_PREFIX, n as ENCRYPTED_PREFIX, e as FIELD_KEY_ID_PATTERN, o as FieldDecryptionError, t as ciphertextKeyId, d as decodeFieldValue, S as decryptFieldsOnRead, l as encodeFieldValue, v as encryptFieldsForWrite, b as encryptFieldsForWriteBatch, m as encryptedColumnsOf, i as fieldBinding, g as fieldBindingsOf, a as isBoundCiphertext, c as isEncrypted, s as isFieldDecryptionError, f as isRawEncoding, y as refuseIdChangeOfSealedRow };
@@ -1,6 +1,6 @@
1
1
  import { id as e, integer as t, json as n, text as r, timestamp as i } from "./columns.js";
2
2
  import { table as a } from "./table.js";
3
- //#region src/frameworkTables.ts
3
+ // src/frameworkTables.ts
4
4
  var o = a("_voltro_migrations", {
5
5
  id: e({ prefix: "mig" }),
6
6
  name: r(),
@@ -61,6 +61,7 @@ var o = a("_voltro_migrations", {
61
61
  id: e({ prefix: "idem" }),
62
62
  scope: r(),
63
63
  key: r(),
64
+ inputHash: r().nullable(),
64
65
  status: r(),
65
66
  response: n().nullable(),
66
67
  createdAt: i().default("now")
@@ -121,10 +122,21 @@ var o = a("_voltro_migrations", {
121
122
  payload: r().nullable(),
122
123
  reachableAt: r().maxLength(255).nullable(),
123
124
  writtenAt: i().default("now").onUpdate("now")
124
- }).unique(["replicaId", "kind"]).index(["kind"]), _ = [
125
+ }).unique(["replicaId", "kind"]).index(["kind"]), _ = a("_voltro_api_role_workload_buckets", {
126
+ id: e({ prefix: "arwb" }),
127
+ replicaId: r().maxLength(191),
128
+ startedAt: i(),
129
+ role: r().maxLength(2080),
130
+ bucketFrom: i(),
131
+ bucketUntil: i(),
132
+ payload: r()
133
+ }).unique("_voltro_arwb_replica_bucket_uq", [
134
+ "replicaId",
135
+ "startedAt",
136
+ "bucketFrom"
137
+ ]).index(["bucketUntil"]), v = [
125
138
  o,
126
139
  s,
127
140
  u
128
141
  ];
129
- //#endregion
130
- export { h as _voltroApiKeysTable, f as _voltroDevicesTable, d as _voltroIdempotencyTable, m as _voltroKvTable, u as _voltroMigrationPlansTable, o as _voltroMigrationsTable, l as _voltroPluginMigrationsTable, g as _voltroReplicaObservationsTable, c as _voltroSchemaOwnerTable, s as _voltroSeedsTable, p as _voltroTenantSelectionsTable, _ as frameworkTables };
142
+ export { h as _voltroApiKeysTable, _ as _voltroApiRoleWorkloadBucketsTable, f as _voltroDevicesTable, d as _voltroIdempotencyTable, m as _voltroKvTable, u as _voltroMigrationPlansTable, o as _voltroMigrationsTable, l as _voltroPluginMigrationsTable, g as _voltroReplicaObservationsTable, c as _voltroSchemaOwnerTable, s as _voltroSeedsTable, p as _voltroTenantSelectionsTable, v as frameworkTables };
package/dist/ginIndex.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/ginIndex.ts
1
+ // src/ginIndex.ts
2
2
  var e = (e) => {
3
3
  let t = e?.gin?.opclass;
4
4
  if (t !== void 0) {
@@ -6,5 +6,4 @@ var e = (e) => {
6
6
  return t.toLowerCase().replace(/^public\./, "");
7
7
  }
8
8
  };
9
- //#endregion
10
9
  export { e as resolveGinOpclass };
@@ -1,5 +1,5 @@
1
1
  import { resolveFullTextIndex as e } from "./queryBuilder.js";
2
- //#region src/hybridSearch.ts
2
+ // src/hybridSearch.ts
3
3
  var t = (t) => {
4
4
  let n = t.alpha ?? .5;
5
5
  if (n < 0 || n > 1) throw Error(`hybridSearch: alpha must be in [0, 1], got ${n}`);
@@ -28,5 +28,4 @@ var t = (t) => {
28
28
  };
29
29
  };
30
30
  };
31
- //#endregion
32
31
  export { t as hybridSearch };
@@ -1,8 +1,8 @@
1
1
  import { validateTypeIdPrefix as e } from "./identifierLimits.js";
2
- import { generateSnowflake as t } from "./snowflake.js";
3
- import { typeid as n } from "typeid-js";
2
+ import { typeId as t } from "./typeId.js";
3
+ import { generateSnowflake as n } from "./snowflake.js";
4
4
  import { ulid as r } from "ulid";
5
- //#region src/idGenerator.ts
5
+ // src/idGenerator.ts
6
6
  var i = (t) => {
7
7
  let n = t.toLowerCase();
8
8
  if (n.startsWith("_")) throw Error(`cannot derive a typeid prefix for table '${t}' (table names starting with '_' are not valid typeid prefixes). Pick an explicit prefix via id({ prefix: '...' }) or use a different scheme like id({ scheme: 'numeric' }) for internal tables.`);
@@ -46,12 +46,11 @@ var i = (t) => {
46
46
  }
47
47
  }, o = (e, i) => {
48
48
  switch (e.kind) {
49
- case "typeid": return n(e.prefix).toString();
49
+ case "typeid": return t(e.prefix);
50
50
  case "ulid": return r();
51
51
  case "numeric": return null;
52
- case "snowflake": return t();
52
+ case "snowflake": return n();
53
53
  case "custom": return e.generate(i);
54
54
  }
55
55
  };
56
- //#endregion
57
56
  export { i as deriveTypeIdPrefix, o as generateId, a as resolveIdScheme };
@@ -1,4 +1,4 @@
1
- //#region src/identifierLimits.ts
1
+ // src/identifierLimits.ts
2
2
  var e = 63, t = 60, n = /* @__PURE__ */ new Set(), r = (e) => {
3
3
  n.add(e);
4
4
  }, i = () => {
@@ -28,5 +28,4 @@ var e = 63, t = 60, n = /* @__PURE__ */ new Set(), r = (e) => {
28
28
  }
29
29
  e.length > 60 && r(`typeid prefix '${e}' on table '${t.tableName}' is ${e.length} chars — close to the 63-byte limit. Generated ids will be even longer (prefix + '_' + 26).`);
30
30
  };
31
- //#endregion
32
31
  export { e as MAX_IDENTIFIER_LENGTH, t as WARN_IDENTIFIER_LENGTH, i as drainIdentifierWarnings, c as validateColumnName, l as validateIndexName, s as validateTableName, u as validateTypeIdPrefix };
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import { Duration } from 'effect/Duration';
4
4
  import { Effect } from 'effect';
5
5
  import { Exit } from 'effect';
6
6
  import { Layer } from 'effect';
7
+ import { NonEmptyReadonlyArray } from 'effect/Array';
7
8
  import { Schedule } from 'effect';
8
9
  import { Schema } from 'effect';
9
10
  import { SqlClient } from 'effect/sql';
@@ -240,12 +241,9 @@ export declare interface AuditActor {
240
241
  export declare const auditAllTableIndexes: (tables: ReadonlyArray<Table<string, Record<string, ColumnDefinition<unknown>>, boolean, string>>) => ReadonlyArray<IndexAuditIssue>;
241
242
 
242
243
  /**
243
- * Find all index-overlap issues on a single table. Pure function; no
244
- * side effects. Returns an empty array when the schema is clean.
245
- *
246
- * Pair deduplication: each (a, b) collision is reported ONCE, never
247
- * (a, b) AND (b, a). For `duplicate-fields` we keep the lexicographic
248
- * lower name as `redundant` so the message is stable across re-runs.
244
+ * Find index-overlap issues on a single table. Each pair is reported once.
245
+ * When coverage holds in both directions, the lower name is redundant for
246
+ * stable output. Otherwise only the proven direction may recommend a drop.
249
247
  */
250
248
  export declare const auditTableIndexes: (table: Table<string, Record<string, ColumnDefinition<unknown>>, boolean, string>) => ReadonlyArray<IndexAuditIssue>;
251
249
 
@@ -293,7 +291,8 @@ export declare const AUTO_FILLED_COLUMNS: readonly ["id", "tenantId", "createdAt
293
291
 
294
292
  declare type AutoFilledColumn = (typeof AUTO_FILLED_COLUMNS)[number];
295
293
 
296
- export declare const avg: (column: string, alias?: string) => AggregateColumn;
294
+ /** `AVG(column)` — always a `number` (a mean is not exact), `null` over no rows. */
295
+ export declare const avg: (column: string, alias?: string) => AggregateColumn<number | null>;
297
296
 
298
297
  export declare const avgOver: (column: string, alias?: string) => WindowBuilder;
299
298
 
@@ -903,7 +902,25 @@ export declare const changeUpdate: (table: string, before: Row, after: Row, meta
903
902
  *
904
903
  * `undefined` limits (an unknown dialect) → one group, unchanged behaviour.
905
904
  */
906
- export declare const chunkRowsForInsert: <T extends Record<string, unknown>>(rows: ReadonlyArray<T>, limits: BulkInsertLimits | undefined) => ReadonlyArray<ReadonlyArray<T>>;
905
+ export declare const chunkRowsForInsert: <T extends Record<string, unknown>>(rows: ReadonlyArray<T>, limits: BulkInsertLimits | undefined) => ReadonlyArray<NonEmptyReadonlyArray<T>>;
906
+
907
+ /**
908
+ * The id of the key a ciphertext names, read off the value without any key —
909
+ * what lets a status count values per key instead of decrypting each one.
910
+ *
911
+ * The id is the LAST `:`-separated part of the envelope
912
+ * (`enc:v2:<r|c>:<iv>:<tag>:<ct>:<kid>`, `enc:v1:<iv>:<tag>:<ct>:<kid>`), after
913
+ * everything a release without key ids reads. That placement is deliberate:
914
+ * such a release takes the first parts and ignores the rest, so during a
915
+ * rolling deploy, and after a rollback, it still reads what the keyring wrote.
916
+ * The id is a lookup hint, not an authenticated field: a value whose id was
917
+ * altered still has to pass GCM under some key of the keyring, and a reader
918
+ * that verifies (the retire gate) decrypts rather than trusts it.
919
+ *
920
+ * `undefined` for a value written before ids, and for anything that is not a
921
+ * ciphertext.
922
+ */
923
+ export declare const ciphertextKeyId: (value: unknown) => string | undefined;
907
924
 
908
925
  /**
909
926
  * Claim the attribution for an echo, or `undefined` if this write did not
@@ -2562,6 +2579,14 @@ export declare interface DataStore {
2562
2579
  * up, on `FluentStore.query`, which is what handlers actually call.
2563
2580
  */
2564
2581
  query(descriptor: QueryDescriptor): Promise<ReadonlyArray<Row>>;
2582
+ /** Server-internal constraint/integrity read on this exact store view.
2583
+ * Bypasses request row visibility only; preserves tenant, deletion/expiry
2584
+ * scopes, codecs, physical namespace and in-flight transaction writes.
2585
+ * Never use its rows as an application response or mutation authorization.
2586
+ * Optional for custom stores: a consumer requiring complete coverage must
2587
+ * refuse when absent, never fall back to an ordinary filtered query.
2588
+ */
2589
+ integrityQuery?(descriptor: QueryDescriptor): Promise<ReadonlyArray<Row>>;
2565
2590
  /** Insert a row. Emits an insert ChangeEvent (deferred if inside `transactional()`). */
2566
2591
  insert(table: string, row: Row): Promise<Row>;
2567
2592
  /**
@@ -2581,8 +2606,14 @@ export declare interface DataStore {
2581
2606
  * - memory store: loop + per-row emit
2582
2607
  *
2583
2608
  * An empty `rows` array is a no-op that returns `[]` (no statement, no events).
2609
+ *
2610
+ * `{ returning: 'none' }` is for a caller that does not read the result: it
2611
+ * resolves to `[]`. The rows are written, and their change events emitted,
2612
+ * exactly as without it; the store reads the inserted rows back only when
2613
+ * change events or an in-transaction recorder need them
2614
+ * (`writeImagesObserved`). A wrapper store forwards `options` unchanged.
2584
2615
  */
2585
- insertMany(table: string, rows: ReadonlyArray<Row>): Promise<ReadonlyArray<Row>>;
2616
+ insertMany(table: string, rows: ReadonlyArray<Row>, options?: InsertManyOptions): Promise<ReadonlyArray<Row>>;
2586
2617
  /**
2587
2618
  * In-place JSON merge: set `value` at `path` inside the JSON column
2588
2619
  * (`path` is the column name, optionally with a nested dot-path, e.g.
@@ -2978,12 +3009,13 @@ export declare interface DataStore {
2978
3009
  * not. Without a savepoint the two are incompatible, so `--atomic` simply
2979
3010
  * could not import such a bundle on those engines.
2980
3011
  *
2981
- * Only the TRANSACTIONAL views implement it, and only for the engines that
2982
- * need it: the mysql family and sqlite leave a transaction usable after a
2983
- * failed statement, so wrapping every row there would buy nothing and cost a
2984
- * round trip per row. Callers guard with `if (store.savepoint)` and run `fn`
2985
- * directly otherwise — which is also correct outside a transaction, where
2986
- * there is nothing to protect.
3012
+ * Every shipped memory and SQL TRANSACTIONAL view implements it. Besides
3013
+ * statement-abort recovery, it isolates a compound operation whose typed
3014
+ * application error may be handled inside the outer transaction. Rollback
3015
+ * discards that operation's buffered committed-change events as well as rows;
3016
+ * earlier events and successful nesting remain intact. Root stores have no
3017
+ * savepoint. Custom stores may omit it; operations requiring local atomicity
3018
+ * must refuse rather than run without protection.
2987
3019
  *
2988
3020
  * Failures propagate: the savepoint is rolled back and the error rethrown, so
2989
3021
  * the caller's own error handling is unchanged.
@@ -3192,6 +3224,9 @@ export declare interface DbOpSample {
3192
3224
  */
3193
3225
  export declare const decimal: (precision: number, scale?: number) => ColumnBuilder<string, "decimal">;
3194
3226
 
3227
+ /** Equality key for exact fixed-point decimal strings; null for invalid input. */
3228
+ export declare const decimalEqualityKey: (value: unknown) => string | null;
3229
+
3195
3230
  /**
3196
3231
  * Declare that an enum value was renamed. Place this NEXT TO the
3197
3232
  * `dbEnum(...)` declaration whose value array now carries the new label:
@@ -3276,6 +3311,12 @@ export declare const decodeRowsForTable: <T extends Record<string, unknown>>(row
3276
3311
  */
3277
3312
  export declare const decodeRowsFromSchema: <T extends Record<string, unknown>>(rows: ReadonlyArray<T>, tableName: string, dialect: DecoderDialectId, query?: QueryDescriptor) => ReadonlyArray<T>;
3278
3313
 
3314
+ /**
3315
+ * Decode the rows of a SELECT: by the query's aggregations when it has them,
3316
+ * by the table's columns otherwise. Every dialect store's read path calls this.
3317
+ */
3318
+ export declare const decodeSelectRows: <T extends Record<string, unknown>>(rows: ReadonlyArray<T>, query: QueryDescriptor, dialect: DecoderDialectId) => ReadonlyArray<T>;
3319
+
3279
3320
  /**
3280
3321
  * Decrypt every `.encrypted()` column's value on read. A value that
3281
3322
  * isn't a framework ciphertext (legacy plaintext, `null`) passes
@@ -3816,6 +3857,9 @@ export declare const encodeFieldValue: (cipher: FieldCipher, value: unknown, bin
3816
3857
  */
3817
3858
  export declare const encodeRowForSchema: <T extends Record<string, unknown>>(row: T, tableName: string, types?: ReadonlyArray<ColumnType>) => T;
3818
3859
 
3860
+ /** 16 bytes → the 26-character lowercase Crockford-base32 suffix of the spec. */
3861
+ export declare const encodeTypeIdSuffix: (src: Uint8Array) => string;
3862
+
3819
3863
  /** The UNBOUND envelope: `enc:v1:<iv>:<tag>:<ct>`. */
3820
3864
  export declare const ENCRYPTED_PREFIX = "enc:v1:";
3821
3865
 
@@ -3975,6 +4019,13 @@ export declare interface FallbackLogger {
3975
4019
  readonly debug: (message: string, fields?: Record<string, unknown>) => void;
3976
4020
  }
3977
4021
 
4022
+ /**
4023
+ * A key id: a short slug, so it can ride inside every envelope. Explicit ids
4024
+ * are chosen by the operator (`VOLTRO_FIELD_ENCRYPTION_KEYS`); derived ids are
4025
+ * `k` + 10 hex characters of a fingerprint that is not the key.
4026
+ */
4027
+ export declare const FIELD_KEY_ID_PATTERN: RegExp;
4028
+
3978
4029
  /**
3979
4030
  * Where a ciphertext BELONGS — the table, the column and, when the row's
3980
4031
  * identity is known at write time, the row. Fed to AES-GCM as additional
@@ -4008,6 +4059,16 @@ export declare interface FieldBinding {
4008
4059
  * its string form and a read of the numeric column agree. */
4009
4060
  export declare const fieldBinding: (table: string, column: string, rowId?: unknown) => FieldBinding;
4010
4061
 
4062
+ /**
4063
+ * Every address a column's values may have been sealed under: its own, and the
4064
+ * names a DECLARED rename says it had — `table('new').renamedFrom('old')`, a
4065
+ * column's `.renamedFrom('old')`. A rename is catalog-only, so the rows keep the
4066
+ * bytes bound to the old name; without this every value of a renamed column
4067
+ * stopped opening. Only declared names are added: a value moved anywhere else
4068
+ * still refuses. The own address comes first — a re-key seals under it.
4069
+ */
4070
+ export declare const fieldBindingsOf: (table: TableLike, column: string, rowId?: unknown) => ReadonlyArray<FieldBinding>;
4071
+
4011
4072
  /** The cipher the store middleware injects. Operates on opaque strings. */
4012
4073
  export declare interface FieldCipher {
4013
4074
  /**
@@ -4030,6 +4091,15 @@ export declare interface FieldCipher {
4030
4091
  * a cipher with one key.
4031
4092
  */
4032
4093
  readonly sealedWithPreviousKey?: (ciphertext: string, binding?: FieldBinding) => boolean;
4094
+ /** The id of the key new values are sealed under — a keyring's active key. */
4095
+ readonly activeKeyId?: string;
4096
+ /**
4097
+ * The id of the key that opens this value: the one the value names when that
4098
+ * key opens it, otherwise the key of the keyring that does (a value written
4099
+ * before ciphertexts carried an id). `undefined` when no key of the keyring
4100
+ * opens it.
4101
+ */
4102
+ readonly keyIdOf?: (ciphertext: string, binding?: FieldBinding) => string | undefined;
4033
4103
  }
4034
4104
 
4035
4105
  /**
@@ -4714,6 +4784,17 @@ export declare const insertIgnoreRowOutcome: <T extends AnyTable>(store: TypedIn
4714
4784
  readonly outcome: "inserted" | "ignored";
4715
4785
  }>;
4716
4786
 
4787
+ /** Options of {@link DataStore.insertMany}. */
4788
+ export declare interface InsertManyOptions {
4789
+ /**
4790
+ * `'rows'` (the default) resolves to the inserted rows as stored. `'none'`
4791
+ * resolves to `[]`, so a store need not send the rows back: a bulk insert
4792
+ * into a table no change event or recorder observes then costs one
4793
+ * statement per chunk and no result rows.
4794
+ */
4795
+ readonly returning?: 'rows' | 'none';
4796
+ }
4797
+
4717
4798
  /**
4718
4799
  * Insert MANY rows, typed against the table — `insertRow`'s batch, and nothing
4719
4800
  * else: it forwards to `store.insertMany` unchanged, so the chunking, the
@@ -4734,8 +4815,9 @@ export declare const insertIgnoreRowOutcome: <T extends AnyTable>(store: TypedIn
4734
4815
  *
4735
4816
  * An empty array is a legal no-op, so a caller does not have to guard the call
4736
4817
  * — `store.insertMany` decides what that means, and it means "insert nothing".
4818
+ * `{ returning: 'none' }` is forwarded as well: it resolves to `[]`.
4737
4819
  */
4738
- export declare const insertManyRows: <T extends AnyTable>(store: TypedInsertManyStore, table: T, rows: ReadonlyArray<InferInsertRow<T>>) => Promise<ReadonlyArray<InferRow<T>>>;
4820
+ export declare const insertManyRows: <T extends AnyTable>(store: TypedInsertManyStore, table: T, rows: ReadonlyArray<InferInsertRow<T>>, options?: InsertManyOptions) => Promise<ReadonlyArray<InferRow<T>>>;
4739
4821
 
4740
4822
  /**
4741
4823
  * Insert a row, typed against the table: the payload must carry every required
@@ -6181,6 +6263,10 @@ Joins extends JoinsMap = {}> {
6181
6263
  * on sqlite (its writers are serialised already).
6182
6264
  */
6183
6265
  forUpdate: () => Query<RowOf, IxNames, Joins>;
6266
+ /** Hold a shared SQL row lock until transaction end; concurrent readers may
6267
+ * proceed. SQLite-family stores retain their transaction isolation without
6268
+ * row-lock SQL, and memory retains optimistic validation. */
6269
+ forShare: () => Query<RowOf, IxNames, Joins>;
6184
6270
  /**
6185
6271
  * Cross-tenant: opt OUT of the runtime's automatic
6186
6272
  * `tenantId = <subject.tenantId>` scope on `tenant()` tables. Use for
@@ -6561,13 +6647,14 @@ export declare interface QueryDescriptor<R = Row> {
6561
6647
  readonly includeExpired?: boolean;
6562
6648
  /**
6563
6649
  * Lock the matched rows for the rest of the enclosing transaction — set by
6564
- * `.forUpdate()`. `SELECT … FOR UPDATE` on postgres / mysql / mariadb,
6565
- * `WITH (UPDLOCK, ROWLOCK)` on mssql; sqlite serialises writers already and
6650
+ * `.forUpdate()` for exclusive intent, `.forShare()` for concurrent readers
6651
+ * that must fence writers. SQL dialects retain the lock until transaction end;
6652
+ * sqlite serialises writers already and
6566
6653
  * emits nothing. Outside a transaction the lock is released as the statement
6567
6654
  * ends, which is harmless and useless — the point is a read-modify-write
6568
6655
  * whose read and write share one transaction.
6569
6656
  */
6570
- readonly lock?: 'update';
6657
+ readonly lock?: 'update' | 'share';
6571
6658
  /**
6572
6659
  * Opt OUT of the runtime's automatic tenant scope on `tenant()` tables.
6573
6660
  * Set by `.unscoped()`. Honored by the runtime, same as `includeDeleted`.
@@ -7063,6 +7150,15 @@ export declare interface ReferenceOptions {
7063
7150
  readonly column?: string;
7064
7151
  }
7065
7152
 
7153
+ /**
7154
+ * Refuse an UPDATE that changes the id of a row with `.encrypted()` columns.
7155
+ * Each sealed value is bound to its row's id: an update that moved the row to a
7156
+ * new id left every sealed value in it bound to the old one, reported success,
7157
+ * and from then on every read of the row failed. Move such a row by writing it
7158
+ * under the new id (insert, then delete the old one) — the store seals it there.
7159
+ */
7160
+ export declare const refuseIdChangeOfSealedRow: (patch: Row, table: TableLike | undefined, rowId: unknown) => void;
7161
+
7066
7162
  /**
7067
7163
  * Build the error a dialect store throws when the framework refuses a write.
7068
7164
  *
@@ -7286,7 +7382,10 @@ export declare const resolveActorSnapshot: (store: ActorLookupStore, subjectId:
7286
7382
  export declare type ResolveAggregateResult<A, RowOf, Joins extends JoinsMap> = A extends {
7287
7383
  readonly op: 'min' | 'max';
7288
7384
  readonly column: infer C extends string;
7289
- } ? ResolveJoinedSource<C, RowOf, Joins> | null : A extends AggregateColumn<infer T> ? T : never;
7385
+ } ? ResolveJoinedSource<C, RowOf, Joins> | null : A extends {
7386
+ readonly op: 'sum';
7387
+ readonly column: infer C extends string;
7388
+ } ? [NonNullable<ResolveJoinedSource<C, RowOf, Joins>>] extends [string] ? string | null : number | null : A extends AggregateColumn<infer T> ? T : never;
7290
7389
 
7291
7390
  /**
7292
7391
  * Pick the branch mechanism. Prioritises the Neon copy-on-write fast-path when
@@ -8385,7 +8484,13 @@ export declare interface SubqueryInPredicate {
8385
8484
  */
8386
8485
  export declare type SubqueryResults = Map< QueryDescriptor, Set<unknown> | number>;
8387
8486
 
8388
- export declare const sum: (column: string, alias?: string) => AggregateColumn;
8487
+ /** `SUM(column)` — a `number` over `integer()` / `real()`, the exact decimal
8488
+ * STRING over `decimal()` / `bigint()` (the column's own type), `null` over no
8489
+ * rows. On a typed query the result type follows the column. */
8490
+ export declare const sum: <C extends string>(column: C, alias?: string) => AggregateColumn<number | null> & {
8491
+ readonly op: "sum";
8492
+ readonly column: C;
8493
+ };
8389
8494
 
8390
8495
  export declare const sumOver: (column: string, alias?: string) => WindowBuilder;
8391
8496
 
@@ -9277,7 +9382,7 @@ export declare interface TypedInsertIgnoreOutcomeStore {
9277
9382
 
9278
9383
  /** The minimal store surface `insertManyRows` needs. */
9279
9384
  export declare interface TypedInsertManyStore {
9280
- readonly insertMany: (table: string, rows: ReadonlyArray<Row>) => Promise<ReadonlyArray<Row>>;
9385
+ readonly insertMany: (table: string, rows: ReadonlyArray<Row>, options?: InsertManyOptions) => Promise<ReadonlyArray<Row>>;
9281
9386
  }
9282
9387
 
9283
9388
  /** The minimal store surface `insertRow` needs — satisfied by `ctx.store`. */
@@ -9305,6 +9410,14 @@ export declare interface TypedUpsertStore {
9305
9410
  readonly upsert: (table: string, row: Row, options: UpsertOptions) => Promise<Row>;
9306
9411
  }
9307
9412
 
9413
+ /**
9414
+ * A fresh TypeID for `prefix`: `user_01h455vb4pex5vsknk084sn02q`. This is what
9415
+ * the `typeid` id scheme writes on insert; call it yourself when a row's id
9416
+ * has to exist before the insert (a self-referencing `createdBy`, a child row
9417
+ * written in the same flow).
9418
+ */
9419
+ export declare const typeId: (prefix: string) => string;
9420
+
9308
9421
  /**
9309
9422
  * `(SELECT ...) UNION (SELECT ...) ...` — dedup-merge. Q6.
9310
9423
  *
@@ -9460,6 +9573,9 @@ export declare const upsertUpdatePatch: (row: Row, existing: Row, options: Upser
9460
9573
  /** Shared driver lowering: omit undefined values and refuse identity rewrites. */
9461
9574
  export declare const upsertUpdateValues: (options: UpsertOptions) => Record<string, unknown>;
9462
9575
 
9576
+ /** The 16 bytes of a fresh UUIDv7 (RFC 9562), monotonic within this process. */
9577
+ export declare const uuidV7Bytes: (now?: number) => Uint8Array;
9578
+
9463
9579
  /**
9464
9580
  * Validate one column-name key on a table's fields map. Throws on
9465
9581
  * empty / invalid characters / > 63 bytes. Records a WARN above 50.
@@ -9706,6 +9822,9 @@ export declare const VOLTRO_LOCAL_FIRST_MIXIN_ID: "voltro/localFirst";
9706
9822
  */
9707
9823
  export declare const _voltroApiKeysTable: SchemaTable;
9708
9824
 
9825
+ /** Finalized role-observation history survives graceful replica shutdown. */
9826
+ export declare const _voltroApiRoleWorkloadBucketsTable: SchemaTable;
9827
+
9709
9828
  export declare const _voltroCdcOffsetsTable: TableLike;
9710
9829
 
9711
9830
  /**
@@ -10001,6 +10120,18 @@ export declare interface WriteAttribution {
10001
10120
  readonly via?: 'agent';
10002
10121
  }
10003
10122
 
10123
+ /**
10124
+ * Does anything read the row images of a write to `table`? A set-based write
10125
+ * (`updateMany` / `deleteMany`) answers with a count; the stores fetch its rows
10126
+ * back — `RETURNING *`, `OUTPUT`, a SELECT first — only to hand them to change
10127
+ * events and in-transaction recorders. A `.nonReactive()` table that no
10128
+ * recorder watches has neither, and the rows then crossed the wire for nobody:
10129
+ * every retired aggregate generation, `rowData` included, on every refresh.
10130
+ *
10131
+ * Asked per statement, not cached: recorders register and suspend at runtime.
10132
+ */
10133
+ export declare const writeImagesObserved: (table: string) => boolean;
10134
+
10004
10135
  export declare type WriteRecorder = (port: TxnRecorderPort, write: RecordedWrite) => Promise<void>;
10005
10136
 
10006
10137
  /** Are recorders suspended right here? Exported so a caller can SAY so. */