@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.
Files changed (148) hide show
  1. package/COOKBOOK.md +114 -11
  2. package/README.md +52 -9
  3. package/dist/capacity.d.ts +15 -55
  4. package/dist/capacity.d.ts.map +1 -1
  5. package/dist/capacity.js +63 -12
  6. package/dist/capacity.js.map +1 -1
  7. package/dist/changes.d.ts +26 -3
  8. package/dist/changes.d.ts.map +1 -1
  9. package/dist/changes.js +89 -44
  10. package/dist/changes.js.map +1 -1
  11. package/dist/closed.d.ts +23 -51
  12. package/dist/closed.d.ts.map +1 -1
  13. package/dist/closed.js +110 -168
  14. package/dist/closed.js.map +1 -1
  15. package/dist/codec.d.ts +4 -9
  16. package/dist/codec.d.ts.map +1 -1
  17. package/dist/codec.js +108 -183
  18. package/dist/codec.js.map +1 -1
  19. package/dist/compile.d.ts +16 -16
  20. package/dist/compile.d.ts.map +1 -1
  21. package/dist/compile.js +36 -15
  22. package/dist/compile.js.map +1 -1
  23. package/dist/db-native.d.ts +39 -0
  24. package/dist/db-native.d.ts.map +1 -1
  25. package/dist/db-native.js +8 -0
  26. package/dist/db-native.js.map +1 -1
  27. package/dist/db.d.ts +28 -8
  28. package/dist/db.d.ts.map +1 -1
  29. package/dist/db.js +91 -38
  30. package/dist/db.js.map +1 -1
  31. package/dist/errors.d.ts +11 -3
  32. package/dist/errors.d.ts.map +1 -1
  33. package/dist/errors.js +13 -1
  34. package/dist/errors.js.map +1 -1
  35. package/dist/face.d.ts +19 -25
  36. package/dist/face.d.ts.map +1 -1
  37. package/dist/face.js +30 -24
  38. package/dist/face.js.map +1 -1
  39. package/dist/fields.d.ts +13 -26
  40. package/dist/fields.d.ts.map +1 -1
  41. package/dist/fields.js +89 -116
  42. package/dist/fields.js.map +1 -1
  43. package/dist/immutable.d.ts +10 -0
  44. package/dist/immutable.d.ts.map +1 -0
  45. package/dist/immutable.js +75 -0
  46. package/dist/immutable.js.map +1 -0
  47. package/dist/index.d.ts +14 -10
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +5 -3
  50. package/dist/index.js.map +1 -1
  51. package/dist/internal/log.d.ts +2 -0
  52. package/dist/internal/log.d.ts.map +1 -1
  53. package/dist/internal/log.js +2 -0
  54. package/dist/internal/log.js.map +1 -1
  55. package/dist/law.d.ts +10 -10
  56. package/dist/law.d.ts.map +1 -1
  57. package/dist/law.js +1 -1
  58. package/dist/law.js.map +1 -1
  59. package/dist/lower.d.ts.map +1 -1
  60. package/dist/lower.js +11 -6
  61. package/dist/lower.js.map +1 -1
  62. package/dist/native.d.ts +1 -1
  63. package/dist/native.d.ts.map +1 -1
  64. package/dist/native.js.map +1 -1
  65. package/dist/query/atom.d.ts +1 -1
  66. package/dist/query/atom.d.ts.map +1 -1
  67. package/dist/query/atom.js.map +1 -1
  68. package/dist/query/description.d.ts +33 -0
  69. package/dist/query/description.d.ts.map +1 -0
  70. package/dist/query/description.js +302 -0
  71. package/dist/query/description.js.map +1 -0
  72. package/dist/query/lower.d.ts +60 -4
  73. package/dist/query/lower.d.ts.map +1 -1
  74. package/dist/query/lower.js +126 -161
  75. package/dist/query/lower.js.map +1 -1
  76. package/dist/query/parse-ir.d.ts +7 -2
  77. package/dist/query/parse-ir.d.ts.map +1 -1
  78. package/dist/query/parse-ir.js +240 -53
  79. package/dist/query/parse-ir.js.map +1 -1
  80. package/dist/query/run.d.ts.map +1 -1
  81. package/dist/query/run.js +8 -10
  82. package/dist/query/run.js.map +1 -1
  83. package/dist/query/scope.d.ts +3 -4
  84. package/dist/query/scope.d.ts.map +1 -1
  85. package/dist/query/scope.js +5 -10
  86. package/dist/query/scope.js.map +1 -1
  87. package/dist/relation.d.ts +22 -48
  88. package/dist/relation.d.ts.map +1 -1
  89. package/dist/relation.js +30 -82
  90. package/dist/relation.js.map +1 -1
  91. package/dist/rows.d.ts +7 -4
  92. package/dist/rows.d.ts.map +1 -1
  93. package/dist/rows.js +44 -198
  94. package/dist/rows.js.map +1 -1
  95. package/dist/runtime-errors.d.ts +26 -2
  96. package/dist/runtime-errors.d.ts.map +1 -1
  97. package/dist/runtime-errors.js +43 -3
  98. package/dist/runtime-errors.js.map +1 -1
  99. package/dist/schema.d.ts +9 -1
  100. package/dist/schema.d.ts.map +1 -1
  101. package/dist/schema.js +85 -24
  102. package/dist/schema.js.map +1 -1
  103. package/dist/selection.d.ts +27 -0
  104. package/dist/selection.d.ts.map +1 -0
  105. package/dist/selection.js +104 -0
  106. package/dist/selection.js.map +1 -0
  107. package/dist/shape.d.ts +6 -9
  108. package/dist/shape.d.ts.map +1 -1
  109. package/dist/statements.d.ts +17 -52
  110. package/dist/statements.d.ts.map +1 -1
  111. package/dist/statements.js +92 -111
  112. package/dist/statements.js.map +1 -1
  113. package/dist/values.d.ts +25 -0
  114. package/dist/values.d.ts.map +1 -0
  115. package/dist/values.js +203 -0
  116. package/dist/values.js.map +1 -0
  117. package/pack-provenance.json +3 -3
  118. package/package.json +4 -4
  119. package/src/capacity.ts +86 -76
  120. package/src/changes.ts +183 -54
  121. package/src/closed.ts +161 -314
  122. package/src/codec.ts +116 -190
  123. package/src/compile.ts +56 -32
  124. package/src/db-native.ts +42 -0
  125. package/src/db.ts +134 -52
  126. package/src/errors.ts +16 -1
  127. package/src/face.ts +44 -74
  128. package/src/fields.ts +100 -146
  129. package/src/immutable.ts +76 -0
  130. package/src/index.ts +34 -28
  131. package/src/internal/log.ts +2 -0
  132. package/src/law.ts +13 -13
  133. package/src/lower.ts +13 -10
  134. package/src/native.ts +2 -0
  135. package/src/query/atom.ts +1 -0
  136. package/src/query/description.ts +394 -0
  137. package/src/query/lower.ts +139 -173
  138. package/src/query/parse-ir.ts +247 -54
  139. package/src/query/run.ts +15 -9
  140. package/src/query/scope.ts +10 -18
  141. package/src/relation.ts +58 -142
  142. package/src/rows.ts +62 -205
  143. package/src/runtime-errors.ts +47 -3
  144. package/src/schema.ts +99 -29
  145. package/src/selection.ts +124 -0
  146. package/src/shape.ts +9 -9
  147. package/src/statements.ts +138 -189
  148. 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 ApplyExpected = { readonly kind: "any" } | { readonly kind: "exact"; readonly at: CoreWitness }
65
+ type WriteExpected = { readonly kind: "any" } | { readonly kind: "exact"; readonly at: CoreWitness }
61
66
 
62
- /** Expected-state intent for an atomic apply. */
63
- type ApplyOptions = { readonly expected: ApplyExpected }
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<R extends Rel<S>>(relation: R, key: Key<R>): Effect.Effect<Option.Option<Fact<R>>, DbError>
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: ApplyOptions): Effect.Effect<ApplyOutcome, DbError>
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 binding (identity, the membership
157
- * rule) and lowers it to IR plus wire params. Pure host preparation; the
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 !== theory) {
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) => (cause instanceof DbError ? cause : refusal("QueryReader.execute", "InvalidArgument"))
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, R extends Rel<S>>(
297
+ function getOn<S extends AnySchema, K extends KeyStatement<Rel<S>, readonly string[]>>(
224
298
  state: SnapshotState<S>,
225
- relation: R,
226
- key: Key<R>
227
- ): Effect.Effect<Option.Option<Fact<R>>, DbError> {
299
+ key: K,
300
+ value: Key<K>
301
+ ): Effect.Effect<Option.Option<Fact<K["owner"]>>, DbError> {
228
302
  return Effect.gen(function* () {
229
- if (state.theory.relations[relation.name] !== relation) {
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
- const primary = tables.primaryKeys.get(relation.name)
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.data, primary.projection, key as Readonly<Record<string, unknown>>),
240
- catch: (cause) => (cause instanceof DbError ? cause : refusal("QueryReader.get", "InvalidArgument"))
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, primary.statementId, cells, callback),
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<R>>()
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: () => refusal("PreparedQuery.execute", "InvalidArgument")
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 !== state.theory) {
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: () => refusal("QueryReader.prepare", "InvalidArgument")
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(relation, key) {
325
- return getOn(state, relation, key)
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 internal = internalChanges(changes)
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, internal.handle, expected, callback),
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<R extends Rel<S>>(relation: R, key: Key<R>) {
548
- return getOn(state, relation, key)
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, AnySelectedClosed, PayloadField } from "#closed.ts"
1
+ import type { AnyClosed } from "#closed.ts"
2
+ import { memberDescriptor, sealedFieldOf } from "#closed.ts"
2
3
  import { AuthoringError } from "#errors.ts"
3
- import type { AnyField, SignatureOf } from "#fields.ts"
4
+ import { type AnyField, assertDeclarationRecord, type SignatureOf } from "#fields.ts"
4
5
  import type { Same } from "#judgment.ts"
5
- import type { AnyRelation, AnySelected, FieldsShape, RelationFields, SelectionBinding } from "#relation.ts"
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 | AnySelectedClosed ? S["relation"] : S
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 FaceData<O extends FaceOwner = FaceOwner, P extends readonly string[] = readonly string[]> {
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
- interface Face<S extends FaceSource, P extends readonly string[]> {
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 | AnySelectedClosed
37
+ type FaceSource = AnyRelation | AnyClosed | AnySelected
39
38
 
40
- type FaceFields<S extends FaceSource> = S extends AnySelected
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 extends AnySelected
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> = F extends Face<infer S, infer P> ? ShapesOf<S, P> : never
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, readonly [F]>
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, P>
114
- function on<S extends FaceSource>(source: S, fields: string | readonly string[]): Face<S, readonly string[]> {
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
- const data: FaceData = Object.freeze({
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
- * The trusted admission seam of the face mint (the pattern's home is
131
- * `isTypedScope` in query/lower.ts): the
132
- * checkable facts — the value carries exactly the source and projection it
133
- * was built from, and `data.owner` is exactly the owner {@link faceParts}
134
- * resolves for that source — are verified before the wide construction is
135
- * admitted at the exact {@link Face} type (whose `data` claims the owner at
136
- * its precise type, the carrier the schema-level law-typing reads).
137
- */
138
- function faceMinted<S extends FaceSource, P extends readonly string[]>(
139
- value: { readonly source: FaceSource; readonly projection: readonly string[]; readonly data: FaceData },
140
- source: S,
141
- projection: P
142
- ): value is Face<S, P> {
143
- const owner = "relation" in source ? source.relation : source
144
- return (
145
- value.source === source &&
146
- value.projection === projection &&
147
- value.data.owner === owner &&
148
- value.data.projection === projection
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: FaceData): string {
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 }