@bjornpagen/bumbledb 0.20.2 → 1.0.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 +283 -1611
- package/README.md +156 -118
- package/dist/capacity.d.ts +16 -12
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +16 -48
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +54 -0
- package/dist/changes.d.ts.map +1 -0
- package/dist/changes.js +245 -0
- package/dist/changes.js.map +1 -0
- package/dist/close.d.ts +24 -0
- package/dist/close.d.ts.map +1 -0
- package/dist/close.js +34 -0
- package/dist/close.js.map +1 -0
- package/dist/closed.d.ts +1 -3
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +24 -12
- package/dist/closed.js.map +1 -1
- package/dist/codec.d.ts +56 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +289 -0
- package/dist/codec.js.map +1 -0
- package/dist/compile.d.ts +68 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +75 -0
- package/dist/compile.js.map +1 -0
- package/dist/db-native.d.ts +170 -0
- package/dist/db-native.d.ts.map +1 -0
- package/dist/db-native.js +5 -0
- package/dist/db-native.js.map +1 -0
- package/dist/db.d.ts +101 -233
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +232 -1084
- package/dist/db.js.map +1 -1
- package/dist/errors.d.ts +57 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +32 -0
- package/dist/errors.js.map +1 -0
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +2 -2
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +65 -24
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +94 -24
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +37 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -21
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +24 -0
- package/dist/internal/log.d.ts.map +1 -0
- package/dist/internal/log.js +10 -0
- package/dist/internal/log.js.map +1 -0
- package/dist/law.d.ts +8 -13
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +6 -59
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts +0 -13
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +9 -17
- package/dist/lower.js.map +1 -1
- package/dist/migration.d.ts +33 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +48 -0
- package/dist/migration.js.map +1 -0
- package/dist/native.d.ts +256 -301
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +65 -108
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +24 -13
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +7 -3
- package/dist/query/atom.js.map +1 -1
- package/dist/query/compute.d.ts +68 -0
- package/dist/query/compute.d.ts.map +1 -0
- package/dist/query/compute.js +141 -0
- package/dist/query/compute.js.map +1 -0
- package/dist/query/find.d.ts +15 -8
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +6 -2
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +23 -2
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +433 -80
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +17 -8
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts +12 -2
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +26 -9
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +47 -3
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +83 -19
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +3 -8
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +16 -10
- package/dist/relation.js.map +1 -1
- package/dist/result.d.ts +47 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +50 -0
- package/dist/result.js.map +1 -0
- package/dist/rows.d.ts +69 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +328 -0
- package/dist/rows.js.map +1 -0
- package/dist/runtime-codes.d.ts +2 -0
- package/dist/runtime-codes.d.ts.map +1 -0
- package/dist/runtime-codes.js +19 -0
- package/dist/runtime-codes.js.map +1 -0
- package/dist/runtime-errors.d.ts +106 -0
- package/dist/runtime-errors.d.ts.map +1 -0
- package/dist/runtime-errors.js +62 -0
- package/dist/runtime-errors.js.map +1 -0
- package/dist/runtime-native.d.ts +171 -0
- package/dist/runtime-native.d.ts.map +1 -0
- package/dist/runtime-native.js +5 -0
- package/dist/runtime-native.js.map +1 -0
- package/dist/runtime.d.ts +73 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +293 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scalar.d.ts +165 -0
- package/dist/scalar.d.ts.map +1 -0
- package/dist/scalar.js +344 -0
- package/dist/scalar.js.map +1 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +34 -37
- package/dist/schema.js.map +1 -1
- package/dist/shape.d.ts +28 -0
- package/dist/shape.d.ts.map +1 -0
- package/dist/shape.js +2 -0
- package/dist/shape.js.map +1 -0
- package/dist/spec.d.ts +27 -3
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js +22 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +16 -3
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +54 -28
- package/dist/statements.js.map +1 -1
- package/dist/uuid.d.ts +23 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +54 -0
- package/dist/uuid.js.map +1 -0
- package/pack-provenance.json +7 -0
- package/package.json +12 -16
- package/src/capacity.ts +34 -54
- package/src/changes.ts +361 -0
- package/src/close.ts +53 -0
- package/src/closed.ts +25 -19
- package/src/codec.ts +379 -0
- package/src/compile.ts +149 -0
- package/src/db-native.ts +247 -0
- package/src/db.ts +461 -1632
- package/src/errors.ts +50 -0
- package/src/face.ts +2 -2
- package/src/fields.ts +182 -55
- package/src/index.ts +62 -126
- package/src/internal/log.ts +51 -0
- package/src/law.ts +13 -28
- package/src/lower.ts +9 -4
- package/src/migration.ts +69 -0
- package/src/native.ts +326 -487
- package/src/query/atom.ts +61 -25
- package/src/query/compute.ts +237 -0
- package/src/query/find.ts +31 -12
- package/src/query/lower.ts +531 -143
- package/src/query/parse-ir.ts +18 -13
- package/src/query/run.ts +32 -16
- package/src/query/scope.ts +140 -8
- package/src/relation.ts +17 -21
- package/src/result.ts +130 -0
- package/src/rows.ts +404 -0
- package/src/runtime-codes.ts +18 -0
- package/src/runtime-errors.ts +69 -0
- package/src/runtime-native.ts +242 -0
- package/src/runtime.ts +440 -0
- package/src/scalar.ts +560 -0
- package/src/schema.ts +35 -37
- package/src/shape.ts +31 -0
- package/src/spec.ts +33 -3
- package/src/statements.ts +75 -68
- package/src/uuid.ts +69 -0
- package/dist/marshal.d.ts +0 -44
- package/dist/marshal.d.ts.map +0 -1
- package/dist/marshal.js +0 -165
- package/dist/marshal.js.map +0 -1
- package/src/marshal.ts +0 -200
package/src/db.ts
CHANGED
|
@@ -1,1720 +1,549 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* A store read is one callback: `db.read((instance, witness) => …)`. The
|
|
9
|
-
* instance is invalid the moment the callback returns; the witness is a
|
|
10
|
-
* cloneable token and may escape. There is no handle-shaped read and no
|
|
11
|
-
* `using snap = db.read`. Builder, owned instance, and witness
|
|
12
|
-
* implement `Symbol.dispose`. Prepared plans are plain values whose
|
|
13
|
-
* engine-side half is reclaimed by a GC finalizer — reclamation only,
|
|
14
|
-
* never correctness.
|
|
15
|
-
*
|
|
16
|
-
* PROCESS MODEL: one process, one exclusive-lock handle per store. The
|
|
17
|
-
* `Db` value owns the LMDB environment's exclusive lock until process
|
|
18
|
-
* exit (or until GC reclaims the native handle); a second engine-level
|
|
19
|
-
* open of the same store is refused by the engine (`EnvironmentLocked`),
|
|
20
|
-
* matching Rust. Resume = reopen in a fresh process, or hold the one `Db`
|
|
21
|
-
* this process already opened. The host owns composition; retry is host
|
|
22
|
-
* policy.
|
|
23
|
-
*
|
|
24
|
-
* REJECTION IS DATA: a rejected commit is a domain outcome (it becomes the
|
|
25
|
-
* LLM repair prompt downstream), returned as a {@link WriteOutcome}
|
|
26
|
-
* carrying {@link Violation} values. A moved generation on
|
|
27
|
-
* {@link Db.writeFrom} is the `{ tag: "moved" }` arm, not an exception.
|
|
28
|
-
* Genuine failures — I/O, used-after-scope, spent handle, marshal shape —
|
|
29
|
-
* throw `@superbuilders/errors` wrapped errors instead.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
import * as path from "node:path"
|
|
33
|
-
import * as errors from "@superbuilders/errors"
|
|
34
|
-
import { regex } from "arkregex"
|
|
35
|
-
import { isClosedMember, sealedFieldsOf } from "#closed.ts"
|
|
36
|
-
import { rosterOf } from "#fields.ts"
|
|
37
|
-
import { lower } from "#lower.ts"
|
|
38
|
-
import { cellOf, factOf, handleOf, isFreshField, type KeyFact, keyRowOf, recordOf, rowOf } from "#marshal.ts"
|
|
39
|
-
|
|
1
|
+
import type { Scope } from "effect"
|
|
2
|
+
import { Effect, Option } from "effect"
|
|
3
|
+
import type { ChangeSet } from "#changes.ts"
|
|
4
|
+
import { internalChanges } from "#changes.ts"
|
|
5
|
+
import { drainClose, releaseOwner } from "#close.ts"
|
|
6
|
+
import type { CompiledSchema, SchemaId } from "#compile.ts"
|
|
7
|
+
import { Schema as CoreSchema, schemaTables } from "#compile.ts"
|
|
40
8
|
import type {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
WireMutationReport,
|
|
53
|
-
Violation as WireViolation,
|
|
54
|
-
ViolationFact as WireViolationFact,
|
|
55
|
-
WitnessHandle
|
|
56
|
-
} from "#native.ts"
|
|
57
|
-
import { bridged, bridgedAsync, errorFromThrow, native } from "#native.ts"
|
|
58
|
-
import type { FindColumn } from "#query/atom.ts"
|
|
59
|
-
import type { Query } from "#query/lower.ts"
|
|
9
|
+
ApplyOutcomeWire,
|
|
10
|
+
DbInspectionWire,
|
|
11
|
+
ExpectedWire,
|
|
12
|
+
SessionHandle,
|
|
13
|
+
SnapshotHandle,
|
|
14
|
+
WitnessWire
|
|
15
|
+
} from "#db-native.ts"
|
|
16
|
+
import { dbNative } from "#db-native.ts"
|
|
17
|
+
import { lower } from "#lower.ts"
|
|
18
|
+
import type { DbHandle, Violation } from "#native.ts"
|
|
19
|
+
import type { AnyQuery } from "#query/lower.ts"
|
|
60
20
|
import { lowerQuery } from "#query/lower.ts"
|
|
61
|
-
import {
|
|
62
|
-
import type {
|
|
63
|
-
import type {
|
|
64
|
-
import type {
|
|
65
|
-
import {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
type CollectionWrite<R extends AnyRelation> = Iterable<Fact<R>>
|
|
77
|
-
|
|
78
|
-
interface FlatCollection {
|
|
79
|
-
readonly rows: bigint
|
|
80
|
-
readonly cells: readonly FactValue[]
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* The flat projector: every fact's cells land in ONE row-major
|
|
85
|
-
* `FactValue` array (length rows×arity) — no JS array per fact exists
|
|
86
|
-
* anywhere between the caller's objects and the native crossing — and
|
|
87
|
-
* the row count is counted
|
|
88
|
-
* while projecting (the {@link FlatCollection} law: the stated count is
|
|
89
|
-
* what the bridge verifies against `rows × arity`, exactly, for every
|
|
90
|
-
* arity). The per-cell judgment is `cellOf` — the one cell judge `rowOf`
|
|
91
|
-
* also speaks (closed handle→id, well-formedness, interval shape) — and
|
|
92
|
-
* the missing-field refusal is `rowOf`'s, byte for byte; only the output
|
|
93
|
-
* form differs (flat, never per-row).
|
|
94
|
-
*/
|
|
95
|
-
function rowsOf<R extends AnyRelation>(relation: R, facts: Iterable<Fact<R>>): FlatCollection {
|
|
96
|
-
const data = relation.data
|
|
97
|
-
const cells: FactValue[] = []
|
|
98
|
-
let rows = 0n
|
|
99
|
-
for (const fact of facts) {
|
|
100
|
-
rows += 1n
|
|
101
|
-
const record = recordOf(fact)
|
|
102
|
-
for (const declared of data.fields) {
|
|
103
|
-
const value = record[declared.name]
|
|
104
|
-
if (value === undefined) {
|
|
105
|
-
throw errors.new(`relation ${data.name}: fact is missing field ${declared.name}`)
|
|
106
|
-
}
|
|
107
|
-
cells.push(cellOf(`relation ${data.name} field ${declared.name}`, declared.field, value))
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return { rows, cells }
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function mutateCollection<R extends AnyRelation>(
|
|
114
|
-
relation: R,
|
|
115
|
-
facts: CollectionWrite<R>,
|
|
116
|
-
apply: (rows: bigint, cells: readonly FactValue[]) => WireMutationReport
|
|
117
|
-
): MutationReport {
|
|
118
|
-
const flat = rowsOf(relation, facts)
|
|
119
|
-
const report = apply(flat.rows, flat.cells)
|
|
120
|
-
return Object.freeze({ submitted: report.submitted, changed: report.changed })
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
type FreshRange =
|
|
124
|
-
| {
|
|
125
|
-
readonly empty: true
|
|
126
|
-
readonly count: 0n
|
|
127
|
-
at(index: bigint): undefined
|
|
128
|
-
[Symbol.iterator](): IterableIterator<bigint>
|
|
129
|
-
}
|
|
130
|
-
| {
|
|
131
|
-
readonly empty: false
|
|
132
|
-
readonly start: bigint
|
|
133
|
-
readonly endExclusive: bigint
|
|
134
|
-
readonly count: bigint
|
|
135
|
-
at(index: bigint): bigint | undefined
|
|
136
|
-
[Symbol.iterator](): IterableIterator<bigint>
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function freshRangeOf(wire: WireFreshRange): FreshRange {
|
|
140
|
-
if (wire.empty) {
|
|
141
|
-
return Object.freeze({
|
|
142
|
-
empty: true,
|
|
143
|
-
count: 0n,
|
|
144
|
-
at(_index: bigint) {
|
|
145
|
-
return undefined
|
|
146
|
-
},
|
|
147
|
-
*[Symbol.iterator](): IterableIterator<bigint> {}
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
|
-
const start = wire.start
|
|
151
|
-
const endExclusive = wire.endExclusive
|
|
152
|
-
const count = endExclusive - start
|
|
153
|
-
return Object.freeze({
|
|
154
|
-
empty: false,
|
|
155
|
-
start,
|
|
156
|
-
endExclusive,
|
|
157
|
-
get count() {
|
|
158
|
-
return count
|
|
159
|
-
},
|
|
160
|
-
at(index: bigint) {
|
|
161
|
-
if (index < 0n || index >= count) {
|
|
162
|
-
return undefined
|
|
163
|
-
}
|
|
164
|
-
return start + index
|
|
165
|
-
},
|
|
166
|
-
*[Symbol.iterator](): IterableIterator<bigint> {
|
|
167
|
-
for (let id = start; id < endExclusive; id++) {
|
|
168
|
-
yield id
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
type DeclaredKeyFact<R extends AnyRelation, Projection extends readonly string[]> = {
|
|
175
|
-
readonly [K in Projection[number] & keyof Fact<R>]: Fact<R>[K]
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
interface OffendingFact<Rels extends SchemaRelations> {
|
|
179
|
-
readonly relation: keyof Rels & string
|
|
180
|
-
readonly fact: Readonly<Record<string, FactValue>>
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
type ViolationBody<Rels extends SchemaRelations> = {
|
|
184
|
-
readonly canonical: string
|
|
185
|
-
readonly facts: readonly OffendingFact<Rels>[]
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
type ImpliedKeyViolation<Rels extends SchemaRelations> = ViolationBody<Rels> & {
|
|
189
|
-
readonly kind: "functionality"
|
|
190
|
-
readonly statement: undefined
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
type DeclaredKeyViolation<Rels extends SchemaRelations> = ViolationBody<Rels> & {
|
|
194
|
-
readonly kind: "functionality"
|
|
195
|
-
readonly statement: Statement
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
type ContainmentViolation<Rels extends SchemaRelations> = ViolationBody<Rels> & {
|
|
199
|
-
readonly kind: "containment"
|
|
200
|
-
readonly statement: Statement
|
|
201
|
-
readonly direction: "sourceUnsatisfied" | "targetRequired"
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
type MirrorViolation<Rels extends SchemaRelations> = ViolationBody<Rels> & {
|
|
205
|
-
readonly kind: "containment"
|
|
206
|
-
readonly statement: Statement
|
|
207
|
-
readonly direction: "sourceUnsatisfied" | "targetRequired"
|
|
208
|
-
readonly orientation: "written" | "mirrored"
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
type CapacityViolation<Rels extends SchemaRelations> = ViolationBody<Rels> & {
|
|
212
|
-
readonly kind: "capacity"
|
|
213
|
-
readonly statement: Statement
|
|
214
|
-
readonly measure: bigint
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
type Violation<Rels extends SchemaRelations> =
|
|
218
|
-
| ImpliedKeyViolation<Rels>
|
|
219
|
-
| DeclaredKeyViolation<Rels>
|
|
220
|
-
| ContainmentViolation<Rels>
|
|
221
|
-
| MirrorViolation<Rels>
|
|
222
|
-
| CapacityViolation<Rels>
|
|
21
|
+
import { wireParams } from "#query/run.ts"
|
|
22
|
+
import type { ParamsRecord } from "#query/scope.ts"
|
|
23
|
+
import type { Fact } from "#relation.ts"
|
|
24
|
+
import type { CompleteResult } from "#result.ts"
|
|
25
|
+
import { internalResult, makeCompleteResult } from "#result.ts"
|
|
26
|
+
import type { CellValue } from "#rows.ts"
|
|
27
|
+
import { factOfCells, keyCellsOf } from "#rows.ts"
|
|
28
|
+
import type { ExecutionPolicy, NativeRuntime } from "#runtime.ts"
|
|
29
|
+
import { nativeOperationWith, policyWire, runtimeHandle } from "#runtime.ts"
|
|
30
|
+
import type { CloseReport } from "#runtime-errors.ts"
|
|
31
|
+
import { DbError } from "#runtime-errors.ts"
|
|
32
|
+
import type { DirectoryHandle } from "#runtime-native.ts"
|
|
33
|
+
import { runtimeNative } from "#runtime-native.ts"
|
|
34
|
+
import type { AnySchema } from "#schema.ts"
|
|
35
|
+
import type { Key, QueryTemplate, Rel } from "#shape.ts"
|
|
223
36
|
|
|
224
37
|
/**
|
|
225
|
-
* The
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* `
|
|
229
|
-
*
|
|
38
|
+
* The chapter 35 core surface: `Db.create`/`Db.open`, scoped coherent
|
|
39
|
+
* `Snapshot`s, reusable `ExecutionSession`s, the shared `QueryReader`
|
|
40
|
+
* capability, one immutable final-state `apply`, bounded `inspect` and
|
|
41
|
+
* honest `close`. Effect-only: every method constructs a lazy effect; all
|
|
42
|
+
* native work runs on the ONE bounded runtime executor under an explicit
|
|
43
|
+
* `ExecutionPolicy`; resources are scoped with `CloseFailure`-defect
|
|
44
|
+
* finalizers. There is no Promise/sync/disposal twin, no transaction
|
|
45
|
+
* callback, no per-row fiber and no Proxy row anywhere.
|
|
46
|
+
*
|
|
47
|
+
* Database open is the managed two-step handshake: acquire the kernel-held
|
|
48
|
+
* directory owner and register its finalizer before the interruptible
|
|
49
|
+
* child-open step. `Db.open` NEVER creates on missing/error; `Db.create`
|
|
50
|
+
* refuses existing authority. Published log snapshots use this same
|
|
51
|
+
* `QueryReader` literally — no second reader or adapter.
|
|
230
52
|
*/
|
|
231
|
-
type AbandonedArm<R> = R extends Abandon<infer P> ? { readonly tag: "abandoned"; readonly abandoned: P } : never
|
|
232
53
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
readonly value: T
|
|
54
|
+
/** The core-local witness: catalog/store identity plus generation — never a log StateStamp. */
|
|
55
|
+
interface CoreWitness {
|
|
56
|
+
readonly store: string
|
|
237
57
|
readonly generation: bigint
|
|
238
58
|
}
|
|
239
59
|
|
|
240
|
-
type
|
|
241
|
-
| { readonly tag: "accepted"; readonly value: T }
|
|
242
|
-
| { readonly tag: "rejected"; readonly violations: readonly Violation<Rels>[] }
|
|
243
|
-
|
|
244
|
-
type WriteOutcome<Rels extends SchemaRelations, R> =
|
|
245
|
-
| { readonly tag: "accepted"; readonly value: Committed<Exclude<R, Abandon<unknown>>> }
|
|
246
|
-
| { readonly tag: "rejected"; readonly violations: readonly Violation<Rels>[] }
|
|
247
|
-
| AbandonedArm<R>
|
|
248
|
-
|
|
249
|
-
type WriteFromOutcome<Rels extends SchemaRelations, R> =
|
|
250
|
-
| WriteOutcome<Rels, R>
|
|
251
|
-
| { readonly tag: "moved"; readonly witnessed: bigint; readonly current: bigint }
|
|
252
|
-
|
|
253
|
-
type DeltaBuild<Rels extends SchemaRelations, R = void> = (tx: WriteTx<Rels>) => R
|
|
254
|
-
|
|
255
|
-
const abandonMark: unique symbol = Symbol("bumbledb.abandon")
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* The abandon sentinel {@link abandon} builds: returning one from a `write`
|
|
259
|
-
* or `writeFrom` callback rolls the transaction back WITHOUT
|
|
260
|
-
* committing (no empty commit is ever issued) and surfaces the payload as
|
|
261
|
-
* `{ tag: "abandoned", abandoned: payload }` (ruled 2026-07-23, R10 — the
|
|
262
|
-
* sentinel's contract is unconditional, whichever write verb received it).
|
|
263
|
-
*/
|
|
264
|
-
interface Abandon<P> {
|
|
265
|
-
readonly [abandonMark]: true
|
|
266
|
-
readonly payload: P
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function abandon<P>(payload: P): Abandon<P> {
|
|
270
|
-
return Object.freeze({ [abandonMark]: true as const, payload })
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
type AbandonedPayload<R> = R extends Abandon<infer P> ? P : never
|
|
274
|
-
|
|
275
|
-
function isAbandon<R>(value: R): value is R & Abandon<AbandonedPayload<R>> {
|
|
276
|
-
return typeof value === "object" && value !== null && abandonMark in value
|
|
277
|
-
}
|
|
60
|
+
type ApplyExpected = { readonly kind: "any" } | { readonly kind: "exact"; readonly at: CoreWitness }
|
|
278
61
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
sentinel: Abandon<AbandonedPayload<R>>
|
|
282
|
-
): outcome is { readonly tag: "abandoned"; readonly abandoned: AbandonedPayload<R> } & WriteOutcome<Rels, R> {
|
|
283
|
-
return isAbandon(sentinel) && outcome.abandoned === sentinel.payload
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function abandonedOutcome<Rels extends SchemaRelations, R>(
|
|
287
|
-
sentinel: Abandon<AbandonedPayload<R>>
|
|
288
|
-
): WriteOutcome<Rels, R> {
|
|
289
|
-
const outcome = Object.freeze({ tag: "abandoned" as const, abandoned: sentinel.payload })
|
|
290
|
-
if (!isAbandonedOutcome<Rels, R>(outcome, sentinel)) {
|
|
291
|
-
throw errors.new("bumbledb abandon outcome construction incomplete")
|
|
292
|
-
}
|
|
293
|
-
return outcome
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
interface WriteTx<Rels extends SchemaRelations> {
|
|
297
|
-
insert<R extends MemberRelation<Rels>>(relation: R, facts: CollectionWrite<R>): MutationReport
|
|
298
|
-
|
|
299
|
-
delete<R extends MemberRelation<Rels>>(relation: R, facts: Iterable<Fact<R>>): MutationReport
|
|
300
|
-
|
|
301
|
-
reserve<R extends MemberRelation<Rels>>(relation: R, field: FreshKeys<R> & string, count: bigint): FreshRange
|
|
302
|
-
|
|
303
|
-
contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean
|
|
304
|
-
|
|
305
|
-
get<R extends MemberRelation<Rels>>(relation: R, key: KeyFact<R>): Fact<R> | undefined
|
|
306
|
-
get<R extends MemberRelation<Rels>, const P extends readonly string[]>(
|
|
307
|
-
relation: R,
|
|
308
|
-
keyStatement: KeyStatement<R, P>,
|
|
309
|
-
key: DeclaredKeyFact<R, P>
|
|
310
|
-
): Fact<R> | undefined
|
|
311
|
-
}
|
|
62
|
+
/** Core work policy plus the expected-state intent (chapter 35). */
|
|
63
|
+
type ApplyOptions = ExecutionPolicy & { readonly expected: ApplyExpected }
|
|
312
64
|
|
|
313
|
-
|
|
65
|
+
type ApplyOutcome =
|
|
66
|
+
| { readonly kind: "accepted"; readonly witness: CoreWitness }
|
|
67
|
+
| { readonly kind: "no-change"; readonly witness: CoreWitness }
|
|
68
|
+
| { readonly kind: "invariant-rejected"; readonly violations: readonly Violation[] }
|
|
69
|
+
| { readonly kind: "moved"; readonly witnessed: CoreWitness; readonly current: CoreWitness }
|
|
314
70
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
interface ReadInstance<Rels extends SchemaRelations> {
|
|
71
|
+
/** Bounded database diagnostics: measurements, never retained rows. */
|
|
72
|
+
interface DbInspection {
|
|
73
|
+
readonly schemaId: SchemaId
|
|
320
74
|
readonly generation: bigint
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
get<R extends MemberRelation<Rels>>(relation: R, key: KeyFact<R>): Fact<R> | undefined
|
|
327
|
-
|
|
328
|
-
get<R extends MemberRelation<Rels>, const P extends readonly string[]>(
|
|
329
|
-
relation: R,
|
|
330
|
-
keyStatement: KeyStatement<R, P>,
|
|
331
|
-
key: DeclaredKeyFact<R, P>
|
|
332
|
-
): Fact<R> | undefined
|
|
333
|
-
|
|
334
|
-
contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean
|
|
335
|
-
|
|
336
|
-
execute<Row, Params extends ParamsRecord>(prepared: Prepared<Rels, Row, Params>, params: Params): Row[]
|
|
337
|
-
prepare<Row, Params extends ParamsRecord>(q: Query<Rels, Row, Params>): Prepared<Rels, Row, Params>
|
|
75
|
+
readonly mapBytes: bigint
|
|
76
|
+
readonly populatedBytes: bigint
|
|
77
|
+
readonly diskBytes: bigint
|
|
78
|
+
readonly residentEstimateBytes: bigint
|
|
79
|
+
readonly retainedOperations: bigint
|
|
338
80
|
}
|
|
339
81
|
|
|
340
|
-
const preparedTypes: unique symbol = Symbol("bumbledb.prepared.types")
|
|
341
|
-
|
|
342
82
|
/**
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
* `
|
|
347
|
-
*
|
|
348
|
-
* correctness — an unreclaimed plan is idle memory, and process exit frees
|
|
349
|
-
* everything).
|
|
83
|
+
* The one shared read capability (chapter 30): the same typed `get` and
|
|
84
|
+
* `execute` on a core snapshot and on a log published snapshot, so a
|
|
85
|
+
* cross-package read helper takes this interface with no adapter. Missing
|
|
86
|
+
* key is `Option.none`, never a fake I/O error or nullable row. It carries
|
|
87
|
+
* no writable authority — never a `Db`, an `apply`, or a raw transaction.
|
|
350
88
|
*/
|
|
351
|
-
interface
|
|
352
|
-
|
|
89
|
+
interface QueryReader<S extends AnySchema> {
|
|
90
|
+
get<R extends Rel<S>>(relation: R, key: Key<R>, work: ExecutionPolicy): Effect.Effect<Option.Option<Fact<R>>, DbError>
|
|
91
|
+
execute<P extends ParamsRecord, A>(
|
|
92
|
+
query: QueryTemplate<S, P, A>,
|
|
93
|
+
params: P,
|
|
94
|
+
work: ExecutionPolicy
|
|
95
|
+
): Effect.Effect<CompleteResult<A>, DbError, Scope.Scope>
|
|
353
96
|
}
|
|
354
97
|
|
|
355
|
-
interface
|
|
356
|
-
readonly
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* One delta transaction: builds the delta synchronously through `fn`,
|
|
361
|
-
* commits, and returns the domain outcome. A throw from `fn` aborts
|
|
362
|
-
* the delta (LMDB untouched) and rethrows wrapped. `fn` may decline to
|
|
363
|
-
* commit by returning {@link abandon}`(payload)`: the transaction rolls
|
|
364
|
-
* back — nothing is committed, not even an empty commit — and the
|
|
365
|
-
* outcome is `{ tag: "abandoned", abandoned: payload }`.
|
|
366
|
-
*/
|
|
367
|
-
write<R>(fn: (tx: WriteTx<Rels>) => SyncResult<R>): WriteOutcome<Rels, SyncResult<R>>
|
|
368
|
-
|
|
369
|
-
writeFrom<R>(witness: Witness<Rels>, fn: (tx: WriteTx<Rels>) => SyncResult<R>): WriteFromOutcome<Rels, SyncResult<R>>
|
|
370
|
-
/**
|
|
371
|
-
* blake3 over the canonical catalog enumeration — the replication
|
|
372
|
-
* equality oracle: equal digests imply identical judged content
|
|
373
|
-
* regardless of page layout or allocation history. Harness-tier
|
|
374
|
-
* (bounded sequential passes over the store), off any hot path.
|
|
375
|
-
*/
|
|
376
|
-
catalogDigest(): Uint8Array
|
|
377
|
-
/**
|
|
378
|
-
* Prepares a query value built against THIS schema (identity is the
|
|
379
|
-
* membership rule): lowers it to the engine IR, pins the plan, and
|
|
380
|
-
* returns the typed {@link Prepared} value. Every IR roster refusal —
|
|
381
|
-
* rule caps, rec roster, type rules — is the ENGINE's typed
|
|
382
|
-
* judgment and throws here carrying its message intact.
|
|
383
|
-
*/
|
|
384
|
-
prepare<Row, Params extends ParamsRecord>(q: Query<Rels, Row, Params>): Prepared<Rels, Row, Params>
|
|
98
|
+
interface Snapshot<S extends AnySchema> extends QueryReader<S> {
|
|
99
|
+
readonly witness: CoreWitness
|
|
100
|
+
session(work: ExecutionPolicy): Effect.Effect<ExecutionSession<S>, DbError, Scope.Scope>
|
|
101
|
+
close(): Effect.Effect<CloseReport>
|
|
385
102
|
}
|
|
386
103
|
|
|
387
|
-
interface
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
104
|
+
interface ExecutionSession<S extends AnySchema> {
|
|
105
|
+
execute<P extends ParamsRecord, A>(
|
|
106
|
+
query: QueryTemplate<S, P, A>,
|
|
107
|
+
params: P,
|
|
108
|
+
work: ExecutionPolicy
|
|
109
|
+
): Effect.Effect<CompleteResult<A>, DbError, Scope.Scope>
|
|
110
|
+
close(): Effect.Effect<CloseReport>
|
|
392
111
|
}
|
|
393
112
|
|
|
394
|
-
interface
|
|
395
|
-
readonly
|
|
396
|
-
|
|
113
|
+
interface Db<S extends AnySchema> {
|
|
114
|
+
readonly schemaId: SchemaId
|
|
115
|
+
snapshot(work: ExecutionPolicy): Effect.Effect<Snapshot<S>, DbError, Scope.Scope>
|
|
116
|
+
apply(changes: ChangeSet<S>, options: ApplyOptions): Effect.Effect<ApplyOutcome, DbError>
|
|
117
|
+
inspect(work: ExecutionPolicy): Effect.Effect<DbInspection, DbError>
|
|
118
|
+
close(): Effect.Effect<CloseReport>
|
|
397
119
|
}
|
|
398
120
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
readonly owner: string
|
|
402
|
-
readonly projection: readonly string[]
|
|
121
|
+
function refusal(operation: string, reason: "InvalidArgument" | "ClosedHandle"): DbError {
|
|
122
|
+
return new DbError({ operation, reason: { _tag: reason } })
|
|
403
123
|
}
|
|
404
124
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
readonly statement: Statement
|
|
408
|
-
readonly owner: string
|
|
409
|
-
readonly projection: readonly string[]
|
|
125
|
+
function witnessOf(wire: WitnessWire): CoreWitness {
|
|
126
|
+
return Object.freeze({ store: wire.store, generation: wire.generation })
|
|
410
127
|
}
|
|
411
128
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
fact: WireViolationFact
|
|
423
|
-
): OffendingFact<Rels> {
|
|
424
|
-
const declared = sealedFieldsOf(member)
|
|
425
|
-
const decoded: Record<string, FactValue> = {}
|
|
426
|
-
for (const cell of fact.fields) {
|
|
427
|
-
const cited = declared.find(function byName(candidate) {
|
|
428
|
-
return candidate.name === cell.name
|
|
429
|
-
})
|
|
430
|
-
const roster = rosterOf(cited?.field)
|
|
431
|
-
decoded[cell.name] =
|
|
432
|
-
roster !== undefined
|
|
433
|
-
? handleOf(`violation fact ${fact.relation} field ${cell.name}`, roster, cell.value)
|
|
434
|
-
: cell.value
|
|
435
|
-
}
|
|
436
|
-
return Object.freeze({ relation, fact: Object.freeze(decoded) })
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function violationFromEntry<Rels extends SchemaRelations>(
|
|
440
|
-
entry: StatementEntry,
|
|
441
|
-
wire: WireViolation,
|
|
442
|
-
facts: readonly OffendingFact<Rels>[]
|
|
443
|
-
): Violation<Rels> {
|
|
444
|
-
const canonical = wire.canonical
|
|
445
|
-
if (entry.kind === "functionality") {
|
|
446
|
-
if (!("statement" in entry)) {
|
|
447
|
-
return Object.freeze({ kind: "functionality", statement: undefined, canonical, facts })
|
|
448
|
-
}
|
|
449
|
-
return Object.freeze({ kind: "functionality", statement: entry.statement, canonical, facts })
|
|
450
|
-
}
|
|
451
|
-
if (entry.kind === "capacity") {
|
|
452
|
-
if (wire.kind !== "capacity") {
|
|
453
|
-
throw errors.new(`bumbledb violation ${wire.statementId} is a capacity slot without a measure`)
|
|
454
|
-
}
|
|
455
|
-
return Object.freeze({
|
|
456
|
-
kind: "capacity",
|
|
457
|
-
statement: entry.statement,
|
|
458
|
-
canonical,
|
|
459
|
-
measure: wire.measure,
|
|
460
|
-
facts
|
|
461
|
-
})
|
|
462
|
-
}
|
|
463
|
-
if (wire.kind !== "containment") {
|
|
464
|
-
throw errors.new(`bumbledb violation ${wire.statementId} is a containment slot without a direction`)
|
|
465
|
-
}
|
|
466
|
-
if (entry.kind === "mirrors") {
|
|
467
|
-
return Object.freeze({
|
|
468
|
-
kind: "containment",
|
|
469
|
-
statement: entry.statement,
|
|
470
|
-
canonical,
|
|
471
|
-
direction: wire.direction,
|
|
472
|
-
orientation: entry.orientation,
|
|
473
|
-
facts
|
|
474
|
-
})
|
|
475
|
-
}
|
|
476
|
-
return Object.freeze({
|
|
477
|
-
kind: "containment",
|
|
478
|
-
statement: entry.statement,
|
|
479
|
-
canonical,
|
|
480
|
-
direction: wire.direction,
|
|
481
|
-
facts
|
|
482
|
-
})
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function materializedEntries(theory: AnySchema): StatementEntry[] {
|
|
486
|
-
const entries = impliedKeyEntries(theory)
|
|
487
|
-
for (const statement of theory.statements) {
|
|
488
|
-
entries.push(...declaredEntries(statement))
|
|
489
|
-
}
|
|
490
|
-
return entries
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
function impliedKeyEntries(theory: AnySchema): StatementEntry[] {
|
|
494
|
-
const entries: StatementEntry[] = []
|
|
495
|
-
for (const member of Object.values(theory.relations)) {
|
|
496
|
-
if (isClosedMember(member)) {
|
|
497
|
-
continue
|
|
498
|
-
}
|
|
499
|
-
for (const declared of member.data.fields) {
|
|
500
|
-
if (isFreshField(declared.field)) {
|
|
501
|
-
entries.push({
|
|
502
|
-
kind: "functionality",
|
|
503
|
-
owner: member.name,
|
|
504
|
-
projection: [declared.name]
|
|
505
|
-
})
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
for (const member of Object.values(theory.relations)) {
|
|
510
|
-
if (isClosedMember(member)) {
|
|
511
|
-
entries.push({
|
|
512
|
-
kind: "functionality",
|
|
513
|
-
owner: member.name,
|
|
514
|
-
projection: ["id"]
|
|
515
|
-
})
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
return entries
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
function declaredEntries(statement: Statement): StatementEntry[] {
|
|
522
|
-
const data = statement.data
|
|
523
|
-
switch (data.kind) {
|
|
524
|
-
case "key": {
|
|
525
|
-
return [
|
|
526
|
-
{
|
|
527
|
-
kind: "functionality",
|
|
528
|
-
statement,
|
|
529
|
-
owner: data.owner.name,
|
|
530
|
-
projection: data.projection
|
|
531
|
-
}
|
|
532
|
-
]
|
|
533
|
-
}
|
|
534
|
-
case "containment": {
|
|
535
|
-
return [{ kind: "containment", statement }]
|
|
536
|
-
}
|
|
537
|
-
case "mirrors": {
|
|
538
|
-
return [
|
|
539
|
-
{ kind: "mirrors", statement, orientation: "written" },
|
|
540
|
-
{ kind: "mirrors", statement, orientation: "mirrored" }
|
|
541
|
-
]
|
|
542
|
-
}
|
|
543
|
-
case "capacity": {
|
|
544
|
-
return [{ kind: "capacity", statement }]
|
|
545
|
-
}
|
|
129
|
+
function outcomeOf(wire: ApplyOutcomeWire): ApplyOutcome {
|
|
130
|
+
switch (wire.tag) {
|
|
131
|
+
case "accepted":
|
|
132
|
+
return Object.freeze({ kind: "accepted", witness: witnessOf(wire.witness) })
|
|
133
|
+
case "no-change":
|
|
134
|
+
return Object.freeze({ kind: "no-change", witness: witnessOf(wire.witness) })
|
|
135
|
+
case "invariant-rejected":
|
|
136
|
+
return Object.freeze({ kind: "invariant-rejected", violations: wire.violations })
|
|
137
|
+
case "moved":
|
|
138
|
+
return Object.freeze({ kind: "moved", witnessed: witnessOf(wire.witnessed), current: witnessOf(wire.current) })
|
|
546
139
|
}
|
|
547
140
|
}
|
|
548
141
|
|
|
549
142
|
/**
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
* be a runtime probe on the returned value.
|
|
143
|
+
* Validates the query template's schema binding (identity, the membership
|
|
144
|
+
* rule) and lowers it to IR plus wire params. Pure host preparation; the
|
|
145
|
+
* engine's IR validation under budget remains the authority.
|
|
554
146
|
*/
|
|
555
|
-
function
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
`bumbledb manifest drift: statement ${statement.id} is ${statement.kind}, the SDK mirror at ${index} expected ${entry?.kind}`
|
|
600
|
-
)
|
|
601
|
-
}
|
|
602
|
-
const engineKind = entry.kind === "mirrors" ? "containment" : entry.kind
|
|
603
|
-
if (engineKind !== statement.kind) {
|
|
604
|
-
throw errors.new(
|
|
605
|
-
`bumbledb manifest drift: statement ${statement.id} is ${statement.kind}, the SDK mirror at ${index} expected ${engineKind}`
|
|
606
|
-
)
|
|
607
|
-
}
|
|
608
|
-
})
|
|
609
|
-
const relations = new Map<string, RelationEntry>()
|
|
610
|
-
for (const relation of manifest.relations) {
|
|
611
|
-
const member = theory.relations[relation.name]
|
|
612
|
-
if (member === undefined) {
|
|
613
|
-
throw errors.new(`bumbledb manifest drift: relation ${relation.name} is not in schema ${theory.name}`)
|
|
614
|
-
}
|
|
615
|
-
const fieldIds = new Map<string, number>()
|
|
616
|
-
for (const field of relation.fields) {
|
|
617
|
-
fieldIds.set(field.name, field.id)
|
|
618
|
-
}
|
|
619
|
-
sealedFieldsOf(member).forEach(function verifyField(declared, fieldOrdinal) {
|
|
620
|
-
if (fieldIds.get(declared.name) !== fieldOrdinal) {
|
|
621
|
-
throw errors.new(
|
|
622
|
-
`bumbledb manifest drift: ${relation.name}.${declared.name} has engine field id ${fieldIds.get(declared.name)}, its sealed ordinal is ${fieldOrdinal}`
|
|
623
|
-
)
|
|
624
|
-
}
|
|
625
|
-
})
|
|
626
|
-
let primaryKey: PrimaryKey | undefined
|
|
627
|
-
entries.forEach(function firstOwnedKey(entry, index) {
|
|
628
|
-
if (primaryKey === undefined && entry.kind === "functionality" && entry.owner === relation.name) {
|
|
629
|
-
primaryKey = Object.freeze({ statementId: index, projection: entry.projection })
|
|
630
|
-
}
|
|
631
|
-
})
|
|
632
|
-
relations.set(relation.name, Object.freeze({ id: relation.id, member, fieldIds, primaryKey }))
|
|
633
|
-
}
|
|
634
|
-
Object.keys(theory.relations).forEach(function verifyRelation(name, ordinal) {
|
|
635
|
-
const entry = relations.get(name)
|
|
636
|
-
if (entry === undefined) {
|
|
637
|
-
throw errors.new(`bumbledb manifest drift: schema relation ${name} is not in the manifest`)
|
|
638
|
-
}
|
|
639
|
-
if (entry.id !== ordinal) {
|
|
640
|
-
throw errors.new(
|
|
641
|
-
`bumbledb manifest drift: relation ${name} has engine id ${entry.id}, its declaration ordinal is ${ordinal} — query lowering depends on declaration order = ids`
|
|
147
|
+
function preparedOf<S extends AnySchema>(
|
|
148
|
+
theory: S,
|
|
149
|
+
query: AnyQuery,
|
|
150
|
+
params: Readonly<Record<string, unknown>>
|
|
151
|
+
): {
|
|
152
|
+
readonly ir: ReturnType<typeof lowerQuery>
|
|
153
|
+
readonly wire: ReturnType<typeof wireParams>
|
|
154
|
+
readonly finds: AnyQuery["data"]["finds"]
|
|
155
|
+
} {
|
|
156
|
+
if (query.schema !== theory) {
|
|
157
|
+
throw refusal("QueryReader.execute", "InvalidArgument")
|
|
158
|
+
}
|
|
159
|
+
const ir = lowerQuery(query)
|
|
160
|
+
const wire = wireParams(query.data.params, params)
|
|
161
|
+
return { ir, wire, finds: query.data.finds }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface SnapshotState<S extends AnySchema> {
|
|
165
|
+
readonly theory: S
|
|
166
|
+
readonly handle: SnapshotHandle
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function executeOn<S extends AnySchema, A>(
|
|
170
|
+
state: SnapshotState<S>,
|
|
171
|
+
session: SessionHandle | undefined,
|
|
172
|
+
query: AnyQuery,
|
|
173
|
+
params: Readonly<Record<string, unknown>>,
|
|
174
|
+
work: ExecutionPolicy
|
|
175
|
+
): Effect.Effect<CompleteResult<A>, DbError, Scope.Scope> {
|
|
176
|
+
return Effect.acquireRelease(
|
|
177
|
+
Effect.gen(function* () {
|
|
178
|
+
const prepared = yield* Effect.try({
|
|
179
|
+
try: () => preparedOf(state.theory, query, params),
|
|
180
|
+
catch: (cause) => (cause instanceof DbError ? cause : refusal("QueryReader.execute", "InvalidArgument"))
|
|
181
|
+
})
|
|
182
|
+
const wire = policyWire(work, "QueryReader.execute")
|
|
183
|
+
const handle = yield* nativeOperationWith(
|
|
184
|
+
"QueryReader.execute",
|
|
185
|
+
(callback) =>
|
|
186
|
+
session === undefined
|
|
187
|
+
? dbNative.runtimeSnapshotExecute(state.handle, wire, prepared.ir, prepared.wire, callback)
|
|
188
|
+
: dbNative.runtimeSessionExecute(session, wire, prepared.ir, prepared.wire, callback),
|
|
189
|
+
dbNative.runtimeResultTake,
|
|
190
|
+
(value) => value
|
|
642
191
|
)
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
const fieldIds = new Map<string, number>()
|
|
657
|
-
sealedFieldsOf(member).forEach(function byField(declared, fieldOrdinal) {
|
|
658
|
-
fieldIds.set(declared.name, fieldOrdinal)
|
|
659
|
-
})
|
|
660
|
-
let primaryKey: PrimaryKey | undefined
|
|
661
|
-
entries.forEach(function firstOwnedKey(entry, index) {
|
|
662
|
-
if (primaryKey === undefined && entry.kind === "functionality" && entry.owner === name) {
|
|
663
|
-
primaryKey = Object.freeze({ statementId: index, projection: entry.projection })
|
|
664
|
-
}
|
|
665
|
-
})
|
|
666
|
-
relations.set(name, Object.freeze({ id: ordinal, member, fieldIds, primaryKey }))
|
|
667
|
-
})
|
|
668
|
-
return Object.freeze({ relations, statements: Object.freeze(entries) })
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
interface PointReads {
|
|
672
|
-
contains(relationId: number, row: readonly FactValue[]): boolean
|
|
673
|
-
get(relationId: number, statementId: number, key: readonly FactValue[]): FactValue[] | null
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
interface InstanceState {
|
|
677
|
-
readonly handle: InstanceHandle
|
|
678
|
-
live: boolean
|
|
679
|
-
readonly owner: object
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
const instanceStates = new WeakMap<object, InstanceState>()
|
|
683
|
-
|
|
684
|
-
interface WitnessState {
|
|
685
|
-
readonly handle: WitnessHandle
|
|
686
|
-
spent: boolean
|
|
687
|
-
readonly owner: object
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
const witnessStates = new WeakMap<object, WitnessState>()
|
|
691
|
-
|
|
692
|
-
const witnessReclaimer = new FinalizationRegistry<WitnessHandle>(function reclaimWitness(handle) {
|
|
693
|
-
const closed = errors.trySync(function closeWitness() {
|
|
694
|
-
native.witnessClose(handle)
|
|
695
|
-
})
|
|
696
|
-
if (closed.error) {
|
|
697
|
-
return
|
|
698
|
-
}
|
|
699
|
-
})
|
|
700
|
-
|
|
701
|
-
interface PreparedPlan {
|
|
702
|
-
readonly handle: PreparedHandle
|
|
703
|
-
readonly owner: object
|
|
704
|
-
readonly params: readonly ParamEntry[]
|
|
705
|
-
readonly finds: readonly FindColumn[]
|
|
192
|
+
return makeCompleteResult<A>(handle, prepared.finds)
|
|
193
|
+
}),
|
|
194
|
+
(result) =>
|
|
195
|
+
Effect.suspend(() => {
|
|
196
|
+
const internal = internalResultHandle(result)
|
|
197
|
+
if (internal === undefined) {
|
|
198
|
+
return Effect.void
|
|
199
|
+
}
|
|
200
|
+
return releaseOwner("CompleteResult.close", (callback) => dbNative.runtimeResultClose(internal, callback))
|
|
201
|
+
}),
|
|
202
|
+
{ interruptible: true }
|
|
203
|
+
)
|
|
706
204
|
}
|
|
707
205
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const planReclaimer = new FinalizationRegistry<PreparedHandle>(function reclaimPlan(handle) {
|
|
711
|
-
const closed = errors.trySync(function closePlan() {
|
|
712
|
-
native.preparedClose(handle)
|
|
713
|
-
})
|
|
714
|
-
if (closed.error) {
|
|
715
|
-
return
|
|
716
|
-
}
|
|
717
|
-
})
|
|
718
|
-
|
|
719
|
-
const ErrAsyncCallback = errors.new(
|
|
720
|
-
"bumbledb asyncCallback: a read or write callback returned a thenable — the callback is synchronous"
|
|
721
|
-
)
|
|
722
|
-
const ErrSpentHandle = errors.new("bumbledb spentHandle: a consumed builder, instance, or witness was used")
|
|
723
|
-
const ErrUseAfterScope = errors.new(
|
|
724
|
-
"bumbledb useAfterScope: a stashed read instance or write transaction was used after its callback returned"
|
|
725
|
-
)
|
|
726
|
-
const ErrForeignPrepared = errors.new("bumbledb foreignPrepared: a prepared query met a foreign instance")
|
|
727
|
-
const ErrForeignWitness = errors.new("bumbledb foreignWitness: a witness met a foreign store")
|
|
728
|
-
|
|
729
|
-
interface CatalogNative {
|
|
730
|
-
scan(relationId: number): FactValue[][]
|
|
731
|
-
count(relationId: number): bigint
|
|
732
|
-
contains(relationId: number, values: readonly FactValue[]): boolean
|
|
733
|
-
get(relationId: number, statementId: number, keyValues: readonly FactValue[]): FactValue[] | null
|
|
734
|
-
prepare(query: ReturnType<typeof lowerQuery>): ReturnType<typeof native.instancePrepare>
|
|
735
|
-
execute(prepared: PreparedHandle, params: ReturnType<typeof wireParams>): FactValue[][]
|
|
206
|
+
function internalResultHandle(result: object) {
|
|
207
|
+
return internalResult(result)?.handle
|
|
736
208
|
}
|
|
737
209
|
|
|
738
|
-
function
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
}
|
|
758
|
-
function contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean {
|
|
759
|
-
assertLive()
|
|
760
|
-
const entry = ordinaryEntry(tables, theory, relation)
|
|
761
|
-
return bridged("bumbledb instance contains", function readContains() {
|
|
762
|
-
return ops.contains(entry.id, rowOf(relation.data, recordOf(fact)))
|
|
210
|
+
function getOn<S extends AnySchema, R extends Rel<S>>(
|
|
211
|
+
state: SnapshotState<S>,
|
|
212
|
+
relation: R,
|
|
213
|
+
key: Key<R>,
|
|
214
|
+
work: ExecutionPolicy
|
|
215
|
+
): Effect.Effect<Option.Option<Fact<R>>, DbError> {
|
|
216
|
+
return Effect.gen(function* () {
|
|
217
|
+
if (state.theory.relations[relation.name] !== relation) {
|
|
218
|
+
return yield* Effect.fail(refusal("QueryReader.get", "InvalidArgument"))
|
|
219
|
+
}
|
|
220
|
+
const tables = schemaTables(state.theory)
|
|
221
|
+
const relationId = tables.relationIds.get(relation.name)
|
|
222
|
+
const primary = tables.primaryKeys.get(relation.name)
|
|
223
|
+
if (relationId === undefined || primary === undefined) {
|
|
224
|
+
return yield* Effect.fail(refusal("QueryReader.get", "InvalidArgument"))
|
|
225
|
+
}
|
|
226
|
+
const cells = yield* Effect.try({
|
|
227
|
+
try: () => keyCellsOf(relation.data, primary.projection, key as Readonly<Record<string, unknown>>),
|
|
228
|
+
catch: (cause) => (cause instanceof DbError ? cause : refusal("QueryReader.get", "InvalidArgument"))
|
|
763
229
|
})
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
const row = bridged("bumbledb instance get", function readGet() {
|
|
778
|
-
return ops.get(entry.id, selected.statementId, keyRowOf(relation.data, selected.projection, recordOf(key)))
|
|
779
|
-
})
|
|
780
|
-
return row === null ? undefined : factOf(relation, row)
|
|
781
|
-
},
|
|
782
|
-
function byPrimary(key) {
|
|
783
|
-
const primaryKey = entry.primaryKey
|
|
784
|
-
if (primaryKey === undefined) {
|
|
785
|
-
throw errors.new(
|
|
786
|
-
`relation ${relation.name} has no candidate key — keyed get requires a fresh field or a declared key statement`
|
|
787
|
-
)
|
|
788
|
-
}
|
|
789
|
-
const row = bridged("bumbledb instance get", function readGet() {
|
|
790
|
-
return ops.get(
|
|
791
|
-
entry.id,
|
|
792
|
-
primaryKey.statementId,
|
|
793
|
-
keyRowOf(relation.data, primaryKey.projection, recordOf(key))
|
|
794
|
-
)
|
|
795
|
-
})
|
|
796
|
-
return row === null ? undefined : factOf(relation, row)
|
|
797
|
-
}
|
|
230
|
+
const row = yield* nativeOperationWith(
|
|
231
|
+
"QueryReader.get",
|
|
232
|
+
(callback) =>
|
|
233
|
+
dbNative.runtimeSnapshotGet(
|
|
234
|
+
state.handle,
|
|
235
|
+
policyWire(work, "QueryReader.get"),
|
|
236
|
+
relationId,
|
|
237
|
+
primary.statementId,
|
|
238
|
+
cells,
|
|
239
|
+
callback
|
|
240
|
+
),
|
|
241
|
+
dbNative.runtimeRowTake,
|
|
242
|
+
(value) => value
|
|
798
243
|
)
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
assertLive()
|
|
802
|
-
const entry = ordinaryEntry(tables, theory, relation)
|
|
803
|
-
const rows = bridged("bumbledb instance scan", function readScan() {
|
|
804
|
-
return ops.scan(entry.id)
|
|
805
|
-
})
|
|
806
|
-
return rows.map(function decodeRow(row) {
|
|
807
|
-
return factOf(relation, row)
|
|
808
|
-
})
|
|
809
|
-
}
|
|
810
|
-
function count<R extends MemberRelation<Rels>>(relation: R): bigint {
|
|
811
|
-
assertLive()
|
|
812
|
-
const entry = ordinaryEntry(tables, theory, relation)
|
|
813
|
-
return bridged("bumbledb instance count", function readCount() {
|
|
814
|
-
return ops.count(entry.id)
|
|
815
|
-
})
|
|
816
|
-
}
|
|
817
|
-
function execute<Row, Params extends ParamsRecord>(prepared: Prepared<Rels, Row, Params>, params: Params): Row[] {
|
|
818
|
-
assertLive()
|
|
819
|
-
const plan = planOf(prepared)
|
|
820
|
-
const wire = wireParams(plan.params, recordOf(params))
|
|
821
|
-
const rows = bridged("execute bumbledb prepared query", function callExecute() {
|
|
822
|
-
return ops.execute(plan.handle, wire)
|
|
823
|
-
})
|
|
824
|
-
return decodeAnswers<Row>(plan.finds, rows)
|
|
825
|
-
}
|
|
826
|
-
function prepare<Row, Params extends ParamsRecord>(q: Query<Rels, Row, Params>): Prepared<Rels, Row, Params> {
|
|
827
|
-
assertLive()
|
|
828
|
-
if (q.schema !== theory) {
|
|
829
|
-
throw errors.new(
|
|
830
|
-
`query was built against schema ${q.schema.name}, not the identical schema value this store opened with — schema identity is the membership rule`
|
|
831
|
-
)
|
|
832
|
-
}
|
|
833
|
-
const queryIr = lowerQuery(q)
|
|
834
|
-
const outcome = bridged("prepare bumbledb query", function callPrepare() {
|
|
835
|
-
return ops.prepare(queryIr)
|
|
836
|
-
})
|
|
837
|
-
if (!outcome.ok) {
|
|
838
|
-
throwPrepareRefusal(outcome.message)
|
|
244
|
+
if (row === null) {
|
|
245
|
+
return Option.none<Fact<R>>()
|
|
839
246
|
}
|
|
840
|
-
|
|
841
|
-
preparedPlans.set(
|
|
842
|
-
prepared,
|
|
843
|
-
Object.freeze({
|
|
844
|
-
handle: outcome.prepared,
|
|
845
|
-
owner,
|
|
846
|
-
params: q.data.params,
|
|
847
|
-
finds: q.data.finds
|
|
848
|
-
})
|
|
849
|
-
)
|
|
850
|
-
planReclaimer.register(prepared, outcome.prepared)
|
|
851
|
-
return prepared
|
|
852
|
-
}
|
|
853
|
-
return { scan, count, get, contains, execute, prepare }
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
function ordinaryEntry(tables: Tables, theory: AnySchema, relation: AnyRelation): RelationEntry {
|
|
857
|
-
const entry = tables.relations.get(relation.name)
|
|
858
|
-
if (entry === undefined || entry.member !== relation) {
|
|
859
|
-
throw errors.new(`relation ${relation.name} is not a member of schema ${theory.name}`)
|
|
860
|
-
}
|
|
861
|
-
if (isClosedMember(relation)) {
|
|
862
|
-
throw errors.new(
|
|
863
|
-
`relation ${relation.name} is closed — its extension is schema data (axioms), never scanned or written`
|
|
864
|
-
)
|
|
865
|
-
}
|
|
866
|
-
return entry
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
function declaredKeyOf(tables: Tables, theory: AnySchema, relation: AnyRelation, statement: Statement): PrimaryKey {
|
|
870
|
-
const statementId = tables.statements.findIndex(function byIdentity(candidate) {
|
|
871
|
-
return "statement" in candidate && candidate.statement === statement
|
|
247
|
+
return Option.some(factOfCells(relation, row as readonly CellValue[]))
|
|
872
248
|
})
|
|
873
|
-
const entry = tables.statements[statementId]
|
|
874
|
-
if (entry === undefined) {
|
|
875
|
-
throw errors.new(
|
|
876
|
-
`keyed get statement is not a declared statement of schema ${theory.name} — statement identity is the membership rule`
|
|
877
|
-
)
|
|
878
|
-
}
|
|
879
|
-
if (entry.kind !== "functionality") {
|
|
880
|
-
throw errors.new("keyed get takes a key() statement — containments and capacity statements key nothing")
|
|
881
|
-
}
|
|
882
|
-
if (entry.owner !== relation.name) {
|
|
883
|
-
throw errors.new(
|
|
884
|
-
`keyed get statement keys ${entry.owner}, not ${relation.name} — the statement must be a declared key of the relation it reads`
|
|
885
|
-
)
|
|
886
|
-
}
|
|
887
|
-
return Object.freeze({ statementId, projection: entry.projection })
|
|
888
249
|
}
|
|
889
250
|
|
|
890
|
-
function
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
)
|
|
896
|
-
|
|
897
|
-
assertLive()
|
|
898
|
-
const entry = ordinaryEntry(tables, theory, relation)
|
|
899
|
-
return reads.contains(entry.id, rowOf(relation.data, recordOf(fact)))
|
|
900
|
-
}
|
|
901
|
-
function readThroughKey<R extends MemberRelation<Rels>>(
|
|
902
|
-
relation: R,
|
|
903
|
-
entry: RelationEntry,
|
|
904
|
-
selected: PrimaryKey,
|
|
905
|
-
key: Readonly<Record<string, unknown>>
|
|
906
|
-
): Fact<R> | undefined {
|
|
907
|
-
const row = reads.get(entry.id, selected.statementId, keyRowOf(relation.data, selected.projection, key))
|
|
908
|
-
if (row === null) {
|
|
909
|
-
return undefined
|
|
251
|
+
function makeSession<S extends AnySchema>(state: SnapshotState<S>, handle: SessionHandle): ExecutionSession<S> {
|
|
252
|
+
const session: ExecutionSession<S> = {
|
|
253
|
+
execute(query, params, work) {
|
|
254
|
+
return executeOn(state, handle, query, params, work)
|
|
255
|
+
},
|
|
256
|
+
close() {
|
|
257
|
+
return drainClose("ExecutionSession.close", (callback) => dbNative.runtimeSessionClose(handle, callback))
|
|
910
258
|
}
|
|
911
|
-
return factOf(relation, row)
|
|
912
259
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
keyStatement: KeyStatement<R, P>,
|
|
917
|
-
key: DeclaredKeyFact<R, P>
|
|
918
|
-
): Fact<R> | undefined
|
|
919
|
-
function get<R extends MemberRelation<Rels>, const P extends readonly string[]>(
|
|
920
|
-
relation: R,
|
|
921
|
-
keyOrStatement: KeyFact<R> | KeyStatement<R, P>,
|
|
922
|
-
declaredKey?: DeclaredKeyFact<R, P>
|
|
923
|
-
): Fact<R> | undefined {
|
|
924
|
-
assertLive()
|
|
925
|
-
const entry = ordinaryEntry(tables, theory, relation)
|
|
926
|
-
return selectKeyRead(
|
|
927
|
-
keyOrStatement,
|
|
928
|
-
declaredKey,
|
|
929
|
-
function byStatement(statement, key) {
|
|
930
|
-
return readThroughKey(relation, entry, declaredKeyOf(tables, theory, relation, statement), recordOf(key))
|
|
931
|
-
},
|
|
932
|
-
function byPrimary(key) {
|
|
933
|
-
const primaryKey = entry.primaryKey
|
|
934
|
-
if (primaryKey === undefined) {
|
|
935
|
-
throw errors.new(
|
|
936
|
-
`relation ${relation.name} has no candidate key — keyed get requires a fresh field or a declared key statement`
|
|
937
|
-
)
|
|
938
|
-
}
|
|
939
|
-
return readThroughKey(relation, entry, primaryKey, recordOf(key))
|
|
940
|
-
}
|
|
941
|
-
)
|
|
942
|
-
}
|
|
943
|
-
return { contains, get }
|
|
260
|
+
Object.freeze(session)
|
|
261
|
+
sessionHandles.set(session, handle)
|
|
262
|
+
return session
|
|
944
263
|
}
|
|
945
264
|
|
|
946
|
-
function
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
const state: InstanceState = { handle: nativeHandle, live: true, owner }
|
|
953
|
-
function assertLive(): void {
|
|
954
|
-
if (!state.live) {
|
|
955
|
-
throw errors.wrap(
|
|
956
|
-
ErrUseAfterScope,
|
|
957
|
-
"bumbledb read instance is invalidated — its owning callback already returned"
|
|
958
|
-
)
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
const methods = catalogMethods(theory, tables, owner, assertLive, {
|
|
962
|
-
scan(relationId) {
|
|
963
|
-
return native.instanceScan(state.handle, relationId)
|
|
265
|
+
function makeSnapshot<S extends AnySchema>(theory: S, handle: SnapshotHandle, witness: CoreWitness): Snapshot<S> {
|
|
266
|
+
const state: SnapshotState<S> = { theory, handle }
|
|
267
|
+
const snapshot: Snapshot<S> = {
|
|
268
|
+
witness,
|
|
269
|
+
get(relation, key, work) {
|
|
270
|
+
return getOn(state, relation, key, work)
|
|
964
271
|
},
|
|
965
|
-
|
|
966
|
-
return
|
|
272
|
+
execute(query, params, work) {
|
|
273
|
+
return executeOn(state, undefined, query, params, work)
|
|
967
274
|
},
|
|
968
|
-
|
|
969
|
-
return
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
275
|
+
session(work) {
|
|
276
|
+
return Effect.acquireRelease(
|
|
277
|
+
nativeOperationWith(
|
|
278
|
+
"Snapshot.session",
|
|
279
|
+
(callback) => dbNative.runtimeSnapshotSession(handle, policyWire(work, "Snapshot.session"), callback),
|
|
280
|
+
dbNative.runtimeSessionTake,
|
|
281
|
+
(value) => makeSession(state, value)
|
|
282
|
+
),
|
|
283
|
+
(session) =>
|
|
284
|
+
Effect.suspend(() =>
|
|
285
|
+
releaseOwner("ExecutionSession.close", (callback) =>
|
|
286
|
+
dbNative.runtimeSessionClose(sessionHandles.get(session) ?? missingSession(), callback)
|
|
287
|
+
)
|
|
288
|
+
),
|
|
289
|
+
{ interruptible: true }
|
|
290
|
+
)
|
|
976
291
|
},
|
|
977
|
-
|
|
978
|
-
return
|
|
292
|
+
close() {
|
|
293
|
+
return drainClose("Snapshot.close", (callback) => dbNative.runtimeSnapshotClose(handle, callback))
|
|
979
294
|
}
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
return bridged("bumbledb instance generation", function readGeneration() {
|
|
985
|
-
return native.instanceGeneration(state.handle)
|
|
986
|
-
})
|
|
987
|
-
},
|
|
988
|
-
...methods
|
|
989
|
-
})
|
|
990
|
-
instanceStates.set(instance, state)
|
|
991
|
-
return instance
|
|
295
|
+
}
|
|
296
|
+
Object.freeze(snapshot)
|
|
297
|
+
snapshotHandles.set(snapshot, handle)
|
|
298
|
+
return snapshot
|
|
992
299
|
}
|
|
993
300
|
|
|
994
|
-
|
|
995
|
-
const tables = tablesOf(theory, manifest)
|
|
996
|
-
/** This store's identity token: read scopes and prepared values carry it, so cross-store use is a typed refusal. */
|
|
997
|
-
const owner = Object.freeze({})
|
|
301
|
+
const sessionHandles = new WeakMap<object, SessionHandle>()
|
|
998
302
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
function violationOf(wire: WireViolation): Violation<Rels> {
|
|
1004
|
-
const entry = tables.statements[wire.statementId]
|
|
1005
|
-
if (entry === undefined) {
|
|
1006
|
-
throw errors.new(`bumbledb violation cites unknown statement id ${wire.statementId}`)
|
|
1007
|
-
}
|
|
1008
|
-
const facts = Object.freeze(
|
|
1009
|
-
wire.facts.map(function offending(fact) {
|
|
1010
|
-
const rel = tables.relations.get(fact.relation)
|
|
1011
|
-
if (rel === undefined || !isMemberName(fact.relation)) {
|
|
1012
|
-
throw errors.new(`bumbledb violation cites unknown relation ${fact.relation}`)
|
|
1013
|
-
}
|
|
1014
|
-
return decodeOffendingFact<Rels>(rel.member, fact.relation, fact)
|
|
1015
|
-
})
|
|
1016
|
-
)
|
|
1017
|
-
return violationFromEntry(entry, wire, facts)
|
|
1018
|
-
}
|
|
303
|
+
function missingSession(): never {
|
|
304
|
+
throw new DbError({ operation: "ExecutionSession.close", reason: { _tag: "Internal" } })
|
|
305
|
+
}
|
|
1019
306
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
307
|
+
interface DbState {
|
|
308
|
+
readonly theory: AnySchema
|
|
309
|
+
readonly db: DbHandle
|
|
310
|
+
readonly directory: DirectoryHandle
|
|
311
|
+
readonly schemaId: SchemaId
|
|
312
|
+
}
|
|
1026
313
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
keyStatement: KeyStatement<R, P>,
|
|
1043
|
-
key: DeclaredKeyFact<R, P>
|
|
1044
|
-
): Fact<R> | undefined
|
|
1045
|
-
function get<R extends MemberRelation<Rels>, const P extends readonly string[]>(
|
|
1046
|
-
relation: R,
|
|
1047
|
-
keyOrStatement: KeyFact<R> | KeyStatement<R, P>,
|
|
1048
|
-
declaredKey?: DeclaredKeyFact<R, P>
|
|
1049
|
-
): Fact<R> | undefined {
|
|
1050
|
-
assertLive()
|
|
1051
|
-
const entry = ordinaryEntry(tables, theory, relation)
|
|
1052
|
-
return selectKeyRead(
|
|
1053
|
-
keyOrStatement,
|
|
1054
|
-
declaredKey,
|
|
1055
|
-
function byStatement(statement, key) {
|
|
1056
|
-
return readThroughKey(relation, entry, declaredKeyOf(tables, theory, relation, statement), recordOf(key))
|
|
1057
|
-
},
|
|
1058
|
-
function byPrimary(key) {
|
|
1059
|
-
const primaryKey = entry.primaryKey
|
|
1060
|
-
if (primaryKey === undefined) {
|
|
1061
|
-
throw errors.new(
|
|
1062
|
-
`relation ${relation.name} has no candidate key — keyed get requires a fresh field or a declared key statement`
|
|
314
|
+
function makeDb<S extends AnySchema>(theory: S, state: DbState): Db<S> {
|
|
315
|
+
const value: Db<S> = {
|
|
316
|
+
schemaId: state.schemaId,
|
|
317
|
+
snapshot(work) {
|
|
318
|
+
return Effect.acquireRelease(
|
|
319
|
+
nativeOperationWith(
|
|
320
|
+
"Db.snapshot",
|
|
321
|
+
(callback) => dbNative.runtimeDbSnapshot(state.db, policyWire(work, "Db.snapshot"), callback),
|
|
322
|
+
dbNative.runtimeSnapshotTake,
|
|
323
|
+
(wire) => makeSnapshot(theory, wire.snapshot, witnessOf(wire.witness))
|
|
324
|
+
),
|
|
325
|
+
(snapshot) =>
|
|
326
|
+
Effect.suspend(() =>
|
|
327
|
+
releaseOwner("Snapshot.close", (callback) =>
|
|
328
|
+
dbNative.runtimeSnapshotClose(snapshotHandles.get(snapshot) ?? missingSnapshot(), callback)
|
|
1063
329
|
)
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
}
|
|
330
|
+
),
|
|
331
|
+
{ interruptible: true }
|
|
1067
332
|
)
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
const prepared: Prepared<Rels, Row, Params> = Object.freeze({})
|
|
1077
|
-
preparedPlans.set(
|
|
1078
|
-
prepared,
|
|
1079
|
-
Object.freeze({
|
|
1080
|
-
handle: preparedHandle,
|
|
1081
|
-
owner,
|
|
1082
|
-
params: q.data.params,
|
|
1083
|
-
finds: q.data.finds
|
|
1084
|
-
})
|
|
1085
|
-
)
|
|
1086
|
-
planReclaimer.register(prepared, preparedHandle)
|
|
1087
|
-
return prepared
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
function makeWitness(nativeHandle: WitnessHandle): Witness<Rels> {
|
|
1091
|
-
const state: WitnessState = { handle: nativeHandle, spent: false, owner }
|
|
1092
|
-
const witness: Witness<Rels> = Object.freeze({
|
|
1093
|
-
[Symbol.dispose](): void {
|
|
1094
|
-
if (state.spent) {
|
|
1095
|
-
return
|
|
1096
|
-
}
|
|
1097
|
-
state.spent = true
|
|
1098
|
-
bridged("close bumbledb witness", function closeWitness() {
|
|
1099
|
-
native.witnessClose(nativeHandle)
|
|
1100
|
-
})
|
|
1101
|
-
}
|
|
1102
|
-
})
|
|
1103
|
-
witnessStates.set(witness, state)
|
|
1104
|
-
witnessReclaimer.register(witness, nativeHandle)
|
|
1105
|
-
return witness
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
function makeInstance(nativeHandle: InstanceHandle): ReadInstance<Rels> {
|
|
1109
|
-
return createReadInstance(nativeHandle, theory, tables, owner)
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
function read<R>(body: (instance: ReadInstance<Rels>, witness: Witness<Rels>) => SyncResult<R>): SyncResult<R> {
|
|
1113
|
-
let captured: R | undefined
|
|
1114
|
-
const result = bridged("bumbledb read", function runRead() {
|
|
1115
|
-
return native.dbRead(handle, function onRead(nativeInstance, nativeWitness) {
|
|
1116
|
-
const instance = makeInstance(nativeInstance)
|
|
1117
|
-
const witness = makeWitness(nativeWitness)
|
|
1118
|
-
const value = body(instance, witness)
|
|
1119
|
-
const state = instanceStates.get(instance)
|
|
1120
|
-
if (state !== undefined) {
|
|
1121
|
-
state.live = false
|
|
1122
|
-
}
|
|
1123
|
-
if (isThenable(value)) {
|
|
1124
|
-
throw errors.wrap(ErrAsyncCallback, "bumbledb read callback returned a thenable")
|
|
1125
|
-
}
|
|
1126
|
-
captured = value
|
|
1127
|
-
return value
|
|
1128
|
-
})
|
|
1129
|
-
})
|
|
1130
|
-
return (captured ?? result) as SyncResult<R>
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
function makeTx(resolveTx: () => TxHandle): { readonly tx: WriteTx<Rels>; spend(): void } {
|
|
1134
|
-
const txState = { spent: false }
|
|
1135
|
-
function assertLive(): void {
|
|
1136
|
-
if (txState.spent) {
|
|
1137
|
-
throw errors.wrap(ErrUseAfterScope, "bumbledb write transaction is spent")
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
const reads = pointReadsOf(assertLive, {
|
|
1141
|
-
contains(relationId, row) {
|
|
1142
|
-
const txHandle = resolveTx()
|
|
1143
|
-
return bridged("bumbledb tx contains", function readContains() {
|
|
1144
|
-
return native.txContains(txHandle, relationId, row)
|
|
1145
|
-
})
|
|
1146
|
-
},
|
|
1147
|
-
get(relationId, statementId, key) {
|
|
1148
|
-
const txHandle = resolveTx()
|
|
1149
|
-
return bridged("bumbledb tx get", function readGet() {
|
|
1150
|
-
return native.txGet(txHandle, relationId, statementId, key)
|
|
1151
|
-
})
|
|
1152
|
-
}
|
|
1153
|
-
})
|
|
1154
|
-
function insert<R extends MemberRelation<Rels>>(relation: R, facts: CollectionWrite<R>): MutationReport {
|
|
1155
|
-
assertLive()
|
|
1156
|
-
const entry = ordinaryEntry(tables, theory, relation)
|
|
1157
|
-
const txHandle = resolveTx()
|
|
1158
|
-
return mutateCollection(relation, facts, function applyCells(rows, cells) {
|
|
1159
|
-
return bridged("bumbledb tx insert", function record() {
|
|
1160
|
-
return native.txInsert(txHandle, entry.id, rows, cells)
|
|
1161
|
-
})
|
|
1162
|
-
})
|
|
1163
|
-
}
|
|
1164
|
-
function remove<R extends MemberRelation<Rels>>(relation: R, facts: Iterable<Fact<R>>): MutationReport {
|
|
1165
|
-
assertLive()
|
|
1166
|
-
const entry = ordinaryEntry(tables, theory, relation)
|
|
1167
|
-
const txHandle = resolveTx()
|
|
1168
|
-
const flat = rowsOf(relation, facts)
|
|
1169
|
-
const report = bridged("bumbledb tx delete", function record() {
|
|
1170
|
-
return native.txDelete(txHandle, entry.id, flat.rows, flat.cells)
|
|
1171
|
-
})
|
|
1172
|
-
return Object.freeze({ submitted: report.submitted, changed: report.changed })
|
|
1173
|
-
}
|
|
1174
|
-
function reserve<R extends MemberRelation<Rels>>(
|
|
1175
|
-
relation: R,
|
|
1176
|
-
field: FreshKeys<R> & string,
|
|
1177
|
-
count: bigint
|
|
1178
|
-
): FreshRange {
|
|
1179
|
-
assertLive()
|
|
1180
|
-
const entry = ordinaryEntry(tables, theory, relation)
|
|
1181
|
-
const declared = relation.data.fields.find(function byName(candidate) {
|
|
1182
|
-
return candidate.name === field
|
|
1183
|
-
})
|
|
1184
|
-
if (declared === undefined || !isFreshField(declared.field)) {
|
|
1185
|
-
throw errors.new(`relation ${relation.name}: field ${field} is not a fresh cell`)
|
|
1186
|
-
}
|
|
1187
|
-
const fieldId = entry.fieldIds.get(field)
|
|
1188
|
-
if (fieldId === undefined) {
|
|
1189
|
-
throw errors.new(`bumbledb manifest drift: relation ${relation.name} has no field id for ${field}`)
|
|
1190
|
-
}
|
|
1191
|
-
const txHandle = resolveTx()
|
|
1192
|
-
const range = bridged("bumbledb tx reserve", function mint() {
|
|
1193
|
-
return native.txReserve(txHandle, entry.id, fieldId, count)
|
|
1194
|
-
})
|
|
1195
|
-
return freshRangeOf(range)
|
|
1196
|
-
}
|
|
1197
|
-
const tx: WriteTx<Rels> = Object.freeze({
|
|
1198
|
-
insert,
|
|
1199
|
-
delete: remove,
|
|
1200
|
-
reserve,
|
|
1201
|
-
contains: reads.contains,
|
|
1202
|
-
get: reads.get
|
|
1203
|
-
})
|
|
1204
|
-
function spend(): void {
|
|
1205
|
-
txState.spent = true
|
|
1206
|
-
}
|
|
1207
|
-
return { tx, spend }
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
function mapNativeWrite<R>(nativeOutcome: NativeWriteOutcome, built: R | undefined): WriteFromOutcome<Rels, R> {
|
|
1211
|
-
if (nativeOutcome.tag === "moved") {
|
|
1212
|
-
return Object.freeze({
|
|
1213
|
-
tag: "moved" as const,
|
|
1214
|
-
witnessed: nativeOutcome.witnessed,
|
|
1215
|
-
current: nativeOutcome.current
|
|
1216
|
-
})
|
|
1217
|
-
}
|
|
1218
|
-
if (nativeOutcome.tag === "rejected") {
|
|
1219
|
-
return Object.freeze({
|
|
1220
|
-
tag: "rejected" as const,
|
|
1221
|
-
violations: Object.freeze(nativeOutcome.violations.map(violationOf))
|
|
1222
|
-
})
|
|
1223
|
-
}
|
|
1224
|
-
if (nativeOutcome.tag === "abandoned") {
|
|
1225
|
-
if (built === undefined || !isAbandon(built)) {
|
|
1226
|
-
throw errors.new("bumbledb write abandoned without an abandon sentinel")
|
|
1227
|
-
}
|
|
1228
|
-
return abandonedOutcome<Rels, R>(built)
|
|
1229
|
-
}
|
|
1230
|
-
return Object.freeze({
|
|
1231
|
-
tag: "accepted" as const,
|
|
1232
|
-
value: Object.freeze({
|
|
1233
|
-
value: built as Exclude<R, Abandon<unknown>>,
|
|
1234
|
-
generation: nativeOutcome.generation
|
|
1235
|
-
})
|
|
1236
|
-
})
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
function runWrite<R>(
|
|
1240
|
-
invoke: (callback: (tx: TxHandle) => boolean) => NativeWriteOutcome,
|
|
1241
|
-
fn: (tx: WriteTx<Rels>) => SyncResult<R>
|
|
1242
|
-
): WriteFromOutcome<Rels, SyncResult<R>> {
|
|
1243
|
-
let built: SyncResult<R> | undefined
|
|
1244
|
-
const nativeOutcome = bridged("bumbledb write", function callWrite() {
|
|
1245
|
-
return invoke(function onWrite(txHandle) {
|
|
1246
|
-
const made = makeTx(function resolveTx() {
|
|
1247
|
-
return txHandle
|
|
1248
|
-
})
|
|
1249
|
-
const result = errors.trySync(function buildDelta() {
|
|
1250
|
-
return fn(made.tx)
|
|
1251
|
-
})
|
|
1252
|
-
made.spend()
|
|
1253
|
-
if (result.error) {
|
|
1254
|
-
throw errors.wrap(result.error, "build write delta")
|
|
333
|
+
},
|
|
334
|
+
apply(changes, options) {
|
|
335
|
+
return Effect.gen(function* () {
|
|
336
|
+
const internal = internalChanges(changes)
|
|
337
|
+
if (internal === undefined) {
|
|
338
|
+
// A foreign object supplied dynamically refuses before
|
|
339
|
+
// any native dispatch.
|
|
340
|
+
return yield* Effect.fail(refusal("Db.apply", "InvalidArgument"))
|
|
1255
341
|
}
|
|
1256
|
-
if (
|
|
1257
|
-
|
|
342
|
+
if (internal.schemaId !== state.schemaId) {
|
|
343
|
+
return yield* Effect.fail(refusal("Db.apply", "InvalidArgument"))
|
|
1258
344
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
345
|
+
const expected: ExpectedWire =
|
|
346
|
+
options.expected.kind === "any"
|
|
347
|
+
? { kind: "any" }
|
|
348
|
+
: { kind: "exact", store: options.expected.at.store, generation: options.expected.at.generation }
|
|
349
|
+
return yield* nativeOperationWith(
|
|
350
|
+
"Db.apply",
|
|
351
|
+
(callback) =>
|
|
352
|
+
dbNative.runtimeDbApply(state.db, policyWire(options, "Db.apply"), internal.handle, expected, callback),
|
|
353
|
+
dbNative.runtimeApplyTake,
|
|
354
|
+
outcomeOf
|
|
355
|
+
)
|
|
1261
356
|
})
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
fn: (tx: WriteTx<Rels>) => SyncResult<R>
|
|
1279
|
-
): WriteFromOutcome<Rels, SyncResult<R>> {
|
|
1280
|
-
const state = witnessStates.get(witness)
|
|
1281
|
-
if (state === undefined) {
|
|
1282
|
-
throw errors.wrap(ErrForeignWitness, "bumbledb writeFrom witness is not a witness of this SDK")
|
|
1283
|
-
}
|
|
1284
|
-
if (state.owner !== owner) {
|
|
1285
|
-
throw errors.wrap(
|
|
1286
|
-
ErrForeignWitness,
|
|
1287
|
-
`bumbledb writeFrom witness belongs to a different store (schema ${theory.name})`
|
|
357
|
+
},
|
|
358
|
+
inspect(work) {
|
|
359
|
+
return nativeOperationWith(
|
|
360
|
+
"Db.inspect",
|
|
361
|
+
(callback) => dbNative.runtimeDbInspect(state.db, policyWire(work, "Db.inspect"), callback),
|
|
362
|
+
dbNative.runtimeDbInspectTake,
|
|
363
|
+
(wire: DbInspectionWire): DbInspection =>
|
|
364
|
+
Object.freeze({
|
|
365
|
+
schemaId: state.schemaId,
|
|
366
|
+
generation: wire.generation,
|
|
367
|
+
mapBytes: wire.mapBytes,
|
|
368
|
+
populatedBytes: wire.populatedBytes,
|
|
369
|
+
diskBytes: wire.diskBytes,
|
|
370
|
+
residentEstimateBytes: wire.residentEstimateBytes,
|
|
371
|
+
retainedOperations: wire.retainedOperations
|
|
372
|
+
})
|
|
1288
373
|
)
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
return
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
return native.dbCatalogDigest(handle)
|
|
1301
|
-
})
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
function prepare<Row, Params extends ParamsRecord>(q: Query<Rels, Row, Params>): Prepared<Rels, Row, Params> {
|
|
1305
|
-
if (q.schema !== theory) {
|
|
1306
|
-
throw errors.new(
|
|
1307
|
-
`query was built against schema ${q.schema.name}, not the identical schema value this store opened with — schema identity is the membership rule`
|
|
374
|
+
},
|
|
375
|
+
close() {
|
|
376
|
+
// The one close authority: database child first, then the
|
|
377
|
+
// directory owner releases its kernel lock LAST (C09). Both
|
|
378
|
+
// joins are idempotent natively.
|
|
379
|
+
return drainClose("Db.close", (callback) => runtimeNative.runtimeManagedDbClose(state.db, callback)).pipe(
|
|
380
|
+
Effect.flatMap((report) =>
|
|
381
|
+
drainClose("Db.directoryClose", (callback) =>
|
|
382
|
+
runtimeNative.runtimeDirectoryClose(state.directory, false, callback)
|
|
383
|
+
).pipe(Effect.map((directoryReport) => (report.kind === "closed" ? directoryReport : report)))
|
|
384
|
+
)
|
|
1308
385
|
)
|
|
1309
386
|
}
|
|
1310
|
-
const queryIr = lowerQuery(q)
|
|
1311
|
-
const outcome = bridged("prepare bumbledb query", function callPrepare() {
|
|
1312
|
-
return native.dbPrepare(handle, queryIr)
|
|
1313
|
-
})
|
|
1314
|
-
if (!outcome.ok) {
|
|
1315
|
-
throwPrepareRefusal(outcome.message)
|
|
1316
|
-
}
|
|
1317
|
-
return pinPrepared(outcome.prepared, q)
|
|
1318
387
|
}
|
|
1319
|
-
|
|
1320
|
-
return
|
|
1321
|
-
schema: theory,
|
|
1322
|
-
read,
|
|
1323
|
-
write,
|
|
1324
|
-
writeFrom,
|
|
1325
|
-
catalogDigest,
|
|
1326
|
-
prepare
|
|
1327
|
-
})
|
|
388
|
+
Object.freeze(value)
|
|
389
|
+
return value
|
|
1328
390
|
}
|
|
1329
391
|
|
|
1330
|
-
const
|
|
1331
|
-
"bumbledb newtypeMismatch: a statement pairs faces whose newtypes disagree — the faces of a dependency agree on their newtype, or neither carries one"
|
|
1332
|
-
)
|
|
1333
|
-
const ErrSchemaError = errors.new("bumbledb schemaError: the declaration failed validation")
|
|
1334
|
-
const ErrFingerprintMismatch = errors.new("bumbledb fingerprintMismatch: the store's schema does not match this theory")
|
|
1335
|
-
const ErrIrError = errors.new("bumbledb irError: the query failed validation")
|
|
392
|
+
const snapshotHandles = new WeakMap<object, SnapshotHandle>()
|
|
1336
393
|
|
|
1337
|
-
function
|
|
1338
|
-
|
|
1339
|
-
canonical: string,
|
|
1340
|
-
kind: "schemaError" | "newtypeMismatch" | "fingerprintMismatch",
|
|
1341
|
-
message: string
|
|
1342
|
-
): never {
|
|
1343
|
-
const detail = `${verb} ${canonical}: ${message}`
|
|
1344
|
-
if (kind === "newtypeMismatch") {
|
|
1345
|
-
throw errors.wrap(ErrNewtypeMismatch, detail)
|
|
1346
|
-
}
|
|
1347
|
-
if (kind === "schemaError") {
|
|
1348
|
-
throw errors.wrap(ErrSchemaError, detail)
|
|
1349
|
-
}
|
|
1350
|
-
throw errors.wrap(ErrFingerprintMismatch, detail)
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
function throwPrepareRefusal(message: string): never {
|
|
1354
|
-
throw errors.wrap(ErrIrError, `prepare: ${message}`)
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
function openFromHandle<Rels extends SchemaRelations>(dbHandle: DbHandle, theory: Schema<Rels>): Db<Rels> {
|
|
1358
|
-
const manifest = bridged("fetch bumbledb manifest", function fetchManifest() {
|
|
1359
|
-
return native.dbManifest(dbHandle)
|
|
1360
|
-
})
|
|
1361
|
-
return openDb(dbHandle, theory, manifest)
|
|
394
|
+
function missingSnapshot(): never {
|
|
395
|
+
throw new DbError({ operation: "Snapshot.close", reason: { _tag: "Internal" } })
|
|
1362
396
|
}
|
|
1363
397
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
theory: Schema<Rels>
|
|
1367
|
-
): Promise<Admission<Rels, Db<Rels>>> {
|
|
1368
|
-
const canonical = path.resolve(storePath)
|
|
1369
|
-
const spec = lower(theory)
|
|
1370
|
-
const created = await bridgedAsync(`create bumbledb store at ${canonical}`, function callBridge() {
|
|
1371
|
-
return native.dbCreate(canonical, spec)
|
|
1372
|
-
})
|
|
1373
|
-
if (created.tag === "schemaError" || created.tag === "newtypeMismatch") {
|
|
1374
|
-
throwOpenRefusal("create", canonical, created.tag, created.message)
|
|
1375
|
-
}
|
|
1376
|
-
if (created.tag === "rejected") {
|
|
1377
|
-
return Object.freeze({
|
|
1378
|
-
tag: "rejected" as const,
|
|
1379
|
-
violations: Object.freeze(
|
|
1380
|
-
created.violations.map(function mapWire(wire) {
|
|
1381
|
-
return mapViolationWithoutStore<Rels>(theory, wire)
|
|
1382
|
-
})
|
|
1383
|
-
)
|
|
1384
|
-
})
|
|
1385
|
-
}
|
|
1386
|
-
return Object.freeze({ tag: "accepted" as const, value: openFromHandle(created.db, theory) })
|
|
1387
|
-
}
|
|
398
|
+
/** The managed child name under the database directory owner. */
|
|
399
|
+
const CHILD = "store"
|
|
1388
400
|
|
|
1389
|
-
function
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
}
|
|
1404
|
-
return decodeOffendingFact<Rels>(member, fact.relation as keyof Rels & string, fact)
|
|
401
|
+
function openDatabase<S extends AnySchema>(
|
|
402
|
+
operation: "Db.create" | "Db.open",
|
|
403
|
+
path: string,
|
|
404
|
+
schema: S,
|
|
405
|
+
work: ExecutionPolicy,
|
|
406
|
+
create: boolean
|
|
407
|
+
): Effect.Effect<Db<S>, DbError, NativeRuntime | Scope.Scope> {
|
|
408
|
+
return Effect.gen(function* () {
|
|
409
|
+
const runtime = yield* runtimeHandle()
|
|
410
|
+
const compiled: CompiledSchema<S> = yield* CoreSchema.compile(schema, work)
|
|
411
|
+
const spec = lower(schema)
|
|
412
|
+
const wire = yield* Effect.try({
|
|
413
|
+
try: () => policyWire(work, operation),
|
|
414
|
+
catch: () => refusal(operation, "InvalidArgument")
|
|
1405
415
|
})
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
reserve<R extends MemberRelation<Rels>>(relation: R, field: FreshKeys<R> & string, count: bigint): FreshRange
|
|
1441
|
-
contains<R extends MemberRelation<Rels>>(relation: R, fact: Fact<R>): boolean
|
|
1442
|
-
|
|
1443
|
-
get<R extends MemberRelation<Rels>>(relation: R, key: KeyFact<R>): Fact<R> | undefined
|
|
1444
|
-
get<R extends MemberRelation<Rels>, const P extends readonly string[]>(
|
|
1445
|
-
relation: R,
|
|
1446
|
-
keyStatement: KeyStatement<R, P>,
|
|
1447
|
-
key: DeclaredKeyFact<R, P>
|
|
1448
|
-
): Fact<R> | undefined
|
|
1449
|
-
admit(): Promise<Admission<Rels, OwnedInstance<Rels>>>
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
const ownedRecords = new WeakMap<object, { handle: OwnedHandle; theory: AnySchema; spent: boolean; owner: object }>()
|
|
1453
|
-
const builderRecords = new WeakMap<object, { handle: BuilderHandle; theory: AnySchema; spent: boolean }>()
|
|
1454
|
-
|
|
1455
|
-
const ownedReclaimer = new FinalizationRegistry<OwnedHandle>(function reclaimOwned(handle) {
|
|
1456
|
-
const closed = errors.trySync(function closeOwned() {
|
|
1457
|
-
native.ownedInstanceClose(handle)
|
|
1458
|
-
})
|
|
1459
|
-
if (closed.error) {
|
|
1460
|
-
return
|
|
1461
|
-
}
|
|
1462
|
-
})
|
|
1463
|
-
|
|
1464
|
-
const builderReclaimer = new FinalizationRegistry<BuilderHandle>(function reclaimBuilder(handle) {
|
|
1465
|
-
const closed = errors.trySync(function closeBuilder() {
|
|
1466
|
-
native.instanceBuilderClose(handle)
|
|
1467
|
-
})
|
|
1468
|
-
if (closed.error) {
|
|
1469
|
-
return
|
|
1470
|
-
}
|
|
1471
|
-
})
|
|
1472
|
-
|
|
1473
|
-
function wrapOwned<Rels extends SchemaRelations>(nativeHandle: OwnedHandle, theory: Schema<Rels>): OwnedInstance<Rels> {
|
|
1474
|
-
const owner = Object.freeze({})
|
|
1475
|
-
const rec = { handle: nativeHandle, theory, spent: false, owner }
|
|
1476
|
-
const tables = tablesFromTheory(theory)
|
|
1477
|
-
function assertLive(): void {
|
|
1478
|
-
if (rec.spent) {
|
|
1479
|
-
throw errors.wrap(ErrSpentHandle, "bumbledb owned instance has been disposed")
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
const methods = catalogMethods(theory, tables, owner, assertLive, {
|
|
1483
|
-
scan(relationId) {
|
|
1484
|
-
return native.ownedScan(nativeHandle, relationId)
|
|
1485
|
-
},
|
|
1486
|
-
count(relationId) {
|
|
1487
|
-
return native.ownedCount(nativeHandle, relationId)
|
|
1488
|
-
},
|
|
1489
|
-
contains(relationId, values) {
|
|
1490
|
-
return native.ownedContains(nativeHandle, relationId, values)
|
|
1491
|
-
},
|
|
1492
|
-
get(relationId, statementId, keyValues) {
|
|
1493
|
-
return native.ownedGet(nativeHandle, relationId, statementId, keyValues)
|
|
1494
|
-
},
|
|
1495
|
-
prepare(query) {
|
|
1496
|
-
return native.ownedPrepare(nativeHandle, query)
|
|
1497
|
-
},
|
|
1498
|
-
execute(prepared, params) {
|
|
1499
|
-
return native.ownedExecute(prepared, nativeHandle, params)
|
|
1500
|
-
}
|
|
1501
|
-
})
|
|
1502
|
-
const instance: OwnedInstance<Rels> = Object.freeze({
|
|
1503
|
-
...methods,
|
|
1504
|
-
[Symbol.dispose](): void {
|
|
1505
|
-
if (rec.spent) {
|
|
1506
|
-
return
|
|
1507
|
-
}
|
|
1508
|
-
try {
|
|
1509
|
-
native.ownedInstanceClose(nativeHandle)
|
|
1510
|
-
} catch (caught) {
|
|
1511
|
-
const error = errorFromThrow(caught)
|
|
1512
|
-
if (LEASED_FOR_PUBLISH.test(error.message)) {
|
|
1513
|
-
throw errors.wrap(ErrSpentHandle, "bumbledb owned instance is leased for publish")
|
|
416
|
+
// Compound acquisition (TS-003): register the directory owner and
|
|
417
|
+
// its finalizer BEFORE any interruptible child-open step.
|
|
418
|
+
const directory = yield* Effect.acquireRelease(
|
|
419
|
+
nativeOperationWith(
|
|
420
|
+
operation,
|
|
421
|
+
(callback) => runtimeNative.runtimeDirectoryAcquire(runtime, wire, path, callback),
|
|
422
|
+
runtimeNative.runtimeDirectoryTake,
|
|
423
|
+
(value) => value
|
|
424
|
+
),
|
|
425
|
+
(dir) =>
|
|
426
|
+
releaseOwner(`${operation}.directoryClose`, (callback) =>
|
|
427
|
+
runtimeNative.runtimeDirectoryClose(dir, false, callback)
|
|
428
|
+
),
|
|
429
|
+
{ interruptible: true }
|
|
430
|
+
)
|
|
431
|
+
return yield* Effect.acquireRelease(
|
|
432
|
+
Effect.gen(function* () {
|
|
433
|
+
const outcome = yield* nativeOperationWith(
|
|
434
|
+
operation,
|
|
435
|
+
(callback) => runtimeNative.runtimeDirectoryDbOpen(directory, wire, CHILD, spec, create, callback),
|
|
436
|
+
runtimeNative.runtimeDbTake,
|
|
437
|
+
(value) => value
|
|
438
|
+
).pipe(
|
|
439
|
+
Effect.catch((error) =>
|
|
440
|
+
drainClose(`${operation}.directoryClose`, (callback) =>
|
|
441
|
+
runtimeNative.runtimeDirectoryClose(directory, false, callback)
|
|
442
|
+
).pipe(Effect.andThen(Effect.fail(error)))
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
if (outcome.tag !== "accepted") {
|
|
446
|
+
yield* drainClose(`${operation}.directoryClose`, (callback) =>
|
|
447
|
+
runtimeNative.runtimeDirectoryClose(directory, false, callback)
|
|
448
|
+
)
|
|
449
|
+
return yield* Effect.fail(refusal(operation, "InvalidArgument"))
|
|
1514
450
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
function assertLive(): void {
|
|
1533
|
-
if (rec.spent) {
|
|
1534
|
-
throw errors.wrap(ErrSpentHandle, "bumbledb instance builder has been spent")
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
const overlay = overlayMethods(theory, tables, assertLive, {
|
|
1538
|
-
contains(relationId, row) {
|
|
1539
|
-
return bridged("bumbledb builder contains", function readContains() {
|
|
1540
|
-
return native.instanceBuilderContains(nativeHandle, relationId, row)
|
|
1541
|
-
})
|
|
1542
|
-
},
|
|
1543
|
-
get(relationId, statementId, key) {
|
|
1544
|
-
return bridged("bumbledb builder get", function readGet() {
|
|
1545
|
-
return native.instanceBuilderGet(nativeHandle, relationId, statementId, key)
|
|
1546
|
-
})
|
|
1547
|
-
}
|
|
1548
|
-
})
|
|
1549
|
-
const builder: InstanceBuilder<Rels> = Object.freeze({
|
|
1550
|
-
load<R extends MemberRelation<Rels>>(relation: R, facts: CollectionWrite<R>): MutationReport {
|
|
1551
|
-
assertLive()
|
|
1552
|
-
const entry = ordinaryEntry(tables, theory, relation)
|
|
1553
|
-
return mutateCollection(relation, facts, function applyCells(rows, cells) {
|
|
1554
|
-
return bridged("bumbledb builder load", function loadCells() {
|
|
1555
|
-
return native.instanceBuilderLoad(nativeHandle, entry.id, rows, cells)
|
|
1556
|
-
})
|
|
1557
|
-
})
|
|
1558
|
-
},
|
|
1559
|
-
delete<R extends MemberRelation<Rels>>(relation: R, facts: Iterable<Fact<R>>): MutationReport {
|
|
1560
|
-
assertLive()
|
|
1561
|
-
const entry = ordinaryEntry(tables, theory, relation)
|
|
1562
|
-
const flat = rowsOf(relation, facts)
|
|
1563
|
-
const report = bridged("bumbledb builder delete", function remove() {
|
|
1564
|
-
return native.instanceBuilderDelete(nativeHandle, entry.id, flat.rows, flat.cells)
|
|
1565
|
-
})
|
|
1566
|
-
return Object.freeze({ submitted: report.submitted, changed: report.changed })
|
|
1567
|
-
},
|
|
1568
|
-
reserve<R extends MemberRelation<Rels>>(relation: R, field: FreshKeys<R> & string, count: bigint): FreshRange {
|
|
1569
|
-
assertLive()
|
|
1570
|
-
const entry = ordinaryEntry(tables, theory, relation)
|
|
1571
|
-
const declared = relation.data.fields.find(function byName(candidate) {
|
|
1572
|
-
return candidate.name === field
|
|
1573
|
-
})
|
|
1574
|
-
if (declared === undefined || !isFreshField(declared.field)) {
|
|
1575
|
-
throw errors.new(`relation ${relation.name}: field ${field} is not a fresh cell`)
|
|
1576
|
-
}
|
|
1577
|
-
const fieldId = entry.fieldIds.get(field)
|
|
1578
|
-
if (fieldId === undefined) {
|
|
1579
|
-
throw errors.new(`bumbledb manifest drift: relation ${relation.name} has no field id for ${field}`)
|
|
1580
|
-
}
|
|
1581
|
-
const range = bridged("bumbledb builder reserve", function mint() {
|
|
1582
|
-
return native.instanceBuilderReserve(nativeHandle, entry.id, fieldId, count)
|
|
1583
|
-
})
|
|
1584
|
-
return freshRangeOf(range)
|
|
1585
|
-
},
|
|
1586
|
-
contains: overlay.contains,
|
|
1587
|
-
get: overlay.get,
|
|
1588
|
-
async admit(): Promise<Admission<Rels, OwnedInstance<Rels>>> {
|
|
1589
|
-
if (rec.spent) {
|
|
1590
|
-
throw errors.wrap(ErrSpentHandle, "bumbledb instance builder has been spent")
|
|
1591
|
-
}
|
|
1592
|
-
rec.spent = true
|
|
1593
|
-
builderReclaimer.unregister(builder)
|
|
1594
|
-
let outcome: AdmitResult
|
|
1595
|
-
try {
|
|
1596
|
-
outcome = await native.instanceBuilderAdmit(nativeHandle)
|
|
1597
|
-
} catch (caught) {
|
|
1598
|
-
throw errors.wrap(errorFromThrow(caught), "admit bumbledb instance")
|
|
1599
|
-
}
|
|
1600
|
-
if (outcome.tag === "rejected") {
|
|
1601
|
-
return Object.freeze({
|
|
1602
|
-
tag: "rejected" as const,
|
|
1603
|
-
violations: Object.freeze(
|
|
1604
|
-
outcome.violations.map(function mapWire(wire) {
|
|
1605
|
-
return mapViolationWithoutStore<Rels>(theory, wire)
|
|
1606
|
-
})
|
|
451
|
+
const state: DbState = { theory: schema, db: outcome.db, directory, schemaId: compiled.schemaId }
|
|
452
|
+
const db = makeDb(schema, state)
|
|
453
|
+
dbStates.set(db, state)
|
|
454
|
+
return db
|
|
455
|
+
}),
|
|
456
|
+
(db) =>
|
|
457
|
+
Effect.suspend(() => {
|
|
458
|
+
const state = dbStates.get(db)
|
|
459
|
+
if (state === undefined) {
|
|
460
|
+
return Effect.void
|
|
461
|
+
}
|
|
462
|
+
return releaseOwner("Db.close", (callback) => runtimeNative.runtimeManagedDbClose(state.db, callback)).pipe(
|
|
463
|
+
Effect.ensuring(
|
|
464
|
+
releaseOwner("Db.directoryClose", (callback) =>
|
|
465
|
+
runtimeNative.runtimeDirectoryClose(state.directory, false, callback)
|
|
466
|
+
)
|
|
467
|
+
)
|
|
1607
468
|
)
|
|
1608
|
-
})
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
tag: "accepted" as const,
|
|
1612
|
-
value: wrapOwned(outcome.value, theory)
|
|
1613
|
-
})
|
|
1614
|
-
},
|
|
1615
|
-
[Symbol.dispose](): void {
|
|
1616
|
-
if (rec.spent) {
|
|
1617
|
-
return
|
|
1618
|
-
}
|
|
1619
|
-
rec.spent = true
|
|
1620
|
-
builderReclaimer.unregister(builder)
|
|
1621
|
-
bridged("close bumbledb instance builder", function closeBuilder() {
|
|
1622
|
-
native.instanceBuilderClose(nativeHandle)
|
|
1623
|
-
})
|
|
1624
|
-
}
|
|
469
|
+
}),
|
|
470
|
+
{ interruptible: true }
|
|
471
|
+
)
|
|
1625
472
|
})
|
|
1626
|
-
builderRecords.set(builder, rec)
|
|
1627
|
-
builderReclaimer.register(builder, nativeHandle, builder)
|
|
1628
|
-
return builder
|
|
1629
473
|
}
|
|
1630
474
|
|
|
1631
|
-
const
|
|
1632
|
-
create<Rels extends SchemaRelations>(theory: Schema<Rels>): InstanceBuilder<Rels> {
|
|
1633
|
-
const spec = lower(theory)
|
|
1634
|
-
const handle = bridged("create bumbledb instance builder", function make() {
|
|
1635
|
-
return native.instanceBuilderNew(spec)
|
|
1636
|
-
})
|
|
1637
|
-
return wrapBuilder(handle, theory)
|
|
1638
|
-
}
|
|
1639
|
-
})
|
|
475
|
+
const dbStates = new WeakMap<object, DbState>()
|
|
1640
476
|
|
|
1641
477
|
/**
|
|
1642
|
-
*
|
|
1643
|
-
*
|
|
1644
|
-
*
|
|
1645
|
-
*
|
|
1646
|
-
* the process owns the environment until GC/exit (durability is the
|
|
1647
|
-
* engine's per-commit fsync). Resume = reopen in a fresh process, or
|
|
1648
|
-
* hold the `Db` this process opened.
|
|
478
|
+
* `Db.create` is the explicit constructor and refuses existing authority;
|
|
479
|
+
* `Db.open` of a missing or unreadable database never creates an empty
|
|
480
|
+
* replacement (chapter 30). Both compile the schema through the same
|
|
481
|
+
* implementation as `Schema.compile` — prior compilation is optional.
|
|
1649
482
|
*/
|
|
1650
483
|
const Db = Object.freeze({
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
theory: Schema<Rels>
|
|
1654
|
-
): Promise<Admission<Rels, Db<Rels>>> {
|
|
1655
|
-
return createStore(storePath, theory)
|
|
484
|
+
create<S extends AnySchema>(path: string, schema: S, work: ExecutionPolicy) {
|
|
485
|
+
return openDatabase("Db.create", path, schema, work, true)
|
|
1656
486
|
},
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
return openStore(storePath, theory)
|
|
1660
|
-
},
|
|
1661
|
-
async fromInstance<Rels extends SchemaRelations>(
|
|
1662
|
-
storePath: string,
|
|
1663
|
-
instance: OwnedInstance<Rels>
|
|
1664
|
-
): Promise<Db<Rels>> {
|
|
1665
|
-
const rec = ownedRecords.get(instance)
|
|
1666
|
-
if (rec === undefined) {
|
|
1667
|
-
throw errors.wrap(ErrSpentHandle, "bumbledb fromInstance target is not an owned instance of this SDK")
|
|
1668
|
-
}
|
|
1669
|
-
if (rec.spent) {
|
|
1670
|
-
throw errors.wrap(ErrSpentHandle, "bumbledb fromInstance target has been disposed")
|
|
1671
|
-
}
|
|
1672
|
-
const canonical = path.resolve(storePath)
|
|
1673
|
-
const dbHandle = await bridgedAsync(`publish bumbledb instance at ${canonical}`, function publish() {
|
|
1674
|
-
return native.dbFromInstance(canonical, rec.handle)
|
|
1675
|
-
})
|
|
1676
|
-
return openFromHandle(dbHandle, rec.theory as Schema<Rels>)
|
|
487
|
+
open<S extends AnySchema>(path: string, schema: S, work: ExecutionPolicy) {
|
|
488
|
+
return openDatabase("Db.open", path, schema, work, false)
|
|
1677
489
|
}
|
|
1678
490
|
})
|
|
1679
491
|
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
492
|
+
/**
|
|
493
|
+
* Private log-integration seam (C10): wraps a PUBLISHED core snapshot
|
|
494
|
+
* handle — minted by the internal log machine's native open/snapshot verbs
|
|
495
|
+
* — in the exact core `QueryReader` plus the scoped session acquisition
|
|
496
|
+
* (`PublishedSnapshot extends QueryReader` in chapter 35; the log adds
|
|
497
|
+
* identity/stamps/freshness AROUND this capability, never a second reader).
|
|
498
|
+
* The argument is the log package's branded handle for the same native
|
|
499
|
+
* registry entry, so the one cast below is a cross-package respelling of
|
|
500
|
+
* one native capability — the native side re-judges kind/generation/owner
|
|
501
|
+
* on every verb, so a forged object refuses there, typed.
|
|
502
|
+
*/
|
|
503
|
+
function internalPublishedReader<S extends AnySchema>(
|
|
504
|
+
core: object,
|
|
505
|
+
theory: S
|
|
506
|
+
): {
|
|
507
|
+
readonly get: QueryReader<S>["get"]
|
|
508
|
+
readonly execute: QueryReader<S>["execute"]
|
|
509
|
+
readonly session: (work: ExecutionPolicy) => Effect.Effect<ExecutionSession<S>, DbError, Scope.Scope>
|
|
510
|
+
} {
|
|
511
|
+
const state: SnapshotState<S> = { theory, handle: core as SnapshotHandle }
|
|
512
|
+
return Object.freeze({
|
|
513
|
+
get<R extends Rel<S>>(relation: R, key: Key<R>, work: ExecutionPolicy) {
|
|
514
|
+
return getOn(state, relation, key, work)
|
|
515
|
+
},
|
|
516
|
+
execute<P extends ParamsRecord, A>(queryValue: QueryTemplate<S, P, A>, params: P, work: ExecutionPolicy) {
|
|
517
|
+
return executeOn<S, A>(state, undefined, queryValue as AnyQuery, params, work)
|
|
518
|
+
},
|
|
519
|
+
session(work: ExecutionPolicy) {
|
|
520
|
+
return Effect.acquireRelease(
|
|
521
|
+
nativeOperationWith(
|
|
522
|
+
"Snapshot.session",
|
|
523
|
+
(callback) => dbNative.runtimeSnapshotSession(state.handle, policyWire(work, "Snapshot.session"), callback),
|
|
524
|
+
dbNative.runtimeSessionTake,
|
|
525
|
+
(value) => makeSession(state, value)
|
|
526
|
+
),
|
|
527
|
+
(session) =>
|
|
528
|
+
Effect.suspend(() =>
|
|
529
|
+
releaseOwner("ExecutionSession.close", (callback) =>
|
|
530
|
+
dbNative.runtimeSessionClose(sessionHandles.get(session) ?? missingSession(), callback)
|
|
531
|
+
)
|
|
532
|
+
),
|
|
533
|
+
{ interruptible: true }
|
|
534
|
+
)
|
|
535
|
+
}
|
|
536
|
+
})
|
|
1720
537
|
}
|
|
538
|
+
|
|
539
|
+
export type {
|
|
540
|
+
ApplyExpected,
|
|
541
|
+
ApplyOptions,
|
|
542
|
+
ApplyOutcome,
|
|
543
|
+
CoreWitness,
|
|
544
|
+
DbInspection,
|
|
545
|
+
ExecutionSession,
|
|
546
|
+
QueryReader,
|
|
547
|
+
Snapshot
|
|
548
|
+
}
|
|
549
|
+
export { Db, internalPublishedReader }
|