@bjornpagen/bumbledb 1.1.0 → 1.2.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 +114 -11
- package/README.md +52 -9
- package/dist/capacity.d.ts +15 -55
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +63 -12
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +26 -3
- package/dist/changes.d.ts.map +1 -1
- package/dist/changes.js +89 -44
- package/dist/changes.js.map +1 -1
- package/dist/closed.d.ts +23 -51
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +110 -168
- package/dist/closed.js.map +1 -1
- package/dist/codec.d.ts +4 -9
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +108 -183
- package/dist/codec.js.map +1 -1
- package/dist/compile.d.ts +16 -16
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +36 -15
- package/dist/compile.js.map +1 -1
- package/dist/db-native.d.ts +39 -0
- package/dist/db-native.d.ts.map +1 -1
- package/dist/db-native.js +8 -0
- package/dist/db-native.js.map +1 -1
- package/dist/db.d.ts +28 -8
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +91 -38
- package/dist/db.js.map +1 -1
- package/dist/errors.d.ts +11 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +13 -1
- package/dist/errors.js.map +1 -1
- package/dist/face.d.ts +19 -25
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +30 -24
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +13 -26
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +89 -116
- package/dist/fields.js.map +1 -1
- package/dist/immutable.d.ts +10 -0
- package/dist/immutable.d.ts.map +1 -0
- package/dist/immutable.js +75 -0
- package/dist/immutable.js.map +1 -0
- package/dist/index.d.ts +14 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +2 -0
- package/dist/internal/log.d.ts.map +1 -1
- package/dist/internal/log.js +2 -0
- package/dist/internal/log.js.map +1 -1
- package/dist/law.d.ts +10 -10
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +1 -1
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +11 -6
- package/dist/lower.js.map +1 -1
- package/dist/native.d.ts +1 -1
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +1 -1
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js.map +1 -1
- package/dist/query/description.d.ts +33 -0
- package/dist/query/description.d.ts.map +1 -0
- package/dist/query/description.js +302 -0
- package/dist/query/description.js.map +1 -0
- package/dist/query/lower.d.ts +60 -4
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +126 -161
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts +7 -2
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +240 -53
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +8 -10
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +3 -4
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +5 -10
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +22 -48
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +30 -82
- package/dist/relation.js.map +1 -1
- package/dist/rows.d.ts +7 -4
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +44 -198
- package/dist/rows.js.map +1 -1
- package/dist/runtime-errors.d.ts +26 -2
- package/dist/runtime-errors.d.ts.map +1 -1
- package/dist/runtime-errors.js +43 -3
- package/dist/runtime-errors.js.map +1 -1
- package/dist/schema.d.ts +9 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +85 -24
- package/dist/schema.js.map +1 -1
- package/dist/selection.d.ts +27 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/selection.js +104 -0
- package/dist/selection.js.map +1 -0
- package/dist/shape.d.ts +6 -9
- package/dist/shape.d.ts.map +1 -1
- package/dist/statements.d.ts +17 -52
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +92 -111
- package/dist/statements.js.map +1 -1
- package/dist/values.d.ts +25 -0
- package/dist/values.d.ts.map +1 -0
- package/dist/values.js +203 -0
- package/dist/values.js.map +1 -0
- package/pack-provenance.json +3 -3
- package/package.json +4 -4
- package/src/capacity.ts +86 -76
- package/src/changes.ts +183 -54
- package/src/closed.ts +161 -314
- package/src/codec.ts +116 -190
- package/src/compile.ts +56 -32
- package/src/db-native.ts +42 -0
- package/src/db.ts +134 -52
- package/src/errors.ts +16 -1
- package/src/face.ts +44 -74
- package/src/fields.ts +100 -146
- package/src/immutable.ts +76 -0
- package/src/index.ts +34 -28
- package/src/internal/log.ts +2 -0
- package/src/law.ts +13 -13
- package/src/lower.ts +13 -10
- package/src/native.ts +2 -0
- package/src/query/atom.ts +1 -0
- package/src/query/description.ts +394 -0
- package/src/query/lower.ts +139 -173
- package/src/query/parse-ir.ts +247 -54
- package/src/query/run.ts +15 -9
- package/src/query/scope.ts +10 -18
- package/src/relation.ts +58 -142
- package/src/rows.ts +62 -205
- package/src/runtime-errors.ts +47 -3
- package/src/schema.ts +99 -29
- package/src/selection.ts +124 -0
- package/src/shape.ts +9 -9
- package/src/statements.ts +138 -189
- package/src/values.ts +223 -0
package/src/db.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import type { Scope } from "effect"
|
|
2
2
|
import { Effect, Option } from "effect"
|
|
3
|
-
import type { ChangeSet } from "#changes.ts"
|
|
3
|
+
import type { ChangeCounts, ChangeSet } from "#changes.ts"
|
|
4
4
|
import { internalChanges } from "#changes.ts"
|
|
5
5
|
import { drainClose, releaseOwner } from "#close.ts"
|
|
6
|
+
import { membersAgree } from "#closed.ts"
|
|
6
7
|
import type { CompiledSchema, SchemaId } from "#compile.ts"
|
|
7
|
-
import { Schema as CoreSchema, schemaTables } from "#compile.ts"
|
|
8
|
+
import { Schema as CoreSchema, declaredKey, schemaTables } from "#compile.ts"
|
|
8
9
|
import type {
|
|
9
10
|
ApplyOutcomeWire,
|
|
10
11
|
DbInspectionWire,
|
|
11
12
|
ExpectedWire,
|
|
13
|
+
JudgeOutcomeWire,
|
|
12
14
|
PreparedHandle,
|
|
13
15
|
SnapshotHandle,
|
|
14
16
|
WitnessWire
|
|
@@ -28,11 +30,14 @@ import { factOfCells, keyCellsOf } from "#rows.ts"
|
|
|
28
30
|
import type { NativeRuntime } from "#runtime.ts"
|
|
29
31
|
import { nativeOperationWith, runtimeHandle } from "#runtime.ts"
|
|
30
32
|
import type { CloseReport } from "#runtime-errors.ts"
|
|
31
|
-
import { DbError } from "#runtime-errors.ts"
|
|
33
|
+
import { argumentError, DbError } from "#runtime-errors.ts"
|
|
32
34
|
import type { DirectoryHandle } from "#runtime-native.ts"
|
|
33
35
|
import { runtimeNative } from "#runtime-native.ts"
|
|
34
36
|
import type { AnySchema } from "#schema.ts"
|
|
37
|
+
import { schemaDescriptor, schemasAgree } from "#schema.ts"
|
|
35
38
|
import type { Key, QueryTemplate, Rel } from "#shape.ts"
|
|
39
|
+
import type { KeyStatement } from "#statements.ts"
|
|
40
|
+
import { integerValue, recordValue } from "#values.ts"
|
|
36
41
|
|
|
37
42
|
/**
|
|
38
43
|
* The core surface: `Db.create`/`Db.open`, scoped coherent
|
|
@@ -57,10 +62,10 @@ interface CoreWitness {
|
|
|
57
62
|
readonly generation: bigint
|
|
58
63
|
}
|
|
59
64
|
|
|
60
|
-
type
|
|
65
|
+
type WriteExpected = { readonly kind: "any" } | { readonly kind: "exact"; readonly at: CoreWitness }
|
|
61
66
|
|
|
62
|
-
/** Expected-state intent
|
|
63
|
-
type
|
|
67
|
+
/** Expected-state intent shared by judgment and application. */
|
|
68
|
+
type WriteOptions = { readonly expected: WriteExpected }
|
|
64
69
|
|
|
65
70
|
type ApplyOutcome =
|
|
66
71
|
| { readonly kind: "accepted"; readonly witness: CoreWitness }
|
|
@@ -68,6 +73,19 @@ type ApplyOutcome =
|
|
|
68
73
|
| { readonly kind: "invariant-rejected"; readonly violations: readonly Violation[] }
|
|
69
74
|
| { readonly kind: "moved"; readonly witnessed: CoreWitness; readonly current: CoreWitness }
|
|
70
75
|
|
|
76
|
+
/** A judgment against the actual current base, not a promise about a later apply.
|
|
77
|
+
* A moved witness is unjudged and therefore carries no made-up change counts.
|
|
78
|
+
*/
|
|
79
|
+
type JudgeOutcome =
|
|
80
|
+
| { readonly kind: "admitted"; readonly base: CoreWitness; readonly changes: ChangeCounts }
|
|
81
|
+
| {
|
|
82
|
+
readonly kind: "invariant-rejected"
|
|
83
|
+
readonly base: CoreWitness
|
|
84
|
+
readonly changes: ChangeCounts
|
|
85
|
+
readonly violations: readonly Violation[]
|
|
86
|
+
}
|
|
87
|
+
| { readonly kind: "moved"; readonly witnessed: CoreWitness; readonly current: CoreWitness }
|
|
88
|
+
|
|
71
89
|
/** Storage measurements, not heap usage or mapped-page residency. */
|
|
72
90
|
interface StorageInspection {
|
|
73
91
|
/** Reserved virtual address range for the LMDB mapping. */
|
|
@@ -96,7 +114,10 @@ interface DbInspection {
|
|
|
96
114
|
* no writable authority — never a `Db`, an `apply`, or a raw transaction.
|
|
97
115
|
*/
|
|
98
116
|
interface QueryReader<S extends AnySchema> {
|
|
99
|
-
get<
|
|
117
|
+
get<K extends KeyStatement<Rel<S>, readonly string[]>>(
|
|
118
|
+
key: K,
|
|
119
|
+
value: NoInfer<Key<K>>
|
|
120
|
+
): Effect.Effect<Option.Option<Fact<K["owner"]>>, DbError>
|
|
100
121
|
execute<P extends ParamsRecord, A>(
|
|
101
122
|
query: QueryTemplate<S, P, A>,
|
|
102
123
|
params: P
|
|
@@ -124,7 +145,9 @@ interface PreparedQuery<P extends ParamsRecord, A> {
|
|
|
124
145
|
interface Db<S extends AnySchema> {
|
|
125
146
|
readonly schemaId: SchemaId
|
|
126
147
|
snapshot(): Effect.Effect<Snapshot<S>, DbError, Scope.Scope>
|
|
127
|
-
apply(changes: ChangeSet<S>, options:
|
|
148
|
+
apply(changes: ChangeSet<S>, options: WriteOptions): Effect.Effect<ApplyOutcome, DbError>
|
|
149
|
+
/** Briefly acquires the writer, judges and aborts. Stores no facts or generation. */
|
|
150
|
+
judge(changes: ChangeSet<S>, options: WriteOptions): Effect.Effect<JudgeOutcome, DbError>
|
|
128
151
|
inspect(): Effect.Effect<DbInspection, DbError>
|
|
129
152
|
/** Clear shared query caches without invalidating live snapshots or results. */
|
|
130
153
|
clearCache(): Effect.Effect<void, DbError>
|
|
@@ -152,9 +175,60 @@ function outcomeOf(wire: ApplyOutcomeWire): ApplyOutcome {
|
|
|
152
175
|
}
|
|
153
176
|
}
|
|
154
177
|
|
|
178
|
+
function judgmentOf(wire: JudgeOutcomeWire): JudgeOutcome {
|
|
179
|
+
switch (wire.tag) {
|
|
180
|
+
case "admitted":
|
|
181
|
+
return Object.freeze({ kind: "admitted", base: witnessOf(wire.base), changes: Object.freeze(wire.changes) })
|
|
182
|
+
case "invariant-rejected":
|
|
183
|
+
return Object.freeze({
|
|
184
|
+
kind: "invariant-rejected",
|
|
185
|
+
base: witnessOf(wire.base),
|
|
186
|
+
changes: Object.freeze(wire.changes),
|
|
187
|
+
violations: wire.violations
|
|
188
|
+
})
|
|
189
|
+
case "moved":
|
|
190
|
+
return Object.freeze({ kind: "moved", witnessed: witnessOf(wire.witnessed), current: witnessOf(wire.current) })
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function writeInputs(state: DbState, changes: object, options: WriteOptions, operation: string) {
|
|
195
|
+
return Effect.try({
|
|
196
|
+
try: () => {
|
|
197
|
+
const internal = internalChanges(changes)
|
|
198
|
+
if (internal === undefined || internal.schemaId !== state.schemaId) {
|
|
199
|
+
throw refusal(operation, "InvalidArgument")
|
|
200
|
+
}
|
|
201
|
+
const record = recordValue("write options", options, ["expected"])
|
|
202
|
+
const intent = recordValue(
|
|
203
|
+
"expected state",
|
|
204
|
+
record.expected,
|
|
205
|
+
options.expected.kind === "any" ? ["kind"] : ["kind", "at"]
|
|
206
|
+
)
|
|
207
|
+
let expected: ExpectedWire
|
|
208
|
+
if (intent.kind === "any") {
|
|
209
|
+
expected = { kind: "any" }
|
|
210
|
+
} else if (intent.kind === "exact") {
|
|
211
|
+
const at = recordValue("expected witness", intent.at, ["store", "generation"])
|
|
212
|
+
if (typeof at.store !== "string" || !/^[0-9a-f]{32}$/.test(at.store)) {
|
|
213
|
+
throw refusal(operation, "InvalidArgument")
|
|
214
|
+
}
|
|
215
|
+
expected = {
|
|
216
|
+
kind: "exact",
|
|
217
|
+
store: at.store,
|
|
218
|
+
generation: integerValue("expected generation", "u64", at.generation)
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
throw refusal(operation, "InvalidArgument")
|
|
222
|
+
}
|
|
223
|
+
return { handle: internal.handle, expected }
|
|
224
|
+
},
|
|
225
|
+
catch: (cause) => argumentError(operation, cause)
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
155
229
|
/**
|
|
156
|
-
* Validates the query template's schema
|
|
157
|
-
*
|
|
230
|
+
* Validates the query template's ordered logical schema and lowers it to
|
|
231
|
+
* IR plus wire params. Pure host preparation; the
|
|
158
232
|
* engine's IR validation remains the authority.
|
|
159
233
|
*/
|
|
160
234
|
function preparedOf<S extends AnySchema>(
|
|
@@ -166,7 +240,7 @@ function preparedOf<S extends AnySchema>(
|
|
|
166
240
|
readonly wire: ReturnType<typeof wireParams>
|
|
167
241
|
readonly finds: AnyQuery["data"]["finds"]
|
|
168
242
|
} {
|
|
169
|
-
if (query.schema
|
|
243
|
+
if (!schemasAgree(query.schema, theory)) {
|
|
170
244
|
throw refusal("QueryReader.execute", "InvalidArgument")
|
|
171
245
|
}
|
|
172
246
|
const ir = lowerQuery(query)
|
|
@@ -188,7 +262,7 @@ function executeOn<S extends AnySchema, A>(
|
|
|
188
262
|
Effect.gen(function* () {
|
|
189
263
|
const prepared = yield* Effect.try({
|
|
190
264
|
try: () => preparedOf(state.theory, query, params),
|
|
191
|
-
catch: (cause) => (
|
|
265
|
+
catch: (cause) => argumentError("QueryReader.execute", cause)
|
|
192
266
|
})
|
|
193
267
|
const handle = yield* nativeOperationWith(
|
|
194
268
|
"QueryReader.execute",
|
|
@@ -220,33 +294,41 @@ function internalResultHandle(result: object) {
|
|
|
220
294
|
return internalResult(result)?.handle
|
|
221
295
|
}
|
|
222
296
|
|
|
223
|
-
function getOn<S extends AnySchema,
|
|
297
|
+
function getOn<S extends AnySchema, K extends KeyStatement<Rel<S>, readonly string[]>>(
|
|
224
298
|
state: SnapshotState<S>,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
): Effect.Effect<Option.Option<Fact<
|
|
299
|
+
key: K,
|
|
300
|
+
value: Key<K>
|
|
301
|
+
): Effect.Effect<Option.Option<Fact<K["owner"]>>, DbError> {
|
|
228
302
|
return Effect.gen(function* () {
|
|
229
|
-
|
|
303
|
+
const tables = schemaTables(state.theory)
|
|
304
|
+
const resolved = yield* Effect.try({
|
|
305
|
+
try: () => declaredKey(state.theory, key),
|
|
306
|
+
catch: (cause) => argumentError("QueryReader.get", cause)
|
|
307
|
+
})
|
|
308
|
+
if (resolved === undefined || key.kind !== "key") {
|
|
309
|
+
return yield* Effect.fail(refusal("QueryReader.get", "InvalidArgument"))
|
|
310
|
+
}
|
|
311
|
+
const { statementId } = resolved
|
|
312
|
+
const relation = resolved.key.owner
|
|
313
|
+
if (!membersAgree(state.theory.relations[relation.name], relation)) {
|
|
230
314
|
return yield* Effect.fail(refusal("QueryReader.get", "InvalidArgument"))
|
|
231
315
|
}
|
|
232
|
-
const tables = schemaTables(state.theory)
|
|
233
316
|
const relationId = tables.relationIds.get(relation.name)
|
|
234
|
-
|
|
235
|
-
if (relationId === undefined || primary === undefined) {
|
|
317
|
+
if (relationId === undefined) {
|
|
236
318
|
return yield* Effect.fail(refusal("QueryReader.get", "InvalidArgument"))
|
|
237
319
|
}
|
|
238
320
|
const cells = yield* Effect.try({
|
|
239
|
-
try: () => keyCellsOf(relation
|
|
240
|
-
catch: (cause) => (
|
|
321
|
+
try: () => keyCellsOf(relation, resolved.key.projection, value),
|
|
322
|
+
catch: (cause) => argumentError("QueryReader.get", cause)
|
|
241
323
|
})
|
|
242
324
|
const row = yield* nativeOperationWith(
|
|
243
325
|
"QueryReader.get",
|
|
244
|
-
(callback) => dbNative.runtimeSnapshotGet(state.handle, relationId,
|
|
326
|
+
(callback) => dbNative.runtimeSnapshotGet(state.handle, relationId, statementId, cells, callback),
|
|
245
327
|
dbNative.runtimeRowTake,
|
|
246
328
|
(value) => value
|
|
247
329
|
)
|
|
248
330
|
if (row === null) {
|
|
249
|
-
return Option.none<Fact<
|
|
331
|
+
return Option.none<Fact<K["owner"]>>()
|
|
250
332
|
}
|
|
251
333
|
return Option.some(factOfCells(relation, row as readonly CellValue[]))
|
|
252
334
|
})
|
|
@@ -271,7 +353,7 @@ function makePrepared<P extends ParamsRecord, A>(
|
|
|
271
353
|
Effect.gen(function* () {
|
|
272
354
|
const args = yield* Effect.try({
|
|
273
355
|
try: () => wireParams(definitions, params),
|
|
274
|
-
catch: () =>
|
|
356
|
+
catch: (cause) => argumentError("PreparedQuery.execute", cause)
|
|
275
357
|
})
|
|
276
358
|
const result = yield* nativeOperationWith(
|
|
277
359
|
"PreparedQuery.execute",
|
|
@@ -296,12 +378,12 @@ function prepareOn<S extends AnySchema, P extends ParamsRecord, A>(
|
|
|
296
378
|
return Effect.gen(function* () {
|
|
297
379
|
const ir = yield* Effect.try({
|
|
298
380
|
try: () => {
|
|
299
|
-
if (query.schema
|
|
381
|
+
if (!schemasAgree(query.schema, state.theory)) {
|
|
300
382
|
throw refusal("QueryReader.prepare", "InvalidArgument")
|
|
301
383
|
}
|
|
302
384
|
return lowerQuery(query)
|
|
303
385
|
},
|
|
304
|
-
catch: () =>
|
|
386
|
+
catch: (cause) => argumentError("QueryReader.prepare", cause)
|
|
305
387
|
})
|
|
306
388
|
const handle = yield* Effect.acquireRelease(
|
|
307
389
|
nativeOperationWith(
|
|
@@ -321,8 +403,8 @@ function makeSnapshot<S extends AnySchema>(theory: S, handle: SnapshotHandle, wi
|
|
|
321
403
|
const state: SnapshotState<S> = { theory, handle }
|
|
322
404
|
const snapshot: Snapshot<S> = {
|
|
323
405
|
witness,
|
|
324
|
-
get(
|
|
325
|
-
return getOn(state,
|
|
406
|
+
get(key, value) {
|
|
407
|
+
return getOn(state, key, value)
|
|
326
408
|
},
|
|
327
409
|
execute(query, params) {
|
|
328
410
|
return executeOn(state, query, params)
|
|
@@ -376,27 +458,26 @@ function makeDb<S extends AnySchema>(theory: S, state: DbState): Db<S> {
|
|
|
376
458
|
},
|
|
377
459
|
apply(changes, options) {
|
|
378
460
|
return Effect.gen(function* () {
|
|
379
|
-
const
|
|
380
|
-
if (internal === undefined) {
|
|
381
|
-
// A foreign object supplied dynamically refuses before
|
|
382
|
-
// any native dispatch.
|
|
383
|
-
return yield* Effect.fail(refusal("Db.apply", "InvalidArgument"))
|
|
384
|
-
}
|
|
385
|
-
if (internal.schemaId !== state.schemaId) {
|
|
386
|
-
return yield* Effect.fail(refusal("Db.apply", "InvalidArgument"))
|
|
387
|
-
}
|
|
388
|
-
const expected: ExpectedWire =
|
|
389
|
-
options.expected.kind === "any"
|
|
390
|
-
? { kind: "any" }
|
|
391
|
-
: { kind: "exact", store: options.expected.at.store, generation: options.expected.at.generation }
|
|
461
|
+
const input = yield* writeInputs(state, changes, options, "Db.apply")
|
|
392
462
|
return yield* nativeOperationWith(
|
|
393
463
|
"Db.apply",
|
|
394
|
-
(callback) => dbNative.runtimeDbApply(state.db,
|
|
464
|
+
(callback) => dbNative.runtimeDbApply(state.db, input.handle, input.expected, callback),
|
|
395
465
|
dbNative.runtimeApplyTake,
|
|
396
466
|
outcomeOf
|
|
397
467
|
)
|
|
398
468
|
})
|
|
399
469
|
},
|
|
470
|
+
judge(changes, options) {
|
|
471
|
+
return Effect.gen(function* () {
|
|
472
|
+
const input = yield* writeInputs(state, changes, options, "Db.judge")
|
|
473
|
+
return yield* nativeOperationWith(
|
|
474
|
+
"Db.judge",
|
|
475
|
+
(callback) => dbNative.runtimeDbJudge(state.db, input.handle, input.expected, callback),
|
|
476
|
+
dbNative.runtimeJudgeTake,
|
|
477
|
+
judgmentOf
|
|
478
|
+
)
|
|
479
|
+
})
|
|
480
|
+
},
|
|
400
481
|
inspect() {
|
|
401
482
|
return nativeOperationWith(
|
|
402
483
|
"Db.inspect",
|
|
@@ -446,7 +527,7 @@ function openDatabase<S extends AnySchema>(
|
|
|
446
527
|
return Effect.gen(function* () {
|
|
447
528
|
const runtime = yield* runtimeHandle()
|
|
448
529
|
const compiled: CompiledSchema<S> = yield* CoreSchema.compile(schema)
|
|
449
|
-
const spec = lower(schema)
|
|
530
|
+
const spec = lower(compiled.schema)
|
|
450
531
|
// Compound acquisition: register the directory owner and
|
|
451
532
|
// its finalizer BEFORE any interruptible child-open step.
|
|
452
533
|
const directory = yield* Effect.acquireRelease(
|
|
@@ -482,8 +563,8 @@ function openDatabase<S extends AnySchema>(
|
|
|
482
563
|
)
|
|
483
564
|
return yield* Effect.fail(refusal(operation, "InvalidArgument"))
|
|
484
565
|
}
|
|
485
|
-
const state: DbState = { theory: schema, db: outcome.db, directory, schemaId: compiled.schemaId }
|
|
486
|
-
const db = makeDb(schema, state)
|
|
566
|
+
const state: DbState = { theory: compiled.schema, db: outcome.db, directory, schemaId: compiled.schemaId }
|
|
567
|
+
const db = makeDb(compiled.schema, state)
|
|
487
568
|
dbStates.set(db, state)
|
|
488
569
|
return db
|
|
489
570
|
}),
|
|
@@ -542,10 +623,10 @@ function internalPublishedReader<S extends AnySchema>(
|
|
|
542
623
|
readonly execute: QueryReader<S>["execute"]
|
|
543
624
|
readonly prepare: QueryReader<S>["prepare"]
|
|
544
625
|
} {
|
|
545
|
-
const state: SnapshotState<S> = { theory, handle: core as SnapshotHandle }
|
|
626
|
+
const state: SnapshotState<S> = { theory: schemaDescriptor(theory), handle: core as SnapshotHandle }
|
|
546
627
|
return Object.freeze({
|
|
547
|
-
get<
|
|
548
|
-
return getOn(state,
|
|
628
|
+
get<K extends KeyStatement<Rel<S>, readonly string[]>>(key: K, value: NoInfer<Key<K>>) {
|
|
629
|
+
return getOn(state, key, value)
|
|
549
630
|
},
|
|
550
631
|
execute<P extends ParamsRecord, A>(queryValue: QueryTemplate<S, P, A>, params: P) {
|
|
551
632
|
return executeOn<S, A>(state, queryValue as AnyQuery, params)
|
|
@@ -557,14 +638,15 @@ function internalPublishedReader<S extends AnySchema>(
|
|
|
557
638
|
}
|
|
558
639
|
|
|
559
640
|
export type {
|
|
560
|
-
ApplyExpected,
|
|
561
|
-
ApplyOptions,
|
|
562
641
|
ApplyOutcome,
|
|
563
642
|
CoreWitness,
|
|
564
643
|
DbInspection,
|
|
644
|
+
JudgeOutcome,
|
|
565
645
|
PreparedQuery,
|
|
566
646
|
QueryReader,
|
|
567
647
|
Snapshot,
|
|
568
|
-
StorageInspection
|
|
648
|
+
StorageInspection,
|
|
649
|
+
WriteExpected,
|
|
650
|
+
WriteOptions
|
|
569
651
|
}
|
|
570
652
|
export { Db, internalPublishedReader }
|
package/src/errors.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Data } from "effect"
|
|
1
|
+
import { Data, Schema } from "effect"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Pure authoring failures, SDK-invariant defects and internal native-boundary
|
|
@@ -8,8 +8,23 @@ import { Data } from "effect"
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/** A pure schema, query, parameter, or value-authoring refusal. */
|
|
11
|
+
export const AuthoringDiagnostic = Schema.Struct({
|
|
12
|
+
code: Schema.Literals([
|
|
13
|
+
"InvalidValue",
|
|
14
|
+
"InvalidRecord",
|
|
15
|
+
"UnknownField",
|
|
16
|
+
"MissingField",
|
|
17
|
+
"InvalidDeclaration",
|
|
18
|
+
"InvalidQuery"
|
|
19
|
+
]),
|
|
20
|
+
context: Schema.String,
|
|
21
|
+
expected: Schema.String
|
|
22
|
+
})
|
|
23
|
+
export type AuthoringDiagnostic = typeof AuthoringDiagnostic.Type
|
|
24
|
+
|
|
11
25
|
export class AuthoringError extends Data.TaggedError("AuthoringError")<{
|
|
12
26
|
readonly message: string
|
|
27
|
+
readonly diagnostic?: AuthoringDiagnostic
|
|
13
28
|
}> {}
|
|
14
29
|
|
|
15
30
|
/** A contradiction in an SDK/native result, not an application refusal. */
|
package/src/face.ts
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
import type { AnyClosed
|
|
1
|
+
import type { AnyClosed } from "#closed.ts"
|
|
2
|
+
import { memberDescriptor, sealedFieldOf } from "#closed.ts"
|
|
2
3
|
import { AuthoringError } from "#errors.ts"
|
|
3
|
-
import type
|
|
4
|
+
import { type AnyField, assertDeclarationRecord, type SignatureOf } from "#fields.ts"
|
|
4
5
|
import type { Same } from "#judgment.ts"
|
|
5
|
-
import type { AnyRelation,
|
|
6
|
+
import type { AnyRelation, FieldsShape } from "#relation.ts"
|
|
7
|
+
import { type AnySelected, type FieldsOf, type SelectionBinding, selectionBindings } from "#selection.ts"
|
|
6
8
|
import { renderLiteralSet } from "#spec.ts"
|
|
9
|
+
import { arrayValue, recordValue } from "#values.ts"
|
|
7
10
|
|
|
8
11
|
const emptySelection: readonly SelectionBinding[] = Object.freeze([])
|
|
9
12
|
|
|
10
|
-
type OwnerOf<S extends FaceSource> = S extends AnySelected
|
|
13
|
+
type OwnerOf<S extends FaceSource> = S extends AnySelected ? S["relation"] : S
|
|
11
14
|
|
|
12
15
|
function faceParts(source: FaceSource): {
|
|
13
16
|
readonly owner: FaceOwner
|
|
14
17
|
readonly selection: readonly SelectionBinding[]
|
|
15
18
|
} {
|
|
19
|
+
assertDeclarationRecord("face source", source)
|
|
16
20
|
if ("relation" in source) {
|
|
21
|
+
recordValue("selected relation", source, ["relation", "selection"])
|
|
17
22
|
return { owner: source.relation, selection: source.selection }
|
|
18
23
|
}
|
|
19
24
|
return { owner: source, selection: emptySelection }
|
|
@@ -21,31 +26,17 @@ function faceParts(source: FaceSource): {
|
|
|
21
26
|
|
|
22
27
|
type FaceOwner = AnyRelation | AnyClosed
|
|
23
28
|
|
|
24
|
-
interface
|
|
29
|
+
interface Face<O extends FaceOwner = FaceOwner, P extends readonly string[] = readonly string[]> {
|
|
25
30
|
readonly owner: O
|
|
26
31
|
readonly projection: P
|
|
27
32
|
readonly selection: readonly SelectionBinding[]
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
readonly source: S
|
|
32
|
-
readonly projection: P
|
|
33
|
-
readonly data: FaceData<OwnerOf<S>, P>
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type AnyFace = Face<FaceSource, readonly string[]>
|
|
35
|
+
type AnyFace = Face<FaceOwner, readonly string[]>
|
|
37
36
|
|
|
38
|
-
type FaceSource = AnyRelation | AnyClosed | AnySelected
|
|
37
|
+
type FaceSource = AnyRelation | AnyClosed | AnySelected
|
|
39
38
|
|
|
40
|
-
type FaceFields<S extends FaceSource> = S
|
|
41
|
-
? keyof RelationFields<S["relation"]> & string
|
|
42
|
-
: S extends AnySelectedClosed
|
|
43
|
-
? "id" | (keyof S["relation"]["columns"] & string)
|
|
44
|
-
: S extends AnyRelation
|
|
45
|
-
? keyof RelationFields<S> & string
|
|
46
|
-
: S extends { readonly axioms: Readonly<Record<string, infer Row>> }
|
|
47
|
-
? "id" | (keyof Row & string)
|
|
48
|
-
: never
|
|
39
|
+
type FaceFields<S extends FaceSource> = keyof FieldsOf<OwnerOf<S>> & string
|
|
49
40
|
|
|
50
41
|
/**
|
|
51
42
|
* The wire shape a face projects: the field's {@link SignatureOf} with an
|
|
@@ -61,28 +52,13 @@ type ShapeIn<Fields extends FieldsShape, K extends string> = K extends keyof Fie
|
|
|
61
52
|
? ProjectedSignature<Fields[K]>
|
|
62
53
|
: undefined
|
|
63
54
|
|
|
64
|
-
type ProjectedShape<S extends FaceSource, K extends string> = S
|
|
65
|
-
? ShapeIn<RelationFields<S["relation"]>, K>
|
|
66
|
-
: S extends AnySelectedClosed
|
|
67
|
-
? K extends "id"
|
|
68
|
-
? ProjectedSignature<S["relation"]["id"]>
|
|
69
|
-
: ShapeIn<S["relation"]["columns"], K>
|
|
70
|
-
: S extends AnyRelation
|
|
71
|
-
? ShapeIn<RelationFields<S>, K>
|
|
72
|
-
: S extends {
|
|
73
|
-
readonly id: infer Id extends AnyField
|
|
74
|
-
readonly columns: infer Cols extends Record<string, PayloadField>
|
|
75
|
-
}
|
|
76
|
-
? K extends "id"
|
|
77
|
-
? ProjectedSignature<Id>
|
|
78
|
-
: ShapeIn<Cols, K>
|
|
79
|
-
: undefined
|
|
55
|
+
type ProjectedShape<S extends FaceSource, K extends string> = ShapeIn<FieldsOf<OwnerOf<S>>, K>
|
|
80
56
|
|
|
81
57
|
type ShapesOf<S extends FaceSource, P extends readonly string[]> = {
|
|
82
58
|
readonly [I in keyof P]: ProjectedShape<S, P[I] & string>
|
|
83
59
|
}
|
|
84
60
|
|
|
85
|
-
type FaceShapes<F extends AnyFace> =
|
|
61
|
+
type FaceShapes<F extends AnyFace> = ShapesOf<F["owner"], F["projection"]>
|
|
86
62
|
|
|
87
63
|
type Arity<F extends AnyFace> = F["projection"]["length"]
|
|
88
64
|
|
|
@@ -106,50 +82,45 @@ interface FaceShapeMismatch<Left, Right> {
|
|
|
106
82
|
type SameShapes<A extends AnyFace, B extends AnyFace> =
|
|
107
83
|
Same<FaceShapes<A>, FaceShapes<B>> extends true ? unknown : FaceShapeMismatch<FaceShapes<A>, FaceShapes<B>>
|
|
108
84
|
|
|
109
|
-
function on<S extends FaceSource, const F extends FaceFields<S>>(source: S, field: F): Face<S
|
|
85
|
+
function on<S extends FaceSource, const F extends FaceFields<S>>(source: S, field: F): Face<OwnerOf<S>, readonly [F]>
|
|
110
86
|
function on<S extends FaceSource, const P extends readonly [FaceFields<S>, ...FaceFields<S>[]]>(
|
|
111
87
|
source: S,
|
|
112
88
|
fields: P
|
|
113
|
-
): Face<S
|
|
114
|
-
function on
|
|
115
|
-
const projection: readonly string[] = Object.freeze(typeof fields === "string" ? [fields] : [...fields])
|
|
89
|
+
): Face<OwnerOf<S>, P>
|
|
90
|
+
function on(source: FaceSource, fields: string | readonly string[]): AnyFace {
|
|
116
91
|
const parts = faceParts(source)
|
|
117
|
-
|
|
92
|
+
return faceDescriptor({
|
|
118
93
|
owner: parts.owner,
|
|
119
|
-
projection,
|
|
94
|
+
projection: typeof fields === "string" ? [fields] : fields,
|
|
120
95
|
selection: parts.selection
|
|
121
96
|
})
|
|
122
|
-
const value = Object.freeze({ source, projection, data })
|
|
123
|
-
if (!faceMinted<S, readonly string[]>(value, source, projection)) {
|
|
124
|
-
throw new AuthoringError({ message: `face over ${parts.owner.name}: face construction incomplete` })
|
|
125
|
-
}
|
|
126
|
-
return value
|
|
127
97
|
}
|
|
128
98
|
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
)
|
|
99
|
+
/** Own and check a structural face. No constructor-only admission token. */
|
|
100
|
+
function faceDescriptor<F extends AnyFace>(input: F): F
|
|
101
|
+
function faceDescriptor(input: unknown): AnyFace
|
|
102
|
+
function faceDescriptor(raw: unknown): AnyFace {
|
|
103
|
+
const input = recordValue("face", raw, ["owner", "projection", "selection"])
|
|
104
|
+
const owner = memberDescriptor(input.owner)
|
|
105
|
+
const projection = arrayValue("face projection", input.projection, (_, value) => {
|
|
106
|
+
if (typeof value !== "string") throw new AuthoringError({ message: "face: expected a field name" })
|
|
107
|
+
return value
|
|
108
|
+
})
|
|
109
|
+
if (projection.length === 0) {
|
|
110
|
+
throw new AuthoringError({ message: "face: expected a nonempty projection" })
|
|
111
|
+
}
|
|
112
|
+
const projected = new Set<string>()
|
|
113
|
+
for (const field of projection) {
|
|
114
|
+
if (typeof field !== "string" || sealedFieldOf(owner, field) === undefined)
|
|
115
|
+
throw new AuthoringError({ message: `face ${owner.name}: unknown field ${String(field)}` })
|
|
116
|
+
if (projected.has(field)) throw new AuthoringError({ message: `face ${owner.name}: duplicate field ${field}` })
|
|
117
|
+
projected.add(field)
|
|
118
|
+
}
|
|
119
|
+
const selection = selectionBindings(owner, input.selection)
|
|
120
|
+
return Object.freeze({ owner, projection, selection })
|
|
150
121
|
}
|
|
151
122
|
|
|
152
|
-
function renderFace(face:
|
|
123
|
+
function renderFace(face: AnyFace): string {
|
|
153
124
|
const projection = face.projection.join(", ")
|
|
154
125
|
if (face.selection.length === 0) {
|
|
155
126
|
return `${face.owner.name}(${projection})`
|
|
@@ -167,7 +138,6 @@ export type {
|
|
|
167
138
|
Arity,
|
|
168
139
|
Face,
|
|
169
140
|
FaceArityMismatch,
|
|
170
|
-
FaceData,
|
|
171
141
|
FaceFields,
|
|
172
142
|
FaceOwner,
|
|
173
143
|
FaceShapeMismatch,
|
|
@@ -178,4 +148,4 @@ export type {
|
|
|
178
148
|
SameArity,
|
|
179
149
|
SameShapes
|
|
180
150
|
}
|
|
181
|
-
export { on, renderFace }
|
|
151
|
+
export { faceDescriptor, on, renderFace }
|