@orkestrel/database 0.0.13 → 0.0.14
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.
- package/README.md +10 -6
- package/dist/src/browser/index.d.ts +54 -51
- package/dist/src/browser/index.js +35 -30
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +109 -42
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +274 -61
- package/dist/src/core/index.d.ts +274 -61
- package/dist/src/core/index.js +109 -42
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +54 -34
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +93 -76
- package/dist/src/server/index.d.ts +93 -76
- package/dist/src/server/index.js +54 -34
- package/dist/src/server/index.js.map +1 -1
- package/package.json +15 -16
package/dist/src/core/index.cjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
@@ -157,6 +160,10 @@ function isKey(value) {
|
|
|
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
|
*/
|
|
@@ -172,6 +179,10 @@ function isColumnSchema(value) {
|
|
|
172
179
|
/**
|
|
173
180
|
* Checks whether a value is a portable table schema.
|
|
174
181
|
*
|
|
182
|
+
* @remarks
|
|
183
|
+
* Total over any input: a hostile getter, a revoked proxy, or a cyclic value is
|
|
184
|
+
* contained as a non-match rather than a throw.
|
|
185
|
+
*
|
|
175
186
|
* @param value - The value to test
|
|
176
187
|
* @returns True if `value` is a complete {@link TableSchema}; false otherwise
|
|
177
188
|
*/
|
|
@@ -191,6 +202,10 @@ function isTableSchema(value) {
|
|
|
191
202
|
/**
|
|
192
203
|
* Checks whether a value is a complete portable driver schema.
|
|
193
204
|
*
|
|
205
|
+
* @remarks
|
|
206
|
+
* Total over any input: a hostile getter, a revoked proxy, or a cyclic value is
|
|
207
|
+
* contained as a non-match rather than a throw.
|
|
208
|
+
*
|
|
194
209
|
* @param value - The value to test
|
|
195
210
|
* @returns True if `value` is a table-schema collection with unique table names; false otherwise
|
|
196
211
|
*/
|
|
@@ -207,6 +222,10 @@ function isDriverSchema(value) {
|
|
|
207
222
|
/**
|
|
208
223
|
* Checks whether a value is one ordered migration step.
|
|
209
224
|
*
|
|
225
|
+
* @remarks
|
|
226
|
+
* Total over any input: a hostile getter, a revoked proxy, or a cyclic value is
|
|
227
|
+
* contained as a non-match rather than a throw.
|
|
228
|
+
*
|
|
210
229
|
* @param value - The value to test
|
|
211
230
|
* @returns True if `value` is a complete {@link MigrationStep}; false otherwise
|
|
212
231
|
*/
|
|
@@ -231,6 +250,10 @@ function isMigrationStep(value) {
|
|
|
231
250
|
/**
|
|
232
251
|
* Checks whether a value is an ordered migration plan.
|
|
233
252
|
*
|
|
253
|
+
* @remarks
|
|
254
|
+
* Total over any input: a hostile getter, a revoked proxy, or a cyclic value is
|
|
255
|
+
* contained as a non-match rather than a throw.
|
|
256
|
+
*
|
|
234
257
|
* @param value - The value to test
|
|
235
258
|
* @returns True if `value` is a complete {@link Migration}; false otherwise
|
|
236
259
|
*/
|
|
@@ -246,6 +269,12 @@ function isMigration(value) {
|
|
|
246
269
|
/**
|
|
247
270
|
* Checks whether a value is persisted driver metadata.
|
|
248
271
|
*
|
|
272
|
+
* @remarks
|
|
273
|
+
* The boundary check a versioning driver's `metadata()` narrows a stored or
|
|
274
|
+
* deserialized record through, so no call site needs an assertion. Total over any
|
|
275
|
+
* input: a hostile getter, a revoked proxy, or a cyclic value is contained as a
|
|
276
|
+
* non-match rather than a throw.
|
|
277
|
+
*
|
|
249
278
|
* @param value - The value to test
|
|
250
279
|
* @returns True if `value` is complete {@link DriverMetadata}; false otherwise
|
|
251
280
|
*/
|
|
@@ -261,6 +290,10 @@ function isDriverMetadata(value) {
|
|
|
261
290
|
/**
|
|
262
291
|
* Checks whether a value is one atomic migration request.
|
|
263
292
|
*
|
|
293
|
+
* @remarks
|
|
294
|
+
* Total over any input: a hostile getter, a revoked proxy, or a cyclic value is
|
|
295
|
+
* contained as a non-match rather than a throw.
|
|
296
|
+
*
|
|
264
297
|
* @param value - The value to test
|
|
265
298
|
* @returns True if `value` is a complete {@link MigrationInput}; false otherwise
|
|
266
299
|
*/
|
|
@@ -278,6 +311,11 @@ function isMigrationInput(value) {
|
|
|
278
311
|
/**
|
|
279
312
|
* Clones unknown driver metadata into a distinct deeply frozen snapshot.
|
|
280
313
|
*
|
|
314
|
+
* @remarks
|
|
315
|
+
* The clone is validated as {@link DriverMetadata} before it is returned, so a
|
|
316
|
+
* malformed or hostile value throws a `VALIDATION` {@link DatabaseError} at
|
|
317
|
+
* `context.path === 'metadata'` rather than surfacing a raw Contract or caller error.
|
|
318
|
+
*
|
|
281
319
|
* @param value - Unknown metadata
|
|
282
320
|
* @returns Owned driver metadata
|
|
283
321
|
*/
|
|
@@ -297,6 +335,11 @@ function cloneDriverMetadata(value) {
|
|
|
297
335
|
/**
|
|
298
336
|
* Clones unknown driver schema into a distinct deeply frozen snapshot.
|
|
299
337
|
*
|
|
338
|
+
* @remarks
|
|
339
|
+
* The clone is validated as a table-schema collection before it is returned, so a
|
|
340
|
+
* malformed or hostile value throws a `VALIDATION` {@link DatabaseError} at
|
|
341
|
+
* `context.path === 'schema'` rather than surfacing a raw Contract or caller error.
|
|
342
|
+
*
|
|
300
343
|
* @param value - Unknown table schema collection
|
|
301
344
|
* @returns Owned driver schema
|
|
302
345
|
*/
|
|
@@ -316,6 +359,11 @@ function cloneDriverSchema(value) {
|
|
|
316
359
|
/**
|
|
317
360
|
* Clones unknown migration input into a distinct deeply frozen snapshot.
|
|
318
361
|
*
|
|
362
|
+
* @remarks
|
|
363
|
+
* The clone is validated as a {@link MigrationInput} before it is returned, so a
|
|
364
|
+
* malformed or hostile value throws a `VALIDATION` {@link DatabaseError} at
|
|
365
|
+
* `context.path === 'migration'` rather than surfacing a raw Contract or caller error.
|
|
366
|
+
*
|
|
319
367
|
* @param value - Unknown migration input
|
|
320
368
|
* @returns Owned migration input
|
|
321
369
|
*/
|
|
@@ -391,9 +439,9 @@ function compareValues(left, right) {
|
|
|
391
439
|
* @remarks
|
|
392
440
|
* Primitives compare by SameValueZero (`NaN` equals itself; `+0` equals `-0`).
|
|
393
441
|
* Arrays compare by index (same length, every element `equalsValue`). Plain
|
|
394
|
-
* records (through `isRecord`) compare by their
|
|
395
|
-
*
|
|
396
|
-
* with a `equalsValue` value — so a key present with value `undefined` is
|
|
442
|
+
* records (through `isRecord`) compare by their own enumerable keys: same key
|
|
443
|
+
* count and, for every key in `left`, `right` has that key (`Object.hasOwn`)
|
|
444
|
+
* with a `equalsValue` value — so a key present with value `undefined` is not
|
|
397
445
|
* equal to that key being absent (both differ in `Object.keys` membership).
|
|
398
446
|
* Anything else (functions, class instances, mismatched shapes) falls through
|
|
399
447
|
* to `false`. Container pairs are tracked iteratively, so self-referential and
|
|
@@ -459,16 +507,16 @@ function equalsValue(left, right) {
|
|
|
459
507
|
}
|
|
460
508
|
}
|
|
461
509
|
/**
|
|
462
|
-
* Matches a value against a wildcard pattern in
|
|
510
|
+
* Matches a value against a wildcard pattern in linear time — the shared, ReDoS-safe
|
|
463
511
|
* engine behind {@link matchesLikePattern} and {@link matchesGlobPattern}.
|
|
464
512
|
*
|
|
465
513
|
* @remarks
|
|
466
|
-
* A backtracking RegExp (`a%b%c` → `^a.*b.*c$`) is
|
|
514
|
+
* A backtracking RegExp (`a%b%c` → `^a.*b.*c$`) is catastrophic on a hostile pattern:
|
|
467
515
|
* `.*` segments separated by literals, matched against a long non-matching input, blow
|
|
468
516
|
* up super-linearly — and JS has no atomic groups / possessive quantifiers to bound it,
|
|
469
517
|
* while a `LIKE` / `GLOB` pattern is a caller-supplied operand this package cannot
|
|
470
|
-
* trust. So this builds
|
|
471
|
-
* the `any` wildcard records its position and, on a later mismatch, backtracks
|
|
518
|
+
* trust. So this builds no regex. It runs the classic greedy two-pointer wildcard match:
|
|
519
|
+
* the `any` wildcard records its position and, on a later mismatch, backtracks only to
|
|
472
520
|
* that last `any` (letting it absorb one more char) — so the work is O(value × pattern),
|
|
473
521
|
* never the exponential / polynomial backtracking a regex would do. The pattern length
|
|
474
522
|
* is capped at {@link MAX_PATTERN_LENGTH} (a `VALIDATION` {@link DatabaseError} over it),
|
|
@@ -476,16 +524,16 @@ function equalsValue(left, right) {
|
|
|
476
524
|
* pattern.
|
|
477
525
|
*
|
|
478
526
|
* The `any` wildcard matches any run (including empty); `single` matches exactly one
|
|
479
|
-
* char; every other pattern char matches itself
|
|
527
|
+
* char; every other pattern char matches itself literally (a pattern `.` / `(` / `\` is
|
|
480
528
|
* a literal — the regex-metacharacter hazard is gone with the regex). `any` is tested
|
|
481
|
-
*
|
|
482
|
-
* shadows the wildcard. Case folding is applied to
|
|
529
|
+
* before a literal match, so a value that literally contains the wildcard char never
|
|
530
|
+
* shadows the wildcard. Case folding is applied to both sides when `fold` is set.
|
|
483
531
|
*
|
|
484
532
|
* @param value - The value to test
|
|
485
533
|
* @param pattern - The wildcard pattern
|
|
486
534
|
* @param any - The any-run wildcard char (`%` for `LIKE`, `*` for `GLOB`)
|
|
487
535
|
* @param single - The single-char wildcard char (`_` for `LIKE`, `?` for `GLOB`)
|
|
488
|
-
* @param fold - Whether to match case-
|
|
536
|
+
* @param fold - Whether to match case-insensitively (`LIKE` folds; `GLOB` does not)
|
|
489
537
|
* @returns True if `value` matches `pattern`; false otherwise
|
|
490
538
|
* @throws A `VALIDATION` {@link DatabaseError} when `pattern` exceeds {@link MAX_PATTERN_LENGTH}
|
|
491
539
|
*/
|
|
@@ -547,7 +595,7 @@ function matchesLikePattern(value, pattern) {
|
|
|
547
595
|
* @remarks
|
|
548
596
|
* `*` matches any run of characters (including none) and `?` matches exactly one
|
|
549
597
|
* character; every other pattern character matches itself literally, so a
|
|
550
|
-
* character class such as `[a-z]` is
|
|
598
|
+
* character class such as `[a-z]` is not interpreted. Runs on
|
|
551
599
|
* {@link matchesWildcardPattern}, so the match is linear in the value length and
|
|
552
600
|
* the pattern is capped at {@link MAX_PATTERN_LENGTH}.
|
|
553
601
|
*
|
|
@@ -573,7 +621,7 @@ function matchesGlobPattern(value, pattern) {
|
|
|
573
621
|
* string is one column; an array descends a nested value) — and applies the
|
|
574
622
|
* operator. Range operators (`above` / `below` / `from` / `to` / `between`) use
|
|
575
623
|
* {@link compareValues}, the total order; the equality family (`equals` / `not`
|
|
576
|
-
* / `any` / `none`) uses {@link equalsValue} —
|
|
624
|
+
* / `any` / `none`) uses {@link equalsValue} — structural equality, not the total
|
|
577
625
|
* order's rank-5-collapses-all-objects behavior, so `equals` on an object/array
|
|
578
626
|
* operand only matches a structurally-equal value, never every row holding any
|
|
579
627
|
* object. This is a semantics change from ranking: `equalsValue` is SameValueZero
|
|
@@ -731,7 +779,9 @@ function computeAggregate(rows, operation, column) {
|
|
|
731
779
|
const total = numbers.reduce((sum, value) => sum + value, 0);
|
|
732
780
|
return operation === "average" ? total / numbers.length : total;
|
|
733
781
|
}
|
|
734
|
-
|
|
782
|
+
let result = operation === "minimum" ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
|
|
783
|
+
for (const value of numbers) result = operation === "minimum" ? Math.min(result, value) : Math.max(result, value);
|
|
784
|
+
return result;
|
|
735
785
|
}
|
|
736
786
|
/**
|
|
737
787
|
* Reads a row's primary key from a column, when it is a usable {@link Key}.
|
|
@@ -909,16 +959,16 @@ function checkAbort(signal) {
|
|
|
909
959
|
* plan labels only; versioning drivers persist and reconcile them through
|
|
910
960
|
* {@link DriverMetadata}.
|
|
911
961
|
*
|
|
912
|
-
* A column present in
|
|
962
|
+
* A column present in both schemas under the same name but with a different
|
|
913
963
|
* `storage`, `optional`, or `nullable` value throws a `MIGRATION`
|
|
914
964
|
* {@link DatabaseError} naming the table, the column, and the from→to
|
|
915
|
-
* difference — a name-only diff would otherwise silently produce
|
|
965
|
+
* difference — a name-only diff would otherwise silently produce no step for
|
|
916
966
|
* the drift, and versioned reconciliation would stamp over it. There is no
|
|
917
967
|
* automatic in-place type-change step: the manual path is to add a new column,
|
|
918
968
|
* copy/convert the data at the application layer, then remove the old column —
|
|
919
969
|
* two separate plans, never a single implicit "alter" step.
|
|
920
970
|
*
|
|
921
|
-
* @param deployed - The table schemas
|
|
971
|
+
* @param deployed - The already-applied table schemas
|
|
922
972
|
* @param declared - The table schemas the caller wants applied
|
|
923
973
|
* @param from - The plan's source version label (defaults to `0`)
|
|
924
974
|
* @param to - The plan's target version label (defaults to `1`)
|
|
@@ -1025,6 +1075,8 @@ function planMigration(deployed, declared, from = 0, to = 1) {
|
|
|
1025
1075
|
}
|
|
1026
1076
|
/**
|
|
1027
1077
|
* Projects migration steps sequentially over a canonical validated owned schema.
|
|
1078
|
+
*
|
|
1079
|
+
* @remarks
|
|
1028
1080
|
* Adding a required non-null column to an existing table rejects with
|
|
1029
1081
|
* `MIGRATION`; optional-only and nullable-only additions remain portable.
|
|
1030
1082
|
*
|
|
@@ -1188,14 +1240,14 @@ function migrateRows(rows, steps) {
|
|
|
1188
1240
|
* default `id`, `posts` keyed by a non-id `slug`) and, calling `factory()`
|
|
1189
1241
|
* fresh for each phase so failures stay isolated, verifies: `open`/`close`;
|
|
1190
1242
|
* `read` of a missing key returns `undefined`; `write`/`read` round-trip
|
|
1191
|
-
* with
|
|
1192
|
-
* including a
|
|
1243
|
+
* with deep copy-in/copy-out isolation (mutating the caller's row —
|
|
1244
|
+
* including a nested field — after `write`, or a row `read` returns, never
|
|
1193
1245
|
* perturbs stored state) and upsert-overwrite; simultaneous same-key
|
|
1194
1246
|
* `insert` calls produce exactly one commit and one `CONFLICT`; pre-aborted
|
|
1195
1247
|
* `write`, `insert`, and `delete` calls leave storage unchanged; `delete`
|
|
1196
1248
|
* returns `true` then `false`; `keys`/`scan` yield in ascending key order;
|
|
1197
1249
|
* `clear` empties only its target table; `snapshot`'s rollback thunk
|
|
1198
|
-
* restores pre-snapshot state, including a
|
|
1250
|
+
* restores pre-snapshot state, including a nested field mutated in place on
|
|
1199
1251
|
* a read-back row between capture and restore; a scoped
|
|
1200
1252
|
* `snapshot(['users'])` rolls back only the named table, leaving a
|
|
1201
1253
|
* concurrent mutation to another table intact; a non-`id` primary key
|
|
@@ -1209,12 +1261,12 @@ function migrateRows(rows, steps) {
|
|
|
1209
1261
|
* store's `metadata()` is `undefined`, and after
|
|
1210
1262
|
* `stamp({ version, schema })`, `metadata()` returns the exact stamped value.
|
|
1211
1263
|
*
|
|
1212
|
-
* Each phase runs within a `try`/`catch`: an
|
|
1213
|
-
* finding built from the assertion, while an
|
|
1264
|
+
* Each phase runs within a `try`/`catch`: an expected mismatch yields a
|
|
1265
|
+
* finding built from the assertion, while an unexpected throw (a driver
|
|
1214
1266
|
* crash mid-phase) is caught and yielded as a finding too, naming the phase
|
|
1215
1267
|
* as `check` and carrying the caught error in `context.error` — a broken
|
|
1216
1268
|
* driver can never escape the battery as an unhandled rejection. Within a
|
|
1217
|
-
* phase, the
|
|
1269
|
+
* phase, the first violated assertion yields and the phase stops (matching
|
|
1218
1270
|
* the historical fail-fast shape at phase granularity); the generator then
|
|
1219
1271
|
* moves on to the next phase regardless. Because this is a **generator**,
|
|
1220
1272
|
* consuming only the first yielded value reproduces true fail-fast (later
|
|
@@ -2025,11 +2077,13 @@ async function* scanDriver(factory) {
|
|
|
2025
2077
|
*
|
|
2026
2078
|
* @remarks
|
|
2027
2079
|
* Consumes only the first value {@link scanDriver} yields: because that
|
|
2028
|
-
* generator is lazy, every
|
|
2080
|
+
* generator is lazy, every later phase never runs — true fail-fast, not
|
|
2029
2081
|
* merely "report only the first". The
|
|
2030
2082
|
* thrown error is byte-compatible with the historical shape: a
|
|
2031
2083
|
* `CONFORMANCE` {@link DatabaseError} whose `message` is the finding's
|
|
2032
|
-
* `message` and whose `context` is `{ check, ...finding.context }`.
|
|
2084
|
+
* `message` and whose `context` is `{ check, ...finding.context }`. The battery
|
|
2085
|
+
* takes a driver factory and reports through a throw, so it binds no test
|
|
2086
|
+
* framework and runs from any runner.
|
|
2033
2087
|
*
|
|
2034
2088
|
* @param factory - Mints a fresh, unopened driver instance (called once per phase)
|
|
2035
2089
|
* @returns Nothing — resolves once every phase has passed
|
|
@@ -2049,7 +2103,7 @@ async function conformDriver(factory) {
|
|
|
2049
2103
|
});
|
|
2050
2104
|
}
|
|
2051
2105
|
/**
|
|
2052
|
-
* Runs the
|
|
2106
|
+
* Runs the full driver-conformance battery and collects every violation — the
|
|
2053
2107
|
* audit entry point for a driver author who wants a complete report rather
|
|
2054
2108
|
* than a single fail-fast throw.
|
|
2055
2109
|
*
|
|
@@ -2719,10 +2773,10 @@ var Query = class {
|
|
|
2719
2773
|
* @remarks
|
|
2720
2774
|
* - **Observable.** The owned {@link emitter} ({@link TableEventMap}) carries the
|
|
2721
2775
|
* per-row mutation moments — `write` (set / add / update), `remove`, `clear` — for
|
|
2722
|
-
* fire-and-forget observers (cache invalidation, sync, an audit log),
|
|
2723
|
-
* database-level lifecycle. Events carry the affected
|
|
2776
|
+
* fire-and-forget observers (cache invalidation, sync, an audit log), alongside the
|
|
2777
|
+
* database-level lifecycle. Events carry the affected key only (no value payload, to
|
|
2724
2778
|
* keep fan-out lean); reads / queries / counts are not emitted. Every event is emitted
|
|
2725
|
-
* directly, strictly
|
|
2779
|
+
* directly, strictly after the driver write / delete / clear completes; the emitter
|
|
2726
2780
|
* isolates a listener throw and routes it to its `error` handler (the `error` option),
|
|
2727
2781
|
* so a buggy observer can never corrupt a write or perturb a transaction.
|
|
2728
2782
|
*/
|
|
@@ -2835,7 +2889,7 @@ var Table = class {
|
|
|
2835
2889
|
* conditions.
|
|
2836
2890
|
*
|
|
2837
2891
|
* @remarks
|
|
2838
|
-
* Unlike {@link count}, `aggregate` operates on
|
|
2892
|
+
* Unlike {@link count}, `aggregate` operates on stored rows without the
|
|
2839
2893
|
* contract guard {@link records} / {@link scan} apply — a non-conforming
|
|
2840
2894
|
* stored row still contributes to the computed aggregate when it matches
|
|
2841
2895
|
* the conditions, even though it would never appear in `records()`'s
|
|
@@ -3163,6 +3217,10 @@ var DatabaseTransaction = class {
|
|
|
3163
3217
|
* generator. Imported views register their physical schemas with the same
|
|
3164
3218
|
* internal context before opening begins, so every view observes one driver,
|
|
3165
3219
|
* merged schema, emitter, status, transaction boundary, and terminal close.
|
|
3220
|
+
*
|
|
3221
|
+
* The view owns the driver and its declared `tables`, connects that driver lazily on
|
|
3222
|
+
* first use, `import`s further tables and `export`s their portable definitions, and
|
|
3223
|
+
* runs `transaction` scopes over the shared context.
|
|
3166
3224
|
*/
|
|
3167
3225
|
var Database = class Database {
|
|
3168
3226
|
#context;
|
|
@@ -3285,7 +3343,7 @@ var Database = class Database {
|
|
|
3285
3343
|
* @remarks
|
|
3286
3344
|
* The in-between made concrete: it runs identically in a browser or on a server,
|
|
3287
3345
|
* so it is the storage behind tests, ephemeral caches, and any code that wants
|
|
3288
|
-
* the database API without a persistent backend. Rows are
|
|
3346
|
+
* the database API without a persistent backend. Rows are deep-copied (through
|
|
3289
3347
|
* `structuredClone`) in and out — at `write`, `read`, `scan`, `stream`, and both
|
|
3290
3348
|
* snapshot capture and restore — so a caller mutating a nested field of an input
|
|
3291
3349
|
* row, a returned row, or a row mutated in place between snapshot and rollback
|
|
@@ -3360,7 +3418,7 @@ var MemoryDriver = class {
|
|
|
3360
3418
|
* toward `offset` / `limit`. Both are applied lazily as matches are found —
|
|
3361
3419
|
* `offset` matches are skipped without being yielded, and iteration stops the
|
|
3362
3420
|
* instant `limit` yields have been produced, so a large table is never fully
|
|
3363
|
-
* walked for a small page. `input.order` is
|
|
3421
|
+
* walked for a small page. `input.order` is ignored (the same contract as
|
|
3364
3422
|
* `TableInterface.scan` and `QueryInterface.stream`): streaming yields key
|
|
3365
3423
|
* order, sorted output is `records()`'s job. Rows yield copy-out, and an
|
|
3366
3424
|
* unknown table mirrors `scan`'s empty-yield behavior.
|
|
@@ -3589,20 +3647,29 @@ var MemoryDriver = class {
|
|
|
3589
3647
|
* `name`, `generator`, `version`, and emitter hooks
|
|
3590
3648
|
* @returns A typed {@link DatabaseInterface}
|
|
3591
3649
|
*
|
|
3592
|
-
* @example
|
|
3650
|
+
* @example Create a database
|
|
3593
3651
|
* ```ts
|
|
3594
3652
|
* import { createDatabase, createMemoryDriver } from '@orkestrel/database'
|
|
3595
3653
|
* import { integerShape, stringShape } from '@orkestrel/contract'
|
|
3596
3654
|
*
|
|
3597
3655
|
* const db = createDatabase({
|
|
3598
|
-
* driver: createMemoryDriver(),
|
|
3656
|
+
* driver: createMemoryDriver(), // any DriverInterface — a persistent backend swaps in, same API
|
|
3599
3657
|
* tables: {
|
|
3600
|
-
* users: { id: stringShape(), age: integerShape() },
|
|
3658
|
+
* users: { id: stringShape(), name: stringShape(), age: integerShape() },
|
|
3601
3659
|
* posts: { slug: stringShape(), title: stringShape() },
|
|
3602
3660
|
* },
|
|
3603
|
-
* primary: { posts: 'slug' },
|
|
3661
|
+
* primary: { posts: 'slug' }, // non-`id` primary-key columns, per table
|
|
3604
3662
|
* })
|
|
3605
|
-
*
|
|
3663
|
+
*
|
|
3664
|
+
* const users = db.table('users') // hold the handle; TableInterface<{ id; name; age }>
|
|
3665
|
+
*
|
|
3666
|
+
* await users.set({ id: 'u1', name: 'Ada', age: 36 }) // coerced + validated through the contract
|
|
3667
|
+
* await users.get('u1') // typed { id; name; age } | undefined — narrowed, never `as`
|
|
3668
|
+
* await users
|
|
3669
|
+
* .query()
|
|
3670
|
+
* .condition({ column: 'age', operator: 'from', values: [18], connector: 'and' })
|
|
3671
|
+
* .order({ column: 'age', direction: 'descending' })
|
|
3672
|
+
* .collect() // typed rows
|
|
3606
3673
|
* ```
|
|
3607
3674
|
*/
|
|
3608
3675
|
function createDatabase(options) {
|