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