@orkestrel/database 0.0.12 → 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 +12 -8
- package/dist/src/browser/index.d.ts +95 -70
- package/dist/src/browser/index.js +115 -86
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +595 -410
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +712 -313
- package/dist/src/core/index.d.ts +712 -313
- package/dist/src/core/index.js +588 -409
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +236 -332
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +213 -223
- package/dist/src/server/index.d.ts +213 -223
- package/dist/src/server/index.js +230 -324
- package/dist/src/server/index.js.map +1 -1
- package/package.json +22 -18
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
|
-
*
|
|
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
|
-
*
|
|
14
|
+
* Sets the longest `LIKE` / `GLOB` pattern the wildcard matcher accepts, 1024 characters, before
|
|
15
|
+
* rejecting it.
|
|
15
16
|
*
|
|
16
17
|
* @remarks
|
|
17
|
-
* A
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* A `LIKE` / `GLOB` pattern is a caller-supplied operand, so
|
|
19
|
+
* `matchesLikePattern` / `matchesGlobPattern` run patterns this package cannot
|
|
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
|
|
@@ -24,10 +25,85 @@ var DEFAULT_PRIMARY = "id";
|
|
|
24
25
|
* `VALIDATION` {@link DatabaseError}; the cap is generous for any legitimate search.
|
|
25
26
|
*/
|
|
26
27
|
var MAX_PATTERN_LENGTH = 1024;
|
|
28
|
+
/**
|
|
29
|
+
* Describes the `users` table the driver-conformance battery opens — keyed by the default
|
|
30
|
+
* `id` primary column.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* `age` is optional and `meta` is a declared `json` column, so the battery's
|
|
34
|
+
* nested-round-trip phase is fair to a typed-column backend: a SQL driver
|
|
35
|
+
* persists only declared columns, while a schemaless backend ignores the
|
|
36
|
+
* declarations entirely.
|
|
37
|
+
*/
|
|
38
|
+
var CONFORMANCE_USERS_SCHEMA = Object.freeze({
|
|
39
|
+
name: "users",
|
|
40
|
+
primary: "id",
|
|
41
|
+
columns: Object.freeze([
|
|
42
|
+
{
|
|
43
|
+
name: "id",
|
|
44
|
+
storage: "text",
|
|
45
|
+
optional: false,
|
|
46
|
+
nullable: false
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "name",
|
|
50
|
+
storage: "text",
|
|
51
|
+
optional: false,
|
|
52
|
+
nullable: false
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "age",
|
|
56
|
+
storage: "integer",
|
|
57
|
+
optional: true,
|
|
58
|
+
nullable: false
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "meta",
|
|
62
|
+
storage: "json",
|
|
63
|
+
optional: true,
|
|
64
|
+
nullable: false
|
|
65
|
+
}
|
|
66
|
+
]),
|
|
67
|
+
indexes: Object.freeze([])
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Describes the `posts` table the driver-conformance battery opens — keyed by a non-`id`
|
|
71
|
+
* `slug` primary column.
|
|
72
|
+
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* Pairs with {@link CONFORMANCE_USERS_SCHEMA} so one battery exercises both
|
|
75
|
+
* primary-key shapes: the default `id` and an explicit override.
|
|
76
|
+
*/
|
|
77
|
+
var CONFORMANCE_POSTS_SCHEMA = Object.freeze({
|
|
78
|
+
name: "posts",
|
|
79
|
+
primary: "slug",
|
|
80
|
+
columns: Object.freeze([{
|
|
81
|
+
name: "slug",
|
|
82
|
+
storage: "text",
|
|
83
|
+
optional: false,
|
|
84
|
+
nullable: false
|
|
85
|
+
}, {
|
|
86
|
+
name: "title",
|
|
87
|
+
storage: "text",
|
|
88
|
+
optional: false,
|
|
89
|
+
nullable: false
|
|
90
|
+
}]),
|
|
91
|
+
indexes: Object.freeze([])
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* Holds the fixed `users` and `posts` schema every driver-conformance phase opens.
|
|
95
|
+
*
|
|
96
|
+
* @remarks
|
|
97
|
+
* Each phase mints a fresh driver and opens this exact schema, so a finding
|
|
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.
|
|
101
|
+
*/
|
|
102
|
+
var CONFORMANCE_SCHEMA = Object.freeze([CONFORMANCE_USERS_SCHEMA, CONFORMANCE_POSTS_SCHEMA]);
|
|
27
103
|
//#endregion
|
|
28
104
|
//#region src/core/errors.ts
|
|
29
105
|
/**
|
|
30
|
-
*
|
|
106
|
+
* Represents an error thrown by the database layer.
|
|
31
107
|
*
|
|
32
108
|
* @remarks
|
|
33
109
|
* Carries a {@link DatabaseErrorCode} and an optional `context` bag naming the
|
|
@@ -38,7 +114,7 @@ var MAX_PATTERN_LENGTH = 1024;
|
|
|
38
114
|
* `context`), an inapplicable {@link Migration} plan (`MIGRATION`), a
|
|
39
115
|
* driver that violates a {@link DriverInterface} invariant, thrown by the
|
|
40
116
|
* `conformDriver` helper (`CONFORMANCE`), and an unexpected infrastructure
|
|
41
|
-
* fault surfaced by a driver seam —
|
|
117
|
+
* fault surfaced by a driver seam — for example a filesystem failure while
|
|
42
118
|
* persisting (`DRIVER`) — as opposed to expected domain conditions, which
|
|
43
119
|
* keep their specific codes.
|
|
44
120
|
*/
|
|
@@ -53,10 +129,10 @@ var DatabaseError = class extends Error {
|
|
|
53
129
|
}
|
|
54
130
|
};
|
|
55
131
|
/**
|
|
56
|
-
*
|
|
132
|
+
* Narrows an unknown caught value to a {@link DatabaseError}.
|
|
57
133
|
*
|
|
58
134
|
* @param value - The value to test (typically a `catch` binding)
|
|
59
|
-
* @returns
|
|
135
|
+
* @returns True if `value` is a {@link DatabaseError}; false otherwise
|
|
60
136
|
*
|
|
61
137
|
* @example
|
|
62
138
|
* ```ts
|
|
@@ -73,43 +149,23 @@ function isDatabaseError(value) {
|
|
|
73
149
|
//#endregion
|
|
74
150
|
//#region src/core/validators.ts
|
|
75
151
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* @remarks
|
|
79
|
-
* A present `limit` or `offset` must be a finite nonnegative integer; zero is
|
|
80
|
-
* valid. Validation is deterministic (`limit` before `offset`). Non-finite
|
|
81
|
-
* values are rendered as strings in error context so JSON serialization cannot
|
|
82
|
-
* collapse `NaN` or infinity to `null`.
|
|
83
|
-
*
|
|
84
|
-
* @param input - The portable query whose paging fields to validate
|
|
85
|
-
* @throws {@link DatabaseError} `VALIDATION` when a paging field is invalid
|
|
86
|
-
*/
|
|
87
|
-
function validatePage(input) {
|
|
88
|
-
const limit = input?.limit;
|
|
89
|
-
if (limit !== void 0 && (!Number.isInteger(limit) || limit < 0)) throw new DatabaseError("VALIDATION", "Query limit must be a nonnegative integer", {
|
|
90
|
-
field: "limit",
|
|
91
|
-
value: Number.isFinite(limit) ? limit : String(limit)
|
|
92
|
-
});
|
|
93
|
-
const offset = input?.offset;
|
|
94
|
-
if (offset !== void 0 && (!Number.isInteger(offset) || offset < 0)) throw new DatabaseError("VALIDATION", "Query offset must be a nonnegative integer", {
|
|
95
|
-
field: "offset",
|
|
96
|
-
value: Number.isFinite(offset) ? offset : String(offset)
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Test whether a value is a usable database key.
|
|
152
|
+
* Checks whether a value is a usable database key.
|
|
101
153
|
*
|
|
102
154
|
* @param value - The value to test
|
|
103
|
-
* @returns
|
|
155
|
+
* @returns True if `value` is a string or a finite number; false otherwise
|
|
104
156
|
*/
|
|
105
157
|
function isKey(value) {
|
|
106
158
|
return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
107
159
|
}
|
|
108
160
|
/**
|
|
109
|
-
*
|
|
161
|
+
* Checks whether a value is a portable column schema.
|
|
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.
|
|
110
166
|
*
|
|
111
167
|
* @param value - The value to test
|
|
112
|
-
* @returns
|
|
168
|
+
* @returns True if `value` is a complete {@link ColumnSchema}; false otherwise
|
|
113
169
|
*/
|
|
114
170
|
function isColumnSchema(value) {
|
|
115
171
|
try {
|
|
@@ -121,10 +177,14 @@ function isColumnSchema(value) {
|
|
|
121
177
|
}
|
|
122
178
|
}
|
|
123
179
|
/**
|
|
124
|
-
*
|
|
180
|
+
* Checks whether a value is a portable table schema.
|
|
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.
|
|
125
185
|
*
|
|
126
186
|
* @param value - The value to test
|
|
127
|
-
* @returns
|
|
187
|
+
* @returns True if `value` is a complete {@link TableSchema}; false otherwise
|
|
128
188
|
*/
|
|
129
189
|
function isTableSchema(value) {
|
|
130
190
|
try {
|
|
@@ -140,10 +200,14 @@ function isTableSchema(value) {
|
|
|
140
200
|
}
|
|
141
201
|
}
|
|
142
202
|
/**
|
|
143
|
-
*
|
|
203
|
+
* Checks whether a value is a complete portable driver schema.
|
|
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.
|
|
144
208
|
*
|
|
145
209
|
* @param value - The value to test
|
|
146
|
-
* @returns
|
|
210
|
+
* @returns True if `value` is a table-schema collection with unique table names; false otherwise
|
|
147
211
|
*/
|
|
148
212
|
function isDriverSchema(value) {
|
|
149
213
|
try {
|
|
@@ -156,10 +220,14 @@ function isDriverSchema(value) {
|
|
|
156
220
|
}
|
|
157
221
|
}
|
|
158
222
|
/**
|
|
159
|
-
*
|
|
223
|
+
* Checks whether a value is one ordered migration step.
|
|
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.
|
|
160
228
|
*
|
|
161
229
|
* @param value - The value to test
|
|
162
|
-
* @returns
|
|
230
|
+
* @returns True if `value` is a complete {@link MigrationStep}; false otherwise
|
|
163
231
|
*/
|
|
164
232
|
function isMigrationStep(value) {
|
|
165
233
|
try {
|
|
@@ -180,10 +248,14 @@ function isMigrationStep(value) {
|
|
|
180
248
|
}
|
|
181
249
|
}
|
|
182
250
|
/**
|
|
183
|
-
*
|
|
251
|
+
* Checks whether a value is an ordered migration plan.
|
|
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.
|
|
184
256
|
*
|
|
185
257
|
* @param value - The value to test
|
|
186
|
-
* @returns
|
|
258
|
+
* @returns True if `value` is a complete {@link Migration}; false otherwise
|
|
187
259
|
*/
|
|
188
260
|
function isMigration(value) {
|
|
189
261
|
try {
|
|
@@ -195,10 +267,16 @@ function isMigration(value) {
|
|
|
195
267
|
}
|
|
196
268
|
}
|
|
197
269
|
/**
|
|
198
|
-
*
|
|
270
|
+
* Checks whether a value is persisted driver metadata.
|
|
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.
|
|
199
277
|
*
|
|
200
278
|
* @param value - The value to test
|
|
201
|
-
* @returns
|
|
279
|
+
* @returns True if `value` is complete {@link DriverMetadata}; false otherwise
|
|
202
280
|
*/
|
|
203
281
|
function isDriverMetadata(value) {
|
|
204
282
|
try {
|
|
@@ -210,10 +288,14 @@ function isDriverMetadata(value) {
|
|
|
210
288
|
}
|
|
211
289
|
}
|
|
212
290
|
/**
|
|
213
|
-
*
|
|
291
|
+
* Checks whether a value is one atomic migration request.
|
|
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.
|
|
214
296
|
*
|
|
215
297
|
* @param value - The value to test
|
|
216
|
-
* @returns
|
|
298
|
+
* @returns True if `value` is a complete {@link MigrationInput}; false otherwise
|
|
217
299
|
*/
|
|
218
300
|
function isMigrationInput(value) {
|
|
219
301
|
try {
|
|
@@ -227,7 +309,12 @@ function isMigrationInput(value) {
|
|
|
227
309
|
//#endregion
|
|
228
310
|
//#region src/core/cloners.ts
|
|
229
311
|
/**
|
|
230
|
-
*
|
|
312
|
+
* Clones unknown driver metadata into a distinct deeply frozen snapshot.
|
|
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.
|
|
231
318
|
*
|
|
232
319
|
* @param value - Unknown metadata
|
|
233
320
|
* @returns Owned driver metadata
|
|
@@ -246,7 +333,12 @@ function cloneDriverMetadata(value) {
|
|
|
246
333
|
}
|
|
247
334
|
}
|
|
248
335
|
/**
|
|
249
|
-
*
|
|
336
|
+
* Clones unknown driver schema into a distinct deeply frozen snapshot.
|
|
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.
|
|
250
342
|
*
|
|
251
343
|
* @param value - Unknown table schema collection
|
|
252
344
|
* @returns Owned driver schema
|
|
@@ -265,7 +357,12 @@ function cloneDriverSchema(value) {
|
|
|
265
357
|
}
|
|
266
358
|
}
|
|
267
359
|
/**
|
|
268
|
-
*
|
|
360
|
+
* Clones unknown migration input into a distinct deeply frozen snapshot.
|
|
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.
|
|
269
366
|
*
|
|
270
367
|
* @param value - Unknown migration input
|
|
271
368
|
* @returns Owned migration input
|
|
@@ -286,8 +383,32 @@ function cloneMigrationInput(value) {
|
|
|
286
383
|
//#endregion
|
|
287
384
|
//#region src/core/helpers.ts
|
|
288
385
|
/**
|
|
289
|
-
*
|
|
290
|
-
*
|
|
386
|
+
* Validates the paging fields of a portable query.
|
|
387
|
+
*
|
|
388
|
+
* @remarks
|
|
389
|
+
* A present `limit` or `offset` must be a finite nonnegative integer; zero is
|
|
390
|
+
* valid. Validation is deterministic (`limit` before `offset`). Non-finite
|
|
391
|
+
* values are rendered as strings in error context so JSON serialization cannot
|
|
392
|
+
* collapse `NaN` or infinity to `null`.
|
|
393
|
+
*
|
|
394
|
+
* @param input - The portable query whose paging fields to validate
|
|
395
|
+
* @throws {@link DatabaseError} `VALIDATION` when a paging field is invalid
|
|
396
|
+
*/
|
|
397
|
+
function validatePage(input) {
|
|
398
|
+
const limit = input?.limit;
|
|
399
|
+
if (limit !== void 0 && (!Number.isInteger(limit) || limit < 0)) throw new DatabaseError("VALIDATION", "Query limit must be a nonnegative integer", {
|
|
400
|
+
field: "limit",
|
|
401
|
+
value: Number.isFinite(limit) ? limit : String(limit)
|
|
402
|
+
});
|
|
403
|
+
const offset = input?.offset;
|
|
404
|
+
if (offset !== void 0 && (!Number.isInteger(offset) || offset < 0)) throw new DatabaseError("VALIDATION", "Query offset must be a nonnegative integer", {
|
|
405
|
+
field: "offset",
|
|
406
|
+
value: Number.isFinite(offset) ? offset : String(offset)
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Compares two arbitrary values under one total order — the comparator behind
|
|
411
|
+
* sorting and the range operators.
|
|
291
412
|
*
|
|
292
413
|
* @remarks
|
|
293
414
|
* Values of different types order by a fixed type rank (`undefined` < `null` <
|
|
@@ -311,15 +432,16 @@ function compareValues(left, right) {
|
|
|
311
432
|
return 0;
|
|
312
433
|
}
|
|
313
434
|
/**
|
|
314
|
-
*
|
|
315
|
-
* checks and any test/fixture that needs "same data", not
|
|
435
|
+
* Compares two values structurally by SameValueZero leaves — the comparator
|
|
436
|
+
* behind conformance checks and any test/fixture that needs "same data", not
|
|
437
|
+
* "same reference".
|
|
316
438
|
*
|
|
317
439
|
* @remarks
|
|
318
440
|
* Primitives compare by SameValueZero (`NaN` equals itself; `+0` equals `-0`).
|
|
319
441
|
* Arrays compare by index (same length, every element `equalsValue`). Plain
|
|
320
|
-
* records (
|
|
321
|
-
*
|
|
322
|
-
* 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
|
|
323
445
|
* equal to that key being absent (both differ in `Object.keys` membership).
|
|
324
446
|
* Anything else (functions, class instances, mismatched shapes) falls through
|
|
325
447
|
* to `false`. Container pairs are tracked iteratively, so self-referential and
|
|
@@ -328,7 +450,7 @@ function compareValues(left, right) {
|
|
|
328
450
|
*
|
|
329
451
|
* @param left - The left value
|
|
330
452
|
* @param right - The right value
|
|
331
|
-
* @returns
|
|
453
|
+
* @returns True if `left` and `right` are structurally equal; false otherwise
|
|
332
454
|
*
|
|
333
455
|
* @example
|
|
334
456
|
* ```ts
|
|
@@ -385,47 +507,16 @@ function equalsValue(left, right) {
|
|
|
385
507
|
}
|
|
386
508
|
}
|
|
387
509
|
/**
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
* @remarks
|
|
391
|
-
* Every query character must appear in order in the value, but the characters
|
|
392
|
-
* do not need to be contiguous. Query characters are literal, including
|
|
393
|
-
* whitespace. Matching applies JavaScript `toLowerCase()` to both inputs
|
|
394
|
-
* without locale-specific folding or Unicode normalization. An empty query
|
|
395
|
-
* matches every value.
|
|
396
|
-
*
|
|
397
|
-
* @param value - The text searched for the query's characters
|
|
398
|
-
* @param query - The characters that must all appear in order
|
|
399
|
-
* @returns Whether the case-folded query is a subsequence of the case-folded value
|
|
400
|
-
*
|
|
401
|
-
* @example
|
|
402
|
-
* ```ts
|
|
403
|
-
* matchesFuzzy('Database', 'dbe') // true
|
|
404
|
-
* matchesFuzzy('Database', 'abd') // false
|
|
405
|
-
* ```
|
|
406
|
-
*/
|
|
407
|
-
function matchesFuzzy(value, query) {
|
|
408
|
-
const folded = value.toLowerCase();
|
|
409
|
-
const wanted = query.toLowerCase();
|
|
410
|
-
let cursor = 0;
|
|
411
|
-
for (const char of wanted) {
|
|
412
|
-
const found = folded.indexOf(char, cursor);
|
|
413
|
-
if (found === -1) return false;
|
|
414
|
-
cursor = found + 1;
|
|
415
|
-
}
|
|
416
|
-
return true;
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* Match a value against a wildcard pattern in LINEAR time — the shared, ReDoS-SAFE
|
|
510
|
+
* Matches a value against a wildcard pattern in linear time — the shared, ReDoS-safe
|
|
420
511
|
* engine behind {@link matchesLikePattern} and {@link matchesGlobPattern}.
|
|
421
512
|
*
|
|
422
513
|
* @remarks
|
|
423
|
-
* 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:
|
|
424
515
|
* `.*` segments separated by literals, matched against a long non-matching input, blow
|
|
425
|
-
* up super-linearly — and JS has no atomic groups / possessive quantifiers to bound it
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
* the `any` wildcard records its position and, on a later mismatch, backtracks
|
|
516
|
+
* up super-linearly — and JS has no atomic groups / possessive quantifiers to bound it,
|
|
517
|
+
* while a `LIKE` / `GLOB` pattern is a caller-supplied operand this package cannot
|
|
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
|
|
429
520
|
* that last `any` (letting it absorb one more char) — so the work is O(value × pattern),
|
|
430
521
|
* never the exponential / polynomial backtracking a regex would do. The pattern length
|
|
431
522
|
* is capped at {@link MAX_PATTERN_LENGTH} (a `VALIDATION` {@link DatabaseError} over it),
|
|
@@ -433,17 +524,17 @@ function matchesFuzzy(value, query) {
|
|
|
433
524
|
* pattern.
|
|
434
525
|
*
|
|
435
526
|
* The `any` wildcard matches any run (including empty); `single` matches exactly one
|
|
436
|
-
* char; every other pattern char matches itself
|
|
527
|
+
* char; every other pattern char matches itself literally (a pattern `.` / `(` / `\` is
|
|
437
528
|
* a literal — the regex-metacharacter hazard is gone with the regex). `any` is tested
|
|
438
|
-
*
|
|
439
|
-
* 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.
|
|
440
531
|
*
|
|
441
532
|
* @param value - The value to test
|
|
442
533
|
* @param pattern - The wildcard pattern
|
|
443
534
|
* @param any - The any-run wildcard char (`%` for `LIKE`, `*` for `GLOB`)
|
|
444
535
|
* @param single - The single-char wildcard char (`_` for `LIKE`, `?` for `GLOB`)
|
|
445
|
-
* @param fold - Whether to match case-
|
|
446
|
-
* @returns
|
|
536
|
+
* @param fold - Whether to match case-insensitively (`LIKE` folds; `GLOB` does not)
|
|
537
|
+
* @returns True if `value` matches `pattern`; false otherwise
|
|
447
538
|
* @throws A `VALIDATION` {@link DatabaseError} when `pattern` exceeds {@link MAX_PATTERN_LENGTH}
|
|
448
539
|
*/
|
|
449
540
|
function matchesWildcardPattern(value, pattern, any, single, fold) {
|
|
@@ -475,32 +566,73 @@ function matchesWildcardPattern(value, pattern, any, single, fold) {
|
|
|
475
566
|
while (pi < needle.length && needle[pi] === any) pi += 1;
|
|
476
567
|
return pi === needle.length;
|
|
477
568
|
}
|
|
569
|
+
/**
|
|
570
|
+
* Matches a value against a SQL `LIKE` pattern, folding case.
|
|
571
|
+
*
|
|
572
|
+
* @remarks
|
|
573
|
+
* `%` matches any run of characters (including none) and `_` matches exactly one
|
|
574
|
+
* character; every other pattern character matches itself literally. Runs on
|
|
575
|
+
* {@link matchesWildcardPattern}, so the match is linear in the value length and
|
|
576
|
+
* the pattern is capped at {@link MAX_PATTERN_LENGTH}.
|
|
577
|
+
*
|
|
578
|
+
* @param value - The value to test
|
|
579
|
+
* @param pattern - The `LIKE` pattern
|
|
580
|
+
* @returns True if `value` matches `pattern` under case folding; false otherwise
|
|
581
|
+
* @throws A `VALIDATION` {@link DatabaseError} when `pattern` exceeds {@link MAX_PATTERN_LENGTH}
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* ```ts
|
|
585
|
+
* matchesLikePattern('Hello', 'h%o') // true — `%` spans any run, and case folds
|
|
586
|
+
* matchesLikePattern('Hello', 'h_llo') // true — `_` matches exactly one character
|
|
587
|
+
* ```
|
|
588
|
+
*/
|
|
478
589
|
function matchesLikePattern(value, pattern) {
|
|
479
590
|
return matchesWildcardPattern(value, pattern, "%", "_", true);
|
|
480
591
|
}
|
|
592
|
+
/**
|
|
593
|
+
* Matches a value against a `GLOB` pattern, preserving case.
|
|
594
|
+
*
|
|
595
|
+
* @remarks
|
|
596
|
+
* `*` matches any run of characters (including none) and `?` matches exactly one
|
|
597
|
+
* character; every other pattern character matches itself literally, so a
|
|
598
|
+
* character class such as `[a-z]` is not interpreted. Runs on
|
|
599
|
+
* {@link matchesWildcardPattern}, so the match is linear in the value length and
|
|
600
|
+
* the pattern is capped at {@link MAX_PATTERN_LENGTH}.
|
|
601
|
+
*
|
|
602
|
+
* @param value - The value to test
|
|
603
|
+
* @param pattern - The `GLOB` pattern
|
|
604
|
+
* @returns True if `value` matches `pattern` case-sensitively; false otherwise
|
|
605
|
+
* @throws A `VALIDATION` {@link DatabaseError} when `pattern` exceeds {@link MAX_PATTERN_LENGTH}
|
|
606
|
+
*
|
|
607
|
+
* @example
|
|
608
|
+
* ```ts
|
|
609
|
+
* matchesGlobPattern('hello', 'h*o') // true — `*` spans any run
|
|
610
|
+
* matchesGlobPattern('Hello', 'h*o') // false — `GLOB` is case-sensitive
|
|
611
|
+
* ```
|
|
612
|
+
*/
|
|
481
613
|
function matchesGlobPattern(value, pattern) {
|
|
482
614
|
return matchesWildcardPattern(value, pattern, "*", "?", false);
|
|
483
615
|
}
|
|
484
616
|
/**
|
|
485
|
-
*
|
|
617
|
+
* Evaluates one {@link Condition} against a row — the per-operator predicate.
|
|
486
618
|
*
|
|
487
619
|
* @remarks
|
|
488
620
|
* Reads the condition's column — a `FieldPath`, resolved with `resolveField` (a
|
|
489
621
|
* string is one column; an array descends a nested value) — and applies the
|
|
490
622
|
* operator. Range operators (`above` / `below` / `from` / `to` / `between`) use
|
|
491
623
|
* {@link compareValues}, the total order; the equality family (`equals` / `not`
|
|
492
|
-
* / `any` / `none`) uses {@link equalsValue} —
|
|
624
|
+
* / `any` / `none`) uses {@link equalsValue} — structural equality, not the total
|
|
493
625
|
* order's rank-5-collapses-all-objects behavior, so `equals` on an object/array
|
|
494
626
|
* operand only matches a structurally-equal value, never every row holding any
|
|
495
627
|
* object. This is a semantics change from ranking: `equalsValue` is SameValueZero
|
|
496
628
|
* on leaves, so `NaN` now equals `NaN` under `equals` / `any` (it never matched
|
|
497
629
|
* anything under the old rank-based comparison). `like` / `glob` / `starts` /
|
|
498
630
|
* `ends` match only strings; `absent` / `present` test nullishness. Total — a
|
|
499
|
-
* type mismatch is
|
|
631
|
+
* type mismatch is a non-match.
|
|
500
632
|
*
|
|
501
633
|
* @param row - The row to test
|
|
502
634
|
* @param condition - The condition to apply
|
|
503
|
-
* @returns
|
|
635
|
+
* @returns True if the row satisfies the condition; false otherwise
|
|
504
636
|
*/
|
|
505
637
|
function matchesCondition(row, condition) {
|
|
506
638
|
const value = (0, _orkestrel_contract.resolveField)(row, condition.column);
|
|
@@ -525,7 +657,7 @@ function matchesCondition(row, condition) {
|
|
|
525
657
|
}
|
|
526
658
|
}
|
|
527
659
|
/**
|
|
528
|
-
*
|
|
660
|
+
* Folds a row through a list of conditions, joining each by its connector.
|
|
529
661
|
*
|
|
530
662
|
* @remarks
|
|
531
663
|
* Evaluated left-to-right: the first condition seeds the result, and each later
|
|
@@ -535,7 +667,7 @@ function matchesCondition(row, condition) {
|
|
|
535
667
|
*
|
|
536
668
|
* @param row - The row to test
|
|
537
669
|
* @param conditions - The conditions to fold
|
|
538
|
-
* @returns
|
|
670
|
+
* @returns True if the row satisfies the combined conditions; false otherwise
|
|
539
671
|
*/
|
|
540
672
|
function matchesQuery(row, conditions) {
|
|
541
673
|
let result = true;
|
|
@@ -550,7 +682,7 @@ function matchesQuery(row, conditions) {
|
|
|
550
682
|
return result;
|
|
551
683
|
}
|
|
552
684
|
/**
|
|
553
|
-
*
|
|
685
|
+
* Filters rows by a list of conditions — the shared basis for a table's count
|
|
554
686
|
* and aggregate paths (no sort/page, unlike {@link applyQuery}).
|
|
555
687
|
*
|
|
556
688
|
* @remarks
|
|
@@ -574,7 +706,7 @@ function filterRows(rows, conditions) {
|
|
|
574
706
|
return rows.filter((row) => matchesQuery(row, conditions));
|
|
575
707
|
}
|
|
576
708
|
/**
|
|
577
|
-
*
|
|
709
|
+
* Sorts rows by an ordering specification, leaving the input untouched.
|
|
578
710
|
*
|
|
579
711
|
* @remarks
|
|
580
712
|
* Applies the terms in priority order — the first term that distinguishes two
|
|
@@ -596,7 +728,7 @@ function sortRows(rows, order) {
|
|
|
596
728
|
return sorted;
|
|
597
729
|
}
|
|
598
730
|
/**
|
|
599
|
-
*
|
|
731
|
+
* Applies a {@link QueryInput} to rows — filter, then sort, then page.
|
|
600
732
|
*
|
|
601
733
|
* @remarks
|
|
602
734
|
* The whole portable read pipeline in one place: conditions filter, `order`
|
|
@@ -621,7 +753,7 @@ function applyQuery(rows, input) {
|
|
|
621
753
|
return result;
|
|
622
754
|
}
|
|
623
755
|
/**
|
|
624
|
-
*
|
|
756
|
+
* Computes an aggregate over a column across rows.
|
|
625
757
|
*
|
|
626
758
|
* @remarks
|
|
627
759
|
* `count` returns the row count. The numeric aggregates coerce each cell with
|
|
@@ -647,10 +779,12 @@ function computeAggregate(rows, operation, column) {
|
|
|
647
779
|
const total = numbers.reduce((sum, value) => sum + value, 0);
|
|
648
780
|
return operation === "average" ? total / numbers.length : total;
|
|
649
781
|
}
|
|
650
|
-
|
|
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;
|
|
651
785
|
}
|
|
652
786
|
/**
|
|
653
|
-
*
|
|
787
|
+
* Reads a row's primary key from a column, when it is a usable {@link Key}.
|
|
654
788
|
*
|
|
655
789
|
* @param row - The row to read
|
|
656
790
|
* @param column - The primary-key column name
|
|
@@ -661,7 +795,7 @@ function extractKey(row, column) {
|
|
|
661
795
|
return isKey(value) ? value : void 0;
|
|
662
796
|
}
|
|
663
797
|
/**
|
|
664
|
-
*
|
|
798
|
+
* Returns a fresh row whose primary column is authoritatively bound to its storage key.
|
|
665
799
|
*
|
|
666
800
|
* @param row - The caller row
|
|
667
801
|
* @param primary - The primary column
|
|
@@ -675,7 +809,7 @@ function bindRowKey(row, primary, key) {
|
|
|
675
809
|
};
|
|
676
810
|
}
|
|
677
811
|
/**
|
|
678
|
-
*
|
|
812
|
+
* Maps a column's {@link ContractShape} to its portable {@link ColumnStorage} — the
|
|
679
813
|
* value a `TableSchema` carries so a native backend can declare a real column.
|
|
680
814
|
*
|
|
681
815
|
* @remarks
|
|
@@ -699,7 +833,7 @@ function bindRowKey(row, primary, key) {
|
|
|
699
833
|
* ```
|
|
700
834
|
*/
|
|
701
835
|
function shapeToColumnStorage(shape) {
|
|
702
|
-
switch (shape.
|
|
836
|
+
switch (shape.category) {
|
|
703
837
|
case "string": return "text";
|
|
704
838
|
case "number": return shape.integer === true ? "integer" : "real";
|
|
705
839
|
case "boolean": return "boolean";
|
|
@@ -718,7 +852,7 @@ function shapeToColumnStorage(shape) {
|
|
|
718
852
|
}
|
|
719
853
|
}
|
|
720
854
|
/**
|
|
721
|
-
*
|
|
855
|
+
* Projects one contract shape into a portable column schema.
|
|
722
856
|
*
|
|
723
857
|
* @param name - The column name
|
|
724
858
|
* @param shape - The column contract shape
|
|
@@ -734,7 +868,55 @@ function shapeToColumnSchema(name, shape) {
|
|
|
734
868
|
};
|
|
735
869
|
}
|
|
736
870
|
/**
|
|
737
|
-
*
|
|
871
|
+
* Reads one flat column's declaration out of a table schema.
|
|
872
|
+
*
|
|
873
|
+
* @remarks
|
|
874
|
+
* The single lookup behind every declared-column question — storage type,
|
|
875
|
+
* optionality, and nullability all come off the returned {@link ColumnSchema},
|
|
876
|
+
* so a caller that needs more than one of them reads them from one result. A
|
|
877
|
+
* nested {@link FieldPath} names no declared column, so resolve the path's head
|
|
878
|
+
* before calling. A schema that does not declare the column returns `undefined`.
|
|
879
|
+
*
|
|
880
|
+
* @param name - The flat column name
|
|
881
|
+
* @param schema - The table's schema
|
|
882
|
+
* @returns The column's {@link ColumnSchema}, or `undefined` when the schema does not declare it
|
|
883
|
+
*
|
|
884
|
+
* @example
|
|
885
|
+
* ```ts
|
|
886
|
+
* findColumn('age', schema)?.storage // 'integer'
|
|
887
|
+
* findColumn('absent', schema) // undefined
|
|
888
|
+
* ```
|
|
889
|
+
*/
|
|
890
|
+
function findColumn(name, schema) {
|
|
891
|
+
return schema.columns.find((candidate) => candidate.name === name);
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Resolves the primary-key column one table keys its rows by.
|
|
895
|
+
*
|
|
896
|
+
* @remarks
|
|
897
|
+
* A table absent from the {@link PrimaryMap} keys its rows by
|
|
898
|
+
* {@link DEFAULT_PRIMARY}, so this is total over any table name.
|
|
899
|
+
*
|
|
900
|
+
* @param primary - The per-table primary-key overrides
|
|
901
|
+
* @param name - The table name
|
|
902
|
+
* @returns The table's primary-key column
|
|
903
|
+
*
|
|
904
|
+
* @example
|
|
905
|
+
* ```ts
|
|
906
|
+
* resolvePrimary({ posts: 'slug' }, 'posts') // 'slug'
|
|
907
|
+
* resolvePrimary({ posts: 'slug' }, 'users') // 'id' — the default primary
|
|
908
|
+
* ```
|
|
909
|
+
*/
|
|
910
|
+
function resolvePrimary(primary, name) {
|
|
911
|
+
return primary[name] ?? "id";
|
|
912
|
+
}
|
|
913
|
+
function requireColumns(tables, name) {
|
|
914
|
+
const columns = tables[name];
|
|
915
|
+
if (columns === void 0) throw new DatabaseError("NOT_FOUND", `Table '${name}' is not declared`, { table: name });
|
|
916
|
+
return columns;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Throws when an {@link OperationOptions.signal | AbortSignal} has fired — the shared
|
|
738
920
|
* abort gate checked at operation boundaries and between streamed rows.
|
|
739
921
|
*
|
|
740
922
|
* @remarks
|
|
@@ -762,7 +944,7 @@ function checkAbort(signal) {
|
|
|
762
944
|
if (signal?.aborted) throw new DatabaseError("ABORTED", "Operation aborted", { reason: signal.reason });
|
|
763
945
|
}
|
|
764
946
|
/**
|
|
765
|
-
*
|
|
947
|
+
* Diffs a deployed and a declared table set structurally into a {@link Migration}
|
|
766
948
|
* plan.
|
|
767
949
|
*
|
|
768
950
|
* @remarks
|
|
@@ -777,17 +959,16 @@ function checkAbort(signal) {
|
|
|
777
959
|
* plan labels only; versioning drivers persist and reconcile them through
|
|
778
960
|
* {@link DriverMetadata}.
|
|
779
961
|
*
|
|
780
|
-
* A column present in
|
|
962
|
+
* A column present in both schemas under the same name but with a different
|
|
781
963
|
* `storage`, `optional`, or `nullable` value throws a `MIGRATION`
|
|
782
|
-
* {@link DatabaseError} naming the
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
*
|
|
790
|
-
* @param deployed - The table schemas currently applied
|
|
964
|
+
* {@link DatabaseError} naming the table, the column, and the from→to
|
|
965
|
+
* difference — a name-only diff would otherwise silently produce no step for
|
|
966
|
+
* the drift, and versioned reconciliation would stamp over it. There is no
|
|
967
|
+
* automatic in-place type-change step: the manual path is to add a new column,
|
|
968
|
+
* copy/convert the data at the application layer, then remove the old column —
|
|
969
|
+
* two separate plans, never a single implicit "alter" step.
|
|
970
|
+
*
|
|
971
|
+
* @param deployed - The already-applied table schemas
|
|
791
972
|
* @param declared - The table schemas the caller wants applied
|
|
792
973
|
* @param from - The plan's source version label (defaults to `0`)
|
|
793
974
|
* @param to - The plan's target version label (defaults to `1`)
|
|
@@ -893,7 +1074,9 @@ function planMigration(deployed, declared, from = 0, to = 1) {
|
|
|
893
1074
|
} }).plan;
|
|
894
1075
|
}
|
|
895
1076
|
/**
|
|
896
|
-
*
|
|
1077
|
+
* Projects migration steps sequentially over a canonical validated owned schema.
|
|
1078
|
+
*
|
|
1079
|
+
* @remarks
|
|
897
1080
|
* Adding a required non-null column to an existing table rejects with
|
|
898
1081
|
* `MIGRATION`; optional-only and nullable-only additions remain portable.
|
|
899
1082
|
*
|
|
@@ -992,7 +1175,7 @@ function projectMigrationSchema(schema, steps) {
|
|
|
992
1175
|
}
|
|
993
1176
|
}
|
|
994
1177
|
/**
|
|
995
|
-
*
|
|
1178
|
+
* Canonicalizes an unknown driver schema into a distinct deeply frozen snapshot.
|
|
996
1179
|
*
|
|
997
1180
|
* @remarks
|
|
998
1181
|
* Table and column lists are sorted by name. The index list is sorted by the
|
|
@@ -1014,11 +1197,11 @@ function normalizeDriverSchema(value) {
|
|
|
1014
1197
|
return cloneDriverSchema(tables);
|
|
1015
1198
|
}
|
|
1016
1199
|
/**
|
|
1017
|
-
*
|
|
1200
|
+
* Applies one table's {@link MigrationStep}s to its rows — a pure row transform.
|
|
1018
1201
|
*
|
|
1019
1202
|
* @remarks
|
|
1020
1203
|
* `column.remove` drops that field from every row (a fresh copy — inputs are
|
|
1021
|
-
* never mutated
|
|
1204
|
+
* never mutated); `column.add` leaves rows as-is (an absent field
|
|
1022
1205
|
* reads as `undefined`, backfill is application policy). `table.add` /
|
|
1023
1206
|
* `table.remove` / `index.add` / `index.remove` are no-ops here (they operate
|
|
1024
1207
|
* on storage shape, not row shape). Steps for tables other than the one
|
|
@@ -1045,7 +1228,7 @@ function migrateRows(rows, steps) {
|
|
|
1045
1228
|
});
|
|
1046
1229
|
}
|
|
1047
1230
|
/**
|
|
1048
|
-
*
|
|
1231
|
+
* Walks the driver-conformance battery against a fresh {@link DriverInterface}
|
|
1049
1232
|
* per phase, yielding one {@link ConformanceFinding} per violated invariant —
|
|
1050
1233
|
* the shared invariant suite every backend (in-memory, SQLite, IndexedDB)
|
|
1051
1234
|
* must uphold to be a drop-in {@link DriverInterface}.
|
|
@@ -1056,34 +1239,34 @@ function migrateRows(rows, steps) {
|
|
|
1056
1239
|
* driver's own README. Opens a fixed two-table schema (`users` keyed by the
|
|
1057
1240
|
* default `id`, `posts` keyed by a non-id `slug`) and, calling `factory()`
|
|
1058
1241
|
* fresh for each phase so failures stay isolated, verifies: `open`/`close`;
|
|
1059
|
-
* `read` of a missing key returns `undefined`; `write`/`read` round-trip
|
|
1060
|
-
*
|
|
1061
|
-
*
|
|
1062
|
-
* stored state) and upsert-overwrite; simultaneous same-key
|
|
1063
|
-
* produce exactly one commit and one `CONFLICT`; pre-aborted
|
|
1064
|
-
* `insert`, and `delete` calls leave storage unchanged; `delete`
|
|
1065
|
-
* `true` then `false`;
|
|
1066
|
-
* `
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
1075
|
-
* `
|
|
1076
|
-
*
|
|
1077
|
-
*
|
|
1078
|
-
*
|
|
1242
|
+
* `read` of a missing key returns `undefined`; `write`/`read` round-trip
|
|
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
|
|
1245
|
+
* perturbs stored state) and upsert-overwrite; simultaneous same-key
|
|
1246
|
+
* `insert` calls produce exactly one commit and one `CONFLICT`; pre-aborted
|
|
1247
|
+
* `write`, `insert`, and `delete` calls leave storage unchanged; `delete`
|
|
1248
|
+
* returns `true` then `false`; `keys`/`scan` yield in ascending key order;
|
|
1249
|
+
* `clear` empties only its target table; `snapshot`'s rollback thunk
|
|
1250
|
+
* restores pre-snapshot state, including a nested field mutated in place on
|
|
1251
|
+
* a read-back row between capture and restore; a scoped
|
|
1252
|
+
* `snapshot(['users'])` rolls back only the named table, leaving a
|
|
1253
|
+
* concurrent mutation to another table intact; a non-`id` primary key
|
|
1254
|
+
* (`posts.slug`) round-trips; a nested-object row round-trips structurally
|
|
1255
|
+
* (through {@link equalsValue}). The optional surface is presence-gated: when
|
|
1256
|
+
* `migrate` exists, a `column.remove` plan strips the column from stored
|
|
1257
|
+
* rows and a plan referencing an unknown table throws `DatabaseError`
|
|
1258
|
+
* `MIGRATION`; when `stream` exists, it yields only condition-matching rows
|
|
1259
|
+
* and honors `offset`/`limit`; when `transaction` exists, `commit` persists
|
|
1260
|
+
* and `rollback` restores; when both `metadata` and `stamp` exist, a fresh
|
|
1261
|
+
* store's `metadata()` is `undefined`, and after
|
|
1079
1262
|
* `stamp({ version, schema })`, `metadata()` returns the exact stamped value.
|
|
1080
1263
|
*
|
|
1081
|
-
* Each phase runs within a `try`/`catch`: an
|
|
1082
|
-
* 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
|
|
1083
1266
|
* crash mid-phase) is caught and yielded as a finding too, naming the phase
|
|
1084
1267
|
* as `check` and carrying the caught error in `context.error` — a broken
|
|
1085
1268
|
* driver can never escape the battery as an unhandled rejection. Within a
|
|
1086
|
-
* phase, the
|
|
1269
|
+
* phase, the first violated assertion yields and the phase stops (matching
|
|
1087
1270
|
* the historical fail-fast shape at phase granularity); the generator then
|
|
1088
1271
|
* moves on to the next phase regardless. Because this is a **generator**,
|
|
1089
1272
|
* consuming only the first yielded value reproduces true fail-fast (later
|
|
@@ -1094,62 +1277,14 @@ function migrateRows(rows, steps) {
|
|
|
1094
1277
|
*
|
|
1095
1278
|
* @example
|
|
1096
1279
|
* ```ts
|
|
1097
|
-
* import { createMemoryDriver,
|
|
1280
|
+
* import { createMemoryDriver, scanDriver } from '@orkestrel/database'
|
|
1098
1281
|
*
|
|
1099
|
-
* for await (const finding of
|
|
1282
|
+
* for await (const finding of scanDriver(() => createMemoryDriver())) {
|
|
1100
1283
|
* console.log(finding.check, finding.message)
|
|
1101
1284
|
* }
|
|
1102
1285
|
* ```
|
|
1103
1286
|
*/
|
|
1104
|
-
async function*
|
|
1105
|
-
const CONFORMANCE_USERS_SCHEMA = {
|
|
1106
|
-
name: "users",
|
|
1107
|
-
primary: "id",
|
|
1108
|
-
columns: [
|
|
1109
|
-
{
|
|
1110
|
-
name: "id",
|
|
1111
|
-
storage: "text",
|
|
1112
|
-
optional: false,
|
|
1113
|
-
nullable: false
|
|
1114
|
-
},
|
|
1115
|
-
{
|
|
1116
|
-
name: "name",
|
|
1117
|
-
storage: "text",
|
|
1118
|
-
optional: false,
|
|
1119
|
-
nullable: false
|
|
1120
|
-
},
|
|
1121
|
-
{
|
|
1122
|
-
name: "age",
|
|
1123
|
-
storage: "integer",
|
|
1124
|
-
optional: true,
|
|
1125
|
-
nullable: false
|
|
1126
|
-
},
|
|
1127
|
-
{
|
|
1128
|
-
name: "meta",
|
|
1129
|
-
storage: "json",
|
|
1130
|
-
optional: true,
|
|
1131
|
-
nullable: false
|
|
1132
|
-
}
|
|
1133
|
-
],
|
|
1134
|
-
indexes: []
|
|
1135
|
-
};
|
|
1136
|
-
const CONFORMANCE_POSTS_SCHEMA = {
|
|
1137
|
-
name: "posts",
|
|
1138
|
-
primary: "slug",
|
|
1139
|
-
columns: [{
|
|
1140
|
-
name: "slug",
|
|
1141
|
-
storage: "text",
|
|
1142
|
-
optional: false,
|
|
1143
|
-
nullable: false
|
|
1144
|
-
}, {
|
|
1145
|
-
name: "title",
|
|
1146
|
-
storage: "text",
|
|
1147
|
-
optional: false,
|
|
1148
|
-
nullable: false
|
|
1149
|
-
}],
|
|
1150
|
-
indexes: []
|
|
1151
|
-
};
|
|
1152
|
-
const CONFORMANCE_SCHEMA = [CONFORMANCE_USERS_SCHEMA, CONFORMANCE_POSTS_SCHEMA];
|
|
1287
|
+
async function* scanDriver(factory) {
|
|
1153
1288
|
try {
|
|
1154
1289
|
const driver = factory();
|
|
1155
1290
|
await driver.open(CONFORMANCE_SCHEMA);
|
|
@@ -1936,17 +2071,19 @@ async function* driverFindings(factory) {
|
|
|
1936
2071
|
}
|
|
1937
2072
|
}
|
|
1938
2073
|
/**
|
|
1939
|
-
*
|
|
2074
|
+
* Runs the driver-conformance battery, throwing on the first violated
|
|
1940
2075
|
* invariant — the fail-fast entry point most callers (test setup, CI smoke
|
|
1941
2076
|
* checks) want.
|
|
1942
2077
|
*
|
|
1943
2078
|
* @remarks
|
|
1944
|
-
*
|
|
1945
|
-
* lazy,
|
|
1946
|
-
*
|
|
2079
|
+
* Consumes only the first value {@link scanDriver} yields: because that
|
|
2080
|
+
* generator is lazy, every later phase never runs — true fail-fast, not
|
|
2081
|
+
* merely "report only the first". The
|
|
1947
2082
|
* thrown error is byte-compatible with the historical shape: a
|
|
1948
2083
|
* `CONFORMANCE` {@link DatabaseError} whose `message` is the finding's
|
|
1949
|
-
* `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.
|
|
1950
2087
|
*
|
|
1951
2088
|
* @param factory - Mints a fresh, unopened driver instance (called once per phase)
|
|
1952
2089
|
* @returns Nothing — resolves once every phase has passed
|
|
@@ -1960,18 +2097,18 @@ async function* driverFindings(factory) {
|
|
|
1960
2097
|
* ```
|
|
1961
2098
|
*/
|
|
1962
2099
|
async function conformDriver(factory) {
|
|
1963
|
-
for await (const finding of
|
|
2100
|
+
for await (const finding of scanDriver(factory)) throw new DatabaseError("CONFORMANCE", finding.message, {
|
|
1964
2101
|
check: finding.check,
|
|
1965
2102
|
...finding.context
|
|
1966
2103
|
});
|
|
1967
2104
|
}
|
|
1968
2105
|
/**
|
|
1969
|
-
*
|
|
2106
|
+
* Runs the full driver-conformance battery and collects every violation — the
|
|
1970
2107
|
* audit entry point for a driver author who wants a complete report rather
|
|
1971
2108
|
* than a single fail-fast throw.
|
|
1972
2109
|
*
|
|
1973
2110
|
* @remarks
|
|
1974
|
-
* Drains {@link
|
|
2111
|
+
* Drains {@link scanDriver} to completion: every phase runs regardless
|
|
1975
2112
|
* of earlier violations, so a driver breaking two independent invariants
|
|
1976
2113
|
* reports both. An empty array means the driver is fully conformant.
|
|
1977
2114
|
*
|
|
@@ -1988,26 +2125,32 @@ async function conformDriver(factory) {
|
|
|
1988
2125
|
*/
|
|
1989
2126
|
async function auditDriver(factory) {
|
|
1990
2127
|
const findings = [];
|
|
1991
|
-
for await (const finding of
|
|
2128
|
+
for await (const finding of scanDriver(factory)) findings.push(finding);
|
|
1992
2129
|
return findings;
|
|
1993
2130
|
}
|
|
1994
2131
|
//#endregion
|
|
1995
|
-
//#region src/core/
|
|
2132
|
+
//#region src/core/ScopedIterator.ts
|
|
1996
2133
|
/**
|
|
1997
|
-
*
|
|
2134
|
+
* Forms the internal continuation admission boundary for one scoped async iterable.
|
|
1998
2135
|
*
|
|
1999
2136
|
* @remarks
|
|
2000
|
-
* Each
|
|
2001
|
-
* so an idle iterator never
|
|
2002
|
-
*
|
|
2137
|
+
* Each continuation enters the owning {@link AdmissionInterface} ledger
|
|
2138
|
+
* independently, so an idle iterator never delays a transaction, a settlement,
|
|
2139
|
+
* or a close. `ready` runs inside the tracked continuation before the source
|
|
2140
|
+
* advances, which is where a root stream re-establishes the lazy connection and
|
|
2141
|
+
* a transaction-scoped stream does nothing. A continuation requested after
|
|
2142
|
+
* admission closes attempts source cleanup exactly once and leaves the iterator
|
|
2143
|
+
* terminal.
|
|
2003
2144
|
*/
|
|
2004
|
-
var
|
|
2145
|
+
var ScopedIterator = class {
|
|
2005
2146
|
#source;
|
|
2006
|
-
#
|
|
2147
|
+
#admission;
|
|
2148
|
+
#ready;
|
|
2007
2149
|
#cleaned = false;
|
|
2008
|
-
constructor(source,
|
|
2150
|
+
constructor(source, admission, ready) {
|
|
2009
2151
|
this.#source = source[Symbol.asyncIterator]();
|
|
2010
|
-
this.#
|
|
2152
|
+
this.#admission = admission;
|
|
2153
|
+
this.#ready = ready;
|
|
2011
2154
|
}
|
|
2012
2155
|
[Symbol.asyncIterator]() {
|
|
2013
2156
|
return this;
|
|
@@ -2026,6 +2169,7 @@ var TransactionIterator = class {
|
|
|
2026
2169
|
done: true,
|
|
2027
2170
|
value: void 0
|
|
2028
2171
|
};
|
|
2172
|
+
await this.#ready();
|
|
2029
2173
|
const result = await this.#source.next();
|
|
2030
2174
|
if (result.done === true) this.#cleaned = true;
|
|
2031
2175
|
return result;
|
|
@@ -2053,8 +2197,8 @@ var TransactionIterator = class {
|
|
|
2053
2197
|
throw error;
|
|
2054
2198
|
}
|
|
2055
2199
|
#continue(operation) {
|
|
2056
|
-
if (!this.#
|
|
2057
|
-
return this.#
|
|
2200
|
+
if (!this.#admission.accepting) this.#cleanup();
|
|
2201
|
+
return this.#admission.track(operation);
|
|
2058
2202
|
}
|
|
2059
2203
|
#cleanup() {
|
|
2060
2204
|
if (this.#cleaned) return;
|
|
@@ -2067,7 +2211,7 @@ var TransactionIterator = class {
|
|
|
2067
2211
|
//#endregion
|
|
2068
2212
|
//#region src/core/TransactionScope.ts
|
|
2069
2213
|
/**
|
|
2070
|
-
*
|
|
2214
|
+
* Forms the internal lifetime boundary for one database transaction callback.
|
|
2071
2215
|
*
|
|
2072
2216
|
* @remarks
|
|
2073
2217
|
* Promise operations enter synchronously through {@link track}. Closing stops new
|
|
@@ -2111,7 +2255,7 @@ var TransactionScope = class {
|
|
|
2111
2255
|
return promise;
|
|
2112
2256
|
}
|
|
2113
2257
|
stream(source) {
|
|
2114
|
-
return new
|
|
2258
|
+
return new ScopedIterator(source, this, () => Promise.resolve());
|
|
2115
2259
|
}
|
|
2116
2260
|
stop() {
|
|
2117
2261
|
this.#accepting = false;
|
|
@@ -2124,7 +2268,7 @@ var TransactionScope = class {
|
|
|
2124
2268
|
//#endregion
|
|
2125
2269
|
//#region src/core/DatabaseContext.ts
|
|
2126
2270
|
/**
|
|
2127
|
-
*
|
|
2271
|
+
* Owns the internal shared state behind every typed view of one database.
|
|
2128
2272
|
*
|
|
2129
2273
|
* @remarks
|
|
2130
2274
|
* A context owns the driver, merged physical schema, lifecycle, observation,
|
|
@@ -2208,7 +2352,21 @@ var DatabaseContext = class {
|
|
|
2208
2352
|
this.#emitter.emit("close");
|
|
2209
2353
|
}
|
|
2210
2354
|
connect() {
|
|
2211
|
-
return this.#
|
|
2355
|
+
if (this.#ready !== void 0) return this.#ready;
|
|
2356
|
+
if (this.#failure !== void 0) throw this.#failure.error;
|
|
2357
|
+
if (this.#status === "closed") throw new DatabaseError("CLOSED", `Database '${this.#name}' is closed`, { name: this.#name });
|
|
2358
|
+
const readiness = this.#driver.open(this.#schema).then(async () => {
|
|
2359
|
+
if (this.#status === "idle") {
|
|
2360
|
+
this.#status = "open";
|
|
2361
|
+
this.#emitter.emit("open");
|
|
2362
|
+
}
|
|
2363
|
+
await this.#reconcile();
|
|
2364
|
+
}).catch((error) => {
|
|
2365
|
+
if (this.#ready === readiness) this.#ready = void 0;
|
|
2366
|
+
throw error;
|
|
2367
|
+
});
|
|
2368
|
+
this.#ready = readiness;
|
|
2369
|
+
return readiness;
|
|
2212
2370
|
}
|
|
2213
2371
|
track(operation) {
|
|
2214
2372
|
try {
|
|
@@ -2237,7 +2395,7 @@ var DatabaseContext = class {
|
|
|
2237
2395
|
this.#transaction = token;
|
|
2238
2396
|
try {
|
|
2239
2397
|
await this.#drain();
|
|
2240
|
-
await this
|
|
2398
|
+
await this.connect();
|
|
2241
2399
|
if (this.#driver.transaction !== void 0) {
|
|
2242
2400
|
const rejection = {
|
|
2243
2401
|
rejected: false,
|
|
@@ -2313,23 +2471,6 @@ var DatabaseContext = class {
|
|
|
2313
2471
|
#outside() {
|
|
2314
2472
|
if (this.#transaction !== void 0) throw new DatabaseError("CONFLICT", `Database '${this.#name}' has an active transaction`, { name: this.#name });
|
|
2315
2473
|
}
|
|
2316
|
-
#connect() {
|
|
2317
|
-
if (this.#ready !== void 0) return this.#ready;
|
|
2318
|
-
if (this.#failure !== void 0) throw this.#failure.error;
|
|
2319
|
-
if (this.#status === "closed") throw new DatabaseError("CLOSED", `Database '${this.#name}' is closed`, { name: this.#name });
|
|
2320
|
-
const readiness = this.#driver.open(this.#schema).then(async () => {
|
|
2321
|
-
if (this.#status === "idle") {
|
|
2322
|
-
this.#status = "open";
|
|
2323
|
-
this.#emitter.emit("open");
|
|
2324
|
-
}
|
|
2325
|
-
await this.#reconcile();
|
|
2326
|
-
}).catch((error) => {
|
|
2327
|
-
if (this.#ready === readiness) this.#ready = void 0;
|
|
2328
|
-
throw error;
|
|
2329
|
-
});
|
|
2330
|
-
this.#ready = readiness;
|
|
2331
|
-
return readiness;
|
|
2332
|
-
}
|
|
2333
2474
|
async #drain() {
|
|
2334
2475
|
if (this.#operations.size === 0) return;
|
|
2335
2476
|
await Promise.allSettled(this.#operations);
|
|
@@ -2412,12 +2553,12 @@ var DatabaseContext = class {
|
|
|
2412
2553
|
//#endregion
|
|
2413
2554
|
//#region src/core/Cursor.ts
|
|
2414
2555
|
/**
|
|
2415
|
-
*
|
|
2556
|
+
* Walks a table's rows forward for bulk in-place mutation.
|
|
2416
2557
|
*
|
|
2417
2558
|
* @remarks
|
|
2418
2559
|
* Iterates a snapshot of the table's keys captured when the cursor was opened,
|
|
2419
2560
|
* reading each row lazily through the owning table — so a mutation made during
|
|
2420
|
-
* iteration cannot corrupt the walk, and a key removed mid-iteration is
|
|
2561
|
+
* iteration cannot corrupt the walk, and a key removed mid-iteration is
|
|
2421
2562
|
* skipped. `update` and `remove` act on the row at the current position.
|
|
2422
2563
|
*/
|
|
2423
2564
|
var Cursor = class {
|
|
@@ -2504,84 +2645,9 @@ var Cursor = class {
|
|
|
2504
2645
|
}
|
|
2505
2646
|
};
|
|
2506
2647
|
//#endregion
|
|
2507
|
-
//#region src/core/DatabaseIterator.ts
|
|
2508
|
-
/**
|
|
2509
|
-
* The internal continuation admission boundary for a root database stream.
|
|
2510
|
-
*
|
|
2511
|
-
* @remarks
|
|
2512
|
-
* Each continuation enters the shared root operation ledger independently, so
|
|
2513
|
-
* an idle iterator never delays a transaction or close. A continuation rejected
|
|
2514
|
-
* after transaction or close admission closes attempts source cleanup exactly
|
|
2515
|
-
* once and leaves the iterator terminal.
|
|
2516
|
-
*/
|
|
2517
|
-
var DatabaseIterator = class {
|
|
2518
|
-
#source;
|
|
2519
|
-
#context;
|
|
2520
|
-
#cleaned = false;
|
|
2521
|
-
constructor(source, context) {
|
|
2522
|
-
this.#source = source[Symbol.asyncIterator]();
|
|
2523
|
-
this.#context = context;
|
|
2524
|
-
}
|
|
2525
|
-
[Symbol.asyncIterator]() {
|
|
2526
|
-
return this;
|
|
2527
|
-
}
|
|
2528
|
-
next() {
|
|
2529
|
-
return this.#continue(() => this.#next());
|
|
2530
|
-
}
|
|
2531
|
-
return() {
|
|
2532
|
-
return this.#continue(() => this.#return());
|
|
2533
|
-
}
|
|
2534
|
-
throw(error) {
|
|
2535
|
-
return this.#continue(() => this.#throw(error));
|
|
2536
|
-
}
|
|
2537
|
-
async #next() {
|
|
2538
|
-
if (this.#cleaned) return {
|
|
2539
|
-
done: true,
|
|
2540
|
-
value: void 0
|
|
2541
|
-
};
|
|
2542
|
-
await this.#context.connect();
|
|
2543
|
-
const result = await this.#source.next();
|
|
2544
|
-
if (result.done === true) this.#cleaned = true;
|
|
2545
|
-
return result;
|
|
2546
|
-
}
|
|
2547
|
-
async #return() {
|
|
2548
|
-
if (this.#cleaned || this.#source.return === void 0) {
|
|
2549
|
-
this.#cleaned = true;
|
|
2550
|
-
return {
|
|
2551
|
-
done: true,
|
|
2552
|
-
value: void 0
|
|
2553
|
-
};
|
|
2554
|
-
}
|
|
2555
|
-
this.#cleaned = true;
|
|
2556
|
-
return this.#source.return();
|
|
2557
|
-
}
|
|
2558
|
-
async #throw(error) {
|
|
2559
|
-
if (this.#source.throw !== void 0) {
|
|
2560
|
-
const result = await this.#source.throw(error);
|
|
2561
|
-
if (result.done === true) this.#cleaned = true;
|
|
2562
|
-
return result;
|
|
2563
|
-
}
|
|
2564
|
-
try {
|
|
2565
|
-
await this.#return();
|
|
2566
|
-
} catch {}
|
|
2567
|
-
throw error;
|
|
2568
|
-
}
|
|
2569
|
-
#continue(operation) {
|
|
2570
|
-
if (!this.#context.accepting) this.#cleanup();
|
|
2571
|
-
return this.#context.track(operation);
|
|
2572
|
-
}
|
|
2573
|
-
#cleanup() {
|
|
2574
|
-
if (this.#cleaned) return;
|
|
2575
|
-
this.#cleaned = true;
|
|
2576
|
-
try {
|
|
2577
|
-
(this.#source.return?.())?.catch(() => {});
|
|
2578
|
-
} catch {}
|
|
2579
|
-
}
|
|
2580
|
-
};
|
|
2581
|
-
//#endregion
|
|
2582
2648
|
//#region src/core/Query.ts
|
|
2583
2649
|
/**
|
|
2584
|
-
*
|
|
2650
|
+
* Builds a read against one table through a fluent chain.
|
|
2585
2651
|
*
|
|
2586
2652
|
* @remarks
|
|
2587
2653
|
* Accumulates typed conditions, ordering, JS filters, and a page. Each builder
|
|
@@ -2642,7 +2708,7 @@ var Query = class {
|
|
|
2642
2708
|
return this.#filtered(fetched).length;
|
|
2643
2709
|
}
|
|
2644
2710
|
/**
|
|
2645
|
-
*
|
|
2711
|
+
* Evaluates conditions, filters, offset, and limit lazily.
|
|
2646
2712
|
*
|
|
2647
2713
|
* @param options - Optional abort options
|
|
2648
2714
|
* @returns Matching rows in storage order
|
|
@@ -2694,23 +2760,23 @@ var Query = class {
|
|
|
2694
2760
|
//#endregion
|
|
2695
2761
|
//#region src/core/Table.ts
|
|
2696
2762
|
/**
|
|
2697
|
-
*
|
|
2763
|
+
* Exposes typed keyed CRUD plus fluent query and cursor access over a driver.
|
|
2698
2764
|
*
|
|
2699
2765
|
* @remarks
|
|
2700
2766
|
* The table's contract is the load-bearing piece: writes go through `parse`
|
|
2701
2767
|
* (coercing inputs and rejecting rows that don't fit with a `VALIDATION` throw),
|
|
2702
2768
|
* reads come back through the contract guard (narrowing a stored {@link Row} to
|
|
2703
|
-
* the table's type — no assertion
|
|
2769
|
+
* the table's type — no assertion), and `contract` is exposed for
|
|
2704
2770
|
* introspection and seeding. The driver only stores and scans; all querying is
|
|
2705
2771
|
* the shared core engine in `helpers.ts`.
|
|
2706
2772
|
*
|
|
2707
2773
|
* @remarks
|
|
2708
|
-
* - **Observable
|
|
2774
|
+
* - **Observable.** The owned {@link emitter} ({@link TableEventMap}) carries the
|
|
2709
2775
|
* per-row mutation moments — `write` (set / add / update), `remove`, `clear` — for
|
|
2710
|
-
* fire-and-forget observers (cache invalidation, sync, an audit log),
|
|
2711
|
-
* 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
|
|
2712
2778
|
* keep fan-out lean); reads / queries / counts are not emitted. Every event is emitted
|
|
2713
|
-
* directly, strictly
|
|
2779
|
+
* directly, strictly after the driver write / delete / clear completes; the emitter
|
|
2714
2780
|
* isolates a listener throw and routes it to its `error` handler (the `error` option),
|
|
2715
2781
|
* so a buggy observer can never corrupt a write or perturb a transaction.
|
|
2716
2782
|
*/
|
|
@@ -2794,7 +2860,7 @@ var Table = class {
|
|
|
2794
2860
|
});
|
|
2795
2861
|
}
|
|
2796
2862
|
/**
|
|
2797
|
-
*
|
|
2863
|
+
* Counts contract-valid rows matching `input`'s conditions.
|
|
2798
2864
|
*
|
|
2799
2865
|
* @remarks
|
|
2800
2866
|
* Paging is ignored. Candidate rows use the driver's native `records` hook
|
|
@@ -2819,11 +2885,11 @@ var Table = class {
|
|
|
2819
2885
|
});
|
|
2820
2886
|
}
|
|
2821
2887
|
/**
|
|
2822
|
-
*
|
|
2888
|
+
* Computes an aggregate over `column` across rows matching `input`'s
|
|
2823
2889
|
* conditions.
|
|
2824
2890
|
*
|
|
2825
2891
|
* @remarks
|
|
2826
|
-
*
|
|
2892
|
+
* Unlike {@link count}, `aggregate` operates on stored rows without the
|
|
2827
2893
|
* contract guard {@link records} / {@link scan} apply — a non-conforming
|
|
2828
2894
|
* stored row still contributes to the computed aggregate when it matches
|
|
2829
2895
|
* the conditions, even though it would never appear in `records()`'s
|
|
@@ -2848,7 +2914,7 @@ var Table = class {
|
|
|
2848
2914
|
});
|
|
2849
2915
|
}
|
|
2850
2916
|
/**
|
|
2851
|
-
*
|
|
2917
|
+
* Streams the table's rows matching `input`, applying offset/limit paging.
|
|
2852
2918
|
*
|
|
2853
2919
|
* @remarks
|
|
2854
2920
|
* `input.limit` counts rows that pass both the input conditions and the
|
|
@@ -2863,7 +2929,7 @@ var Table = class {
|
|
|
2863
2929
|
scan(input, options) {
|
|
2864
2930
|
validatePage(input);
|
|
2865
2931
|
const source = this.#scan(input, options);
|
|
2866
|
-
if (this.#context !== void 0) return new
|
|
2932
|
+
if (this.#context !== void 0) return new ScopedIterator(source, this.#context, this.#ready);
|
|
2867
2933
|
return this.#scope === void 0 ? source : this.#scope.stream(source);
|
|
2868
2934
|
}
|
|
2869
2935
|
set(rows, options) {
|
|
@@ -3107,7 +3173,7 @@ var Table = class {
|
|
|
3107
3173
|
//#endregion
|
|
3108
3174
|
//#region src/core/DatabaseTransaction.ts
|
|
3109
3175
|
/**
|
|
3110
|
-
*
|
|
3176
|
+
* Binds a table-only database view to one driver transaction scope.
|
|
3111
3177
|
*
|
|
3112
3178
|
* @typeParam T - The declared table shape map
|
|
3113
3179
|
*
|
|
@@ -3134,31 +3200,27 @@ var DatabaseTransaction = class {
|
|
|
3134
3200
|
}
|
|
3135
3201
|
table(name) {
|
|
3136
3202
|
this.#scope.check();
|
|
3137
|
-
const columns = this.#
|
|
3138
|
-
return this.#build(name, this.#
|
|
3203
|
+
const columns = requireColumns(this.#tables, name);
|
|
3204
|
+
return this.#build(name, resolvePrimary(this.#primary, name), (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.objectShape)(columns)));
|
|
3139
3205
|
}
|
|
3140
3206
|
#build(name, key, contract) {
|
|
3141
3207
|
return new Table(() => Promise.resolve(), this.#driver, name, key, contract, this.#generate, this.#error, void 0, this.#scope);
|
|
3142
3208
|
}
|
|
3143
|
-
#key(name) {
|
|
3144
|
-
return this.#primary[name] ?? "id";
|
|
3145
|
-
}
|
|
3146
|
-
#columns(name) {
|
|
3147
|
-
const columns = this.#tables[name];
|
|
3148
|
-
if (columns === void 0) throw new DatabaseError("NOT_FOUND", `Table '${name}' is not declared`, { table: name });
|
|
3149
|
-
return columns;
|
|
3150
|
-
}
|
|
3151
3209
|
};
|
|
3152
3210
|
//#endregion
|
|
3153
3211
|
//#region src/core/Database.ts
|
|
3154
3212
|
/**
|
|
3155
|
-
*
|
|
3213
|
+
* Exposes a typed view over one shared internal lifecycle and storage context.
|
|
3156
3214
|
*
|
|
3157
3215
|
* @remarks
|
|
3158
3216
|
* Each view owns only its table contracts, primary columns, indexes, and key
|
|
3159
3217
|
* generator. Imported views register their physical schemas with the same
|
|
3160
3218
|
* internal context before opening begins, so every view observes one driver,
|
|
3161
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.
|
|
3162
3224
|
*/
|
|
3163
3225
|
var Database = class Database {
|
|
3164
3226
|
#context;
|
|
@@ -3185,8 +3247,8 @@ var Database = class Database {
|
|
|
3185
3247
|
}
|
|
3186
3248
|
table(name) {
|
|
3187
3249
|
if (this.#context.status === "closed") throw new DatabaseError("CLOSED", `Database '${this.#context.name}' is closed`, { name: this.#context.name });
|
|
3188
|
-
const columns = this.#
|
|
3189
|
-
return this.#build(name, this.#
|
|
3250
|
+
const columns = requireColumns(this.#tables, name);
|
|
3251
|
+
return this.#build(name, resolvePrimary(this.#primary, name), (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.objectShape)(columns)));
|
|
3190
3252
|
}
|
|
3191
3253
|
import(tables, primary) {
|
|
3192
3254
|
return this.#spawn(tables, {
|
|
@@ -3197,9 +3259,9 @@ var Database = class Database {
|
|
|
3197
3259
|
export() {
|
|
3198
3260
|
const result = {};
|
|
3199
3261
|
for (const name of Object.keys(this.#tables)) {
|
|
3200
|
-
const columns = this.#
|
|
3262
|
+
const columns = requireColumns(this.#tables, name);
|
|
3201
3263
|
result[name] = {
|
|
3202
|
-
primary: this.#
|
|
3264
|
+
primary: resolvePrimary(this.#primary, name),
|
|
3203
3265
|
columns,
|
|
3204
3266
|
schema: (0, _orkestrel_contract.compileSchema)((0, _orkestrel_contract.objectShape)(columns))
|
|
3205
3267
|
};
|
|
@@ -3235,20 +3297,12 @@ var Database = class Database {
|
|
|
3235
3297
|
...this.#context.version === void 0 ? {} : { version: this.#context.version }
|
|
3236
3298
|
}, this.#context);
|
|
3237
3299
|
}
|
|
3238
|
-
#key(name) {
|
|
3239
|
-
return this.#primary[name] ?? "id";
|
|
3240
|
-
}
|
|
3241
|
-
#columns(name) {
|
|
3242
|
-
const columns = this.#tables[name];
|
|
3243
|
-
if (columns === void 0) throw new DatabaseError("NOT_FOUND", `Table '${name}' is not declared`, { table: name });
|
|
3244
|
-
return columns;
|
|
3245
|
-
}
|
|
3246
3300
|
#schema() {
|
|
3247
3301
|
return Object.keys(this.#tables).map((name) => {
|
|
3248
|
-
const columns = this.#
|
|
3302
|
+
const columns = requireColumns(this.#tables, name);
|
|
3249
3303
|
return {
|
|
3250
3304
|
name,
|
|
3251
|
-
primary: this.#
|
|
3305
|
+
primary: resolvePrimary(this.#primary, name),
|
|
3252
3306
|
columns: Object.entries(columns).map(([column, shape]) => shapeToColumnSchema(column, shape)),
|
|
3253
3307
|
indexes: this.#indexes[name] ?? []
|
|
3254
3308
|
};
|
|
@@ -3284,16 +3338,16 @@ var Database = class Database {
|
|
|
3284
3338
|
//#endregion
|
|
3285
3339
|
//#region src/core/drivers/MemoryDriver.ts
|
|
3286
3340
|
/**
|
|
3287
|
-
*
|
|
3341
|
+
* Implements the reference {@link DriverInterface} — nested maps, no I/O.
|
|
3288
3342
|
*
|
|
3289
3343
|
* @remarks
|
|
3290
3344
|
* The in-between made concrete: it runs identically in a browser or on a server,
|
|
3291
3345
|
* so it is the storage behind tests, ephemeral caches, and any code that wants
|
|
3292
|
-
* the database API without a persistent backend. Rows are
|
|
3346
|
+
* the database API without a persistent backend. Rows are deep-copied (through
|
|
3293
3347
|
* `structuredClone`) in and out — at `write`, `read`, `scan`, `stream`, and both
|
|
3294
3348
|
* snapshot capture and restore — so a caller mutating a nested field of an input
|
|
3295
3349
|
* row, a returned row, or a row mutated in place between snapshot and rollback
|
|
3296
|
-
* can never perturb stored state
|
|
3350
|
+
* can never perturb stored state; a shallow `{ ...row }` spread
|
|
3297
3351
|
* would still share nested object/array references. Metadata instead routes
|
|
3298
3352
|
* through `cloneDriverMetadata`: `stamp` and migration snapshot exact JSON at
|
|
3299
3353
|
* ingress, and `metadata` returns a distinct deeply frozen owned copy. `snapshot`
|
|
@@ -3355,19 +3409,19 @@ var MemoryDriver = class {
|
|
|
3355
3409
|
}
|
|
3356
3410
|
}
|
|
3357
3411
|
/**
|
|
3358
|
-
*
|
|
3412
|
+
* Iterates rows lazily with native filtering — the {@link DriverInterface.stream} hook.
|
|
3359
3413
|
*
|
|
3360
3414
|
* @remarks
|
|
3361
3415
|
* Iterates the table's keys in the same key order `scan` and `keys` yield
|
|
3362
3416
|
* (sorted by {@link compareValues}), testing each row against
|
|
3363
|
-
* `input.conditions` (
|
|
3417
|
+
* `input.conditions` (through {@link matchesQuery}) before counting it
|
|
3364
3418
|
* toward `offset` / `limit`. Both are applied lazily as matches are found —
|
|
3365
3419
|
* `offset` matches are skipped without being yielded, and iteration stops the
|
|
3366
3420
|
* instant `limit` yields have been produced, so a large table is never fully
|
|
3367
|
-
* walked for a small page. `input.order` is
|
|
3421
|
+
* walked for a small page. `input.order` is ignored (the same contract as
|
|
3368
3422
|
* `TableInterface.scan` and `QueryInterface.stream`): streaming yields key
|
|
3369
|
-
* order, sorted output is `records()`'s job. Rows yield copy-out
|
|
3370
|
-
*
|
|
3423
|
+
* order, sorted output is `records()`'s job. Rows yield copy-out, and an
|
|
3424
|
+
* unknown table mirrors `scan`'s empty-yield behavior.
|
|
3371
3425
|
*
|
|
3372
3426
|
* @param table - The table to stream
|
|
3373
3427
|
* @param input - The filter / offset / limit to apply lazily
|
|
@@ -3383,31 +3437,11 @@ var MemoryDriver = class {
|
|
|
3383
3437
|
validatePage(input);
|
|
3384
3438
|
return this.#stream(table, input);
|
|
3385
3439
|
}
|
|
3386
|
-
async *#stream(table, input) {
|
|
3387
|
-
const store = this.#store(table);
|
|
3388
|
-
const conditions = input.conditions;
|
|
3389
|
-
const offset = input.offset ?? 0;
|
|
3390
|
-
const limit = input.limit;
|
|
3391
|
-
let skipped = 0;
|
|
3392
|
-
let yielded = 0;
|
|
3393
|
-
for (const key of this.#ordered(table)) {
|
|
3394
|
-
if (limit !== void 0 && yielded >= limit) return;
|
|
3395
|
-
const row = store.get(key);
|
|
3396
|
-
if (row === void 0) continue;
|
|
3397
|
-
if (conditions !== void 0 && conditions.length > 0 && !matchesQuery(row, conditions)) continue;
|
|
3398
|
-
if (skipped < offset) {
|
|
3399
|
-
skipped += 1;
|
|
3400
|
-
continue;
|
|
3401
|
-
}
|
|
3402
|
-
yield structuredClone(row);
|
|
3403
|
-
yielded += 1;
|
|
3404
|
-
}
|
|
3405
|
-
}
|
|
3406
3440
|
async clear(table) {
|
|
3407
3441
|
this.#store(table).clear();
|
|
3408
3442
|
}
|
|
3409
3443
|
/**
|
|
3410
|
-
*
|
|
3444
|
+
* Captures the current state and returns a thunk that rolls back to it.
|
|
3411
3445
|
*
|
|
3412
3446
|
* @remarks
|
|
3413
3447
|
* Capture owns rows, schema, and one session-local table identity. Replay
|
|
@@ -3464,7 +3498,7 @@ var MemoryDriver = class {
|
|
|
3464
3498
|
};
|
|
3465
3499
|
}
|
|
3466
3500
|
/**
|
|
3467
|
-
*
|
|
3501
|
+
* Returns the persisted {@link DriverMetadata}, or `undefined` when the store has
|
|
3468
3502
|
* never been stamped.
|
|
3469
3503
|
*
|
|
3470
3504
|
* @remarks
|
|
@@ -3479,7 +3513,7 @@ var MemoryDriver = class {
|
|
|
3479
3513
|
return this.#metadata === void 0 ? void 0 : cloneDriverMetadata(this.#metadata);
|
|
3480
3514
|
}
|
|
3481
3515
|
/**
|
|
3482
|
-
*
|
|
3516
|
+
* Persists an owned snapshot for a later `metadata()` to return.
|
|
3483
3517
|
*
|
|
3484
3518
|
* @param metadata - The {@link DriverMetadata} to persist
|
|
3485
3519
|
*/
|
|
@@ -3487,7 +3521,7 @@ var MemoryDriver = class {
|
|
|
3487
3521
|
this.#metadata = cloneDriverMetadata(metadata);
|
|
3488
3522
|
}
|
|
3489
3523
|
/**
|
|
3490
|
-
*
|
|
3524
|
+
* Applies a {@link Migration} plan's steps against the in-memory store.
|
|
3491
3525
|
*
|
|
3492
3526
|
* @remarks
|
|
3493
3527
|
* Steps apply against an isolated candidate. Rows, schema changes, and
|
|
@@ -3569,6 +3603,26 @@ var MemoryDriver = class {
|
|
|
3569
3603
|
case "index.remove": this.#require(tables, step.table);
|
|
3570
3604
|
}
|
|
3571
3605
|
}
|
|
3606
|
+
async *#stream(table, input) {
|
|
3607
|
+
const store = this.#store(table);
|
|
3608
|
+
const conditions = input.conditions;
|
|
3609
|
+
const offset = input.offset ?? 0;
|
|
3610
|
+
const limit = input.limit;
|
|
3611
|
+
let skipped = 0;
|
|
3612
|
+
let yielded = 0;
|
|
3613
|
+
for (const key of this.#ordered(table)) {
|
|
3614
|
+
if (limit !== void 0 && yielded >= limit) return;
|
|
3615
|
+
const row = store.get(key);
|
|
3616
|
+
if (row === void 0) continue;
|
|
3617
|
+
if (conditions !== void 0 && conditions.length > 0 && !matchesQuery(row, conditions)) continue;
|
|
3618
|
+
if (skipped < offset) {
|
|
3619
|
+
skipped += 1;
|
|
3620
|
+
continue;
|
|
3621
|
+
}
|
|
3622
|
+
yield structuredClone(row);
|
|
3623
|
+
yielded += 1;
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3572
3626
|
#store(table) {
|
|
3573
3627
|
this.#table(table);
|
|
3574
3628
|
const store = this.#tables.get(table);
|
|
@@ -3579,41 +3633,50 @@ var MemoryDriver = class {
|
|
|
3579
3633
|
//#endregion
|
|
3580
3634
|
//#region src/core/factories.ts
|
|
3581
3635
|
/**
|
|
3582
|
-
*
|
|
3636
|
+
* Creates a database over a driver and a declared `tables` schema.
|
|
3583
3637
|
*
|
|
3584
3638
|
* @remarks
|
|
3585
3639
|
* `tables` maps each name to its columns (a `column → shape` map); the database
|
|
3586
3640
|
* wraps each in an `objectShape`, so you never write `objectShape` at the table
|
|
3587
3641
|
* level. The `const` type parameter captures the literal names and columns, so
|
|
3588
3642
|
* `db.table('users')` is checked against the schema and typed by `Infer` of its
|
|
3589
|
-
* columns — no annotations. Name a non-`id` primary-key column per table
|
|
3590
|
-
* optional `primary` and `indexes` maps.
|
|
3643
|
+
* columns — no annotations. Name a non-`id` primary-key column per table through
|
|
3644
|
+
* the optional `primary` and `indexes` maps.
|
|
3591
3645
|
*
|
|
3592
3646
|
* @param options - The driver, `tables`, and optional `primary`, `indexes`,
|
|
3593
3647
|
* `name`, `generator`, `version`, and emitter hooks
|
|
3594
3648
|
* @returns A typed {@link DatabaseInterface}
|
|
3595
3649
|
*
|
|
3596
|
-
* @example
|
|
3650
|
+
* @example Create a database
|
|
3597
3651
|
* ```ts
|
|
3598
3652
|
* import { createDatabase, createMemoryDriver } from '@orkestrel/database'
|
|
3599
3653
|
* import { integerShape, stringShape } from '@orkestrel/contract'
|
|
3600
3654
|
*
|
|
3601
3655
|
* const db = createDatabase({
|
|
3602
|
-
* driver: createMemoryDriver(),
|
|
3656
|
+
* driver: createMemoryDriver(), // any DriverInterface — a persistent backend swaps in, same API
|
|
3603
3657
|
* tables: {
|
|
3604
|
-
* users: { id: stringShape(), age: integerShape() },
|
|
3658
|
+
* users: { id: stringShape(), name: stringShape(), age: integerShape() },
|
|
3605
3659
|
* posts: { slug: stringShape(), title: stringShape() },
|
|
3606
3660
|
* },
|
|
3607
|
-
* primary: { posts: 'slug' },
|
|
3661
|
+
* primary: { posts: 'slug' }, // non-`id` primary-key columns, per table
|
|
3608
3662
|
* })
|
|
3609
|
-
*
|
|
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
|
|
3610
3673
|
* ```
|
|
3611
3674
|
*/
|
|
3612
3675
|
function createDatabase(options) {
|
|
3613
3676
|
return new Database(options);
|
|
3614
3677
|
}
|
|
3615
3678
|
/**
|
|
3616
|
-
*
|
|
3679
|
+
* Creates the in-memory reference {@link DriverInterface}.
|
|
3617
3680
|
*
|
|
3618
3681
|
* @remarks
|
|
3619
3682
|
* Backed by nested maps with no I/O — the same driver runs in a browser or on a
|
|
@@ -3625,9 +3688,129 @@ function createMemoryDriver() {
|
|
|
3625
3688
|
return new MemoryDriver();
|
|
3626
3689
|
}
|
|
3627
3690
|
//#endregion
|
|
3691
|
+
//#region src/core/DriverIterator.ts
|
|
3692
|
+
/**
|
|
3693
|
+
* Forms the internal continuation boundary for a root driver async iterator.
|
|
3694
|
+
*
|
|
3695
|
+
* @remarks
|
|
3696
|
+
* A driver transaction can begin while a caller holds an idle root iterator.
|
|
3697
|
+
* Every `next` therefore checks the driver's root-state guard immediately
|
|
3698
|
+
* before and after advancing the source. A failed continuation terminalizes the
|
|
3699
|
+
* iterator, discards any row produced before the post-advance guard failed, and
|
|
3700
|
+
* attempts source cleanup exactly once.
|
|
3701
|
+
*
|
|
3702
|
+
* A driver implementing the published `DriverInterface` extension seam wraps its
|
|
3703
|
+
* own source iterator in one so a root `scan` / `stream` cannot outlive the
|
|
3704
|
+
* driver state it was opened against.
|
|
3705
|
+
*
|
|
3706
|
+
* @typeParam T - The value the wrapped source yields
|
|
3707
|
+
*
|
|
3708
|
+
* @example
|
|
3709
|
+
* ```ts
|
|
3710
|
+
* import type { Row } from '@orkestrel/database'
|
|
3711
|
+
* import { DatabaseError, DriverIterator } from '@orkestrel/database'
|
|
3712
|
+
*
|
|
3713
|
+
* // Inside a driver's `scan`, over its own row source and root-state guard.
|
|
3714
|
+
* declare const rows: AsyncIterator<Row>
|
|
3715
|
+
* declare const transacting: () => boolean
|
|
3716
|
+
* const scan = new DriverIterator(rows, () => {
|
|
3717
|
+
* if (transacting()) {
|
|
3718
|
+
* throw new DatabaseError('CONFLICT', 'scan: a transaction is active')
|
|
3719
|
+
* }
|
|
3720
|
+
* })
|
|
3721
|
+
* for await (const row of scan) row // one row at a time, guarded around each advance
|
|
3722
|
+
* ```
|
|
3723
|
+
*/
|
|
3724
|
+
var DriverIterator = class {
|
|
3725
|
+
#source;
|
|
3726
|
+
#guard;
|
|
3727
|
+
#terminal = false;
|
|
3728
|
+
#cleaned = false;
|
|
3729
|
+
/**
|
|
3730
|
+
* Wraps one source iterator in the continuation boundary.
|
|
3731
|
+
*
|
|
3732
|
+
* @param source - The driver's own row iterator, advanced once per `next`
|
|
3733
|
+
* @param guard - The root-state check, run immediately before and after each advance; it throws to terminalize the iteration
|
|
3734
|
+
*/
|
|
3735
|
+
constructor(source, guard) {
|
|
3736
|
+
this.#source = source;
|
|
3737
|
+
this.#guard = guard;
|
|
3738
|
+
}
|
|
3739
|
+
[Symbol.asyncIterator]() {
|
|
3740
|
+
return this;
|
|
3741
|
+
}
|
|
3742
|
+
async next() {
|
|
3743
|
+
if (this.#terminal) return {
|
|
3744
|
+
done: true,
|
|
3745
|
+
value: void 0
|
|
3746
|
+
};
|
|
3747
|
+
try {
|
|
3748
|
+
this.#guard();
|
|
3749
|
+
const result = await this.#source.next();
|
|
3750
|
+
this.#guard();
|
|
3751
|
+
if (result.done === true) {
|
|
3752
|
+
this.#terminal = true;
|
|
3753
|
+
this.#cleaned = true;
|
|
3754
|
+
}
|
|
3755
|
+
return result;
|
|
3756
|
+
} catch (error) {
|
|
3757
|
+
this.#terminal = true;
|
|
3758
|
+
await this.#discard();
|
|
3759
|
+
throw error;
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
async return() {
|
|
3763
|
+
if (this.#terminal) return {
|
|
3764
|
+
done: true,
|
|
3765
|
+
value: void 0
|
|
3766
|
+
};
|
|
3767
|
+
this.#terminal = true;
|
|
3768
|
+
if (this.#cleaned || this.#source.return === void 0) {
|
|
3769
|
+
this.#cleaned = true;
|
|
3770
|
+
return {
|
|
3771
|
+
done: true,
|
|
3772
|
+
value: void 0
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3775
|
+
this.#cleaned = true;
|
|
3776
|
+
return this.#source.return();
|
|
3777
|
+
}
|
|
3778
|
+
async throw(error) {
|
|
3779
|
+
if (this.#terminal) throw error;
|
|
3780
|
+
if (this.#source.throw === void 0) {
|
|
3781
|
+
this.#terminal = true;
|
|
3782
|
+
await this.#discard();
|
|
3783
|
+
throw error;
|
|
3784
|
+
}
|
|
3785
|
+
try {
|
|
3786
|
+
const result = await this.#source.throw(error);
|
|
3787
|
+
if (result.done === true) {
|
|
3788
|
+
this.#terminal = true;
|
|
3789
|
+
this.#cleaned = true;
|
|
3790
|
+
}
|
|
3791
|
+
return result;
|
|
3792
|
+
} catch (cause) {
|
|
3793
|
+
this.#terminal = true;
|
|
3794
|
+
await this.#discard();
|
|
3795
|
+
throw cause;
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
async #discard() {
|
|
3799
|
+
if (this.#cleaned) return;
|
|
3800
|
+
this.#cleaned = true;
|
|
3801
|
+
try {
|
|
3802
|
+
await this.#source.return?.();
|
|
3803
|
+
} catch {}
|
|
3804
|
+
}
|
|
3805
|
+
};
|
|
3806
|
+
//#endregion
|
|
3807
|
+
exports.CONFORMANCE_POSTS_SCHEMA = CONFORMANCE_POSTS_SCHEMA;
|
|
3808
|
+
exports.CONFORMANCE_SCHEMA = CONFORMANCE_SCHEMA;
|
|
3809
|
+
exports.CONFORMANCE_USERS_SCHEMA = CONFORMANCE_USERS_SCHEMA;
|
|
3628
3810
|
exports.DEFAULT_PRIMARY = DEFAULT_PRIMARY;
|
|
3629
3811
|
exports.Database = Database;
|
|
3630
3812
|
exports.DatabaseError = DatabaseError;
|
|
3813
|
+
exports.DriverIterator = DriverIterator;
|
|
3631
3814
|
exports.MAX_PATTERN_LENGTH = MAX_PATTERN_LENGTH;
|
|
3632
3815
|
exports.MemoryDriver = MemoryDriver;
|
|
3633
3816
|
exports.applyQuery = applyQuery;
|
|
@@ -3642,10 +3825,10 @@ exports.computeAggregate = computeAggregate;
|
|
|
3642
3825
|
exports.conformDriver = conformDriver;
|
|
3643
3826
|
exports.createDatabase = createDatabase;
|
|
3644
3827
|
exports.createMemoryDriver = createMemoryDriver;
|
|
3645
|
-
exports.driverFindings = driverFindings;
|
|
3646
3828
|
exports.equalsValue = equalsValue;
|
|
3647
3829
|
exports.extractKey = extractKey;
|
|
3648
3830
|
exports.filterRows = filterRows;
|
|
3831
|
+
exports.findColumn = findColumn;
|
|
3649
3832
|
exports.isColumnSchema = isColumnSchema;
|
|
3650
3833
|
exports.isDatabaseError = isDatabaseError;
|
|
3651
3834
|
exports.isDriverMetadata = isDriverMetadata;
|
|
@@ -3656,7 +3839,6 @@ exports.isMigrationInput = isMigrationInput;
|
|
|
3656
3839
|
exports.isMigrationStep = isMigrationStep;
|
|
3657
3840
|
exports.isTableSchema = isTableSchema;
|
|
3658
3841
|
exports.matchesCondition = matchesCondition;
|
|
3659
|
-
exports.matchesFuzzy = matchesFuzzy;
|
|
3660
3842
|
exports.matchesGlobPattern = matchesGlobPattern;
|
|
3661
3843
|
exports.matchesLikePattern = matchesLikePattern;
|
|
3662
3844
|
exports.matchesQuery = matchesQuery;
|
|
@@ -3665,6 +3847,9 @@ exports.migrateRows = migrateRows;
|
|
|
3665
3847
|
exports.normalizeDriverSchema = normalizeDriverSchema;
|
|
3666
3848
|
exports.planMigration = planMigration;
|
|
3667
3849
|
exports.projectMigrationSchema = projectMigrationSchema;
|
|
3850
|
+
exports.requireColumns = requireColumns;
|
|
3851
|
+
exports.resolvePrimary = resolvePrimary;
|
|
3852
|
+
exports.scanDriver = scanDriver;
|
|
3668
3853
|
exports.shapeToColumnSchema = shapeToColumnSchema;
|
|
3669
3854
|
exports.shapeToColumnStorage = shapeToColumnStorage;
|
|
3670
3855
|
exports.sortRows = sortRows;
|