@orkestrel/database 0.0.13 → 0.0.15

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.
@@ -3,7 +3,7 @@ let _orkestrel_contract = require("@orkestrel/contract");
3
3
  let _orkestrel_emitter = require("@orkestrel/emitter");
4
4
  //#region src/core/constants.ts
5
5
  /**
6
- * Supplies the primary-key column assumed when {@link PrimaryMap} does not name one.
6
+ * Supplies the primary-key column, `'id'`, assumed when {@link PrimaryMap} does not name one.
7
7
  *
8
8
  * @remarks
9
9
  * `id` is the convention IndexedDB (`keyPath: 'id'`) and SQL (`id` / rowid) both
@@ -11,12 +11,13 @@ let _orkestrel_emitter = require("@orkestrel/emitter");
11
11
  */
12
12
  var DEFAULT_PRIMARY = "id";
13
13
  /**
14
- * Sets the longest `LIKE` / `GLOB` pattern the wildcard matcher accepts before rejecting it.
14
+ * Sets the longest `LIKE` / `GLOB` pattern the wildcard matcher accepts, 1024 characters, before
15
+ * rejecting it.
15
16
  *
16
17
  * @remarks
17
18
  * A `LIKE` / `GLOB` pattern is a caller-supplied operand, so
18
19
  * `matchesLikePattern` / `matchesGlobPattern` run patterns this package cannot
19
- * trust. The matcher is the LINEAR greedy two-pointer wildcard match — never a
20
+ * trust. The matcher is the linear greedy two-pointer wildcard match — never a
20
21
  * backtracking regex (`.*`-segments-separated-by-literals against a long input is the
21
22
  * catastrophic shape JS cannot bound without atomic groups), so it is O(value ×
22
23
  * pattern). Capping the pattern length bounds that pattern factor, leaving a match
@@ -90,11 +91,13 @@ var CONFORMANCE_POSTS_SCHEMA = Object.freeze({
90
91
  indexes: Object.freeze([])
91
92
  });
92
93
  /**
93
- * Holds the fixed two-table schema every driver-conformance phase opens.
94
+ * Holds the fixed `users` and `posts` schema every driver-conformance phase opens.
94
95
  *
95
96
  * @remarks
96
97
  * Each phase mints a fresh driver and opens this exact schema, so a finding
97
- * names a violated invariant rather than a setup difference between phases.
98
+ * names a violated invariant rather than a setup difference between phases. The
99
+ * array and each schema in it are frozen, so a consumer holding it cannot change
100
+ * what a later phase opens.
98
101
  */
99
102
  var CONFORMANCE_SCHEMA = Object.freeze([CONFORMANCE_USERS_SCHEMA, CONFORMANCE_POSTS_SCHEMA]);
100
103
  //#endregion
@@ -141,7 +144,7 @@ var DatabaseError = class extends Error {
141
144
  * ```
142
145
  */
143
146
  function isDatabaseError(value) {
144
- return value instanceof DatabaseError;
147
+ return (0, _orkestrel_contract.isInstance)(value, DatabaseError);
145
148
  }
146
149
  //#endregion
147
150
  //#region src/core/validators.ts
@@ -152,132 +155,153 @@ function isDatabaseError(value) {
152
155
  * @returns True if `value` is a string or a finite number; false otherwise
153
156
  */
154
157
  function isKey(value) {
155
- return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
158
+ return (0, _orkestrel_contract.isString)(value) || (0, _orkestrel_contract.isFiniteNumber)(value);
156
159
  }
157
160
  /**
158
161
  * Checks whether a value is a portable column schema.
159
162
  *
163
+ * @remarks
164
+ * Total over any input: a hostile getter, a revoked proxy, or a cyclic value is
165
+ * contained as a non-match rather than a throw.
166
+ *
160
167
  * @param value - The value to test
161
168
  * @returns True if `value` is a complete {@link ColumnSchema}; false otherwise
162
169
  */
163
170
  function isColumnSchema(value) {
164
- try {
171
+ return (0, _orkestrel_contract.holds)(() => {
165
172
  const column = (0, _orkestrel_contract.cloneJSONRecord)(value);
166
173
  const keys = Object.keys(column);
167
- return keys.length === 4 && keys.includes("name") && keys.includes("storage") && keys.includes("optional") && keys.includes("nullable") && typeof column.name === "string" && column.name.length > 0 && (column.storage === "text" || column.storage === "integer" || column.storage === "real" || column.storage === "boolean" || column.storage === "json" || column.storage === "blob") && typeof column.optional === "boolean" && typeof column.nullable === "boolean";
168
- } catch {
169
- return false;
170
- }
174
+ return keys.length === 4 && keys.includes("name") && keys.includes("storage") && keys.includes("optional") && keys.includes("nullable") && (0, _orkestrel_contract.isString)(column.name) && column.name.length > 0 && (column.storage === "text" || column.storage === "integer" || column.storage === "real" || column.storage === "boolean" || column.storage === "json" || column.storage === "blob") && (0, _orkestrel_contract.isBoolean)(column.optional) && (0, _orkestrel_contract.isBoolean)(column.nullable);
175
+ });
171
176
  }
172
177
  /**
173
178
  * Checks whether a value is a portable table schema.
174
179
  *
180
+ * @remarks
181
+ * Total over any input: a hostile getter, a revoked proxy, or a cyclic value is
182
+ * contained as a non-match rather than a throw.
183
+ *
175
184
  * @param value - The value to test
176
185
  * @returns True if `value` is a complete {@link TableSchema}; false otherwise
177
186
  */
178
187
  function isTableSchema(value) {
179
- try {
188
+ return (0, _orkestrel_contract.holds)(() => {
180
189
  const table = (0, _orkestrel_contract.cloneJSONRecord)(value);
181
190
  const keys = Object.keys(table);
182
- if (keys.length !== 4 || !keys.includes("name") || !keys.includes("primary") || !keys.includes("columns") || !keys.includes("indexes") || typeof table.name !== "string" || table.name.length === 0 || typeof table.primary !== "string" || table.primary.length === 0 || !Array.isArray(table.columns) || !Array.isArray(table.indexes) || !table.columns.every(isColumnSchema)) return false;
191
+ if (keys.length !== 4 || !keys.includes("name") || !keys.includes("primary") || !keys.includes("columns") || !keys.includes("indexes") || !(0, _orkestrel_contract.isString)(table.name) || table.name.length === 0 || !(0, _orkestrel_contract.isString)(table.primary) || table.primary.length === 0 || !(0, _orkestrel_contract.arrayOf)(isColumnSchema)(table.columns) || !(0, _orkestrel_contract.isArray)(table.indexes)) return false;
183
192
  const names = table.columns.map((column) => column.name);
184
- if (new Set(names).size !== names.length || !names.includes(table.primary) || !table.indexes.every((index) => Array.isArray(index) && index.length > 0 && index.every((column) => typeof column === "string" && names.includes(column)))) return false;
193
+ if (new Set(names).size !== names.length || !names.includes(table.primary) || !table.indexes.every((index) => (0, _orkestrel_contract.isArray)(index) && index.length > 0 && index.every((column) => (0, _orkestrel_contract.isString)(column) && names.includes(column)))) return false;
185
194
  const indexes = table.indexes.map((index) => JSON.stringify(index));
186
195
  return new Set(indexes).size === indexes.length;
187
- } catch {
188
- return false;
189
- }
196
+ });
190
197
  }
191
198
  /**
192
199
  * Checks whether a value is a complete portable driver schema.
193
200
  *
201
+ * @remarks
202
+ * Total over any input: a hostile getter, a revoked proxy, or a cyclic value is
203
+ * contained as a non-match rather than a throw.
204
+ *
194
205
  * @param value - The value to test
195
206
  * @returns True if `value` is a table-schema collection with unique table names; false otherwise
196
207
  */
197
208
  function isDriverSchema(value) {
198
- try {
209
+ return (0, _orkestrel_contract.holds)(() => {
199
210
  const schema = (0, _orkestrel_contract.cloneJSONValue)(value);
200
- if (!Array.isArray(schema) || !schema.every(isTableSchema)) return false;
211
+ if (!(0, _orkestrel_contract.arrayOf)(isTableSchema)(schema)) return false;
201
212
  const names = schema.map((table) => table.name);
202
213
  return new Set(names).size === names.length;
203
- } catch {
204
- return false;
205
- }
214
+ });
206
215
  }
207
216
  /**
208
217
  * Checks whether a value is one ordered migration step.
209
218
  *
219
+ * @remarks
220
+ * Total over any input: a hostile getter, a revoked proxy, or a cyclic value is
221
+ * contained as a non-match rather than a throw.
222
+ *
210
223
  * @param value - The value to test
211
224
  * @returns True if `value` is a complete {@link MigrationStep}; false otherwise
212
225
  */
213
226
  function isMigrationStep(value) {
214
- try {
227
+ return (0, _orkestrel_contract.holds)(() => {
215
228
  const step = (0, _orkestrel_contract.cloneJSONRecord)(value);
216
- if (typeof step.operation !== "string") return false;
229
+ if (!(0, _orkestrel_contract.isString)(step.operation)) return false;
217
230
  const keys = Object.keys(step);
218
231
  switch (step.operation) {
219
232
  case "table.add": return keys.length === 2 && keys.includes("operation") && keys.includes("table") && isTableSchema(step.table);
220
- case "table.remove": return keys.length === 2 && keys.includes("operation") && keys.includes("table") && typeof step.table === "string" && step.table.length > 0;
221
- case "column.add": return keys.length === 3 && keys.includes("operation") && keys.includes("table") && keys.includes("column") && typeof step.table === "string" && step.table.length > 0 && isColumnSchema(step.column);
222
- case "column.remove": return keys.length === 3 && keys.includes("operation") && keys.includes("table") && keys.includes("column") && typeof step.table === "string" && step.table.length > 0 && typeof step.column === "string" && step.column.length > 0;
233
+ case "table.remove": return keys.length === 2 && keys.includes("operation") && keys.includes("table") && (0, _orkestrel_contract.isString)(step.table) && step.table.length > 0;
234
+ case "column.add": return keys.length === 3 && keys.includes("operation") && keys.includes("table") && keys.includes("column") && (0, _orkestrel_contract.isString)(step.table) && step.table.length > 0 && isColumnSchema(step.column);
235
+ case "column.remove": return keys.length === 3 && keys.includes("operation") && keys.includes("table") && keys.includes("column") && (0, _orkestrel_contract.isString)(step.table) && step.table.length > 0 && (0, _orkestrel_contract.isString)(step.column) && step.column.length > 0;
223
236
  case "index.add":
224
- case "index.remove": return keys.length === 3 && keys.includes("operation") && keys.includes("table") && keys.includes("index") && typeof step.table === "string" && step.table.length > 0 && Array.isArray(step.index) && step.index.length > 0 && step.index.every((column) => typeof column === "string" && column.length > 0);
237
+ case "index.remove": return keys.length === 3 && keys.includes("operation") && keys.includes("table") && keys.includes("index") && (0, _orkestrel_contract.isString)(step.table) && step.table.length > 0 && (0, _orkestrel_contract.isArray)(step.index) && step.index.length > 0 && step.index.every((column) => (0, _orkestrel_contract.isString)(column) && column.length > 0);
225
238
  default: return false;
226
239
  }
227
- } catch {
228
- return false;
229
- }
240
+ });
230
241
  }
231
242
  /**
232
243
  * Checks whether a value is an ordered migration plan.
233
244
  *
245
+ * @remarks
246
+ * Total over any input: a hostile getter, a revoked proxy, or a cyclic value is
247
+ * contained as a non-match rather than a throw.
248
+ *
234
249
  * @param value - The value to test
235
250
  * @returns True if `value` is a complete {@link Migration}; false otherwise
236
251
  */
237
252
  function isMigration(value) {
238
- try {
253
+ return (0, _orkestrel_contract.holds)(() => {
239
254
  const migration = (0, _orkestrel_contract.cloneJSONRecord)(value);
240
255
  const keys = Object.keys(migration);
241
- return keys.length === 3 && keys.includes("from") && keys.includes("to") && keys.includes("steps") && typeof migration.from === "number" && Number.isFinite(migration.from) && typeof migration.to === "number" && Number.isFinite(migration.to) && Array.isArray(migration.steps) && migration.steps.every(isMigrationStep);
242
- } catch {
243
- return false;
244
- }
256
+ return keys.length === 3 && keys.includes("from") && keys.includes("to") && keys.includes("steps") && (0, _orkestrel_contract.isFiniteNumber)(migration.from) && (0, _orkestrel_contract.isFiniteNumber)(migration.to) && (0, _orkestrel_contract.isArray)(migration.steps) && migration.steps.every(isMigrationStep);
257
+ });
245
258
  }
246
259
  /**
247
260
  * Checks whether a value is persisted driver metadata.
248
261
  *
262
+ * @remarks
263
+ * The boundary check a versioning driver's `metadata()` narrows a stored or
264
+ * deserialized record through, so no call site needs an assertion. Total over any
265
+ * input: a hostile getter, a revoked proxy, or a cyclic value is contained as a
266
+ * non-match rather than a throw.
267
+ *
249
268
  * @param value - The value to test
250
269
  * @returns True if `value` is complete {@link DriverMetadata}; false otherwise
251
270
  */
252
271
  function isDriverMetadata(value) {
253
- try {
272
+ return (0, _orkestrel_contract.holds)(() => {
254
273
  const metadata = (0, _orkestrel_contract.cloneJSONRecord)(value);
255
274
  const keys = Object.keys(metadata);
256
- return keys.length === 2 && keys.includes("version") && keys.includes("schema") && typeof metadata.version === "number" && Number.isFinite(metadata.version) && isDriverSchema(metadata.schema);
257
- } catch {
258
- return false;
259
- }
275
+ return keys.length === 2 && keys.includes("version") && keys.includes("schema") && (0, _orkestrel_contract.isFiniteNumber)(metadata.version) && isDriverSchema(metadata.schema);
276
+ });
260
277
  }
261
278
  /**
262
279
  * Checks whether a value is one atomic migration request.
263
280
  *
281
+ * @remarks
282
+ * Total over any input: a hostile getter, a revoked proxy, or a cyclic value is
283
+ * contained as a non-match rather than a throw.
284
+ *
264
285
  * @param value - The value to test
265
286
  * @returns True if `value` is a complete {@link MigrationInput}; false otherwise
266
287
  */
267
288
  function isMigrationInput(value) {
268
- try {
289
+ return (0, _orkestrel_contract.holds)(() => {
269
290
  const input = (0, _orkestrel_contract.cloneJSONRecord)(value);
270
291
  const keys = Object.keys(input);
271
292
  return (keys.length === 1 || keys.length === 2) && keys.includes("plan") && (keys.length === 1 || keys.includes("metadata")) && isMigration(input.plan) && (input.metadata === void 0 || isDriverMetadata(input.metadata));
272
- } catch {
273
- return false;
274
- }
293
+ });
275
294
  }
276
295
  //#endregion
277
296
  //#region src/core/cloners.ts
278
297
  /**
279
298
  * Clones unknown driver metadata into a distinct deeply frozen snapshot.
280
299
  *
300
+ * @remarks
301
+ * The clone is validated as {@link DriverMetadata} before it is returned, so a
302
+ * malformed or hostile value throws a `VALIDATION` {@link DatabaseError} at
303
+ * `context.path === 'metadata'` rather than surfacing a raw Contract or caller error.
304
+ *
281
305
  * @param value - Unknown metadata
282
306
  * @returns Owned driver metadata
283
307
  */
@@ -287,7 +311,7 @@ function cloneDriverMetadata(value) {
287
311
  if (isDriverMetadata(metadata)) return metadata;
288
312
  throw new DatabaseError("VALIDATION", "Driver metadata is invalid", { path: "metadata" });
289
313
  } catch (error) {
290
- if (error instanceof DatabaseError) throw error;
314
+ if (isDatabaseError(error)) throw error;
291
315
  throw new DatabaseError("VALIDATION", "Driver metadata is invalid", {
292
316
  path: "metadata",
293
317
  cause: error
@@ -297,6 +321,11 @@ function cloneDriverMetadata(value) {
297
321
  /**
298
322
  * Clones unknown driver schema into a distinct deeply frozen snapshot.
299
323
  *
324
+ * @remarks
325
+ * The clone is validated as a table-schema collection before it is returned, so a
326
+ * malformed or hostile value throws a `VALIDATION` {@link DatabaseError} at
327
+ * `context.path === 'schema'` rather than surfacing a raw Contract or caller error.
328
+ *
300
329
  * @param value - Unknown table schema collection
301
330
  * @returns Owned driver schema
302
331
  */
@@ -306,7 +335,7 @@ function cloneDriverSchema(value) {
306
335
  if (isDriverSchema(schema)) return schema;
307
336
  throw new DatabaseError("VALIDATION", "Driver schema is invalid", { path: "schema" });
308
337
  } catch (error) {
309
- if (error instanceof DatabaseError) throw error;
338
+ if (isDatabaseError(error)) throw error;
310
339
  throw new DatabaseError("VALIDATION", "Driver schema is invalid", {
311
340
  path: "schema",
312
341
  cause: error
@@ -316,6 +345,11 @@ function cloneDriverSchema(value) {
316
345
  /**
317
346
  * Clones unknown migration input into a distinct deeply frozen snapshot.
318
347
  *
348
+ * @remarks
349
+ * The clone is validated as a {@link MigrationInput} before it is returned, so a
350
+ * malformed or hostile value throws a `VALIDATION` {@link DatabaseError} at
351
+ * `context.path === 'migration'` rather than surfacing a raw Contract or caller error.
352
+ *
319
353
  * @param value - Unknown migration input
320
354
  * @returns Owned migration input
321
355
  */
@@ -325,7 +359,7 @@ function cloneMigrationInput(value) {
325
359
  if (isMigrationInput(input)) return input;
326
360
  throw new DatabaseError("VALIDATION", "Migration input is invalid", { path: "migration" });
327
361
  } catch (error) {
328
- if (error instanceof DatabaseError) throw error;
362
+ if (isDatabaseError(error)) throw error;
329
363
  throw new DatabaseError("VALIDATION", "Migration input is invalid", {
330
364
  path: "migration",
331
365
  cause: error
@@ -348,14 +382,14 @@ function cloneMigrationInput(value) {
348
382
  */
349
383
  function validatePage(input) {
350
384
  const limit = input?.limit;
351
- if (limit !== void 0 && (!Number.isInteger(limit) || limit < 0)) throw new DatabaseError("VALIDATION", "Query limit must be a nonnegative integer", {
385
+ if (limit !== void 0 && (!(0, _orkestrel_contract.isInteger)(limit) || limit < 0)) throw new DatabaseError("VALIDATION", "Query limit must be a nonnegative integer", {
352
386
  field: "limit",
353
- value: Number.isFinite(limit) ? limit : String(limit)
387
+ value: (0, _orkestrel_contract.isFiniteNumber)(limit) ? limit : String(limit)
354
388
  });
355
389
  const offset = input?.offset;
356
- if (offset !== void 0 && (!Number.isInteger(offset) || offset < 0)) throw new DatabaseError("VALIDATION", "Query offset must be a nonnegative integer", {
390
+ if (offset !== void 0 && (!(0, _orkestrel_contract.isInteger)(offset) || offset < 0)) throw new DatabaseError("VALIDATION", "Query offset must be a nonnegative integer", {
357
391
  field: "offset",
358
- value: Number.isFinite(offset) ? offset : String(offset)
392
+ value: (0, _orkestrel_contract.isFiniteNumber)(offset) ? offset : String(offset)
359
393
  });
360
394
  }
361
395
  /**
@@ -373,14 +407,14 @@ function validatePage(input) {
373
407
  * @returns `-1`, `0`, or `1`
374
408
  */
375
409
  function compareValues(left, right) {
376
- const [leftRank = 5, rightRank = 5] = [left, right].map((value) => value === void 0 ? 0 : value === null ? 1 : typeof value === "boolean" ? 2 : typeof value === "number" ? 3 : typeof value === "string" ? 4 : 5);
410
+ const [leftRank = 5, rightRank = 5] = [left, right].map((value) => value === void 0 ? 0 : value === null ? 1 : (0, _orkestrel_contract.isBoolean)(value) ? 2 : (0, _orkestrel_contract.isNumber)(value) ? 3 : (0, _orkestrel_contract.isString)(value) ? 4 : 5);
377
411
  if (leftRank !== rightRank) return leftRank < rightRank ? -1 : 1;
378
- if (typeof left === "number" && typeof right === "number") {
412
+ if ((0, _orkestrel_contract.isNumber)(left) && (0, _orkestrel_contract.isNumber)(right)) {
379
413
  if (Number.isNaN(left) || Number.isNaN(right)) return Number.isNaN(left) ? Number.isNaN(right) ? 0 : 1 : -1;
380
414
  return left < right ? -1 : left > right ? 1 : 0;
381
415
  }
382
- if (typeof left === "string" && typeof right === "string") return left < right ? -1 : left > right ? 1 : 0;
383
- if (typeof left === "boolean" && typeof right === "boolean") return left === right ? 0 : left ? 1 : -1;
416
+ if ((0, _orkestrel_contract.isString)(left) && (0, _orkestrel_contract.isString)(right)) return left < right ? -1 : left > right ? 1 : 0;
417
+ if ((0, _orkestrel_contract.isBoolean)(left) && (0, _orkestrel_contract.isBoolean)(right)) return left === right ? 0 : left ? 1 : -1;
384
418
  return 0;
385
419
  }
386
420
  /**
@@ -391,9 +425,9 @@ function compareValues(left, right) {
391
425
  * @remarks
392
426
  * Primitives compare by SameValueZero (`NaN` equals itself; `+0` equals `-0`).
393
427
  * Arrays compare by index (same length, every element `equalsValue`). Plain
394
- * records (through `isRecord`) compare by their OWN enumerable keys: same key
395
- * COUNT and, for every key in `left`, `right` has that key (`Object.hasOwn`)
396
- * with a `equalsValue` value — so a key present with value `undefined` is NOT
428
+ * records (through `isRecord`) compare by their own enumerable keys: same key
429
+ * count and, for every key in `left`, `right` has that key (`Object.hasOwn`)
430
+ * with a `equalsValue` value — so a key present with value `undefined` is not
397
431
  * equal to that key being absent (both differ in `Object.keys` membership).
398
432
  * Anything else (functions, class instances, mismatched shapes) falls through
399
433
  * to `false`. Container pairs are tracked iteratively, so self-referential and
@@ -419,13 +453,13 @@ function equalsValue(left, right) {
419
453
  const pair = pending.pop();
420
454
  if (pair === void 0) continue;
421
455
  const [currentLeft, currentRight] = pair;
422
- if (typeof currentLeft === "number" && typeof currentRight === "number") {
456
+ if ((0, _orkestrel_contract.isNumber)(currentLeft) && (0, _orkestrel_contract.isNumber)(currentRight)) {
423
457
  if (Number.isNaN(currentLeft) && Number.isNaN(currentRight) || currentLeft === currentRight) continue;
424
458
  return false;
425
459
  }
426
460
  if (currentLeft === currentRight) continue;
427
- const leftArray = Array.isArray(currentLeft);
428
- const rightArray = Array.isArray(currentRight);
461
+ const leftArray = (0, _orkestrel_contract.isArray)(currentLeft);
462
+ const rightArray = (0, _orkestrel_contract.isArray)(currentRight);
429
463
  const leftRecord = (0, _orkestrel_contract.isRecord)(currentLeft);
430
464
  const rightRecord = (0, _orkestrel_contract.isRecord)(currentRight);
431
465
  if (leftArray !== rightArray || leftRecord !== rightRecord) return false;
@@ -459,16 +493,16 @@ function equalsValue(left, right) {
459
493
  }
460
494
  }
461
495
  /**
462
- * Matches a value against a wildcard pattern in LINEAR time — the shared, ReDoS-SAFE
496
+ * Matches a value against a wildcard pattern in linear time — the shared, ReDoS-safe
463
497
  * engine behind {@link matchesLikePattern} and {@link matchesGlobPattern}.
464
498
  *
465
499
  * @remarks
466
- * A backtracking RegExp (`a%b%c` → `^a.*b.*c$`) is CATASTROPHIC on a hostile pattern:
500
+ * A backtracking RegExp (`a%b%c` → `^a.*b.*c$`) is catastrophic on a hostile pattern:
467
501
  * `.*` segments separated by literals, matched against a long non-matching input, blow
468
502
  * up super-linearly — and JS has no atomic groups / possessive quantifiers to bound it,
469
503
  * while a `LIKE` / `GLOB` pattern is a caller-supplied operand this package cannot
470
- * trust. So this builds NO regex. It runs the classic GREEDY TWO-POINTER wildcard match:
471
- * the `any` wildcard records its position and, on a later mismatch, backtracks ONLY to
504
+ * trust. So this builds no regex. It runs the classic greedy two-pointer wildcard match:
505
+ * the `any` wildcard records its position and, on a later mismatch, backtracks only to
472
506
  * that last `any` (letting it absorb one more char) — so the work is O(value × pattern),
473
507
  * never the exponential / polynomial backtracking a regex would do. The pattern length
474
508
  * is capped at {@link MAX_PATTERN_LENGTH} (a `VALIDATION` {@link DatabaseError} over it),
@@ -476,16 +510,16 @@ function equalsValue(left, right) {
476
510
  * pattern.
477
511
  *
478
512
  * The `any` wildcard matches any run (including empty); `single` matches exactly one
479
- * char; every other pattern char matches itself LITERALLY (a pattern `.` / `(` / `\` is
513
+ * char; every other pattern char matches itself literally (a pattern `.` / `(` / `\` is
480
514
  * a literal — the regex-metacharacter hazard is gone with the regex). `any` is tested
481
- * BEFORE a literal match, so a value that literally contains the wildcard char never
482
- * shadows the wildcard. Case folding is applied to BOTH sides when `fold` is set.
515
+ * before a literal match, so a value that literally contains the wildcard char never
516
+ * shadows the wildcard. Case folding is applied to both sides when `fold` is set.
483
517
  *
484
518
  * @param value - The value to test
485
519
  * @param pattern - The wildcard pattern
486
520
  * @param any - The any-run wildcard char (`%` for `LIKE`, `*` for `GLOB`)
487
521
  * @param single - The single-char wildcard char (`_` for `LIKE`, `?` for `GLOB`)
488
- * @param fold - Whether to match case-INSENSITIVELY (`LIKE` folds; `GLOB` does not)
522
+ * @param fold - Whether to match case-insensitively (`LIKE` folds; `GLOB` does not)
489
523
  * @returns True if `value` matches `pattern`; false otherwise
490
524
  * @throws A `VALIDATION` {@link DatabaseError} when `pattern` exceeds {@link MAX_PATTERN_LENGTH}
491
525
  */
@@ -547,7 +581,7 @@ function matchesLikePattern(value, pattern) {
547
581
  * @remarks
548
582
  * `*` matches any run of characters (including none) and `?` matches exactly one
549
583
  * character; every other pattern character matches itself literally, so a
550
- * character class such as `[a-z]` is NOT interpreted. Runs on
584
+ * character class such as `[a-z]` is not interpreted. Runs on
551
585
  * {@link matchesWildcardPattern}, so the match is linear in the value length and
552
586
  * the pattern is capped at {@link MAX_PATTERN_LENGTH}.
553
587
  *
@@ -573,7 +607,7 @@ function matchesGlobPattern(value, pattern) {
573
607
  * string is one column; an array descends a nested value) — and applies the
574
608
  * operator. Range operators (`above` / `below` / `from` / `to` / `between`) use
575
609
  * {@link compareValues}, the total order; the equality family (`equals` / `not`
576
- * / `any` / `none`) uses {@link equalsValue} — STRUCTURAL equality, not the total
610
+ * / `any` / `none`) uses {@link equalsValue} — structural equality, not the total
577
611
  * order's rank-5-collapses-all-objects behavior, so `equals` on an object/array
578
612
  * operand only matches a structurally-equal value, never every row holding any
579
613
  * object. This is a semantics change from ranking: `equalsValue` is SameValueZero
@@ -731,7 +765,9 @@ function computeAggregate(rows, operation, column) {
731
765
  const total = numbers.reduce((sum, value) => sum + value, 0);
732
766
  return operation === "average" ? total / numbers.length : total;
733
767
  }
734
- return operation === "minimum" ? Math.min(...numbers) : Math.max(...numbers);
768
+ let result = operation === "minimum" ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
769
+ for (const value of numbers) result = operation === "minimum" ? Math.min(result, value) : Math.max(result, value);
770
+ return result;
735
771
  }
736
772
  /**
737
773
  * Reads a row's primary key from a column, when it is a usable {@link Key}.
@@ -788,8 +824,8 @@ function shapeToColumnStorage(shape) {
788
824
  case "number": return shape.integer === true ? "integer" : "real";
789
825
  case "boolean": return "boolean";
790
826
  case "literal":
791
- if (shape.values.every((value) => typeof value === "boolean")) return "boolean";
792
- if (shape.values.every((value) => typeof value === "number")) return shape.values.every((value) => Number.isInteger(value)) ? "integer" : "real";
827
+ if (shape.values.every((value) => (0, _orkestrel_contract.isBoolean)(value))) return "boolean";
828
+ if (shape.values.every((value) => (0, _orkestrel_contract.isNumber)(value))) return shape.values.every((value) => (0, _orkestrel_contract.isInteger)(value)) ? "integer" : "real";
793
829
  return "text";
794
830
  case "optional":
795
831
  case "nullable": return shapeToColumnStorage(shape.inner);
@@ -909,16 +945,16 @@ function checkAbort(signal) {
909
945
  * plan labels only; versioning drivers persist and reconcile them through
910
946
  * {@link DriverMetadata}.
911
947
  *
912
- * A column present in BOTH schemas under the same name but with a different
948
+ * A column present in both schemas under the same name but with a different
913
949
  * `storage`, `optional`, or `nullable` value throws a `MIGRATION`
914
950
  * {@link DatabaseError} naming the table, the column, and the from→to
915
- * difference — a name-only diff would otherwise silently produce NO step for
951
+ * difference — a name-only diff would otherwise silently produce no step for
916
952
  * the drift, and versioned reconciliation would stamp over it. There is no
917
953
  * automatic in-place type-change step: the manual path is to add a new column,
918
954
  * copy/convert the data at the application layer, then remove the old column —
919
955
  * two separate plans, never a single implicit "alter" step.
920
956
  *
921
- * @param deployed - The table schemas currently applied
957
+ * @param deployed - The already-applied table schemas
922
958
  * @param declared - The table schemas the caller wants applied
923
959
  * @param from - The plan's source version label (defaults to `0`)
924
960
  * @param to - The plan's target version label (defaults to `1`)
@@ -938,7 +974,7 @@ function checkAbort(signal) {
938
974
  * ```
939
975
  */
940
976
  function planMigration(deployed, declared, from = 0, to = 1) {
941
- if (!Number.isFinite(from) || !Number.isFinite(to)) throw new DatabaseError("MIGRATION", "Migration versions must be finite", {
977
+ if (!(0, _orkestrel_contract.isFiniteNumber)(from) || !(0, _orkestrel_contract.isFiniteNumber)(to)) throw new DatabaseError("MIGRATION", "Migration versions must be finite", {
942
978
  from,
943
979
  to
944
980
  });
@@ -1025,6 +1061,8 @@ function planMigration(deployed, declared, from = 0, to = 1) {
1025
1061
  }
1026
1062
  /**
1027
1063
  * Projects migration steps sequentially over a canonical validated owned schema.
1064
+ *
1065
+ * @remarks
1028
1066
  * Adding a required non-null column to an existing table rejects with
1029
1067
  * `MIGRATION`; optional-only and nullable-only additions remain portable.
1030
1068
  *
@@ -1188,14 +1226,14 @@ function migrateRows(rows, steps) {
1188
1226
  * default `id`, `posts` keyed by a non-id `slug`) and, calling `factory()`
1189
1227
  * fresh for each phase so failures stay isolated, verifies: `open`/`close`;
1190
1228
  * `read` of a missing key returns `undefined`; `write`/`read` round-trip
1191
- * with DEEP copy-in/copy-out isolation (mutating the caller's row —
1192
- * including a NESTED field — after `write`, or a row `read` returns, never
1229
+ * with deep copy-in/copy-out isolation (mutating the caller's row —
1230
+ * including a nested field — after `write`, or a row `read` returns, never
1193
1231
  * perturbs stored state) and upsert-overwrite; simultaneous same-key
1194
1232
  * `insert` calls produce exactly one commit and one `CONFLICT`; pre-aborted
1195
1233
  * `write`, `insert`, and `delete` calls leave storage unchanged; `delete`
1196
1234
  * returns `true` then `false`; `keys`/`scan` yield in ascending key order;
1197
1235
  * `clear` empties only its target table; `snapshot`'s rollback thunk
1198
- * restores pre-snapshot state, including a NESTED field mutated in place on
1236
+ * restores pre-snapshot state, including a nested field mutated in place on
1199
1237
  * a read-back row between capture and restore; a scoped
1200
1238
  * `snapshot(['users'])` rolls back only the named table, leaving a
1201
1239
  * concurrent mutation to another table intact; a non-`id` primary key
@@ -1209,12 +1247,12 @@ function migrateRows(rows, steps) {
1209
1247
  * store's `metadata()` is `undefined`, and after
1210
1248
  * `stamp({ version, schema })`, `metadata()` returns the exact stamped value.
1211
1249
  *
1212
- * Each phase runs within a `try`/`catch`: an EXPECTED mismatch yields a
1213
- * finding built from the assertion, while an UNEXPECTED throw (a driver
1250
+ * Each phase runs within a `try`/`catch`: an expected mismatch yields a
1251
+ * finding built from the assertion, while an unexpected throw (a driver
1214
1252
  * crash mid-phase) is caught and yielded as a finding too, naming the phase
1215
1253
  * as `check` and carrying the caught error in `context.error` — a broken
1216
1254
  * driver can never escape the battery as an unhandled rejection. Within a
1217
- * phase, the FIRST violated assertion yields and the phase stops (matching
1255
+ * phase, the first violated assertion yields and the phase stops (matching
1218
1256
  * the historical fail-fast shape at phase granularity); the generator then
1219
1257
  * moves on to the next phase regardless. Because this is a **generator**,
1220
1258
  * consuming only the first yielded value reproduces true fail-fast (later
@@ -1240,7 +1278,7 @@ async function* scanDriver(factory) {
1240
1278
  } catch (error) {
1241
1279
  yield {
1242
1280
  check: "open-close",
1243
- message: error instanceof Error ? error.message : String(error),
1281
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1244
1282
  context: { error }
1245
1283
  };
1246
1284
  }
@@ -1261,7 +1299,7 @@ async function* scanDriver(factory) {
1261
1299
  } catch (error) {
1262
1300
  yield {
1263
1301
  check: "read-missing",
1264
- message: error instanceof Error ? error.message : String(error),
1302
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1265
1303
  context: { error }
1266
1304
  };
1267
1305
  }
@@ -1337,7 +1375,7 @@ async function* scanDriver(factory) {
1337
1375
  } catch (error) {
1338
1376
  yield {
1339
1377
  check: "write-read",
1340
- message: error instanceof Error ? error.message : String(error),
1378
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1341
1379
  context: { error }
1342
1380
  };
1343
1381
  }
@@ -1378,7 +1416,7 @@ async function* scanDriver(factory) {
1378
1416
  } catch (error) {
1379
1417
  yield {
1380
1418
  check: "insert-atomic",
1381
- message: error instanceof Error ? error.message : String(error),
1419
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1382
1420
  context: { error }
1383
1421
  };
1384
1422
  }
@@ -1418,7 +1456,7 @@ async function* scanDriver(factory) {
1418
1456
  } catch (error) {
1419
1457
  yield {
1420
1458
  check: "delete",
1421
- message: error instanceof Error ? error.message : String(error),
1459
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1422
1460
  context: { error }
1423
1461
  };
1424
1462
  }
@@ -1481,7 +1519,7 @@ async function* scanDriver(factory) {
1481
1519
  } catch (error) {
1482
1520
  yield {
1483
1521
  check: "mutation-abort",
1484
- message: error instanceof Error ? error.message : String(error),
1522
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1485
1523
  context: { error }
1486
1524
  };
1487
1525
  }
@@ -1540,7 +1578,7 @@ async function* scanDriver(factory) {
1540
1578
  } catch (error) {
1541
1579
  yield {
1542
1580
  check: "order",
1543
- message: error instanceof Error ? error.message : String(error),
1581
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1544
1582
  context: { error }
1545
1583
  };
1546
1584
  }
@@ -1584,7 +1622,7 @@ async function* scanDriver(factory) {
1584
1622
  } catch (error) {
1585
1623
  yield {
1586
1624
  check: "clear",
1587
- message: error instanceof Error ? error.message : String(error),
1625
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1588
1626
  context: { error }
1589
1627
  };
1590
1628
  }
@@ -1633,7 +1671,7 @@ async function* scanDriver(factory) {
1633
1671
  } catch (error) {
1634
1672
  yield {
1635
1673
  check: "snapshot",
1636
- message: error instanceof Error ? error.message : String(error),
1674
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1637
1675
  context: { error }
1638
1676
  };
1639
1677
  }
@@ -1671,7 +1709,7 @@ async function* scanDriver(factory) {
1671
1709
  } catch (error) {
1672
1710
  yield {
1673
1711
  check: "snapshot-nested",
1674
- message: error instanceof Error ? error.message : String(error),
1712
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1675
1713
  context: { error }
1676
1714
  };
1677
1715
  }
@@ -1697,7 +1735,7 @@ async function* scanDriver(factory) {
1697
1735
  } catch (error) {
1698
1736
  yield {
1699
1737
  check: "non-id-primary",
1700
- message: error instanceof Error ? error.message : String(error),
1738
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1701
1739
  context: { error }
1702
1740
  };
1703
1741
  }
@@ -1728,7 +1766,7 @@ async function* scanDriver(factory) {
1728
1766
  } catch (error) {
1729
1767
  yield {
1730
1768
  check: "nested-roundtrip",
1731
- message: error instanceof Error ? error.message : String(error),
1769
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1732
1770
  context: { error }
1733
1771
  };
1734
1772
  }
@@ -1793,7 +1831,7 @@ async function* scanDriver(factory) {
1793
1831
  } catch (error) {
1794
1832
  yield {
1795
1833
  check: "migrate",
1796
- message: error instanceof Error ? error.message : String(error),
1834
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1797
1835
  context: { error }
1798
1836
  };
1799
1837
  }
@@ -1858,7 +1896,7 @@ async function* scanDriver(factory) {
1858
1896
  } catch (error) {
1859
1897
  yield {
1860
1898
  check: "stream",
1861
- message: error instanceof Error ? error.message : String(error),
1899
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1862
1900
  context: { error }
1863
1901
  };
1864
1902
  }
@@ -1919,7 +1957,7 @@ async function* scanDriver(factory) {
1919
1957
  } catch (error) {
1920
1958
  yield {
1921
1959
  check: "transaction",
1922
- message: error instanceof Error ? error.message : String(error),
1960
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1923
1961
  context: { error }
1924
1962
  };
1925
1963
  }
@@ -1958,7 +1996,7 @@ async function* scanDriver(factory) {
1958
1996
  } catch (error) {
1959
1997
  yield {
1960
1998
  check: "metadata-stamp",
1961
- message: error instanceof Error ? error.message : String(error),
1999
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
1962
2000
  context: { error }
1963
2001
  };
1964
2002
  }
@@ -2013,7 +2051,7 @@ async function* scanDriver(factory) {
2013
2051
  } catch (error) {
2014
2052
  yield {
2015
2053
  check: "snapshot-scoped",
2016
- message: error instanceof Error ? error.message : String(error),
2054
+ message: (0, _orkestrel_contract.isError)(error) ? error.message : String(error),
2017
2055
  context: { error }
2018
2056
  };
2019
2057
  }
@@ -2025,11 +2063,13 @@ async function* scanDriver(factory) {
2025
2063
  *
2026
2064
  * @remarks
2027
2065
  * Consumes only the first value {@link scanDriver} yields: because that
2028
- * generator is lazy, every LATER phase never runs — true fail-fast, not
2066
+ * generator is lazy, every later phase never runs — true fail-fast, not
2029
2067
  * merely "report only the first". The
2030
2068
  * thrown error is byte-compatible with the historical shape: a
2031
2069
  * `CONFORMANCE` {@link DatabaseError} whose `message` is the finding's
2032
- * `message` and whose `context` is `{ check, ...finding.context }`.
2070
+ * `message` and whose `context` is `{ check, ...finding.context }`. The battery
2071
+ * takes a driver factory and reports through a throw, so it binds no test
2072
+ * framework and runs from any runner.
2033
2073
  *
2034
2074
  * @param factory - Mints a fresh, unopened driver instance (called once per phase)
2035
2075
  * @returns Nothing — resolves once every phase has passed
@@ -2049,7 +2089,7 @@ async function conformDriver(factory) {
2049
2089
  });
2050
2090
  }
2051
2091
  /**
2052
- * Runs the FULL driver-conformance battery and collects every violation — the
2092
+ * Runs the full driver-conformance battery and collects every violation — the
2053
2093
  * audit entry point for a driver author who wants a complete report rather
2054
2094
  * than a single fail-fast throw.
2055
2095
  *
@@ -2719,10 +2759,10 @@ var Query = class {
2719
2759
  * @remarks
2720
2760
  * - **Observable.** The owned {@link emitter} ({@link TableEventMap}) carries the
2721
2761
  * per-row mutation moments — `write` (set / add / update), `remove`, `clear` — for
2722
- * fire-and-forget observers (cache invalidation, sync, an audit log), ALONGSIDE the
2723
- * database-level lifecycle. Events carry the affected KEY only (no value payload, to
2762
+ * fire-and-forget observers (cache invalidation, sync, an audit log), alongside the
2763
+ * database-level lifecycle. Events carry the affected key only (no value payload, to
2724
2764
  * keep fan-out lean); reads / queries / counts are not emitted. Every event is emitted
2725
- * directly, strictly AFTER the driver write / delete / clear completes; the emitter
2765
+ * directly, strictly after the driver write / delete / clear completes; the emitter
2726
2766
  * isolates a listener throw and routes it to its `error` handler (the `error` option),
2727
2767
  * so a buggy observer can never corrupt a write or perturb a transaction.
2728
2768
  */
@@ -2835,7 +2875,7 @@ var Table = class {
2835
2875
  * conditions.
2836
2876
  *
2837
2877
  * @remarks
2838
- * Unlike {@link count}, `aggregate` operates on STORED rows WITHOUT the
2878
+ * Unlike {@link count}, `aggregate` operates on stored rows without the
2839
2879
  * contract guard {@link records} / {@link scan} apply — a non-conforming
2840
2880
  * stored row still contributes to the computed aggregate when it matches
2841
2881
  * the conditions, even though it would never appear in `records()`'s
@@ -3163,6 +3203,10 @@ var DatabaseTransaction = class {
3163
3203
  * generator. Imported views register their physical schemas with the same
3164
3204
  * internal context before opening begins, so every view observes one driver,
3165
3205
  * merged schema, emitter, status, transaction boundary, and terminal close.
3206
+ *
3207
+ * The view owns the driver and its declared `tables`, connects that driver lazily on
3208
+ * first use, `import`s further tables and `export`s their portable definitions, and
3209
+ * runs `transaction` scopes over the shared context.
3166
3210
  */
3167
3211
  var Database = class Database {
3168
3212
  #context;
@@ -3285,7 +3329,7 @@ var Database = class Database {
3285
3329
  * @remarks
3286
3330
  * The in-between made concrete: it runs identically in a browser or on a server,
3287
3331
  * so it is the storage behind tests, ephemeral caches, and any code that wants
3288
- * the database API without a persistent backend. Rows are DEEP-copied (through
3332
+ * the database API without a persistent backend. Rows are deep-copied (through
3289
3333
  * `structuredClone`) in and out — at `write`, `read`, `scan`, `stream`, and both
3290
3334
  * snapshot capture and restore — so a caller mutating a nested field of an input
3291
3335
  * row, a returned row, or a row mutated in place between snapshot and rollback
@@ -3360,7 +3404,7 @@ var MemoryDriver = class {
3360
3404
  * toward `offset` / `limit`. Both are applied lazily as matches are found —
3361
3405
  * `offset` matches are skipped without being yielded, and iteration stops the
3362
3406
  * instant `limit` yields have been produced, so a large table is never fully
3363
- * walked for a small page. `input.order` is IGNORED (the same contract as
3407
+ * walked for a small page. `input.order` is ignored (the same contract as
3364
3408
  * `TableInterface.scan` and `QueryInterface.stream`): streaming yields key
3365
3409
  * order, sorted output is `records()`'s job. Rows yield copy-out, and an
3366
3410
  * unknown table mirrors `scan`'s empty-yield behavior.
@@ -3589,20 +3633,29 @@ var MemoryDriver = class {
3589
3633
  * `name`, `generator`, `version`, and emitter hooks
3590
3634
  * @returns A typed {@link DatabaseInterface}
3591
3635
  *
3592
- * @example
3636
+ * @example Create a database
3593
3637
  * ```ts
3594
3638
  * import { createDatabase, createMemoryDriver } from '@orkestrel/database'
3595
3639
  * import { integerShape, stringShape } from '@orkestrel/contract'
3596
3640
  *
3597
3641
  * const db = createDatabase({
3598
- * driver: createMemoryDriver(),
3642
+ * driver: createMemoryDriver(), // any DriverInterface — a persistent backend swaps in, same API
3599
3643
  * tables: {
3600
- * users: { id: stringShape(), age: integerShape() },
3644
+ * users: { id: stringShape(), name: stringShape(), age: integerShape() },
3601
3645
  * posts: { slug: stringShape(), title: stringShape() },
3602
3646
  * },
3603
- * primary: { posts: 'slug' },
3647
+ * primary: { posts: 'slug' }, // non-`id` primary-key columns, per table
3604
3648
  * })
3605
- * await db.table('users').set({ id: 'u1', age: 36 }) // typed; coerced + validated
3649
+ *
3650
+ * const users = db.table('users') // hold the handle; TableInterface<{ id; name; age }>
3651
+ *
3652
+ * await users.set({ id: 'u1', name: 'Ada', age: 36 }) // coerced + validated through the contract
3653
+ * await users.get('u1') // typed { id; name; age } | undefined — narrowed, never `as`
3654
+ * await users
3655
+ * .query()
3656
+ * .condition({ column: 'age', operator: 'from', values: [18], connector: 'and' })
3657
+ * .order({ column: 'age', direction: 'descending' })
3658
+ * .collect() // typed rows
3606
3659
  * ```
3607
3660
  */
3608
3661
  function createDatabase(options) {