@orkestrel/database 0.0.18 → 0.0.19

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.
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _orkestrel_contract = require("@orkestrel/contract");
3
3
  let _orkestrel_emitter = require("@orkestrel/emitter");
4
- //#region src/core/constants.ts
4
+ // src/core/constants.ts
5
5
  /**
6
6
  * Supplies the primary-key column, `'id'`, assumed when {@link PrimaryMap} does not name one.
7
7
  *
@@ -100,8 +100,7 @@ var CONFORMANCE_POSTS_SCHEMA = Object.freeze({
100
100
  * what a later phase opens.
101
101
  */
102
102
  var CONFORMANCE_SCHEMA = Object.freeze([CONFORMANCE_USERS_SCHEMA, CONFORMANCE_POSTS_SCHEMA]);
103
- //#endregion
104
- //#region src/core/errors.ts
103
+ // src/core/errors.ts
105
104
  /**
106
105
  * Represents an error thrown by the database layer.
107
106
  *
@@ -123,6 +122,7 @@ var DatabaseError = class extends Error {
123
122
  context;
124
123
  constructor(code, message, context) {
125
124
  super(message);
125
+ _orkestrel_contract.INTRINSICS.define(this, Symbol.for("@orkestrel/database.DatabaseError"), { value: this });
126
126
  this.name = "DatabaseError";
127
127
  this.code = code;
128
128
  if (context !== void 0) this.context = context;
@@ -131,6 +131,11 @@ var DatabaseError = class extends Error {
131
131
  /**
132
132
  * Narrows an unknown caught value to a {@link DatabaseError}.
133
133
  *
134
+ * @remarks
135
+ * Accepts branded instances and subclasses across package copies and module formats.
136
+ * Requires the declared code and context shape. Refuses look-alikes and transparent
137
+ * proxies without requiring an exact `name`.
138
+ *
134
139
  * @param value - The value to test (typically a `catch` binding)
135
140
  * @returns True if `value` is a {@link DatabaseError}; false otherwise
136
141
  *
@@ -144,10 +149,17 @@ var DatabaseError = class extends Error {
144
149
  * ```
145
150
  */
146
151
  function isDatabaseError(value) {
147
- return (0, _orkestrel_contract.isInstance)(value, DatabaseError);
152
+ return (0, _orkestrel_contract.holds)(() => {
153
+ if (!(0, _orkestrel_contract.isInstance)(value, Error) || _orkestrel_contract.INTRINSICS.prototype(value) === Error.prototype) return false;
154
+ const brand = _orkestrel_contract.INTRINSICS.describe(value, Symbol.for("@orkestrel/database.DatabaseError"));
155
+ if (brand === void 0 || brand.value !== value) return false;
156
+ const code = _orkestrel_contract.INTRINSICS.reflect.read(value, "code");
157
+ if (code !== "CLOSED" && code !== "NOT_FOUND" && code !== "CONFLICT" && code !== "VALIDATION" && code !== "ABORTED" && code !== "MIGRATION" && code !== "CONFORMANCE" && code !== "DRIVER") return false;
158
+ const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
159
+ return context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context);
160
+ });
148
161
  }
149
- //#endregion
150
- //#region src/core/validators.ts
162
+ // src/core/validators.ts
151
163
  /**
152
164
  * Checks whether a value is a usable database key.
153
165
  *
@@ -292,8 +304,7 @@ function isMigrationInput(value) {
292
304
  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));
293
305
  });
294
306
  }
295
- //#endregion
296
- //#region src/core/cloners.ts
307
+ // src/core/cloners.ts
297
308
  /**
298
309
  * Clones unknown driver metadata into a distinct deeply frozen snapshot.
299
310
  *
@@ -366,8 +377,7 @@ function cloneMigrationInput(value) {
366
377
  });
367
378
  }
368
379
  }
369
- //#endregion
370
- //#region src/core/helpers.ts
380
+ // src/core/helpers.ts
371
381
  /**
372
382
  * Validates the paging fields of a portable query.
373
383
  *
@@ -470,7 +480,7 @@ function equalsValue(left, right) {
470
480
  else prior.add(currentRight);
471
481
  if (leftArray && rightArray) {
472
482
  if (currentLeft.length !== currentRight.length) return false;
473
- for (let index = 0; index < currentLeft.length; index += 1) {
483
+ for (const index of Array.prototype.keys.call(currentLeft)) {
474
484
  const leftOwn = Object.hasOwn(currentLeft, index);
475
485
  if (leftOwn !== Object.hasOwn(currentRight, index)) return false;
476
486
  if (leftOwn) pending.push([currentLeft[index], currentRight[index]]);
@@ -493,6 +503,27 @@ function equalsValue(left, right) {
493
503
  }
494
504
  }
495
505
  /**
506
+ * Checks whether a fixed-length wildcard segment matches a value at an offset.
507
+ *
508
+ * @remarks
509
+ * Compares UTF-16 code units: `single` matches any one unit, and every other segment
510
+ * unit matches itself. An offset that leaves the segment outside the value fails.
511
+ *
512
+ * @param value - The value to read
513
+ * @param segment - The segment, holding no any-run wildcard
514
+ * @param at - The value offset the segment starts at
515
+ * @param single - The single-char wildcard char
516
+ * @returns True if every segment unit matches the value unit at its offset; false otherwise
517
+ *
518
+ * @example
519
+ * ```ts
520
+ * matchesWildcardSegment('hello', 'l_o', 2, '_') // true
521
+ * ```
522
+ */
523
+ function matchesWildcardSegment(value, segment, at, single) {
524
+ return at >= 0 && at + segment.length <= value.length && segment.split("").every((unit, offset) => unit === single || unit === value[at + offset]);
525
+ }
526
+ /**
496
527
  * Matches a value against a wildcard pattern in linear time — the shared, ReDoS-safe
497
528
  * engine behind {@link matchesLikePattern} and {@link matchesGlobPattern}.
498
529
  *
@@ -501,19 +532,20 @@ function equalsValue(left, right) {
501
532
  * `.*` segments separated by literals, matched against a long non-matching input, blow
502
533
  * up super-linearly — and JS has no atomic groups / possessive quantifiers to bound it,
503
534
  * while a `LIKE` / `GLOB` pattern is a caller-supplied operand this package cannot
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
506
- * that last `any` (letting it absorb one more char) — so the work is O(value × pattern),
507
- * never the exponential / polynomial backtracking a regex would do. The pattern length
508
- * is capped at {@link MAX_PATTERN_LENGTH} (a `VALIDATION` {@link DatabaseError} over it),
509
- * bounding the pattern factor so a match stays linear in the value length whatever the
510
- * pattern.
535
+ * trust. This builds no regex. It splits the pattern on `any` into fixed-length
536
+ * segments, anchors the first at the value's start and the last at its end, and places
537
+ * each middle segment at its leftmost match after the one before it, which is the only
538
+ * placement a match needs, so the work is O(value × pattern) at worst, never the
539
+ * exponential / polynomial backtracking a regex would do. The pattern length is capped
540
+ * at {@link MAX_PATTERN_LENGTH} (a `VALIDATION` {@link DatabaseError} over it), bounding
541
+ * the pattern factor so a match stays linear in the value length whatever the pattern.
511
542
  *
512
543
  * The `any` wildcard matches any run (including empty); `single` matches exactly one
513
- * char; every other pattern char matches itself literally (a pattern `.` / `(` / `\` is
514
- * a literal — the regex-metacharacter hazard is gone with the regex). `any` is tested
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.
544
+ * UTF-16 code unit; every other pattern char matches itself literally (a pattern `.` /
545
+ * `(` / `\` is a literal — the regex-metacharacter hazard is gone with the regex). The
546
+ * pattern splits on `any` before any literal comparison, so a value that literally
547
+ * contains the wildcard char never shadows the wildcard. Case folding is applied to
548
+ * both sides when `fold` is set.
517
549
  *
518
550
  * @param value - The value to test
519
551
  * @param pattern - The wildcard pattern
@@ -530,27 +562,17 @@ function matchesWildcardPattern(value, pattern, any, single, fold) {
530
562
  });
531
563
  const haystack = fold ? value.toLowerCase() : value;
532
564
  const needle = fold ? pattern.toLowerCase() : pattern;
533
- let vi = 0;
534
- let pi = 0;
535
- let star = -1;
536
- let mark = 0;
537
- while (vi < haystack.length) {
538
- const pc = pi < needle.length ? needle[pi] : void 0;
539
- if (pc === any) {
540
- star = pi;
541
- mark = vi;
542
- pi += 1;
543
- } else if (pc !== void 0 && (pc === single || pc === haystack[vi])) {
544
- vi += 1;
545
- pi += 1;
546
- } else if (star !== -1) {
547
- pi = star + 1;
548
- mark += 1;
549
- vi = mark;
550
- } else return false;
551
- }
552
- while (pi < needle.length && needle[pi] === any) pi += 1;
553
- return pi === needle.length;
565
+ const segments = any.length === 1 ? needle.split(any) : [needle];
566
+ const head = segments[0] ?? "";
567
+ const tail = segments.at(-1) ?? "";
568
+ if (segments.length === 1) return haystack.length === head.length && matchesWildcardSegment(haystack, head, 0, single);
569
+ const end = haystack.length - tail.length;
570
+ if (end < head.length || !matchesWildcardSegment(haystack, head, 0, single) || !matchesWildcardSegment(haystack, tail, end, single)) return false;
571
+ return segments.slice(1, -1).reduce((from, segment) => {
572
+ if (from === void 0) return void 0;
573
+ const at = segment.includes(single) ? new Array(Math.max(0, end - segment.length - from + 1)).keys().map((offset) => from + offset).find((candidate) => matchesWildcardSegment(haystack, segment, candidate, single)) : haystack.indexOf(segment, from);
574
+ return at === void 0 || at < 0 || at + segment.length > end ? void 0 : at + segment.length;
575
+ }, head.length) !== void 0;
554
576
  }
555
577
  /**
556
578
  * Matches a value against a SQL `LIKE` pattern, folding case.
@@ -656,16 +678,10 @@ function matchesCondition(row, condition) {
656
678
  * @returns True if the row satisfies the combined conditions; false otherwise
657
679
  */
658
680
  function matchesQuery(row, conditions) {
659
- let result = true;
660
- let seeded = false;
661
- for (const condition of conditions) {
681
+ return Iterator.from(conditions).reduce((previous, condition) => {
662
682
  const match = matchesCondition(row, condition);
663
- if (!seeded) {
664
- result = match;
665
- seeded = true;
666
- } else result = condition.connector === "or" ? result || match : result && match;
667
- }
668
- return result;
683
+ return previous === void 0 ? match : condition.connector === "or" ? previous || match : previous && match;
684
+ }, void 0) ?? true;
669
685
  }
670
686
  /**
671
687
  * Filters rows by a list of conditions — the shared basis for a table's count
@@ -728,15 +744,13 @@ function sortRows(rows, order) {
728
744
  */
729
745
  function applyQuery(rows, input) {
730
746
  validatePage(input);
731
- let result = rows;
732
747
  const conditions = input?.conditions;
733
- if (conditions !== void 0 && conditions.length > 0) result = result.filter((row) => matchesQuery(row, conditions));
748
+ const filtered = conditions !== void 0 && conditions.length > 0 ? rows.filter((row) => matchesQuery(row, conditions)) : rows;
734
749
  const order = input?.order;
735
- if (order !== void 0 && order.length > 0) result = sortRows(result, order);
750
+ const sorted = order !== void 0 && order.length > 0 ? sortRows(filtered, order) : filtered;
736
751
  const offset = input?.offset ?? 0;
737
752
  const limit = input?.limit;
738
- if (offset > 0 || limit !== void 0) result = result.slice(offset, limit !== void 0 ? offset + limit : void 0);
739
- return result;
753
+ return offset > 0 || limit !== void 0 ? sorted.slice(offset, limit !== void 0 ? offset + limit : void 0) : sorted;
740
754
  }
741
755
  /**
742
756
  * Computes an aggregate over a column across rows.
@@ -754,20 +768,17 @@ function applyQuery(rows, input) {
754
768
  */
755
769
  function computeAggregate(rows, operation, column) {
756
770
  if (operation === "count") return rows.length;
757
- const numbers = [];
758
- for (const row of rows) {
759
- if (!(0, _orkestrel_contract.isRecord)(row)) continue;
771
+ const numbers = Iterator.from(rows).flatMap((row) => {
772
+ if (!(0, _orkestrel_contract.isRecord)(row)) return [];
760
773
  const value = (0, _orkestrel_contract.parseNumber)((0, _orkestrel_contract.resolveField)(row, column));
761
- if (value !== void 0) numbers.push(value);
762
- }
774
+ return value === void 0 ? [] : [value];
775
+ }).toArray();
763
776
  if (numbers.length === 0) return void 0;
764
777
  if (operation === "sum" || operation === "average") {
765
778
  const total = numbers.reduce((sum, value) => sum + value, 0);
766
779
  return operation === "average" ? total / numbers.length : total;
767
780
  }
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;
781
+ return numbers.reduce((result, value) => operation === "minimum" ? Math.min(result, value) : Math.max(result, value), operation === "minimum" ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY);
771
782
  }
772
783
  /**
773
784
  * Reads a row's primary key from a column, when it is a usable {@link Key}.
@@ -978,14 +989,12 @@ function planMigration(deployed, declared, from = 0, to = 1) {
978
989
  from,
979
990
  to
980
991
  });
981
- let beforeSchema;
982
- let targetSchema;
983
- try {
984
- beforeSchema = normalizeDriverSchema(deployed);
985
- targetSchema = normalizeDriverSchema(declared);
986
- } catch (error) {
987
- throw new DatabaseError("MIGRATION", "Migration schema is invalid", { cause: error });
988
- }
992
+ const schemas = (0, _orkestrel_contract.attempt)(() => ({
993
+ before: normalizeDriverSchema(deployed),
994
+ target: normalizeDriverSchema(declared)
995
+ }));
996
+ if (!schemas.success) throw new DatabaseError("MIGRATION", "Migration schema is invalid", { cause: schemas.error });
997
+ const { before: beforeSchema, target: targetSchema } = schemas.value;
989
998
  const deployedByName = new Map(beforeSchema.map((table) => [table.name, table]));
990
999
  const declaredByName = new Map(targetSchema.map((table) => [table.name, table]));
991
1000
  const steps = [];
@@ -1071,18 +1080,16 @@ function planMigration(deployed, declared, from = 0, to = 1) {
1071
1080
  * @returns A fresh owned final schema
1072
1081
  */
1073
1082
  function projectMigrationSchema(schema, steps) {
1074
- let owned;
1075
- let projectedSteps;
1076
- try {
1077
- owned = normalizeDriverSchema(schema);
1078
- projectedSteps = cloneMigrationInput({ plan: {
1083
+ const captured = (0, _orkestrel_contract.attempt)(() => ({
1084
+ owned: normalizeDriverSchema(schema),
1085
+ steps: cloneMigrationInput({ plan: {
1079
1086
  from: 0,
1080
1087
  to: 1,
1081
1088
  steps
1082
- } }).plan.steps;
1083
- } catch (error) {
1084
- throw new DatabaseError("MIGRATION", "Migration input is invalid", { cause: error });
1085
- }
1089
+ } }).plan.steps
1090
+ }));
1091
+ if (!captured.success) throw new DatabaseError("MIGRATION", "Migration input is invalid", { cause: captured.error });
1092
+ const { owned, steps: projectedSteps } = captured.value;
1086
1093
  const tables = new Map(owned.map((table) => [table.name, table]));
1087
1094
  for (const step of projectedSteps) {
1088
1095
  if (step.operation === "table.add") {
@@ -1391,10 +1398,8 @@ async function* scanDriver(factory) {
1391
1398
  name: "Grace",
1392
1399
  age: 40
1393
1400
  })]);
1394
- let fulfilled = 0;
1395
- let conflicted = 0;
1396
- for (const outcome of outcomes) if (outcome.status === "fulfilled") fulfilled += 1;
1397
- else if (isDatabaseError(outcome.reason) && outcome.reason.code === "CONFLICT") conflicted += 1;
1401
+ const fulfilled = outcomes.filter((outcome) => outcome.status === "fulfilled").length;
1402
+ const conflicted = outcomes.filter((outcome) => outcome.status === "rejected" && isDatabaseError(outcome.reason) && outcome.reason.code === "CONFLICT").length;
1398
1403
  const keys = await driver.keys("users");
1399
1404
  await driver.close();
1400
1405
  if (fulfilled !== 1 || conflicted !== 1 || !equalsValue(keys, ["u1"])) yield {
@@ -1470,32 +1475,17 @@ async function* scanDriver(factory) {
1470
1475
  });
1471
1476
  const controller = new AbortController();
1472
1477
  controller.abort("conformance abort");
1473
- let writeError;
1474
- let insertError;
1475
- let deleteError;
1476
- try {
1477
- await driver.write("users", "u2", {
1478
- id: "u2",
1479
- name: "Grace",
1480
- age: 40
1481
- }, { signal: controller.signal });
1482
- } catch (error) {
1483
- writeError = error;
1484
- }
1485
- try {
1486
- await driver.insert("users", "u2", {
1487
- id: "u2",
1488
- name: "Grace",
1489
- age: 40
1490
- }, { signal: controller.signal });
1491
- } catch (error) {
1492
- insertError = error;
1493
- }
1494
- try {
1495
- await driver.delete("users", "u1", { signal: controller.signal });
1496
- } catch (error) {
1497
- deleteError = error;
1498
- }
1478
+ const writeError = await captureFailure(() => driver.write("users", "u2", {
1479
+ id: "u2",
1480
+ name: "Grace",
1481
+ age: 40
1482
+ }, { signal: controller.signal }));
1483
+ const insertError = await captureFailure(() => driver.insert("users", "u2", {
1484
+ id: "u2",
1485
+ name: "Grace",
1486
+ age: 40
1487
+ }, { signal: controller.signal }));
1488
+ const deleteError = await captureFailure(() => driver.delete("users", "u1", { signal: controller.signal }));
1499
1489
  const keys = await driver.keys("users");
1500
1490
  await driver.close();
1501
1491
  if (!isDatabaseError(writeError) || writeError.code !== "ABORTED" || !isDatabaseError(insertError) || insertError.code !== "ABORTED" || !isDatabaseError(deleteError) || deleteError.code !== "ABORTED" || !equalsValue(keys, ["u1"])) yield {
@@ -1805,19 +1795,15 @@ async function* scanDriver(factory) {
1805
1795
  };
1806
1796
  break migratePhase;
1807
1797
  }
1808
- let caught;
1809
- try {
1810
- await driver.migrate({ plan: {
1811
- from: 0,
1812
- to: 1,
1813
- steps: [{
1814
- operation: "table.remove",
1815
- table: "ghost"
1816
- }]
1817
- } });
1818
- } catch (error) {
1819
- caught = error;
1820
- }
1798
+ const migrate = driver.migrate.bind(driver);
1799
+ const caught = await captureFailure(() => migrate({ plan: {
1800
+ from: 0,
1801
+ to: 1,
1802
+ steps: [{
1803
+ operation: "table.remove",
1804
+ table: "ghost"
1805
+ }]
1806
+ } }));
1821
1807
  await driver.close();
1822
1808
  if (!isDatabaseError(caught) || caught.code !== "MIGRATION") yield {
1823
1809
  check: "migrate-unknown-table",
@@ -2114,8 +2100,23 @@ async function auditDriver(factory) {
2114
2100
  for await (const finding of scanDriver(factory)) findings.push(finding);
2115
2101
  return findings;
2116
2102
  }
2117
- //#endregion
2118
- //#region src/core/ScopedIterator.ts
2103
+ /**
2104
+ * Captures the exact rejection or synchronous throw from a driver conformance operation.
2105
+ *
2106
+ * @example
2107
+ * ```ts
2108
+ * await captureFailure(() => Promise.reject('refused')) // 'refused'
2109
+ * ```
2110
+ */
2111
+ async function captureFailure(operation) {
2112
+ try {
2113
+ await operation();
2114
+ return;
2115
+ } catch (error) {
2116
+ return error;
2117
+ }
2118
+ }
2119
+ // src/core/ScopedIterator.ts
2119
2120
  /**
2120
2121
  * Forms the internal continuation admission boundary for one scoped async iterable.
2121
2122
  *
@@ -2194,8 +2195,7 @@ var ScopedIterator = class {
2194
2195
  } catch {}
2195
2196
  }
2196
2197
  };
2197
- //#endregion
2198
- //#region src/core/TransactionScope.ts
2198
+ // src/core/TransactionScope.ts
2199
2199
  /**
2200
2200
  * Forms the internal lifetime boundary for one database transaction callback.
2201
2201
  *
@@ -2208,8 +2208,7 @@ var ScopedIterator = class {
2208
2208
  var TransactionScope = class {
2209
2209
  #operations = /* @__PURE__ */ new Set();
2210
2210
  #accepting = true;
2211
- #failed = false;
2212
- #error;
2211
+ #failure;
2213
2212
  get accepting() {
2214
2213
  return this.#accepting;
2215
2214
  }
@@ -2222,21 +2221,17 @@ var TransactionScope = class {
2222
2221
  } catch (error) {
2223
2222
  return Promise.reject(error);
2224
2223
  }
2225
- let promise;
2226
- try {
2227
- promise = operation();
2228
- } catch (error) {
2229
- promise = Promise.reject(error);
2230
- }
2224
+ const outcome = (0, _orkestrel_contract.attempt)(operation);
2225
+ const promise = outcome.success ? outcome.value : Promise.reject(outcome.error);
2231
2226
  this.#operations.add(promise);
2232
2227
  promise.then(() => {
2233
2228
  this.#operations.delete(promise);
2234
2229
  }, (error) => {
2235
2230
  this.#operations.delete(promise);
2236
- if (!this.#failed) {
2237
- this.#failed = true;
2238
- this.#error = error;
2239
- }
2231
+ this.#failure ??= {
2232
+ success: false,
2233
+ error
2234
+ };
2240
2235
  });
2241
2236
  return promise;
2242
2237
  }
@@ -2248,11 +2243,10 @@ var TransactionScope = class {
2248
2243
  }
2249
2244
  async drain() {
2250
2245
  while (this.#operations.size > 0) await Promise.allSettled(this.#operations);
2251
- if (this.#failed) throw this.#error;
2246
+ if (this.#failure !== void 0 && !this.#failure.success) throw this.#failure.error;
2252
2247
  }
2253
2248
  };
2254
- //#endregion
2255
- //#region src/core/DatabaseContext.ts
2249
+ // src/core/DatabaseContext.ts
2256
2250
  /**
2257
2251
  * Owns the internal shared state behind every typed view of one database.
2258
2252
  *
@@ -2269,7 +2263,7 @@ var DatabaseContext = class {
2269
2263
  #emitter;
2270
2264
  #operations = /* @__PURE__ */ new Set();
2271
2265
  #schema = [];
2272
- #transaction;
2266
+ #transactions = /* @__PURE__ */ new Set();
2273
2267
  #status = "idle";
2274
2268
  #ready;
2275
2269
  #failure;
@@ -2296,7 +2290,7 @@ var DatabaseContext = class {
2296
2290
  return this.#name;
2297
2291
  }
2298
2292
  get accepting() {
2299
- return this.#status !== "closed" && this.#transaction === void 0;
2293
+ return this.#status !== "closed" && this.#transactions.size === 0;
2300
2294
  }
2301
2295
  get status() {
2302
2296
  return this.#status;
@@ -2306,7 +2300,7 @@ var DatabaseContext = class {
2306
2300
  }
2307
2301
  register(schema) {
2308
2302
  if (this.#status === "closed") throw new DatabaseError("CLOSED", `Database '${this.#name}' is closed`, { name: this.#name });
2309
- if (this.#ready !== void 0 || this.#transaction !== void 0) throw new DatabaseError("CONFLICT", `Database '${this.#name}' cannot import tables after opening has started`, {
2303
+ if (this.#ready !== void 0 || this.#transactions.size !== 0) throw new DatabaseError("CONFLICT", `Database '${this.#name}' cannot import tables after opening has started`, {
2310
2304
  name: this.#name,
2311
2305
  status: this.#status
2312
2306
  });
@@ -2360,12 +2354,8 @@ var DatabaseContext = class {
2360
2354
  } catch (error) {
2361
2355
  return Promise.reject(error);
2362
2356
  }
2363
- let promise;
2364
- try {
2365
- promise = operation();
2366
- } catch (error) {
2367
- promise = Promise.reject(error);
2368
- }
2357
+ const outcome = (0, _orkestrel_contract.attempt)(operation);
2358
+ const promise = outcome.success ? outcome.value : Promise.reject(outcome.error);
2369
2359
  this.#operations.add(promise);
2370
2360
  promise.then(() => {
2371
2361
  this.#operations.delete(promise);
@@ -2377,44 +2367,40 @@ var DatabaseContext = class {
2377
2367
  async transaction(scope, options) {
2378
2368
  checkAbort(options?.signal);
2379
2369
  this.#admit();
2380
- const token = {};
2381
- this.#transaction = token;
2370
+ const lifetime = new TransactionScope();
2371
+ this.#transactions.add(lifetime);
2382
2372
  try {
2383
2373
  await this.#drain();
2384
2374
  await this.connect();
2385
2375
  if (this.#driver.transaction !== void 0) {
2386
- const rejection = {
2387
- rejected: false,
2388
- error: void 0,
2389
- marker: {}
2390
- };
2376
+ const rejection = new AbortController();
2391
2377
  try {
2392
2378
  const value = await this.#driver.transaction(async (storage) => {
2393
2379
  this.#emitter.emit("transaction");
2394
- const outcome = await scope(storage, new TransactionScope());
2380
+ const outcome = await scope(storage, lifetime);
2395
2381
  if (!outcome.success) {
2396
- rejection.rejected = true;
2397
- rejection.error = outcome.error;
2398
- throw rejection.marker;
2382
+ rejection.abort(outcome);
2383
+ throw rejection.signal;
2399
2384
  }
2400
2385
  return outcome.value;
2401
2386
  });
2402
2387
  this.#emitter.emit("commit");
2403
2388
  return value;
2404
2389
  } catch (error) {
2405
- if (rejection.rejected) {
2406
- if (Object.is(error, rejection.marker)) {
2407
- this.#emitter.emit("rollback", rejection.error);
2408
- throw rejection.error;
2390
+ const failure = rejection.signal.reason;
2391
+ if (rejection.signal.aborted && (0, _orkestrel_contract.isRecord)(failure) && failure.success === false) {
2392
+ if (Object.is(error, rejection.signal)) {
2393
+ this.#emitter.emit("rollback", failure.error);
2394
+ throw failure.error;
2409
2395
  }
2410
- throw this.#rollbackError(rejection.error, error);
2396
+ throw this.#rollbackError(failure.error, error);
2411
2397
  }
2412
2398
  throw error;
2413
2399
  }
2414
2400
  }
2415
2401
  const rollback = await this.#driver.snapshot();
2416
2402
  this.#emitter.emit("transaction");
2417
- const outcome = await scope(this.#driver, new TransactionScope());
2403
+ const outcome = await scope(this.#driver, lifetime);
2418
2404
  if (outcome.success) {
2419
2405
  this.#emitter.emit("commit");
2420
2406
  return outcome.value;
@@ -2427,7 +2413,7 @@ var DatabaseContext = class {
2427
2413
  this.#emitter.emit("rollback", outcome.error);
2428
2414
  throw outcome.error;
2429
2415
  } finally {
2430
- if (this.#transaction === token) this.#transaction = void 0;
2416
+ this.#transactions.delete(lifetime);
2431
2417
  }
2432
2418
  }
2433
2419
  async migrate(deployed, options) {
@@ -2451,11 +2437,11 @@ var DatabaseContext = class {
2451
2437
  }
2452
2438
  #admit() {
2453
2439
  if (this.#status === "closed") throw new DatabaseError("CLOSED", `Database '${this.#name}' is closed`, { name: this.#name });
2454
- if (this.#transaction !== void 0) throw new DatabaseError("CONFLICT", `Database '${this.#name}' has an active transaction`, { name: this.#name });
2440
+ if (this.#transactions.size !== 0) throw new DatabaseError("CONFLICT", `Database '${this.#name}' has an active transaction`, { name: this.#name });
2455
2441
  if (this.#failure !== void 0) throw this.#failure.error;
2456
2442
  }
2457
2443
  #outside() {
2458
- if (this.#transaction !== void 0) throw new DatabaseError("CONFLICT", `Database '${this.#name}' has an active transaction`, { name: this.#name });
2444
+ if (this.#transactions.size !== 0) throw new DatabaseError("CONFLICT", `Database '${this.#name}' has an active transaction`, { name: this.#name });
2459
2445
  }
2460
2446
  async #drain() {
2461
2447
  if (this.#operations.size === 0) return;
@@ -2536,8 +2522,7 @@ var DatabaseContext = class {
2536
2522
  });
2537
2523
  }
2538
2524
  };
2539
- //#endregion
2540
- //#region src/core/Cursor.ts
2525
+ // src/core/Cursor.ts
2541
2526
  /**
2542
2527
  * Walks a table's rows forward for bulk in-place mutation.
2543
2528
  *
@@ -2557,12 +2542,14 @@ var Cursor = class {
2557
2542
  #index = -1;
2558
2543
  #value;
2559
2544
  #closed = false;
2560
- constructor(keys, read, update, remove, track) {
2545
+ #initial;
2546
+ constructor(keys, read, update, remove, track, initial = false) {
2561
2547
  this.#keys = keys;
2562
2548
  this.#read = read;
2563
2549
  this.#update = update;
2564
2550
  this.#remove = remove;
2565
2551
  this.#track = track;
2552
+ this.#initial = initial;
2566
2553
  }
2567
2554
  get value() {
2568
2555
  return this.#value;
@@ -2574,6 +2561,13 @@ var Cursor = class {
2574
2561
  return this.#closed || this.#index >= this.#keys.length;
2575
2562
  }
2576
2563
  next() {
2564
+ if (this.#initial) {
2565
+ const initial = this.#queue(() => this.#advance());
2566
+ initial.then(() => {
2567
+ this.#initial = false;
2568
+ }, () => {});
2569
+ return initial;
2570
+ }
2577
2571
  return this.#track(() => this.#queue(() => this.#advance()));
2578
2572
  }
2579
2573
  update(changes) {
@@ -2630,8 +2624,33 @@ var Cursor = class {
2630
2624
  return result;
2631
2625
  }
2632
2626
  };
2633
- //#endregion
2634
- //#region src/core/Query.ts
2627
+ // src/core/StreamPage.ts
2628
+ /**
2629
+ * Tracks the matching-row position of one streaming page.
2630
+ *
2631
+ * @example
2632
+ * ```ts
2633
+ * const page = new StreamPage(1)
2634
+ * page.next() // false
2635
+ * page.next() // true
2636
+ * page.count // 1
2637
+ * ```
2638
+ */
2639
+ var StreamPage = class {
2640
+ #offset;
2641
+ #position = 0;
2642
+ constructor(offset) {
2643
+ this.#offset = offset;
2644
+ }
2645
+ get count() {
2646
+ return Math.max(0, this.#position - this.#offset);
2647
+ }
2648
+ next() {
2649
+ this.#position += 1;
2650
+ return this.#position > this.#offset;
2651
+ }
2652
+ };
2653
+ // src/core/Query.ts
2635
2654
  /**
2636
2655
  * Builds a read against one table through a fluent chain.
2637
2656
  *
@@ -2708,23 +2727,11 @@ var Query = class {
2708
2727
  }, options);
2709
2728
  return;
2710
2729
  }
2711
- const offset = this.#offset ?? 0;
2712
- let matched = 0;
2713
- let yielded = 0;
2730
+ const page = new StreamPage(this.#offset ?? 0);
2714
2731
  for await (const row of this.#table.scan({ conditions: this.#conditions }, options)) {
2715
- if (this.#limit !== void 0 && yielded >= this.#limit) break;
2716
- let matches = true;
2717
- for (const predicate of this.#filters) if (!predicate(row)) {
2718
- matches = false;
2719
- break;
2720
- }
2721
- if (!matches) continue;
2722
- if (matched < offset) {
2723
- matched += 1;
2724
- continue;
2725
- }
2726
- matched += 1;
2727
- yielded += 1;
2732
+ if (this.#limit !== void 0 && page.count >= this.#limit) break;
2733
+ if (!Iterator.from(this.#filters).every((predicate) => predicate(row))) continue;
2734
+ if (!page.next()) continue;
2728
2735
  yield row;
2729
2736
  }
2730
2737
  }
@@ -2733,9 +2740,7 @@ var Query = class {
2733
2740
  return this.#table.records({ conditions: this.#conditions }).then((fetched) => computeAggregate(this.#filtered(fetched), operation, column));
2734
2741
  }
2735
2742
  #filtered(rows) {
2736
- let result = rows;
2737
- for (const predicate of this.#filters) result = result.filter(predicate);
2738
- return result;
2743
+ return Iterator.from(this.#filters).reduce((result, predicate) => result.filter(predicate), rows);
2739
2744
  }
2740
2745
  #page(rows) {
2741
2746
  const offset = this.#offset ?? 0;
@@ -2743,8 +2748,7 @@ var Query = class {
2743
2748
  return rows.slice(offset, this.#limit === void 0 ? void 0 : offset + this.#limit);
2744
2749
  }
2745
2750
  };
2746
- //#endregion
2747
- //#region src/core/Table.ts
2751
+ // src/core/Table.ts
2748
2752
  /**
2749
2753
  * Exposes typed keyed CRUD plus fluent query and cursor access over a driver.
2750
2754
  *
@@ -2865,9 +2869,7 @@ var Table = class {
2865
2869
  const conditions = input?.conditions;
2866
2870
  const candidate = conditions === void 0 ? {} : { conditions };
2867
2871
  const rows = await this.#driver.records?.(this.#name, candidate) ?? filterRows(await this.#collect(), conditions ?? []);
2868
- let count = 0;
2869
- for (const row of rows) if (this.#guard(row)) count += 1;
2870
- return count;
2872
+ return Iterator.from(rows).reduce((count, row) => count + (this.#guard(row) ? 1 : 0), 0);
2871
2873
  });
2872
2874
  }
2873
2875
  /**
@@ -2959,10 +2961,8 @@ var Table = class {
2959
2961
  cursor() {
2960
2962
  return this.#track(async () => {
2961
2963
  await this.#ready();
2962
- let initializing = true;
2963
- const cursor = new Cursor(await this.#driver.keys(this.#name), (key) => this.#readCursor(key), (key, changes) => this.#updateCursor(key, changes), (key) => this.#deleteCursor(key), (operation) => initializing ? operation() : this.#track(operation));
2964
+ const cursor = new Cursor(await this.#driver.keys(this.#name), (key) => this.#readCursor(key), (key, changes) => this.#updateCursor(key, changes), (key) => this.#deleteCursor(key), (operation) => this.#track(operation), true);
2964
2965
  await cursor.next();
2965
- initializing = false;
2966
2966
  return cursor;
2967
2967
  });
2968
2968
  }
@@ -2972,14 +2972,13 @@ var Table = class {
2972
2972
  const conditions = input?.conditions;
2973
2973
  const offset = input?.offset ?? 0;
2974
2974
  const limit = input?.limit;
2975
- let matched = 0;
2976
- let yielded = 0;
2975
+ const page = new StreamPage(offset);
2977
2976
  const iterator = (this.#driver.stream === void 0 ? this.#driver.scan(this.#name) : this.#driver.stream(this.#name, conditions === void 0 ? {} : { conditions }))[Symbol.asyncIterator]();
2978
2977
  try {
2979
2978
  while (true) {
2980
2979
  await this.#ready();
2981
2980
  checkAbort(options?.signal);
2982
- if (limit !== void 0 && yielded >= limit) return;
2981
+ if (limit !== void 0 && page.count >= limit) return;
2983
2982
  const step = await iterator.next();
2984
2983
  await this.#ready();
2985
2984
  checkAbort(options?.signal);
@@ -2988,12 +2987,7 @@ var Table = class {
2988
2987
  if (conditions !== void 0 && conditions.length > 0 && !matchesQuery(row, conditions)) continue;
2989
2988
  const narrowed = this.#cast(row);
2990
2989
  if (narrowed === void 0) continue;
2991
- if (matched < offset) {
2992
- matched += 1;
2993
- continue;
2994
- }
2995
- matched += 1;
2996
- yielded += 1;
2990
+ if (!page.next()) continue;
2997
2991
  yield narrowed;
2998
2992
  }
2999
2993
  } finally {
@@ -3156,8 +3150,7 @@ var Table = class {
3156
3150
  return this.#scope === void 0 ? operation() : this.#scope.track(operation);
3157
3151
  }
3158
3152
  };
3159
- //#endregion
3160
- //#region src/core/DatabaseTransaction.ts
3153
+ // src/core/DatabaseTransaction.ts
3161
3154
  /**
3162
3155
  * Binds a table-only database view to one driver transaction scope.
3163
3156
  *
@@ -3193,8 +3186,7 @@ var DatabaseTransaction = class {
3193
3186
  return new Table(() => Promise.resolve(), this.#driver, name, key, contract, this.#generate, this.#error, void 0, this.#scope);
3194
3187
  }
3195
3188
  };
3196
- //#endregion
3197
- //#region src/core/Database.ts
3189
+ // src/core/Database.ts
3198
3190
  /**
3199
3191
  * Exposes a typed view over one shared internal lifecycle and storage context.
3200
3192
  *
@@ -3321,8 +3313,7 @@ var Database = class Database {
3321
3313
  return database;
3322
3314
  }
3323
3315
  };
3324
- //#endregion
3325
- //#region src/core/drivers/MemoryDriver.ts
3316
+ // src/core/drivers/MemoryDriver.ts
3326
3317
  /**
3327
3318
  * Implements the reference {@link DriverInterface} — nested maps, no I/O.
3328
3319
  *
@@ -3594,19 +3585,14 @@ var MemoryDriver = class {
3594
3585
  const conditions = input.conditions;
3595
3586
  const offset = input.offset ?? 0;
3596
3587
  const limit = input.limit;
3597
- let skipped = 0;
3598
- let yielded = 0;
3588
+ const page = new StreamPage(offset);
3599
3589
  for (const key of this.#ordered(table)) {
3600
- if (limit !== void 0 && yielded >= limit) return;
3590
+ if (limit !== void 0 && page.count >= limit) return;
3601
3591
  const row = store.get(key);
3602
3592
  if (row === void 0) continue;
3603
3593
  if (conditions !== void 0 && conditions.length > 0 && !matchesQuery(row, conditions)) continue;
3604
- if (skipped < offset) {
3605
- skipped += 1;
3606
- continue;
3607
- }
3594
+ if (!page.next()) continue;
3608
3595
  yield structuredClone(row);
3609
- yielded += 1;
3610
3596
  }
3611
3597
  }
3612
3598
  #store(table) {
@@ -3616,8 +3602,7 @@ var MemoryDriver = class {
3616
3602
  return store;
3617
3603
  }
3618
3604
  };
3619
- //#endregion
3620
- //#region src/core/factories.ts
3605
+ // src/core/factories.ts
3621
3606
  /**
3622
3607
  * Creates a database over a driver and a declared `tables` schema.
3623
3608
  *
@@ -3673,8 +3658,7 @@ function createDatabase(options) {
3673
3658
  function createMemoryDriver() {
3674
3659
  return new MemoryDriver();
3675
3660
  }
3676
- //#endregion
3677
- //#region src/core/DriverIterator.ts
3661
+ // src/core/DriverIterator.ts
3678
3662
  /**
3679
3663
  * Forms the internal continuation boundary for a root driver async iterator.
3680
3664
  *
@@ -3789,7 +3773,6 @@ var DriverIterator = class {
3789
3773
  } catch {}
3790
3774
  }
3791
3775
  };
3792
- //#endregion
3793
3776
  exports.CONFORMANCE_POSTS_SCHEMA = CONFORMANCE_POSTS_SCHEMA;
3794
3777
  exports.CONFORMANCE_SCHEMA = CONFORMANCE_SCHEMA;
3795
3778
  exports.CONFORMANCE_USERS_SCHEMA = CONFORMANCE_USERS_SCHEMA;
@@ -3799,9 +3782,11 @@ exports.DatabaseError = DatabaseError;
3799
3782
  exports.DriverIterator = DriverIterator;
3800
3783
  exports.MAX_PATTERN_LENGTH = MAX_PATTERN_LENGTH;
3801
3784
  exports.MemoryDriver = MemoryDriver;
3785
+ exports.StreamPage = StreamPage;
3802
3786
  exports.applyQuery = applyQuery;
3803
3787
  exports.auditDriver = auditDriver;
3804
3788
  exports.bindRowKey = bindRowKey;
3789
+ exports.captureFailure = captureFailure;
3805
3790
  exports.checkAbort = checkAbort;
3806
3791
  exports.cloneDriverMetadata = cloneDriverMetadata;
3807
3792
  exports.cloneDriverSchema = cloneDriverSchema;
@@ -3829,6 +3814,7 @@ exports.matchesGlobPattern = matchesGlobPattern;
3829
3814
  exports.matchesLikePattern = matchesLikePattern;
3830
3815
  exports.matchesQuery = matchesQuery;
3831
3816
  exports.matchesWildcardPattern = matchesWildcardPattern;
3817
+ exports.matchesWildcardSegment = matchesWildcardSegment;
3832
3818
  exports.migrateRows = migrateRows;
3833
3819
  exports.normalizeDriverSchema = normalizeDriverSchema;
3834
3820
  exports.planMigration = planMigration;