@bjornpagen/bumbledb 0.15.0 → 0.17.0
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/COOKBOOK.md +33 -49
- package/README.md +3 -3
- package/dist/capacity.d.ts +24 -136
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +18 -40
- package/dist/capacity.js.map +1 -1
- package/dist/closed.d.ts +0 -156
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +0 -104
- package/dist/closed.js.map +1 -1
- package/dist/db.d.ts +7 -223
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +147 -396
- package/dist/db.js.map +1 -1
- package/dist/face.d.ts +0 -133
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +0 -33
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +1 -145
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +2 -91
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +11 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -13
- package/dist/index.js.map +1 -1
- package/dist/law.d.ts +111 -93
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +23 -27
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts +9 -35
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +8 -53
- package/dist/lower.js.map +1 -1
- package/dist/marshal.d.ts +0 -65
- package/dist/marshal.d.ts.map +1 -1
- package/dist/marshal.js +0 -72
- package/dist/marshal.js.map +1 -1
- package/dist/native.d.ts +25 -290
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +6 -66
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +10 -276
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +1 -96
- package/dist/query/atom.js.map +1 -1
- package/dist/query/find.d.ts +10 -76
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +0 -30
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +64 -146
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +19 -256
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +0 -7
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +1 -13
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts +0 -36
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +0 -44
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +24 -180
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +2 -66
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +2 -50
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +2 -37
- package/dist/relation.js.map +1 -1
- package/dist/schema.d.ts +13 -63
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +118 -92
- package/dist/schema.js.map +1 -1
- package/dist/spec.d.ts +1 -140
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js +1 -68
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +6 -137
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +16 -119
- package/dist/statements.js.map +1 -1
- package/package.json +2 -2
- package/src/capacity.ts +26 -140
- package/src/closed.ts +5 -206
- package/src/db.ts +203 -692
- package/src/face.ts +0 -142
- package/src/fields.ts +4 -172
- package/src/index.ts +9 -15
- package/src/law.ts +201 -129
- package/src/lower.ts +8 -53
- package/src/marshal.ts +1 -85
- package/src/native.ts +47 -323
- package/src/query/atom.ts +26 -313
- package/src/query/find.ts +24 -110
- package/src/query/lower.ts +132 -377
- package/src/query/parse-ir.ts +1 -14
- package/src/query/run.ts +0 -45
- package/src/query/scope.ts +25 -186
- package/src/relation.ts +2 -66
- package/src/schema.ts +143 -122
- package/src/spec.ts +1 -160
- package/src/statements.ts +22 -174
package/src/db.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* A store read is one callback: `db.read((instance, witness) => …)`. The
|
|
9
9
|
* instance is invalid the moment the callback returns; the witness is a
|
|
10
10
|
* cloneable token and may escape. There is no handle-shaped read and no
|
|
11
|
-
* `using snap = db.read
|
|
11
|
+
* `using snap = db.read`. Builder, owned instance, and witness
|
|
12
12
|
* implement `Symbol.dispose`. Prepared plans are plain values whose
|
|
13
13
|
* engine-side half is reclaimed by a GC finalizer — reclamation only,
|
|
14
14
|
* never correctness.
|
|
@@ -48,9 +48,9 @@ import type {
|
|
|
48
48
|
PreparedHandle,
|
|
49
49
|
TxHandle,
|
|
50
50
|
WireFreshRange,
|
|
51
|
+
WireMutationReport,
|
|
51
52
|
Violation as WireViolation,
|
|
52
53
|
ViolationFact as WireViolationFact,
|
|
53
|
-
WireMutationReport,
|
|
54
54
|
WitnessHandle
|
|
55
55
|
} from "#native.ts"
|
|
56
56
|
import { bridged, bridgedAsync, errorFromThrow, native } from "#native.ts"
|
|
@@ -63,86 +63,60 @@ import type { AnyRelation, Fact, FreshKeys } from "#relation.ts"
|
|
|
63
63
|
import type { AnySchema, Schema, SchemaRelation, SchemaRelations } from "#schema.ts"
|
|
64
64
|
import { isStatement, type KeyStatement, type Statement } from "#statements.ts"
|
|
65
65
|
|
|
66
|
-
/**
|
|
67
|
-
* The ordinary (writable, scannable) relations of a schema's record — the
|
|
68
|
-
* only values the runtime methods accept: closed relations lack the
|
|
69
|
-
* relation shape entirely, so passing one is a type error.
|
|
70
|
-
*/
|
|
71
66
|
type MemberRelation<Rels extends SchemaRelations> = Extract<Rels[keyof Rels], AnyRelation>
|
|
72
67
|
|
|
73
|
-
/**
|
|
74
|
-
* Facts consumed vs facts that changed the in-memory final-state view.
|
|
75
|
-
* The length-1 report is `{ submitted: 1n, changed: 0n | 1n }`.
|
|
76
|
-
*/
|
|
77
68
|
interface MutationReport {
|
|
78
69
|
readonly submitted: bigint
|
|
79
70
|
readonly changed: bigint
|
|
80
71
|
}
|
|
81
72
|
|
|
82
|
-
|
|
83
|
-
* Column-major collection write: one array per declared field, every
|
|
84
|
-
* column the same length. The second transport of `load` / `insert` —
|
|
85
|
-
* objects and columns are two ways to spell the same batch.
|
|
86
|
-
*/
|
|
87
|
-
type ColumnBatch<R extends AnyRelation> = {
|
|
88
|
-
readonly [K in keyof Fact<R> & string]: readonly Fact<R>[K][]
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
type CollectionWrite<R extends AnyRelation> = Iterable<Fact<R>> | ColumnBatch<R>
|
|
92
|
-
|
|
93
|
-
function isColumnBatch(value: object): boolean {
|
|
94
|
-
return !(Symbol.iterator in value)
|
|
95
|
-
}
|
|
73
|
+
type CollectionWrite<R extends AnyRelation> = Iterable<Fact<R>>
|
|
96
74
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
rows.push(rowOf(relation.data, recordOf(fact)))
|
|
101
|
-
}
|
|
102
|
-
return rows
|
|
75
|
+
interface FlatCollection {
|
|
76
|
+
readonly rows: bigint
|
|
77
|
+
readonly cells: readonly FactValue[]
|
|
103
78
|
}
|
|
104
79
|
|
|
105
80
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
81
|
+
* The flat projector: every fact's cells land in ONE row-major
|
|
82
|
+
* `FactValue` array (length rows×arity) — no JS array per fact exists
|
|
83
|
+
* anywhere between the caller's objects and the native crossing
|
|
84
|
+
* (proposals/one-representation/20, V1) — and the row count is counted
|
|
85
|
+
* while projecting (the {@link FlatCollection} law: the stated count is
|
|
86
|
+
* what the bridge verifies against `rows × arity`, exactly, for every
|
|
87
|
+
* arity). The per-cell judgment is `cellOf` — the one cell judge `rowOf`
|
|
88
|
+
* also speaks (closed handle→id, well-formedness, interval shape) — and
|
|
89
|
+
* the missing-field refusal is `rowOf`'s, byte for byte; only the output
|
|
90
|
+
* form differs (flat, never per-row).
|
|
108
91
|
*/
|
|
109
|
-
function
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
)
|
|
92
|
+
function rowsOf<R extends AnyRelation>(relation: R, facts: Iterable<Fact<R>>): FlatCollection {
|
|
93
|
+
const data = relation.data
|
|
94
|
+
const cells: FactValue[] = []
|
|
95
|
+
let rows = 0n
|
|
96
|
+
for (const fact of facts) {
|
|
97
|
+
rows += 1n
|
|
98
|
+
const record = recordOf(fact)
|
|
99
|
+
for (const declared of data.fields) {
|
|
100
|
+
const value = record[declared.name]
|
|
101
|
+
if (value === undefined) {
|
|
102
|
+
throw errors.new(`relation ${data.name}: fact is missing field ${declared.name}`)
|
|
103
|
+
}
|
|
104
|
+
cells.push(cellOf(`relation ${data.name} field ${declared.name}`, declared.field, value))
|
|
123
105
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
})
|
|
127
|
-
})
|
|
106
|
+
}
|
|
107
|
+
return { rows, cells }
|
|
128
108
|
}
|
|
129
109
|
|
|
130
110
|
function mutateCollection<R extends AnyRelation>(
|
|
131
111
|
relation: R,
|
|
132
112
|
facts: CollectionWrite<R>,
|
|
133
|
-
|
|
134
|
-
applyColumns: (columns: readonly FactValue[][]) => WireMutationReport
|
|
113
|
+
apply: (rows: bigint, cells: readonly FactValue[]) => WireMutationReport
|
|
135
114
|
): MutationReport {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
: applyRows(rowsOf(relation, facts as Iterable<Fact<R>>))
|
|
115
|
+
const flat = rowsOf(relation, facts)
|
|
116
|
+
const report = apply(flat.rows, flat.cells)
|
|
139
117
|
return Object.freeze({ submitted: report.submitted, changed: report.changed })
|
|
140
118
|
}
|
|
141
119
|
|
|
142
|
-
/**
|
|
143
|
-
* Half-open fresh-id range from one `reserve`. Empty cannot yield a
|
|
144
|
-
* minted id — `start` exists only on the nonempty arm.
|
|
145
|
-
*/
|
|
146
120
|
type FreshRange =
|
|
147
121
|
| {
|
|
148
122
|
readonly empty: true
|
|
@@ -194,80 +168,36 @@ function freshRangeOf(wire: WireFreshRange): FreshRange {
|
|
|
194
168
|
})
|
|
195
169
|
}
|
|
196
170
|
|
|
197
|
-
/**
|
|
198
|
-
* The key object of a key-statement-selected `get`: exactly the selected
|
|
199
|
-
* `key()` statement's projection fields, each at the relation's own BARE
|
|
200
|
-
* structural value type — the {@link KeyFact} rule generalized from the
|
|
201
|
-
* primary key to ANY declared key statement.
|
|
202
|
-
*/
|
|
203
171
|
type DeclaredKeyFact<R extends AnyRelation, Projection extends readonly string[]> = {
|
|
204
172
|
readonly [K in Projection[number] & keyof Fact<R>]: Fact<R>[K]
|
|
205
173
|
}
|
|
206
174
|
|
|
207
|
-
/**
|
|
208
|
-
* One offending fact of a violation: the cited relation's name (a member
|
|
209
|
-
* of the schema's record) and the fact decoded to a named natural-value
|
|
210
|
-
* object — partial exactly as the engine cites it. Closed-referencing
|
|
211
|
-
* cells arrive as handle NAMES (the marshal bijection's read half), so the
|
|
212
|
-
* record and the violation's `canonical` string — which the engine already
|
|
213
|
-
* renders with handle names — agree on the one spelling.
|
|
214
|
-
*/
|
|
215
175
|
interface OffendingFact<Rels extends SchemaRelations> {
|
|
216
176
|
readonly relation: keyof Rels & string
|
|
217
177
|
readonly fact: Readonly<Record<string, FactValue>>
|
|
218
178
|
}
|
|
219
179
|
|
|
220
|
-
/**
|
|
221
|
-
* Shared body of every violation arm: the engine's canonical rendering and
|
|
222
|
-
* the cited facts. `statement` is NOT here — its presence is the
|
|
223
|
-
* discriminant. Implied auto-keys have no SDK spelling (`statement` is
|
|
224
|
-
* the value `undefined`); every declared form carries the IDENTICAL
|
|
225
|
-
* statement value the schema declared (consumers `===`-match it).
|
|
226
|
-
*/
|
|
227
180
|
type ViolationBody<Rels extends SchemaRelations> = {
|
|
228
181
|
readonly canonical: string
|
|
229
182
|
readonly facts: readonly OffendingFact<Rels>[]
|
|
230
183
|
}
|
|
231
184
|
|
|
232
|
-
/**
|
|
233
|
-
* A functionality violation of an engine-materialized fresh-implied or
|
|
234
|
-
* closed auto-key. These slots have no declared spelling (`schema()`
|
|
235
|
-
* rejects an explicit duplicate); `statement` is present and `undefined`.
|
|
236
|
-
*/
|
|
237
185
|
type ImpliedKeyViolation<Rels extends SchemaRelations> = ViolationBody<Rels> & {
|
|
238
186
|
readonly kind: "functionality"
|
|
239
187
|
readonly statement: undefined
|
|
240
188
|
}
|
|
241
189
|
|
|
242
|
-
/**
|
|
243
|
-
* A functionality violation of a declared `key()` statement. `statement`
|
|
244
|
-
* is the IDENTICAL SDK value the schema declared.
|
|
245
|
-
*/
|
|
246
190
|
type DeclaredKeyViolation<Rels extends SchemaRelations> = ViolationBody<Rels> & {
|
|
247
191
|
readonly kind: "functionality"
|
|
248
192
|
readonly statement: Statement
|
|
249
193
|
}
|
|
250
194
|
|
|
251
|
-
/**
|
|
252
|
-
* A containment violation of a declared `contained()` statement (no
|
|
253
|
-
* `orientation` — that property exists exactly on {@link MirrorViolation}).
|
|
254
|
-
*/
|
|
255
195
|
type ContainmentViolation<Rels extends SchemaRelations> = ViolationBody<Rels> & {
|
|
256
196
|
readonly kind: "containment"
|
|
257
197
|
readonly statement: Statement
|
|
258
198
|
readonly direction: "sourceUnsatisfied" | "targetRequired"
|
|
259
199
|
}
|
|
260
200
|
|
|
261
|
-
/**
|
|
262
|
-
* A containment violation of one slot of a declared `mirrors()` statement.
|
|
263
|
-
* BOTH materialized slots render as the one `==` utterance in the written
|
|
264
|
-
* orientation (identical `canonical` strings; the engine's `render.rs`
|
|
265
|
-
* never emits a bare `<=` for a mirrored pair). `direction` is relative
|
|
266
|
-
* to the violated SLOT's own orientation, so it alone cannot say which
|
|
267
|
-
* side of the `==` was violated: `written` is the `source <= target` slot
|
|
268
|
-
* as the statement was spelled, `mirrored` the engine-materialized
|
|
269
|
-
* `target <= source` partner.
|
|
270
|
-
*/
|
|
271
201
|
type MirrorViolation<Rels extends SchemaRelations> = ViolationBody<Rels> & {
|
|
272
202
|
readonly kind: "containment"
|
|
273
203
|
readonly statement: Statement
|
|
@@ -275,25 +205,12 @@ type MirrorViolation<Rels extends SchemaRelations> = ViolationBody<Rels> & {
|
|
|
275
205
|
readonly orientation: "written" | "mirrored"
|
|
276
206
|
}
|
|
277
207
|
|
|
278
|
-
/**
|
|
279
|
-
* A capacity violation of a declared `capacity()` statement. `measure` is
|
|
280
|
-
* the engine's witnessed group total — u128-wide, crossing whole as
|
|
281
|
-
* bigint (C3: truncation is unrepresentable).
|
|
282
|
-
*/
|
|
283
208
|
type CapacityViolation<Rels extends SchemaRelations> = ViolationBody<Rels> & {
|
|
284
209
|
readonly kind: "capacity"
|
|
285
210
|
readonly statement: Statement
|
|
286
211
|
readonly measure: bigint
|
|
287
212
|
}
|
|
288
213
|
|
|
289
|
-
/**
|
|
290
|
-
* One violated statement of a rejected commit, as a typed value. The
|
|
291
|
-
* arms are a true discriminant: `statement === undefined` is exactly the
|
|
292
|
-
* implied-auto-key arm; every declared form carries `Statement` (not
|
|
293
|
-
* `Statement | undefined`, not an omit-optional). `canonical` is the
|
|
294
|
-
* ENGINE's rendering. `direction` / `measure` pass through from the
|
|
295
|
-
* engine VERBATIM.
|
|
296
|
-
*/
|
|
297
214
|
type Violation<Rels extends SchemaRelations> =
|
|
298
215
|
| ImpliedKeyViolation<Rels>
|
|
299
216
|
| DeclaredKeyViolation<Rels>
|
|
@@ -310,7 +227,6 @@ type Violation<Rels extends SchemaRelations> =
|
|
|
310
227
|
*/
|
|
311
228
|
type AbandonedArm<R> = R extends Abandon<infer P> ? { readonly tag: "abandoned"; readonly abandoned: P } : never
|
|
312
229
|
|
|
313
|
-
/** A callback return that is not Promise-like. TypeScript `never` is not a runtime boundary. */
|
|
314
230
|
type SyncResult<R> = R extends PromiseLike<unknown> ? never : R
|
|
315
231
|
|
|
316
232
|
interface Committed<T> {
|
|
@@ -322,10 +238,6 @@ type Admission<Rels extends SchemaRelations, T> =
|
|
|
322
238
|
| { readonly tag: "accepted"; readonly value: T }
|
|
323
239
|
| { readonly tag: "rejected"; readonly violations: readonly Violation<Rels>[] }
|
|
324
240
|
|
|
325
|
-
/**
|
|
326
|
-
* A write's domain outcome. One discriminant: narrow on `tag`. The
|
|
327
|
-
* abandoned arm is present exactly when the callback can abandon.
|
|
328
|
-
*/
|
|
329
241
|
type WriteOutcome<Rels extends SchemaRelations, R> =
|
|
330
242
|
| { readonly tag: "accepted"; readonly value: Committed<Exclude<R, Abandon<unknown>>> }
|
|
331
243
|
| { readonly tag: "rejected"; readonly violations: readonly Violation<Rels>[] }
|
|
@@ -335,19 +247,8 @@ type WriteFromOutcome<Rels extends SchemaRelations, R> =
|
|
|
335
247
|
| WriteOutcome<Rels, R>
|
|
336
248
|
| { readonly tag: "moved"; readonly witnessed: bigint; readonly current: bigint }
|
|
337
249
|
|
|
338
|
-
/**
|
|
339
|
-
* The delta-building callback of a write: runs synchronously against the
|
|
340
|
-
* live transaction. Returning {@link abandon}`(payload)` rolls the
|
|
341
|
-
* transaction back (R10) — the result type carries the payload arm exactly
|
|
342
|
-
* then.
|
|
343
|
-
*/
|
|
344
250
|
type DeltaBuild<Rels extends SchemaRelations, R = void> = (tx: WriteTx<Rels>) => R
|
|
345
251
|
|
|
346
|
-
/**
|
|
347
|
-
* The runtime discriminant of {@link Abandon} values — a property probe is
|
|
348
|
-
* how `write`/`writeFrom` distinguish "abort without committing" from an
|
|
349
|
-
* ordinary callback result, never a guess about the host's own value shapes.
|
|
350
|
-
*/
|
|
351
252
|
const abandonMark: unique symbol = Symbol("bumbledb.abandon")
|
|
352
253
|
|
|
353
254
|
/**
|
|
@@ -362,43 +263,16 @@ interface Abandon<P> {
|
|
|
362
263
|
readonly payload: P
|
|
363
264
|
}
|
|
364
265
|
|
|
365
|
-
/**
|
|
366
|
-
* Wraps a payload in the {@link Abandon} sentinel — the one way a write
|
|
367
|
-
* callback declines to commit: `return abandon(payload)` aborts the delta
|
|
368
|
-
* (nothing is committed, not even an empty commit) and the write resolves
|
|
369
|
-
* to `{ tag: "abandoned", abandoned: payload }`, from `write` and `writeFrom`
|
|
370
|
-
* alike (R10).
|
|
371
|
-
*/
|
|
372
266
|
function abandon<P>(payload: P): Abandon<P> {
|
|
373
267
|
return Object.freeze({ [abandonMark]: true as const, payload })
|
|
374
268
|
}
|
|
375
269
|
|
|
376
|
-
/**
|
|
377
|
-
* The abandon payload type a write callback's return type implies: the
|
|
378
|
-
* payload of its `Abandon` arm, `never` when the callback can never
|
|
379
|
-
* abandon (the `abandoned` outcome is then statically unreachable and
|
|
380
|
-
* {@link AbandonedArm} erases it from the sum).
|
|
381
|
-
*/
|
|
382
270
|
type AbandonedPayload<R> = R extends Abandon<infer P> ? P : never
|
|
383
271
|
|
|
384
|
-
/**
|
|
385
|
-
* Narrows a write callback result to the abandon sentinel. The probe is
|
|
386
|
-
* the private {@link abandonMark} symbol only {@link abandon} sets, and
|
|
387
|
-
* `R`'s `Abandon` arm is the only way a sentinel can flow out of the
|
|
388
|
-
* callback — so the narrowed payload type is sound by construction.
|
|
389
|
-
*/
|
|
390
272
|
function isAbandon<R>(value: R): value is R & Abandon<AbandonedPayload<R>> {
|
|
391
273
|
return typeof value === "object" && value !== null && abandonMark in value
|
|
392
274
|
}
|
|
393
275
|
|
|
394
|
-
/**
|
|
395
|
-
* The abandon outcome's trusted admission seam: the value's shape is the
|
|
396
|
-
* checkable half (the sentinel mark only {@link abandon} mints, and the
|
|
397
|
-
* outcome carrying that sentinel's own payload), and the sentinel's
|
|
398
|
-
* existence IS the proof `R` carries an `Abandon` arm — so the outcome is
|
|
399
|
-
* admitted at the conditional {@link AbandonedArm} face the type tier
|
|
400
|
-
* cannot resolve over an open `R`.
|
|
401
|
-
*/
|
|
402
276
|
function isAbandonedOutcome<Rels extends SchemaRelations, R>(
|
|
403
277
|
outcome: { readonly tag: "abandoned"; readonly abandoned: AbandonedPayload<R> },
|
|
404
278
|
sentinel: Abandon<AbandonedPayload<R>>
|
|
@@ -406,7 +280,6 @@ function isAbandonedOutcome<Rels extends SchemaRelations, R>(
|
|
|
406
280
|
return isAbandon(sentinel) && outcome.abandoned === sentinel.payload
|
|
407
281
|
}
|
|
408
282
|
|
|
409
|
-
/** Builds the abandoned write outcome from the callback's own sentinel (the R10 arm's one mint). */
|
|
410
283
|
function abandonedOutcome<Rels extends SchemaRelations, R>(
|
|
411
284
|
sentinel: Abandon<AbandonedPayload<R>>
|
|
412
285
|
): WriteOutcome<Rels, R> {
|
|
@@ -417,43 +290,16 @@ function abandonedOutcome<Rels extends SchemaRelations, R>(
|
|
|
417
290
|
return outcome
|
|
418
291
|
}
|
|
419
292
|
|
|
420
|
-
/**
|
|
421
|
-
* One live write transaction: the submitted delta with the engine's
|
|
422
|
-
* FINAL-STATE point-read view (base + pending delta — the exact state the
|
|
423
|
-
* commit judgment judges, so check-then-act is race-free by construction).
|
|
424
|
-
* Spent when its owning `write`/`writeFrom` call resolves the attempt;
|
|
425
|
-
* any later use throws.
|
|
426
|
-
*/
|
|
427
293
|
interface WriteTx<Rels extends SchemaRelations> {
|
|
428
|
-
/**
|
|
429
|
-
* Records a collection of inserts. Singleton is `[fact]`. Empty is
|
|
430
|
-
* lawful. Returns how many facts were consumed and how many changed
|
|
431
|
-
* the in-memory final-state view. Every fact is complete — omitted
|
|
432
|
-
* fresh cells are a type error; mint first with {@link WriteTx.reserve}.
|
|
433
|
-
*/
|
|
434
294
|
insert<R extends MemberRelation<Rels>>(relation: R, facts: CollectionWrite<R>): MutationReport
|
|
435
|
-
|
|
436
|
-
* Records a collection of deletes. Singleton is `[fact]`. Returns
|
|
437
|
-
* how many facts were consumed and how many changed the view.
|
|
438
|
-
*/
|
|
295
|
+
|
|
439
296
|
delete<R extends MemberRelation<Rels>>(relation: R, facts: Iterable<Fact<R>>): MutationReport
|
|
440
|
-
|
|
441
|
-
* Mints `count` consecutive fresh values for a `.fresh` field.
|
|
442
|
-
* `count === 0n` is empty and does not yield a start.
|
|
443
|
-
*/
|
|
297
|
+
|
|
444
298
|
reserve<R extends MemberRelation<Rels>>(relation: R, field: FreshKeys<R> & string, count: bigint): FreshRange
|
|
445
|
-
|
|
299
|
+
|
|
446
300
|
contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean
|
|
447
|
-
|
|
448
|
-
* Final-state point lookup through the relation's primary key (the
|
|
449
|
-
* {@link KeyFact} rule); `undefined` on a miss.
|
|
450
|
-
*/
|
|
301
|
+
|
|
451
302
|
get<R extends MemberRelation<Rels>>(relation: R, key: KeyFact<R>): Fact<R> | undefined
|
|
452
|
-
/**
|
|
453
|
-
* Final-state point lookup through a DECLARED `key()` statement of this
|
|
454
|
-
* schema — the key object is typed by the statement's own projection;
|
|
455
|
-
* `undefined` on a miss.
|
|
456
|
-
*/
|
|
457
303
|
get<R extends MemberRelation<Rels>, const P extends readonly string[]>(
|
|
458
304
|
relation: R,
|
|
459
305
|
keyStatement: KeyStatement<R, P>,
|
|
@@ -461,74 +307,40 @@ interface WriteTx<Rels extends SchemaRelations> {
|
|
|
461
307
|
): Fact<R> | undefined
|
|
462
308
|
}
|
|
463
309
|
|
|
464
|
-
type Tx<Rels extends SchemaRelations> = WriteTx<Rels>
|
|
465
|
-
|
|
466
310
|
const witnessTypes: unique symbol = Symbol("bumbledb.witness.types")
|
|
467
311
|
|
|
468
|
-
/**
|
|
469
|
-
* Cloneable generation evidence from one store read. May cross `await`.
|
|
470
|
-
* Disposal is idempotent; later use throws {@link ErrSpentHandle}.
|
|
471
|
-
*/
|
|
472
312
|
interface Witness<Rels extends SchemaRelations> extends Disposable {
|
|
473
313
|
readonly [witnessTypes]?: Rels
|
|
474
314
|
}
|
|
475
315
|
|
|
476
|
-
/**
|
|
477
|
-
* The borrowed instance one `db.read((instance, witness) => …)` callback
|
|
478
|
-
* receives. Invalid the moment the callback returns. A stashed value
|
|
479
|
-
* throws {@link ErrUseAfterScope}. Not a handle: there is no `db.read()`.
|
|
480
|
-
*/
|
|
481
316
|
interface ReadInstance<Rels extends SchemaRelations> {
|
|
482
|
-
/**
|
|
483
|
-
* The committed generation this instance witnessed — read inside the
|
|
484
|
-
* lease's own transaction.
|
|
485
|
-
*/
|
|
486
317
|
readonly generation: bigint
|
|
487
|
-
|
|
318
|
+
|
|
488
319
|
scan<R extends MemberRelation<Rels>>(relation: R): Fact<R>[]
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
*/
|
|
320
|
+
|
|
321
|
+
count<R extends MemberRelation<Rels>>(relation: R): bigint
|
|
322
|
+
|
|
493
323
|
get<R extends MemberRelation<Rels>>(relation: R, key: KeyFact<R>): Fact<R> | undefined
|
|
494
|
-
|
|
495
|
-
* Committed-state point lookup through a DECLARED `key()` statement of
|
|
496
|
-
* this schema — the key object is typed by the statement's own
|
|
497
|
-
* projection; `undefined` on a miss.
|
|
498
|
-
*/
|
|
324
|
+
|
|
499
325
|
get<R extends MemberRelation<Rels>, const P extends readonly string[]>(
|
|
500
326
|
relation: R,
|
|
501
327
|
keyStatement: KeyStatement<R, P>,
|
|
502
328
|
key: DeclaredKeyFact<R, P>
|
|
503
329
|
): Fact<R> | undefined
|
|
504
|
-
|
|
330
|
+
|
|
505
331
|
contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean
|
|
506
|
-
|
|
507
|
-
* Executes a prepared query against this instance with the typed
|
|
508
|
-
* params object; returns the answer SET as plain rows with bare
|
|
509
|
-
* structural values (no order — the host sorts). This is the ONE
|
|
510
|
-
* execution spelling ({@link Prepared} carries no `execute`).
|
|
511
|
-
*/
|
|
332
|
+
|
|
512
333
|
execute<Row, Params extends ParamsRecord>(prepared: Prepared<Rels, Row, Params>, params: Params): Row[]
|
|
513
334
|
prepare<Row, Params extends ParamsRecord>(q: Query<Rels, Row, Params>): Prepared<Rels, Row, Params>
|
|
514
335
|
}
|
|
515
336
|
|
|
516
|
-
/**
|
|
517
|
-
* The module-private inference slot of {@link Prepared}: an optional symbol
|
|
518
|
-
* property (never set at runtime) that keeps the prepared value's `Row` and
|
|
519
|
-
* `Params` type arguments load-bearing, so `execute` infers the typed rows
|
|
520
|
-
* and the typed params object from the value alone — the query module's
|
|
521
|
-
* `inferred` pattern, local to this module. A type-level carrier only:
|
|
522
|
-
* values stay bare, nothing is asserted.
|
|
523
|
-
*/
|
|
524
337
|
const preparedTypes: unique symbol = Symbol("bumbledb.prepared.types")
|
|
525
338
|
|
|
526
339
|
/**
|
|
527
340
|
* One prepared query as a plain VALUE: explicit visible compilation
|
|
528
341
|
* (`db.prepare(q)` lowers, pins the plan, and surfaces every engine roster
|
|
529
342
|
* refusal), no lifecycle. Execution happens ONLY through
|
|
530
|
-
* `instance.execute(prepared, params)
|
|
531
|
-
* symmetry rule's one spelling. The engine-side plan is reclaimed by a GC
|
|
343
|
+
* `instance.execute(prepared, params)`. The engine-side plan is reclaimed by a GC
|
|
532
344
|
* finalizer when this value becomes unreachable (reclamation only, never
|
|
533
345
|
* correctness — an unreclaimed plan is idle memory, and process exit frees
|
|
534
346
|
* everything).
|
|
@@ -537,38 +349,10 @@ interface Prepared<Rels extends SchemaRelations, Row, Params extends ParamsRecor
|
|
|
537
349
|
readonly [preparedTypes]?: { readonly rels: Rels; readonly row: Row; readonly params: Params }
|
|
538
350
|
}
|
|
539
351
|
|
|
540
|
-
/**
|
|
541
|
-
* An open store. There is no close: read through `read`/the read sugar,
|
|
542
|
-
* write through `write`/`writeFrom`, and let the process own the
|
|
543
|
-
* environment's lifetime (the engine fsyncs every commit, so durability
|
|
544
|
-
* never waits on a close). A second `open`/`create` of the same path
|
|
545
|
-
* while this handle lives is the engine's `EnvironmentLocked`.
|
|
546
|
-
*/
|
|
547
352
|
interface Db<Rels extends SchemaRelations> {
|
|
548
|
-
/** The theory this store was opened with (fingerprint-verified by the engine). */
|
|
549
353
|
readonly schema: Schema<Rels>
|
|
550
|
-
|
|
551
|
-
* One store read: runs `body` SYNCHRONOUSLY inside the engine lease
|
|
552
|
-
* and returns its result. The {@link ReadInstance} is invalidated
|
|
553
|
-
* when `body` returns — a stashed use throws {@link ErrUseAfterScope}.
|
|
554
|
-
* The {@link Witness} is a clone and may escape. A thenable return
|
|
555
|
-
* throws {@link ErrAsyncCallback}.
|
|
556
|
-
*/
|
|
354
|
+
|
|
557
355
|
read<R>(body: (instance: ReadInstance<Rels>, witness: Witness<Rels>) => SyncResult<R>): SyncResult<R>
|
|
558
|
-
/** `db.scan(r)` === `db.read(instance => instance.scan(r))` — the symmetry rule. */
|
|
559
|
-
scan<R extends MemberRelation<Rels>>(relation: R): Fact<R>[]
|
|
560
|
-
/** `db.get(r, k)` === `db.read(instance => instance.get(r, k))` — the symmetry rule. */
|
|
561
|
-
get<R extends MemberRelation<Rels>>(relation: R, key: KeyFact<R>): Fact<R> | undefined
|
|
562
|
-
/** `db.get(r, s, k)` === `db.read(instance => instance.get(r, s, k))` — the symmetry rule, keyed form. */
|
|
563
|
-
get<R extends MemberRelation<Rels>, const P extends readonly string[]>(
|
|
564
|
-
relation: R,
|
|
565
|
-
keyStatement: KeyStatement<R, P>,
|
|
566
|
-
key: DeclaredKeyFact<R, P>
|
|
567
|
-
): Fact<R> | undefined
|
|
568
|
-
/** `db.contains(r, f)` === `db.read(instance => instance.contains(r, f))` — the symmetry rule. */
|
|
569
|
-
contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean
|
|
570
|
-
/** `db.execute(p, params)` === `db.read(instance => instance.execute(p, params))` — the symmetry rule. */
|
|
571
|
-
execute<Row, Params extends ParamsRecord>(prepared: Prepared<Rels, Row, Params>, params: Params): Row[]
|
|
572
356
|
/**
|
|
573
357
|
* One delta transaction: builds the delta synchronously through `fn`,
|
|
574
358
|
* commits, and returns the domain outcome. A throw from `fn` aborts
|
|
@@ -578,11 +362,7 @@ interface Db<Rels extends SchemaRelations> {
|
|
|
578
362
|
* outcome is `{ tag: "abandoned", abandoned: payload }`.
|
|
579
363
|
*/
|
|
580
364
|
write<R>(fn: (tx: WriteTx<Rels>) => SyncResult<R>): WriteOutcome<Rels, SyncResult<R>>
|
|
581
|
-
|
|
582
|
-
* Witnessed write: commits only if no state-changing commit landed
|
|
583
|
-
* since `witness` was minted. A moved generation is
|
|
584
|
-
* `{ tag: "moved" }` — retry is host policy; this method never loops.
|
|
585
|
-
*/
|
|
365
|
+
|
|
586
366
|
writeFrom<R>(witness: Witness<Rels>, fn: (tx: WriteTx<Rels>) => SyncResult<R>): WriteFromOutcome<Rels, SyncResult<R>>
|
|
587
367
|
/**
|
|
588
368
|
* Prepares a query value built against THIS schema (identity is the
|
|
@@ -594,7 +374,6 @@ interface Db<Rels extends SchemaRelations> {
|
|
|
594
374
|
prepare<Row, Params extends ParamsRecord>(q: Query<Rels, Row, Params>): Prepared<Rels, Row, Params>
|
|
595
375
|
}
|
|
596
376
|
|
|
597
|
-
/** One relation's runtime tables: engine id, the identical schema member, field ids, primary key. */
|
|
598
377
|
interface RelationEntry {
|
|
599
378
|
readonly id: number
|
|
600
379
|
readonly member: SchemaRelation
|
|
@@ -602,19 +381,11 @@ interface RelationEntry {
|
|
|
602
381
|
readonly primaryKey: PrimaryKey | undefined
|
|
603
382
|
}
|
|
604
383
|
|
|
605
|
-
/** One relation's primary candidate key: its materialized statement id and projection. */
|
|
606
384
|
interface PrimaryKey {
|
|
607
385
|
readonly statementId: number
|
|
608
386
|
readonly projection: readonly string[]
|
|
609
387
|
}
|
|
610
388
|
|
|
611
|
-
/**
|
|
612
|
-
* One materialized-statement slot as the SDK mirrors it. Implied auto-keys
|
|
613
|
-
* omit `statement` (the engine owns those slots); every declared form
|
|
614
|
-
* carries the SDK value that lowered to it. Functionality forms also
|
|
615
|
-
* carry the key's owner and projection (what keyed point reads resolve
|
|
616
|
-
* through).
|
|
617
|
-
*/
|
|
618
389
|
type ImpliedKeyEntry = {
|
|
619
390
|
readonly kind: "functionality"
|
|
620
391
|
readonly owner: string
|
|
@@ -635,17 +406,72 @@ type StatementEntry =
|
|
|
635
406
|
| { readonly kind: "mirrors"; readonly statement: Statement; readonly orientation: "written" | "mirrored" }
|
|
636
407
|
| { readonly kind: "capacity"; readonly statement: Statement }
|
|
637
408
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
409
|
+
function decodeOffendingFact<Rels extends SchemaRelations>(
|
|
410
|
+
member: SchemaRelation,
|
|
411
|
+
relation: keyof Rels & string,
|
|
412
|
+
fact: WireViolationFact
|
|
413
|
+
): OffendingFact<Rels> {
|
|
414
|
+
const declared = sealedFieldsOf(member)
|
|
415
|
+
const decoded: Record<string, FactValue> = {}
|
|
416
|
+
for (const cell of fact.fields) {
|
|
417
|
+
const cited = declared.find(function byName(candidate) {
|
|
418
|
+
return candidate.name === cell.name
|
|
419
|
+
})
|
|
420
|
+
const roster = rosterOf(cited?.field)
|
|
421
|
+
decoded[cell.name] =
|
|
422
|
+
roster !== undefined
|
|
423
|
+
? handleOf(`violation fact ${fact.relation} field ${cell.name}`, roster, cell.value)
|
|
424
|
+
: cell.value
|
|
425
|
+
}
|
|
426
|
+
return Object.freeze({ relation, fact: Object.freeze(decoded) })
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function violationFromEntry<Rels extends SchemaRelations>(
|
|
430
|
+
entry: StatementEntry,
|
|
431
|
+
wire: WireViolation,
|
|
432
|
+
facts: readonly OffendingFact<Rels>[]
|
|
433
|
+
): Violation<Rels> {
|
|
434
|
+
const canonical = wire.canonical
|
|
435
|
+
if (entry.kind === "functionality") {
|
|
436
|
+
if (!("statement" in entry)) {
|
|
437
|
+
return Object.freeze({ kind: "functionality", statement: undefined, canonical, facts })
|
|
438
|
+
}
|
|
439
|
+
return Object.freeze({ kind: "functionality", statement: entry.statement, canonical, facts })
|
|
440
|
+
}
|
|
441
|
+
if (entry.kind === "capacity") {
|
|
442
|
+
if (wire.kind !== "capacity") {
|
|
443
|
+
throw errors.new(`bumbledb violation ${wire.statementId} is a capacity slot without a measure`)
|
|
444
|
+
}
|
|
445
|
+
return Object.freeze({
|
|
446
|
+
kind: "capacity",
|
|
447
|
+
statement: entry.statement,
|
|
448
|
+
canonical,
|
|
449
|
+
measure: wire.measure,
|
|
450
|
+
facts
|
|
451
|
+
})
|
|
452
|
+
}
|
|
453
|
+
if (wire.kind !== "containment") {
|
|
454
|
+
throw errors.new(`bumbledb violation ${wire.statementId} is a containment slot without a direction`)
|
|
455
|
+
}
|
|
456
|
+
if (entry.kind === "mirrors") {
|
|
457
|
+
return Object.freeze({
|
|
458
|
+
kind: "containment",
|
|
459
|
+
statement: entry.statement,
|
|
460
|
+
canonical,
|
|
461
|
+
direction: wire.direction,
|
|
462
|
+
orientation: entry.orientation,
|
|
463
|
+
facts
|
|
464
|
+
})
|
|
465
|
+
}
|
|
466
|
+
return Object.freeze({
|
|
467
|
+
kind: "containment",
|
|
468
|
+
statement: entry.statement,
|
|
469
|
+
canonical,
|
|
470
|
+
direction: wire.direction,
|
|
471
|
+
facts
|
|
472
|
+
})
|
|
473
|
+
}
|
|
474
|
+
|
|
649
475
|
function materializedEntries(theory: AnySchema): StatementEntry[] {
|
|
650
476
|
const entries = impliedKeyEntries(theory)
|
|
651
477
|
for (const statement of theory.statements) {
|
|
@@ -654,13 +480,6 @@ function materializedEntries(theory: AnySchema): StatementEntry[] {
|
|
|
654
480
|
return entries
|
|
655
481
|
}
|
|
656
482
|
|
|
657
|
-
/**
|
|
658
|
-
* The engine-materialized implied keys, in the engine's pinned order: one
|
|
659
|
-
* auto-key per fresh field (relation declaration order, then field order),
|
|
660
|
-
* then one closed auto-key `R(id) -> R` per closed relation (declaration
|
|
661
|
-
* order). These slots carry no SDK statement value — the engine owns them
|
|
662
|
-
* (`schema()` rejects an explicit duplicate).
|
|
663
|
-
*/
|
|
664
483
|
function impliedKeyEntries(theory: AnySchema): StatementEntry[] {
|
|
665
484
|
const entries: StatementEntry[] = []
|
|
666
485
|
for (const member of Object.values(theory.relations)) {
|
|
@@ -689,12 +508,6 @@ function impliedKeyEntries(theory: AnySchema): StatementEntry[] {
|
|
|
689
508
|
return entries
|
|
690
509
|
}
|
|
691
510
|
|
|
692
|
-
/**
|
|
693
|
-
* One declared statement's materialized slots: a key or capacity statement
|
|
694
|
-
* occupies one, a `mirrors` occupies two adjacent slots (the engine lowers
|
|
695
|
-
* `==` to two containments, `source <= target` first), both owned by the
|
|
696
|
-
* one SDK value.
|
|
697
|
-
*/
|
|
698
511
|
function declaredEntries(statement: Statement): StatementEntry[] {
|
|
699
512
|
const data = statement.data
|
|
700
513
|
switch (data.kind) {
|
|
@@ -733,29 +546,12 @@ function isThenable(value: unknown): boolean {
|
|
|
733
546
|
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function"
|
|
734
547
|
}
|
|
735
548
|
|
|
736
|
-
/**
|
|
737
|
-
* Narrows a keyed-get middle argument to a statement value (vs a key
|
|
738
|
-
* object) through the statement module's admission brand — a
|
|
739
|
-
* REPRESENTATION, never a shape probe: fact cell shapes are structurally
|
|
740
|
-
* OPEN (an interval value carrying an excess `kind` property is a legal
|
|
741
|
-
* cell), so no property probe could ever be sound here, but no host-built
|
|
742
|
-
* key object can spell the module-private brand symbol.
|
|
743
|
-
*/
|
|
744
549
|
function isStatementValue<R extends AnyRelation, P extends readonly string[]>(
|
|
745
550
|
value: KeyFact<R> | KeyStatement<R, P>
|
|
746
551
|
): value is KeyStatement<R, P> {
|
|
747
552
|
return isStatement(value)
|
|
748
553
|
}
|
|
749
554
|
|
|
750
|
-
/**
|
|
751
|
-
* THE one selector dispatch of the `get` overload pair (primary-key vs
|
|
752
|
-
* key-statement, `docs/architecture/70-api.md` § the freeze): judges the
|
|
753
|
-
* middle argument once and hands the narrowed pieces to the chosen
|
|
754
|
-
* continuation. `Db.get` and the read scope's `get` both dispatch through
|
|
755
|
-
* here, so the two mismatch refusals speak with one voice and the symmetry
|
|
756
|
-
* rule (`db.get(...) === db.read(snap => snap.get(...))`) holds by
|
|
757
|
-
* construction.
|
|
758
|
-
*/
|
|
759
555
|
function selectKeyRead<R extends AnyRelation, P extends readonly string[], T>(
|
|
760
556
|
keyOrStatement: KeyFact<R> | KeyStatement<R, P>,
|
|
761
557
|
declaredKey: DeclaredKeyFact<R, P> | undefined,
|
|
@@ -774,22 +570,11 @@ function selectKeyRead<R extends AnyRelation, P extends readonly string[], T>(
|
|
|
774
570
|
return byPrimary(keyOrStatement)
|
|
775
571
|
}
|
|
776
572
|
|
|
777
|
-
/** The id-resolution tables one open builds: relation entries by name, statement slots by id. */
|
|
778
573
|
interface Tables {
|
|
779
574
|
readonly relations: ReadonlyMap<string, RelationEntry>
|
|
780
575
|
readonly statements: readonly StatementEntry[]
|
|
781
576
|
}
|
|
782
577
|
|
|
783
|
-
/**
|
|
784
|
-
* Builds the id-resolution tables from the manifest, verifying the SDK's
|
|
785
|
-
* positional mirror against the engine's reported order — any drift
|
|
786
|
-
* (count, kind, id, or membership) is a construction-time failure, never a
|
|
787
|
-
* silent misattribution of a violation to the wrong statement value. The
|
|
788
|
-
* declaration-ordinal law the query lowering leans on is verified in the
|
|
789
|
-
* same walks: relation ids and sealed field ids both equal declaration
|
|
790
|
-
* order, so a constructed `Tables` IS the proof and `prepare` inherits it
|
|
791
|
-
* structurally — never a silently misaddressed query.
|
|
792
|
-
*/
|
|
793
578
|
function tablesOf(theory: AnySchema, manifest: Manifest): Tables {
|
|
794
579
|
const entries = materializedEntries(theory)
|
|
795
580
|
if (entries.length !== manifest.statements.length) {
|
|
@@ -873,16 +658,11 @@ function tablesFromTheory(theory: AnySchema): Tables {
|
|
|
873
658
|
return Object.freeze({ relations, statements: Object.freeze(entries) })
|
|
874
659
|
}
|
|
875
660
|
|
|
876
|
-
/** The point-read half a transaction and a read scope share, over their own handle. */
|
|
877
661
|
interface PointReads {
|
|
878
662
|
contains(relationId: number, row: readonly FactValue[]): boolean
|
|
879
663
|
get(relationId: number, statementId: number, key: readonly FactValue[]): FactValue[] | null
|
|
880
664
|
}
|
|
881
665
|
|
|
882
|
-
/**
|
|
883
|
-
* One borrowed instance's PRIVATE lifetime record. Held in
|
|
884
|
-
* {@link instanceStates} — the native handle is never a public value.
|
|
885
|
-
*/
|
|
886
666
|
interface InstanceState {
|
|
887
667
|
readonly handle: InstanceHandle
|
|
888
668
|
live: boolean
|
|
@@ -908,12 +688,6 @@ const witnessReclaimer = new FinalizationRegistry<WitnessHandle>(function reclai
|
|
|
908
688
|
}
|
|
909
689
|
})
|
|
910
690
|
|
|
911
|
-
/**
|
|
912
|
-
* One prepared value's PRIVATE engine half: the pinned plan handle, the
|
|
913
|
-
* owning store's identity token, and the query's marshaling tables (params
|
|
914
|
-
* in declaration order, select columns in head order). Held in
|
|
915
|
-
* {@link preparedPlans} — the plan handle is never a public value.
|
|
916
|
-
*/
|
|
917
691
|
interface PreparedPlan {
|
|
918
692
|
readonly handle: PreparedHandle
|
|
919
693
|
readonly owner: object
|
|
@@ -921,15 +695,8 @@ interface PreparedPlan {
|
|
|
921
695
|
readonly finds: readonly FindColumn[]
|
|
922
696
|
}
|
|
923
697
|
|
|
924
|
-
/** The private engine halves of this module's prepared values. */
|
|
925
698
|
const preparedPlans = new WeakMap<object, PreparedPlan>()
|
|
926
699
|
|
|
927
|
-
/**
|
|
928
|
-
* Reclaims the engine-side plan of a garbage-collected {@link Prepared}
|
|
929
|
-
* value. RECLAMATION ONLY, never correctness: a plan the collector never
|
|
930
|
-
* visits is idle engine memory until process exit, and a failure to close
|
|
931
|
-
* is swallowed (there is no one left to care — the owning value is gone).
|
|
932
|
-
*/
|
|
933
700
|
const planReclaimer = new FinalizationRegistry<PreparedHandle>(function reclaimPlan(handle) {
|
|
934
701
|
const closed = errors.trySync(function closePlan() {
|
|
935
702
|
native.preparedClose(handle)
|
|
@@ -949,13 +716,9 @@ const ErrUseAfterScope = errors.new(
|
|
|
949
716
|
const ErrForeignPrepared = errors.new("bumbledb foreignPrepared: a prepared query met a foreign instance")
|
|
950
717
|
const ErrForeignWitness = errors.new("bumbledb foreignWitness: a witness met a foreign store")
|
|
951
718
|
|
|
952
|
-
/**
|
|
953
|
-
* The shared typed read surface: store leases and owned instances both
|
|
954
|
-
* expose scan/get/contains/execute/prepare. The native ops are the only
|
|
955
|
-
* difference — one way to read, two handle kinds.
|
|
956
|
-
*/
|
|
957
719
|
interface CatalogNative {
|
|
958
720
|
scan(relationId: number): FactValue[][]
|
|
721
|
+
count(relationId: number): bigint
|
|
959
722
|
contains(relationId: number, values: readonly FactValue[]): boolean
|
|
960
723
|
get(relationId: number, statementId: number, keyValues: readonly FactValue[]): FactValue[] | null
|
|
961
724
|
prepare(query: ReturnType<typeof lowerQuery>): ReturnType<typeof native.instancePrepare>
|
|
@@ -968,39 +731,7 @@ function catalogMethods<Rels extends SchemaRelations>(
|
|
|
968
731
|
owner: object,
|
|
969
732
|
assertLive: () => void,
|
|
970
733
|
ops: CatalogNative
|
|
971
|
-
): Pick<ReadInstance<Rels>, "scan" | "get" | "contains" | "execute" | "prepare"> {
|
|
972
|
-
function resolveOrdinary(relation: AnyRelation): RelationEntry {
|
|
973
|
-
const entry = tables.relations.get(relation.name)
|
|
974
|
-
if (entry === undefined || entry.member !== relation) {
|
|
975
|
-
throw errors.new(`relation ${relation.name} is not a member of schema ${theory.name}`)
|
|
976
|
-
}
|
|
977
|
-
if (isClosedMember(relation)) {
|
|
978
|
-
throw errors.new(
|
|
979
|
-
`relation ${relation.name} is closed — its extension is schema data (axioms), never scanned or written`
|
|
980
|
-
)
|
|
981
|
-
}
|
|
982
|
-
return entry
|
|
983
|
-
}
|
|
984
|
-
function declaredKeyOf(relation: AnyRelation, statement: Statement): PrimaryKey {
|
|
985
|
-
const statementId = tables.statements.findIndex(function byIdentity(candidate) {
|
|
986
|
-
return "statement" in candidate && candidate.statement === statement
|
|
987
|
-
})
|
|
988
|
-
const entry = tables.statements[statementId]
|
|
989
|
-
if (entry === undefined) {
|
|
990
|
-
throw errors.new(
|
|
991
|
-
`keyed get statement is not a declared statement of schema ${theory.name} — statement identity is the membership rule`
|
|
992
|
-
)
|
|
993
|
-
}
|
|
994
|
-
if (entry.kind !== "functionality") {
|
|
995
|
-
throw errors.new("keyed get takes a key() statement — containments and capacity statements key nothing")
|
|
996
|
-
}
|
|
997
|
-
if (entry.owner !== relation.name) {
|
|
998
|
-
throw errors.new(
|
|
999
|
-
`keyed get statement keys ${entry.owner}, not ${relation.name} — the statement must be a declared key of the relation it reads`
|
|
1000
|
-
)
|
|
1001
|
-
}
|
|
1002
|
-
return Object.freeze({ statementId, projection: entry.projection })
|
|
1003
|
-
}
|
|
734
|
+
): Pick<ReadInstance<Rels>, "scan" | "count" | "get" | "contains" | "execute" | "prepare"> {
|
|
1004
735
|
function planOf(prepared: object): PreparedPlan {
|
|
1005
736
|
const plan = preparedPlans.get(prepared)
|
|
1006
737
|
if (plan === undefined) {
|
|
@@ -1016,7 +747,7 @@ function catalogMethods<Rels extends SchemaRelations>(
|
|
|
1016
747
|
}
|
|
1017
748
|
function contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean {
|
|
1018
749
|
assertLive()
|
|
1019
|
-
const entry =
|
|
750
|
+
const entry = ordinaryEntry(tables, theory, relation)
|
|
1020
751
|
return bridged("bumbledb instance contains", function readContains() {
|
|
1021
752
|
return ops.contains(entry.id, rowOf(relation.data, recordOf(fact)))
|
|
1022
753
|
})
|
|
@@ -1027,12 +758,12 @@ function catalogMethods<Rels extends SchemaRelations>(
|
|
|
1027
758
|
declaredKey?: DeclaredKeyFact<R, P>
|
|
1028
759
|
): Fact<R> | undefined {
|
|
1029
760
|
assertLive()
|
|
1030
|
-
const entry =
|
|
761
|
+
const entry = ordinaryEntry(tables, theory, relation)
|
|
1031
762
|
return selectKeyRead(
|
|
1032
763
|
keyOrStatement,
|
|
1033
764
|
declaredKey,
|
|
1034
765
|
function byStatement(statement, key) {
|
|
1035
|
-
const selected = declaredKeyOf(relation, statement)
|
|
766
|
+
const selected = declaredKeyOf(tables, theory, relation, statement)
|
|
1036
767
|
const row = bridged("bumbledb instance get", function readGet() {
|
|
1037
768
|
return ops.get(entry.id, selected.statementId, keyRowOf(relation.data, selected.projection, recordOf(key)))
|
|
1038
769
|
})
|
|
@@ -1058,7 +789,7 @@ function catalogMethods<Rels extends SchemaRelations>(
|
|
|
1058
789
|
}
|
|
1059
790
|
function scan<R extends MemberRelation<Rels>>(relation: R): Fact<R>[] {
|
|
1060
791
|
assertLive()
|
|
1061
|
-
const entry =
|
|
792
|
+
const entry = ordinaryEntry(tables, theory, relation)
|
|
1062
793
|
const rows = bridged("bumbledb instance scan", function readScan() {
|
|
1063
794
|
return ops.scan(entry.id)
|
|
1064
795
|
})
|
|
@@ -1066,6 +797,13 @@ function catalogMethods<Rels extends SchemaRelations>(
|
|
|
1066
797
|
return factOf(relation, row)
|
|
1067
798
|
})
|
|
1068
799
|
}
|
|
800
|
+
function count<R extends MemberRelation<Rels>>(relation: R): bigint {
|
|
801
|
+
assertLive()
|
|
802
|
+
const entry = ordinaryEntry(tables, theory, relation)
|
|
803
|
+
return bridged("bumbledb instance count", function readCount() {
|
|
804
|
+
return ops.count(entry.id)
|
|
805
|
+
})
|
|
806
|
+
}
|
|
1069
807
|
function execute<Row, Params extends ParamsRecord>(prepared: Prepared<Rels, Row, Params>, params: Params): Row[] {
|
|
1070
808
|
assertLive()
|
|
1071
809
|
const plan = planOf(prepared)
|
|
@@ -1102,7 +840,7 @@ function catalogMethods<Rels extends SchemaRelations>(
|
|
|
1102
840
|
planReclaimer.register(prepared, outcome.prepared)
|
|
1103
841
|
return prepared
|
|
1104
842
|
}
|
|
1105
|
-
return { scan, get, contains, execute, prepare }
|
|
843
|
+
return { scan, count, get, contains, execute, prepare }
|
|
1106
844
|
}
|
|
1107
845
|
|
|
1108
846
|
function ordinaryEntry(tables: Tables, theory: AnySchema, relation: AnyRelation): RelationEntry {
|
|
@@ -1118,32 +856,33 @@ function ordinaryEntry(tables: Tables, theory: AnySchema, relation: AnyRelation)
|
|
|
1118
856
|
return entry
|
|
1119
857
|
}
|
|
1120
858
|
|
|
859
|
+
function declaredKeyOf(tables: Tables, theory: AnySchema, relation: AnyRelation, statement: Statement): PrimaryKey {
|
|
860
|
+
const statementId = tables.statements.findIndex(function byIdentity(candidate) {
|
|
861
|
+
return "statement" in candidate && candidate.statement === statement
|
|
862
|
+
})
|
|
863
|
+
const entry = tables.statements[statementId]
|
|
864
|
+
if (entry === undefined) {
|
|
865
|
+
throw errors.new(
|
|
866
|
+
`keyed get statement is not a declared statement of schema ${theory.name} — statement identity is the membership rule`
|
|
867
|
+
)
|
|
868
|
+
}
|
|
869
|
+
if (entry.kind !== "functionality") {
|
|
870
|
+
throw errors.new("keyed get takes a key() statement — containments and capacity statements key nothing")
|
|
871
|
+
}
|
|
872
|
+
if (entry.owner !== relation.name) {
|
|
873
|
+
throw errors.new(
|
|
874
|
+
`keyed get statement keys ${entry.owner}, not ${relation.name} — the statement must be a declared key of the relation it reads`
|
|
875
|
+
)
|
|
876
|
+
}
|
|
877
|
+
return Object.freeze({ statementId, projection: entry.projection })
|
|
878
|
+
}
|
|
879
|
+
|
|
1121
880
|
function overlayMethods<Rels extends SchemaRelations>(
|
|
1122
881
|
theory: Schema<Rels>,
|
|
1123
882
|
tables: Tables,
|
|
1124
883
|
assertLive: () => void,
|
|
1125
884
|
reads: PointReads
|
|
1126
885
|
): Pick<WriteTx<Rels>, "contains" | "get"> {
|
|
1127
|
-
function declaredKeyOf(relation: AnyRelation, statement: Statement): PrimaryKey {
|
|
1128
|
-
const statementId = tables.statements.findIndex(function byIdentity(candidate) {
|
|
1129
|
-
return "statement" in candidate && candidate.statement === statement
|
|
1130
|
-
})
|
|
1131
|
-
const entry = tables.statements[statementId]
|
|
1132
|
-
if (entry === undefined) {
|
|
1133
|
-
throw errors.new(
|
|
1134
|
-
`keyed get statement is not a declared statement of schema ${theory.name} — statement identity is the membership rule`
|
|
1135
|
-
)
|
|
1136
|
-
}
|
|
1137
|
-
if (entry.kind !== "functionality") {
|
|
1138
|
-
throw errors.new("keyed get takes a key() statement — containments and capacity statements key nothing")
|
|
1139
|
-
}
|
|
1140
|
-
if (entry.owner !== relation.name) {
|
|
1141
|
-
throw errors.new(
|
|
1142
|
-
`keyed get statement keys ${entry.owner}, not ${relation.name} — the statement must be a declared key of the relation it reads`
|
|
1143
|
-
)
|
|
1144
|
-
}
|
|
1145
|
-
return Object.freeze({ statementId, projection: entry.projection })
|
|
1146
|
-
}
|
|
1147
886
|
function contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean {
|
|
1148
887
|
assertLive()
|
|
1149
888
|
const entry = ordinaryEntry(tables, theory, relation)
|
|
@@ -1178,7 +917,7 @@ function overlayMethods<Rels extends SchemaRelations>(
|
|
|
1178
917
|
keyOrStatement,
|
|
1179
918
|
declaredKey,
|
|
1180
919
|
function byStatement(statement, key) {
|
|
1181
|
-
return readThroughKey(relation, entry, declaredKeyOf(relation, statement), recordOf(key))
|
|
920
|
+
return readThroughKey(relation, entry, declaredKeyOf(tables, theory, relation, statement), recordOf(key))
|
|
1182
921
|
},
|
|
1183
922
|
function byPrimary(key) {
|
|
1184
923
|
const primaryKey = entry.primaryKey
|
|
@@ -1213,6 +952,9 @@ function createReadInstance<Rels extends SchemaRelations>(
|
|
|
1213
952
|
scan(relationId) {
|
|
1214
953
|
return native.instanceScan(state.handle, relationId)
|
|
1215
954
|
},
|
|
955
|
+
count(relationId) {
|
|
956
|
+
return native.instanceCount(state.handle, relationId)
|
|
957
|
+
},
|
|
1216
958
|
contains(relationId, values) {
|
|
1217
959
|
return native.instanceContains(state.handle, relationId, values)
|
|
1218
960
|
},
|
|
@@ -1239,12 +981,6 @@ function createReadInstance<Rels extends SchemaRelations>(
|
|
|
1239
981
|
return instance
|
|
1240
982
|
}
|
|
1241
983
|
|
|
1242
|
-
/**
|
|
1243
|
-
* Constructs one open `Db` over an already-admitted handle: builds the
|
|
1244
|
-
* id-resolution tables once and closes over them — the `Db` owns handle
|
|
1245
|
-
* and tables and nothing else. Handle lifetime is the process's: the store
|
|
1246
|
-
* cache holds the environment handle until the exit hook closes it.
|
|
1247
|
-
*/
|
|
1248
984
|
function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<Rels>, manifest: Manifest): Db<Rels> {
|
|
1249
985
|
const tables = tablesOf(theory, manifest)
|
|
1250
986
|
/** This store's identity token: read scopes and prepared values carry it, so cross-store use is a typed refusal. */
|
|
@@ -1254,121 +990,30 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
1254
990
|
return tables.relations.has(name)
|
|
1255
991
|
}
|
|
1256
992
|
|
|
1257
|
-
function resolveOrdinary(relation: AnyRelation): RelationEntry {
|
|
1258
|
-
const entry = tables.relations.get(relation.name)
|
|
1259
|
-
if (entry === undefined || entry.member !== relation) {
|
|
1260
|
-
throw errors.new(`relation ${relation.name} is not a member of schema ${theory.name}`)
|
|
1261
|
-
}
|
|
1262
|
-
if (isClosedMember(relation)) {
|
|
1263
|
-
throw errors.new(
|
|
1264
|
-
`relation ${relation.name} is closed — its extension is schema data (axioms), never scanned or written`
|
|
1265
|
-
)
|
|
1266
|
-
}
|
|
1267
|
-
return entry
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
function offendingFactOf(fact: WireViolationFact): OffendingFact<Rels> {
|
|
1271
|
-
const entry = tables.relations.get(fact.relation)
|
|
1272
|
-
if (entry === undefined || !isMemberName(fact.relation)) {
|
|
1273
|
-
throw errors.new(`bumbledb violation cites unknown relation ${fact.relation}`)
|
|
1274
|
-
}
|
|
1275
|
-
const declared = sealedFieldsOf(entry.member)
|
|
1276
|
-
const decoded: Record<string, FactValue> = {}
|
|
1277
|
-
for (const cell of fact.fields) {
|
|
1278
|
-
const cited = declared.find(function byName(candidate) {
|
|
1279
|
-
return candidate.name === cell.name
|
|
1280
|
-
})
|
|
1281
|
-
const roster = rosterOf(cited?.field)
|
|
1282
|
-
decoded[cell.name] =
|
|
1283
|
-
roster !== undefined
|
|
1284
|
-
? handleOf(`violation fact ${fact.relation} field ${cell.name}`, roster, cell.value)
|
|
1285
|
-
: cell.value
|
|
1286
|
-
}
|
|
1287
|
-
return Object.freeze({ relation: fact.relation, fact: Object.freeze(decoded) })
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
993
|
function violationOf(wire: WireViolation): Violation<Rels> {
|
|
1291
994
|
const entry = tables.statements[wire.statementId]
|
|
1292
995
|
if (entry === undefined) {
|
|
1293
996
|
throw errors.new(`bumbledb violation cites unknown statement id ${wire.statementId}`)
|
|
1294
997
|
}
|
|
1295
|
-
const facts = Object.freeze(
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
}
|
|
1303
|
-
if (entry.kind === "capacity") {
|
|
1304
|
-
if (wire.kind !== "capacity") {
|
|
1305
|
-
throw errors.new(`bumbledb violation ${wire.statementId} is a capacity slot without a measure`)
|
|
1306
|
-
}
|
|
1307
|
-
return Object.freeze({
|
|
1308
|
-
kind: "capacity",
|
|
1309
|
-
statement: entry.statement,
|
|
1310
|
-
canonical,
|
|
1311
|
-
measure: wire.measure,
|
|
1312
|
-
facts
|
|
1313
|
-
})
|
|
1314
|
-
}
|
|
1315
|
-
if (wire.kind !== "containment") {
|
|
1316
|
-
throw errors.new(`bumbledb violation ${wire.statementId} is a containment slot without a direction`)
|
|
1317
|
-
}
|
|
1318
|
-
if (entry.kind === "mirrors") {
|
|
1319
|
-
return Object.freeze({
|
|
1320
|
-
kind: "containment",
|
|
1321
|
-
statement: entry.statement,
|
|
1322
|
-
canonical,
|
|
1323
|
-
direction: wire.direction,
|
|
1324
|
-
orientation: entry.orientation,
|
|
1325
|
-
facts
|
|
998
|
+
const facts = Object.freeze(
|
|
999
|
+
wire.facts.map(function offending(fact) {
|
|
1000
|
+
const rel = tables.relations.get(fact.relation)
|
|
1001
|
+
if (rel === undefined || !isMemberName(fact.relation)) {
|
|
1002
|
+
throw errors.new(`bumbledb violation cites unknown relation ${fact.relation}`)
|
|
1003
|
+
}
|
|
1004
|
+
return decodeOffendingFact<Rels>(rel.member, fact.relation, fact)
|
|
1326
1005
|
})
|
|
1327
|
-
|
|
1328
|
-
return
|
|
1329
|
-
kind: "containment",
|
|
1330
|
-
statement: entry.statement,
|
|
1331
|
-
canonical,
|
|
1332
|
-
direction: wire.direction,
|
|
1333
|
-
facts
|
|
1334
|
-
})
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
/**
|
|
1338
|
-
* Resolves a key-statement-selected read: the statement must be the
|
|
1339
|
-
* IDENTICAL `key()` value this schema declared (identity is the
|
|
1340
|
-
* membership rule) and must key `relation` — its materialized statement
|
|
1341
|
-
* id comes from the positional mirror, so the engine point-reads through
|
|
1342
|
-
* exactly the declared projection.
|
|
1343
|
-
*/
|
|
1344
|
-
function declaredKeyOf(relation: AnyRelation, statement: Statement): PrimaryKey {
|
|
1345
|
-
const statementId = tables.statements.findIndex(function byIdentity(candidate) {
|
|
1346
|
-
return "statement" in candidate && candidate.statement === statement
|
|
1347
|
-
})
|
|
1348
|
-
const entry = tables.statements[statementId]
|
|
1349
|
-
if (entry === undefined) {
|
|
1350
|
-
throw errors.new(
|
|
1351
|
-
`keyed get statement is not a declared statement of schema ${theory.name} — statement identity is the membership rule`
|
|
1352
|
-
)
|
|
1353
|
-
}
|
|
1354
|
-
if (entry.kind !== "functionality") {
|
|
1355
|
-
throw errors.new("keyed get takes a key() statement — containments and capacity statements key nothing")
|
|
1356
|
-
}
|
|
1357
|
-
if (entry.owner !== relation.name) {
|
|
1358
|
-
throw errors.new(
|
|
1359
|
-
`keyed get statement keys ${entry.owner}, not ${relation.name} — the statement must be a declared key of the relation it reads`
|
|
1360
|
-
)
|
|
1361
|
-
}
|
|
1362
|
-
return Object.freeze({ statementId, projection: entry.projection })
|
|
1006
|
+
)
|
|
1007
|
+
return violationFromEntry(entry, wire, facts)
|
|
1363
1008
|
}
|
|
1364
1009
|
|
|
1365
1010
|
function pointReadsOf(assertLive: () => void, reads: PointReads) {
|
|
1366
1011
|
function contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean {
|
|
1367
1012
|
assertLive()
|
|
1368
|
-
const entry =
|
|
1013
|
+
const entry = ordinaryEntry(tables, theory, relation)
|
|
1369
1014
|
return reads.contains(entry.id, rowOf(relation.data, recordOf(fact)))
|
|
1370
1015
|
}
|
|
1371
|
-
|
|
1016
|
+
|
|
1372
1017
|
function readThroughKey<R extends MemberRelation<Rels>>(
|
|
1373
1018
|
relation: R,
|
|
1374
1019
|
entry: RelationEntry,
|
|
@@ -1393,12 +1038,12 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
1393
1038
|
declaredKey?: DeclaredKeyFact<R, P>
|
|
1394
1039
|
): Fact<R> | undefined {
|
|
1395
1040
|
assertLive()
|
|
1396
|
-
const entry =
|
|
1041
|
+
const entry = ordinaryEntry(tables, theory, relation)
|
|
1397
1042
|
return selectKeyRead(
|
|
1398
1043
|
keyOrStatement,
|
|
1399
1044
|
declaredKey,
|
|
1400
1045
|
function byStatement(statement, key) {
|
|
1401
|
-
return readThroughKey(relation, entry, declaredKeyOf(relation, statement), recordOf(key))
|
|
1046
|
+
return readThroughKey(relation, entry, declaredKeyOf(tables, theory, relation, statement), recordOf(key))
|
|
1402
1047
|
},
|
|
1403
1048
|
function byPrimary(key) {
|
|
1404
1049
|
const primaryKey = entry.primaryKey
|
|
@@ -1475,51 +1120,6 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
1475
1120
|
return (captured ?? result) as SyncResult<R>
|
|
1476
1121
|
}
|
|
1477
1122
|
|
|
1478
|
-
function scan<R extends MemberRelation<Rels>>(relation: R): Fact<R>[] {
|
|
1479
|
-
return read(function scanInScope(instance) {
|
|
1480
|
-
return instance.scan(relation)
|
|
1481
|
-
})
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
function get<R extends MemberRelation<Rels>>(relation: R, key: KeyFact<R>): Fact<R> | undefined
|
|
1485
|
-
function get<R extends MemberRelation<Rels>, const P extends readonly string[]>(
|
|
1486
|
-
relation: R,
|
|
1487
|
-
keyStatement: KeyStatement<R, P>,
|
|
1488
|
-
key: DeclaredKeyFact<R, P>
|
|
1489
|
-
): Fact<R> | undefined
|
|
1490
|
-
function get<R extends MemberRelation<Rels>, const P extends readonly string[]>(
|
|
1491
|
-
relation: R,
|
|
1492
|
-
keyOrStatement: KeyFact<R> | KeyStatement<R, P>,
|
|
1493
|
-
declaredKey?: DeclaredKeyFact<R, P>
|
|
1494
|
-
): Fact<R> | undefined {
|
|
1495
|
-
let found: Fact<R> | undefined
|
|
1496
|
-
read(function getInScope(instance) {
|
|
1497
|
-
found = selectKeyRead(
|
|
1498
|
-
keyOrStatement,
|
|
1499
|
-
declaredKey,
|
|
1500
|
-
function byStatement(statement, key) {
|
|
1501
|
-
return instance.get(relation, statement, key)
|
|
1502
|
-
},
|
|
1503
|
-
function byPrimary(key) {
|
|
1504
|
-
return instance.get(relation, key)
|
|
1505
|
-
}
|
|
1506
|
-
)
|
|
1507
|
-
})
|
|
1508
|
-
return found
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
function contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean {
|
|
1512
|
-
return read(function containsInScope(instance) {
|
|
1513
|
-
return instance.contains(relation, fact)
|
|
1514
|
-
})
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
function execute<Row, Params extends ParamsRecord>(prepared: Prepared<Rels, Row, Params>, params: Params): Row[] {
|
|
1518
|
-
return read(function executeInScope(instance) {
|
|
1519
|
-
return instance.execute(prepared, params)
|
|
1520
|
-
})
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
1123
|
function makeTx(resolveTx: () => TxHandle): { readonly tx: WriteTx<Rels>; spend(): void } {
|
|
1524
1124
|
const txState = { spent: false }
|
|
1525
1125
|
function assertLive(): void {
|
|
@@ -1543,29 +1143,21 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
1543
1143
|
})
|
|
1544
1144
|
function insert<R extends MemberRelation<Rels>>(relation: R, facts: CollectionWrite<R>): MutationReport {
|
|
1545
1145
|
assertLive()
|
|
1546
|
-
const entry =
|
|
1146
|
+
const entry = ordinaryEntry(tables, theory, relation)
|
|
1547
1147
|
const txHandle = resolveTx()
|
|
1548
|
-
return mutateCollection(
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
return native.txInsert(txHandle, entry.id, rows)
|
|
1554
|
-
})
|
|
1555
|
-
},
|
|
1556
|
-
function applyColumns(columns) {
|
|
1557
|
-
return bridged("bumbledb tx insert", function recordColumns() {
|
|
1558
|
-
return native.txInsertColumns(txHandle, entry.id, columns)
|
|
1559
|
-
})
|
|
1560
|
-
}
|
|
1561
|
-
)
|
|
1148
|
+
return mutateCollection(relation, facts, function applyCells(rows, cells) {
|
|
1149
|
+
return bridged("bumbledb tx insert", function record() {
|
|
1150
|
+
return native.txInsert(txHandle, entry.id, rows, cells)
|
|
1151
|
+
})
|
|
1152
|
+
})
|
|
1562
1153
|
}
|
|
1563
1154
|
function remove<R extends MemberRelation<Rels>>(relation: R, facts: Iterable<Fact<R>>): MutationReport {
|
|
1564
1155
|
assertLive()
|
|
1565
|
-
const entry =
|
|
1156
|
+
const entry = ordinaryEntry(tables, theory, relation)
|
|
1566
1157
|
const txHandle = resolveTx()
|
|
1158
|
+
const flat = rowsOf(relation, facts)
|
|
1567
1159
|
const report = bridged("bumbledb tx delete", function record() {
|
|
1568
|
-
return native.txDelete(txHandle, entry.id,
|
|
1160
|
+
return native.txDelete(txHandle, entry.id, flat.rows, flat.cells)
|
|
1569
1161
|
})
|
|
1570
1162
|
return Object.freeze({ submitted: report.submitted, changed: report.changed })
|
|
1571
1163
|
}
|
|
@@ -1575,7 +1167,7 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
1575
1167
|
count: bigint
|
|
1576
1168
|
): FreshRange {
|
|
1577
1169
|
assertLive()
|
|
1578
|
-
const entry =
|
|
1170
|
+
const entry = ordinaryEntry(tables, theory, relation)
|
|
1579
1171
|
const declared = relation.data.fields.find(function byName(candidate) {
|
|
1580
1172
|
return candidate.name === field
|
|
1581
1173
|
})
|
|
@@ -1712,33 +1304,17 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
|
|
|
1712
1304
|
return Object.freeze({
|
|
1713
1305
|
schema: theory,
|
|
1714
1306
|
read,
|
|
1715
|
-
scan,
|
|
1716
|
-
get,
|
|
1717
|
-
contains,
|
|
1718
|
-
execute,
|
|
1719
1307
|
write,
|
|
1720
1308
|
writeFrom,
|
|
1721
1309
|
prepare
|
|
1722
1310
|
})
|
|
1723
1311
|
}
|
|
1724
1312
|
|
|
1725
|
-
/**
|
|
1726
|
-
* The engine twin of the schema-level class wall, as a matchable value
|
|
1727
|
-
* (`errors.is`): the shared lowering rejected a spec whose statement pairs
|
|
1728
|
-
* faces with disagreeing newtype labels — the faces of a dependency agree
|
|
1729
|
-
* on their newtype, or neither carries one. UNREACHABLE through the typed
|
|
1730
|
-
* builder (the SDK computes every label from the laws, so its lowered
|
|
1731
|
-
* specs cohere by construction); a raw spec handed to the bridge is the
|
|
1732
|
-
* one road here, and the runtime referee that proves the engine judges
|
|
1733
|
-
* what the types claim.
|
|
1734
|
-
*/
|
|
1735
1313
|
const ErrNewtypeMismatch = errors.new(
|
|
1736
1314
|
"bumbledb newtypeMismatch: a statement pairs faces whose newtypes disagree — the faces of a dependency agree on their newtype, or neither carries one"
|
|
1737
1315
|
)
|
|
1738
1316
|
const ErrSchemaError = errors.new("bumbledb schemaError: the declaration failed validation")
|
|
1739
|
-
const ErrFingerprintMismatch = errors.new(
|
|
1740
|
-
"bumbledb fingerprintMismatch: the store's schema does not match this theory"
|
|
1741
|
-
)
|
|
1317
|
+
const ErrFingerprintMismatch = errors.new("bumbledb fingerprintMismatch: the store's schema does not match this theory")
|
|
1742
1318
|
const ErrIrError = errors.new("bumbledb irError: the query failed validation")
|
|
1743
1319
|
|
|
1744
1320
|
function throwOpenRefusal(
|
|
@@ -1802,71 +1378,19 @@ function mapViolationWithoutStore<Rels extends SchemaRelations>(
|
|
|
1802
1378
|
if (entry === undefined) {
|
|
1803
1379
|
throw errors.new(`bumbledb violation cites unknown statement id ${wire.statementId}`)
|
|
1804
1380
|
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
for (const cell of fact.fields) {
|
|
1813
|
-
const cited = declared.find(function byName(candidate) {
|
|
1814
|
-
return candidate.name === cell.name
|
|
1815
|
-
})
|
|
1816
|
-
const roster = rosterOf(cited?.field)
|
|
1817
|
-
decoded[cell.name] =
|
|
1818
|
-
roster !== undefined
|
|
1819
|
-
? handleOf(`violation fact ${fact.relation} field ${cell.name}`, roster, cell.value)
|
|
1820
|
-
: cell.value
|
|
1821
|
-
}
|
|
1822
|
-
return Object.freeze({ relation: fact.relation as keyof Rels & string, fact: Object.freeze(decoded) })
|
|
1823
|
-
}
|
|
1824
|
-
const facts = Object.freeze(wire.facts.map(offending))
|
|
1825
|
-
const canonical = wire.canonical
|
|
1826
|
-
if (entry.kind === "functionality") {
|
|
1827
|
-
if (!("statement" in entry)) {
|
|
1828
|
-
return Object.freeze({ kind: "functionality", statement: undefined, canonical, facts })
|
|
1829
|
-
}
|
|
1830
|
-
return Object.freeze({ kind: "functionality", statement: entry.statement, canonical, facts })
|
|
1831
|
-
}
|
|
1832
|
-
if (entry.kind === "capacity") {
|
|
1833
|
-
if (wire.kind !== "capacity") {
|
|
1834
|
-
throw errors.new(`bumbledb violation ${wire.statementId} is a capacity slot without a measure`)
|
|
1835
|
-
}
|
|
1836
|
-
return Object.freeze({
|
|
1837
|
-
kind: "capacity",
|
|
1838
|
-
statement: entry.statement,
|
|
1839
|
-
canonical,
|
|
1840
|
-
measure: wire.measure,
|
|
1841
|
-
facts
|
|
1842
|
-
})
|
|
1843
|
-
}
|
|
1844
|
-
if (wire.kind !== "containment") {
|
|
1845
|
-
throw errors.new(`bumbledb violation ${wire.statementId} is a containment slot without a direction`)
|
|
1846
|
-
}
|
|
1847
|
-
if (entry.kind === "mirrors") {
|
|
1848
|
-
return Object.freeze({
|
|
1849
|
-
kind: "containment",
|
|
1850
|
-
statement: entry.statement,
|
|
1851
|
-
canonical,
|
|
1852
|
-
direction: wire.direction,
|
|
1853
|
-
orientation: entry.orientation,
|
|
1854
|
-
facts
|
|
1381
|
+
const facts = Object.freeze(
|
|
1382
|
+
wire.facts.map(function offending(fact) {
|
|
1383
|
+
const member = theory.relations[fact.relation]
|
|
1384
|
+
if (member === undefined || !(fact.relation in theory.relations)) {
|
|
1385
|
+
throw errors.new(`bumbledb violation cites unknown relation ${fact.relation}`)
|
|
1386
|
+
}
|
|
1387
|
+
return decodeOffendingFact<Rels>(member, fact.relation as keyof Rels & string, fact)
|
|
1855
1388
|
})
|
|
1856
|
-
|
|
1857
|
-
return
|
|
1858
|
-
kind: "containment",
|
|
1859
|
-
statement: entry.statement,
|
|
1860
|
-
canonical,
|
|
1861
|
-
direction: wire.direction,
|
|
1862
|
-
facts
|
|
1863
|
-
})
|
|
1389
|
+
)
|
|
1390
|
+
return violationFromEntry(entry, wire, facts)
|
|
1864
1391
|
}
|
|
1865
1392
|
|
|
1866
|
-
async function openStore<Rels extends SchemaRelations>(
|
|
1867
|
-
storePath: string,
|
|
1868
|
-
theory: Schema<Rels>
|
|
1869
|
-
): Promise<Db<Rels>> {
|
|
1393
|
+
async function openStore<Rels extends SchemaRelations>(storePath: string, theory: Schema<Rels>): Promise<Db<Rels>> {
|
|
1870
1394
|
const canonical = path.resolve(storePath)
|
|
1871
1395
|
const spec = lower(theory)
|
|
1872
1396
|
const opened = await bridgedAsync(`open bumbledb store at ${canonical}`, function callBridge() {
|
|
@@ -1882,7 +1406,9 @@ interface OwnedInstance<Rels extends SchemaRelations> extends Disposable {
|
|
|
1882
1406
|
prepare<Row, Params extends ParamsRecord>(q: Query<Rels, Row, Params>): Prepared<Rels, Row, Params>
|
|
1883
1407
|
execute<Row, Params extends ParamsRecord>(prepared: Prepared<Rels, Row, Params>, params: Params): Row[]
|
|
1884
1408
|
scan<R extends MemberRelation<Rels>>(relation: R): Fact<R>[]
|
|
1409
|
+
count<R extends MemberRelation<Rels>>(relation: R): bigint
|
|
1885
1410
|
contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean
|
|
1411
|
+
|
|
1886
1412
|
get<R extends MemberRelation<Rels>>(relation: R, key: KeyFact<R>): Fact<R> | undefined
|
|
1887
1413
|
get<R extends MemberRelation<Rels>, const P extends readonly string[]>(
|
|
1888
1414
|
relation: R,
|
|
@@ -1896,6 +1422,7 @@ interface InstanceBuilder<Rels extends SchemaRelations> extends Disposable {
|
|
|
1896
1422
|
delete<R extends MemberRelation<Rels>>(relation: R, facts: Iterable<Fact<R>>): MutationReport
|
|
1897
1423
|
reserve<R extends MemberRelation<Rels>>(relation: R, field: FreshKeys<R> & string, count: bigint): FreshRange
|
|
1898
1424
|
contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean
|
|
1425
|
+
|
|
1899
1426
|
get<R extends MemberRelation<Rels>>(relation: R, key: KeyFact<R>): Fact<R> | undefined
|
|
1900
1427
|
get<R extends MemberRelation<Rels>, const P extends readonly string[]>(
|
|
1901
1428
|
relation: R,
|
|
@@ -1939,6 +1466,9 @@ function wrapOwned<Rels extends SchemaRelations>(nativeHandle: OwnedHandle, theo
|
|
|
1939
1466
|
scan(relationId) {
|
|
1940
1467
|
return native.ownedScan(nativeHandle, relationId)
|
|
1941
1468
|
},
|
|
1469
|
+
count(relationId) {
|
|
1470
|
+
return native.ownedCount(nativeHandle, relationId)
|
|
1471
|
+
},
|
|
1942
1472
|
contains(relationId, values) {
|
|
1943
1473
|
return native.ownedContains(nativeHandle, relationId, values)
|
|
1944
1474
|
},
|
|
@@ -2003,34 +1533,22 @@ function wrapBuilder<Rels extends SchemaRelations>(
|
|
|
2003
1533
|
load<R extends MemberRelation<Rels>>(relation: R, facts: CollectionWrite<R>): MutationReport {
|
|
2004
1534
|
assertLive()
|
|
2005
1535
|
const entry = ordinaryEntry(tables, theory, relation)
|
|
2006
|
-
return mutateCollection(
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
return native.instanceBuilderLoad(nativeHandle, entry.id, rows)
|
|
2012
|
-
})
|
|
2013
|
-
},
|
|
2014
|
-
function applyColumns(columns) {
|
|
2015
|
-
return bridged("bumbledb builder load", function loadColumns() {
|
|
2016
|
-
return native.instanceBuilderLoadColumns(nativeHandle, entry.id, columns)
|
|
2017
|
-
})
|
|
2018
|
-
}
|
|
2019
|
-
)
|
|
1536
|
+
return mutateCollection(relation, facts, function applyCells(rows, cells) {
|
|
1537
|
+
return bridged("bumbledb builder load", function loadCells() {
|
|
1538
|
+
return native.instanceBuilderLoad(nativeHandle, entry.id, rows, cells)
|
|
1539
|
+
})
|
|
1540
|
+
})
|
|
2020
1541
|
},
|
|
2021
1542
|
delete<R extends MemberRelation<Rels>>(relation: R, facts: Iterable<Fact<R>>): MutationReport {
|
|
2022
1543
|
assertLive()
|
|
2023
1544
|
const entry = ordinaryEntry(tables, theory, relation)
|
|
1545
|
+
const flat = rowsOf(relation, facts)
|
|
2024
1546
|
const report = bridged("bumbledb builder delete", function remove() {
|
|
2025
|
-
return native.instanceBuilderDelete(nativeHandle, entry.id,
|
|
1547
|
+
return native.instanceBuilderDelete(nativeHandle, entry.id, flat.rows, flat.cells)
|
|
2026
1548
|
})
|
|
2027
1549
|
return Object.freeze({ submitted: report.submitted, changed: report.changed })
|
|
2028
1550
|
},
|
|
2029
|
-
reserve<R extends MemberRelation<Rels>>(
|
|
2030
|
-
relation: R,
|
|
2031
|
-
field: FreshKeys<R> & string,
|
|
2032
|
-
count: bigint
|
|
2033
|
-
): FreshRange {
|
|
1551
|
+
reserve<R extends MemberRelation<Rels>>(relation: R, field: FreshKeys<R> & string, count: bigint): FreshRange {
|
|
2034
1552
|
assertLive()
|
|
2035
1553
|
const entry = ordinaryEntry(tables, theory, relation)
|
|
2036
1554
|
const declared = relation.data.fields.find(function byName(candidate) {
|
|
@@ -2113,18 +1631,13 @@ const InstanceBuilder = Object.freeze({
|
|
|
2113
1631
|
* hold the `Db` this process opened.
|
|
2114
1632
|
*/
|
|
2115
1633
|
const Db = Object.freeze({
|
|
2116
|
-
/** Creates a fresh durable store at `path` from the schema. */
|
|
2117
1634
|
async create<Rels extends SchemaRelations>(
|
|
2118
1635
|
storePath: string,
|
|
2119
1636
|
theory: Schema<Rels>
|
|
2120
1637
|
): Promise<Admission<Rels, Db<Rels>>> {
|
|
2121
1638
|
return createStore(storePath, theory)
|
|
2122
1639
|
},
|
|
2123
|
-
|
|
2124
|
-
* Opens an existing durable store at `path` with the same theory.
|
|
2125
|
-
* Format 8 open never back-fills a descriptor. A second open of a
|
|
2126
|
-
* still-live path is `EnvironmentLocked`.
|
|
2127
|
-
*/
|
|
1640
|
+
|
|
2128
1641
|
async open<Rels extends SchemaRelations>(storePath: string, theory: Schema<Rels>): Promise<Db<Rels>> {
|
|
2129
1642
|
return openStore(storePath, theory)
|
|
2130
1643
|
},
|
|
@@ -2152,7 +1665,6 @@ export type {
|
|
|
2152
1665
|
AbandonedArm,
|
|
2153
1666
|
Admission,
|
|
2154
1667
|
CapacityViolation,
|
|
2155
|
-
ColumnBatch,
|
|
2156
1668
|
Committed,
|
|
2157
1669
|
ContainmentViolation,
|
|
2158
1670
|
DeclaredKeyFact,
|
|
@@ -2168,7 +1680,6 @@ export type {
|
|
|
2168
1680
|
Prepared,
|
|
2169
1681
|
ReadInstance,
|
|
2170
1682
|
SyncResult,
|
|
2171
|
-
Tx,
|
|
2172
1683
|
Violation,
|
|
2173
1684
|
Witness,
|
|
2174
1685
|
WriteFromOutcome,
|
|
@@ -2179,9 +1690,9 @@ export {
|
|
|
2179
1690
|
abandon,
|
|
2180
1691
|
Db,
|
|
2181
1692
|
ErrAsyncCallback,
|
|
1693
|
+
ErrFingerprintMismatch,
|
|
2182
1694
|
ErrForeignPrepared,
|
|
2183
1695
|
ErrForeignWitness,
|
|
2184
|
-
ErrFingerprintMismatch,
|
|
2185
1696
|
ErrIrError,
|
|
2186
1697
|
ErrNewtypeMismatch,
|
|
2187
1698
|
ErrSchemaError,
|