@bjornpagen/bumbledb 1.0.1 → 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 (172) hide show
  1. package/COOKBOOK.md +176 -36
  2. package/README.md +70 -26
  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 +28 -6
  8. package/dist/changes.d.ts.map +1 -1
  9. package/dist/changes.js +119 -71
  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 +8 -14
  16. package/dist/codec.d.ts.map +1 -1
  17. package/dist/codec.js +113 -188
  18. package/dist/codec.js.map +1 -1
  19. package/dist/compile.d.ts +17 -18
  20. package/dist/compile.d.ts.map +1 -1
  21. package/dist/compile.js +41 -20
  22. package/dist/compile.js.map +1 -1
  23. package/dist/db-native.d.ts +76 -41
  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 +61 -26
  28. package/dist/db.d.ts.map +1 -1
  29. package/dist/db.js +152 -85
  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 +16 -12
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +6 -4
  50. package/dist/index.js.map +1 -1
  51. package/dist/internal/log.d.ts +6 -4
  52. package/dist/internal/log.d.ts.map +1 -1
  53. package/dist/internal/log.js +3 -1
  54. package/dist/internal/log.js.map +1 -1
  55. package/dist/law.d.ts +11 -11
  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/migration.d.ts +2 -3
  63. package/dist/migration.d.ts.map +1 -1
  64. package/dist/migration.js +5 -5
  65. package/dist/migration.js.map +1 -1
  66. package/dist/native.d.ts +2 -2
  67. package/dist/native.d.ts.map +1 -1
  68. package/dist/native.js.map +1 -1
  69. package/dist/query/atom.d.ts +1 -1
  70. package/dist/query/atom.d.ts.map +1 -1
  71. package/dist/query/atom.js.map +1 -1
  72. package/dist/query/description.d.ts +33 -0
  73. package/dist/query/description.d.ts.map +1 -0
  74. package/dist/query/description.js +302 -0
  75. package/dist/query/description.js.map +1 -0
  76. package/dist/query/lower.d.ts +60 -4
  77. package/dist/query/lower.d.ts.map +1 -1
  78. package/dist/query/lower.js +126 -161
  79. package/dist/query/lower.js.map +1 -1
  80. package/dist/query/parse-ir.d.ts +7 -2
  81. package/dist/query/parse-ir.d.ts.map +1 -1
  82. package/dist/query/parse-ir.js +240 -53
  83. package/dist/query/parse-ir.js.map +1 -1
  84. package/dist/query/run.d.ts.map +1 -1
  85. package/dist/query/run.js +8 -10
  86. package/dist/query/run.js.map +1 -1
  87. package/dist/query/scope.d.ts +3 -4
  88. package/dist/query/scope.d.ts.map +1 -1
  89. package/dist/query/scope.js +5 -10
  90. package/dist/query/scope.js.map +1 -1
  91. package/dist/relation.d.ts +22 -48
  92. package/dist/relation.d.ts.map +1 -1
  93. package/dist/relation.js +30 -82
  94. package/dist/relation.js.map +1 -1
  95. package/dist/result.d.ts +10 -25
  96. package/dist/result.d.ts.map +1 -1
  97. package/dist/result.js +7 -12
  98. package/dist/result.js.map +1 -1
  99. package/dist/rows.d.ts +11 -14
  100. package/dist/rows.d.ts.map +1 -1
  101. package/dist/rows.js +49 -237
  102. package/dist/rows.js.map +1 -1
  103. package/dist/runtime-codes.d.ts +1 -1
  104. package/dist/runtime-codes.d.ts.map +1 -1
  105. package/dist/runtime-codes.js +1 -2
  106. package/dist/runtime-codes.js.map +1 -1
  107. package/dist/runtime-errors.d.ts +27 -15
  108. package/dist/runtime-errors.d.ts.map +1 -1
  109. package/dist/runtime-errors.js +44 -8
  110. package/dist/runtime-errors.js.map +1 -1
  111. package/dist/runtime-native.d.ts +16 -34
  112. package/dist/runtime-native.d.ts.map +1 -1
  113. package/dist/runtime-native.js.map +1 -1
  114. package/dist/runtime.d.ts +11 -28
  115. package/dist/runtime.d.ts.map +1 -1
  116. package/dist/runtime.js +14 -54
  117. package/dist/runtime.js.map +1 -1
  118. package/dist/schema.d.ts +9 -1
  119. package/dist/schema.d.ts.map +1 -1
  120. package/dist/schema.js +85 -24
  121. package/dist/schema.js.map +1 -1
  122. package/dist/selection.d.ts +27 -0
  123. package/dist/selection.d.ts.map +1 -0
  124. package/dist/selection.js +104 -0
  125. package/dist/selection.js.map +1 -0
  126. package/dist/shape.d.ts +6 -9
  127. package/dist/shape.d.ts.map +1 -1
  128. package/dist/statements.d.ts +17 -52
  129. package/dist/statements.d.ts.map +1 -1
  130. package/dist/statements.js +92 -111
  131. package/dist/statements.js.map +1 -1
  132. package/dist/values.d.ts +25 -0
  133. package/dist/values.d.ts.map +1 -0
  134. package/dist/values.js +203 -0
  135. package/dist/values.js.map +1 -0
  136. package/pack-provenance.json +3 -3
  137. package/package.json +4 -4
  138. package/src/capacity.ts +86 -76
  139. package/src/changes.ts +225 -94
  140. package/src/closed.ts +161 -314
  141. package/src/codec.ts +123 -213
  142. package/src/compile.ts +61 -38
  143. package/src/db-native.ts +73 -68
  144. package/src/db.ts +268 -165
  145. package/src/errors.ts +16 -1
  146. package/src/face.ts +44 -74
  147. package/src/fields.ts +100 -146
  148. package/src/immutable.ts +76 -0
  149. package/src/index.ts +38 -31
  150. package/src/internal/log.ts +4 -5
  151. package/src/law.ts +14 -14
  152. package/src/lower.ts +13 -10
  153. package/src/migration.ts +5 -12
  154. package/src/native.ts +2 -1
  155. package/src/query/atom.ts +1 -0
  156. package/src/query/description.ts +394 -0
  157. package/src/query/lower.ts +139 -173
  158. package/src/query/parse-ir.ts +247 -54
  159. package/src/query/run.ts +15 -9
  160. package/src/query/scope.ts +10 -18
  161. package/src/relation.ts +58 -142
  162. package/src/result.ts +16 -45
  163. package/src/rows.ts +57 -250
  164. package/src/runtime-codes.ts +1 -2
  165. package/src/runtime-errors.ts +48 -8
  166. package/src/runtime-native.ts +13 -46
  167. package/src/runtime.ts +51 -119
  168. package/src/schema.ts +99 -29
  169. package/src/selection.ts +124 -0
  170. package/src/shape.ts +9 -9
  171. package/src/statements.ts +138 -189
  172. package/src/values.ts +223 -0
package/src/db.ts CHANGED
@@ -1,15 +1,17 @@
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,
12
- SessionHandle,
13
+ JudgeOutcomeWire,
14
+ PreparedHandle,
13
15
  SnapshotHandle,
14
16
  WitnessWire
15
17
  } from "#db-native.ts"
@@ -25,22 +27,25 @@ import type { CompleteResult } from "#result.ts"
25
27
  import { internalResult, makeCompleteResult } from "#result.ts"
26
28
  import type { CellValue } from "#rows.ts"
27
29
  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 { NativeRuntime } from "#runtime.ts"
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
39
- * `Snapshot`s, reusable `ExecutionSession`s, the shared `QueryReader`
44
+ * `Snapshot`s, reusable `PreparedQuery`s, the shared `QueryReader`
40
45
  * capability, one immutable final-state `apply`, bounded `inspect` and
41
46
  * 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
47
+ * native work runs on the shared executor with cooperative cancellation.
48
+ * Resources are scoped with `CloseFailure`-defect
44
49
  * finalizers. There is no Promise/sync/disposal twin, no transaction
45
50
  * callback, no per-row fiber and no Proxy row anywhere.
46
51
  *
@@ -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
- /** Core work policy plus the expected-state intent. */
63
- type ApplyOptions = ExecutionPolicy & { 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,14 +73,36 @@ 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
 
71
- /** Bounded database diagnostics: measurements, never retained rows. */
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
+
89
+ /** Storage measurements, not heap usage or mapped-page residency. */
90
+ interface StorageInspection {
91
+ /** Reserved virtual address range for the LMDB mapping. */
92
+ readonly virtualMapBytes: bigint
93
+ /** File length; may include sparse regions and free pages. */
94
+ readonly populatedFileBytes: bigint
95
+ /** LMDB's non-free branch, leaf and overflow pages. Not resident RAM. */
96
+ readonly nonFreePageBytes: bigint
97
+ /** Allocated filesystem blocks, or null when the OS cannot report them. */
98
+ readonly allocatedDiskBytes: bigint | null
99
+ }
100
+
101
+ /** Database diagnostics: measurements, never retained rows. */
72
102
  interface DbInspection {
73
103
  readonly schemaId: SchemaId
74
104
  readonly generation: bigint
75
- readonly mapBytes: bigint
76
- readonly populatedBytes: bigint
77
- readonly diskBytes: bigint
78
- readonly residentEstimateBytes: bigint
105
+ readonly storage: StorageInspection
79
106
  readonly retainedOperations: bigint
80
107
  }
81
108
 
@@ -87,34 +114,43 @@ interface DbInspection {
87
114
  * no writable authority — never a `Db`, an `apply`, or a raw transaction.
88
115
  */
89
116
  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>
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>
91
121
  execute<P extends ParamsRecord, A>(
92
122
  query: QueryTemplate<S, P, A>,
93
- params: P,
94
- work: ExecutionPolicy
123
+ params: P
95
124
  ): Effect.Effect<CompleteResult<A>, DbError, Scope.Scope>
125
+ prepare<P extends ParamsRecord, A>(
126
+ query: QueryTemplate<S, P, A>
127
+ ): Effect.Effect<PreparedQuery<P, A>, DbError, Scope.Scope>
96
128
  }
97
129
 
98
130
  interface Snapshot<S extends AnySchema> extends QueryReader<S> {
99
131
  readonly witness: CoreWitness
100
- session(work: ExecutionPolicy): Effect.Effect<ExecutionSession<S>, DbError, Scope.Scope>
101
132
  close(): Effect.Effect<CloseReport>
102
133
  }
103
134
 
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>
135
+ /** One compiled plan and its reusable buffers, pinned to the preparing snapshot.
136
+ * Closing it releases its own state, not the snapshot or completed results.
137
+ */
138
+ interface PreparedQuery<P extends ParamsRecord, A> {
139
+ execute(params: P): Effect.Effect<CompleteResult<A>, DbError, Scope.Scope>
140
+ /** Drop reusable execution buffers, keeping the compiled query and snapshot. */
141
+ releaseMemory(): Effect.Effect<void, DbError>
110
142
  close(): Effect.Effect<CloseReport>
111
143
  }
112
144
 
113
145
  interface Db<S extends AnySchema> {
114
146
  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>
147
+ snapshot(): Effect.Effect<Snapshot<S>, DbError, Scope.Scope>
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>
151
+ inspect(): Effect.Effect<DbInspection, DbError>
152
+ /** Clear shared query caches without invalidating live snapshots or results. */
153
+ clearCache(): Effect.Effect<void, DbError>
118
154
  close(): Effect.Effect<CloseReport>
119
155
  }
120
156
 
@@ -139,10 +175,61 @@ function outcomeOf(wire: ApplyOutcomeWire): ApplyOutcome {
139
175
  }
140
176
  }
141
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
+
142
229
  /**
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.
230
+ * Validates the query template's ordered logical schema and lowers it to
231
+ * IR plus wire params. Pure host preparation; the
232
+ * engine's IR validation remains the authority.
146
233
  */
147
234
  function preparedOf<S extends AnySchema>(
148
235
  theory: S,
@@ -153,7 +240,7 @@ function preparedOf<S extends AnySchema>(
153
240
  readonly wire: ReturnType<typeof wireParams>
154
241
  readonly finds: AnyQuery["data"]["finds"]
155
242
  } {
156
- if (query.schema !== theory) {
243
+ if (!schemasAgree(query.schema, theory)) {
157
244
  throw refusal("QueryReader.execute", "InvalidArgument")
158
245
  }
159
246
  const ir = lowerQuery(query)
@@ -168,29 +255,29 @@ interface SnapshotState<S extends AnySchema> {
168
255
 
169
256
  function executeOn<S extends AnySchema, A>(
170
257
  state: SnapshotState<S>,
171
- session: SessionHandle | undefined,
172
258
  query: AnyQuery,
173
- params: Readonly<Record<string, unknown>>,
174
- work: ExecutionPolicy
259
+ params: Readonly<Record<string, unknown>>
175
260
  ): Effect.Effect<CompleteResult<A>, DbError, Scope.Scope> {
176
- return Effect.acquireRelease(
261
+ return scopedResult(
177
262
  Effect.gen(function* () {
178
263
  const prepared = yield* Effect.try({
179
264
  try: () => preparedOf(state.theory, query, params),
180
- catch: (cause) => (cause instanceof DbError ? cause : refusal("QueryReader.execute", "InvalidArgument"))
265
+ catch: (cause) => argumentError("QueryReader.execute", cause)
181
266
  })
182
- const wire = policyWire(work, "QueryReader.execute")
183
267
  const handle = yield* nativeOperationWith(
184
268
  "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),
269
+ (callback) => dbNative.runtimeSnapshotExecute(state.handle, prepared.ir, prepared.wire, callback),
189
270
  dbNative.runtimeResultTake,
190
271
  (value) => value
191
272
  )
192
273
  return makeCompleteResult<A>(handle, prepared.finds)
193
- }),
274
+ })
275
+ )
276
+ }
277
+
278
+ function scopedResult<A>(acquire: Effect.Effect<CompleteResult<A>, DbError>) {
279
+ return Effect.acquireRelease(
280
+ acquire,
194
281
  (result) =>
195
282
  Effect.suspend(() => {
196
283
  const internal = internalResultHandle(result)
@@ -207,87 +294,123 @@ function internalResultHandle(result: object) {
207
294
  return internalResult(result)?.handle
208
295
  }
209
296
 
210
- function getOn<S extends AnySchema, R extends Rel<S>>(
297
+ function getOn<S extends AnySchema, K extends KeyStatement<Rel<S>, readonly string[]>>(
211
298
  state: SnapshotState<S>,
212
- relation: R,
213
- key: Key<R>,
214
- work: ExecutionPolicy
215
- ): Effect.Effect<Option.Option<Fact<R>>, DbError> {
299
+ key: K,
300
+ value: Key<K>
301
+ ): Effect.Effect<Option.Option<Fact<K["owner"]>>, DbError> {
216
302
  return Effect.gen(function* () {
217
- 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)) {
218
314
  return yield* Effect.fail(refusal("QueryReader.get", "InvalidArgument"))
219
315
  }
220
- const tables = schemaTables(state.theory)
221
316
  const relationId = tables.relationIds.get(relation.name)
222
- const primary = tables.primaryKeys.get(relation.name)
223
- if (relationId === undefined || primary === undefined) {
317
+ if (relationId === undefined) {
224
318
  return yield* Effect.fail(refusal("QueryReader.get", "InvalidArgument"))
225
319
  }
226
320
  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"))
321
+ try: () => keyCellsOf(relation, resolved.key.projection, value),
322
+ catch: (cause) => argumentError("QueryReader.get", cause)
229
323
  })
230
324
  const row = yield* nativeOperationWith(
231
325
  "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
- ),
326
+ (callback) => dbNative.runtimeSnapshotGet(state.handle, relationId, statementId, cells, callback),
241
327
  dbNative.runtimeRowTake,
242
328
  (value) => value
243
329
  )
244
330
  if (row === null) {
245
- return Option.none<Fact<R>>()
331
+ return Option.none<Fact<K["owner"]>>()
246
332
  }
247
333
  return Option.some(factOfCells(relation, row as readonly CellValue[]))
248
334
  })
249
335
  }
250
336
 
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)
337
+ function makePrepared<P extends ParamsRecord, A>(
338
+ handle: PreparedHandle,
339
+ definitions: AnyQuery["data"]["params"],
340
+ finds: AnyQuery["data"]["finds"]
341
+ ): PreparedQuery<P, A> {
342
+ return Object.freeze({
343
+ releaseMemory() {
344
+ return nativeOperationWith(
345
+ "PreparedQuery.releaseMemory",
346
+ (callback) => dbNative.runtimePreparedReleaseMemory(handle, callback),
347
+ runtimeNative.runtimeTake,
348
+ () => undefined
349
+ )
350
+ },
351
+ execute(params: P) {
352
+ return scopedResult(
353
+ Effect.gen(function* () {
354
+ const args = yield* Effect.try({
355
+ try: () => wireParams(definitions, params),
356
+ catch: (cause) => argumentError("PreparedQuery.execute", cause)
357
+ })
358
+ const result = yield* nativeOperationWith(
359
+ "PreparedQuery.execute",
360
+ (callback) => dbNative.runtimePreparedExecute(handle, args, callback),
361
+ dbNative.runtimeResultTake,
362
+ (value) => makeCompleteResult<A>(value, finds)
363
+ )
364
+ return result
365
+ })
366
+ )
255
367
  },
256
368
  close() {
257
- return drainClose("ExecutionSession.close", (callback) => dbNative.runtimeSessionClose(handle, callback))
369
+ return drainClose("PreparedQuery.close", (callback) => dbNative.runtimePreparedClose(handle, callback))
258
370
  }
259
- }
260
- Object.freeze(session)
261
- sessionHandles.set(session, handle)
262
- return session
371
+ })
372
+ }
373
+
374
+ function prepareOn<S extends AnySchema, P extends ParamsRecord, A>(
375
+ state: SnapshotState<S>,
376
+ query: QueryTemplate<S, P, A>
377
+ ): Effect.Effect<PreparedQuery<P, A>, DbError, Scope.Scope> {
378
+ return Effect.gen(function* () {
379
+ const ir = yield* Effect.try({
380
+ try: () => {
381
+ if (!schemasAgree(query.schema, state.theory)) {
382
+ throw refusal("QueryReader.prepare", "InvalidArgument")
383
+ }
384
+ return lowerQuery(query)
385
+ },
386
+ catch: (cause) => argumentError("QueryReader.prepare", cause)
387
+ })
388
+ const handle = yield* Effect.acquireRelease(
389
+ nativeOperationWith(
390
+ "QueryReader.prepare",
391
+ (callback) => dbNative.runtimeSnapshotPrepare(state.handle, ir, callback),
392
+ dbNative.runtimePreparedTake,
393
+ (value) => value
394
+ ),
395
+ (value) => releaseOwner("PreparedQuery.close", (callback) => dbNative.runtimePreparedClose(value, callback)),
396
+ { interruptible: true }
397
+ )
398
+ return makePrepared<P, A>(handle, query.data.params, query.data.finds)
399
+ })
263
400
  }
264
401
 
265
402
  function makeSnapshot<S extends AnySchema>(theory: S, handle: SnapshotHandle, witness: CoreWitness): Snapshot<S> {
266
403
  const state: SnapshotState<S> = { theory, handle }
267
404
  const snapshot: Snapshot<S> = {
268
405
  witness,
269
- get(relation, key, work) {
270
- return getOn(state, relation, key, work)
406
+ get(key, value) {
407
+ return getOn(state, key, value)
271
408
  },
272
- execute(query, params, work) {
273
- return executeOn(state, undefined, query, params, work)
409
+ execute(query, params) {
410
+ return executeOn(state, query, params)
274
411
  },
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
- )
412
+ prepare(query) {
413
+ return prepareOn(state, query)
291
414
  },
292
415
  close() {
293
416
  return drainClose("Snapshot.close", (callback) => dbNative.runtimeSnapshotClose(handle, callback))
@@ -298,12 +421,6 @@ function makeSnapshot<S extends AnySchema>(theory: S, handle: SnapshotHandle, wi
298
421
  return snapshot
299
422
  }
300
423
 
301
- const sessionHandles = new WeakMap<object, SessionHandle>()
302
-
303
- function missingSession(): never {
304
- throw new DbError({ operation: "ExecutionSession.close", reason: { _tag: "Internal" } })
305
- }
306
-
307
424
  interface DbState {
308
425
  readonly theory: AnySchema
309
426
  readonly db: DbHandle
@@ -314,11 +431,19 @@ interface DbState {
314
431
  function makeDb<S extends AnySchema>(theory: S, state: DbState): Db<S> {
315
432
  const value: Db<S> = {
316
433
  schemaId: state.schemaId,
317
- snapshot(work) {
434
+ clearCache() {
435
+ return nativeOperationWith(
436
+ "Db.clearCache",
437
+ (callback) => dbNative.runtimeDbClearCache(state.db, callback),
438
+ runtimeNative.runtimeTake,
439
+ () => undefined
440
+ )
441
+ },
442
+ snapshot() {
318
443
  return Effect.acquireRelease(
319
444
  nativeOperationWith(
320
445
  "Db.snapshot",
321
- (callback) => dbNative.runtimeDbSnapshot(state.db, policyWire(work, "Db.snapshot"), callback),
446
+ (callback) => dbNative.runtimeDbSnapshot(state.db, callback),
322
447
  dbNative.runtimeSnapshotTake,
323
448
  (wire) => makeSnapshot(theory, wire.snapshot, witnessOf(wire.witness))
324
449
  ),
@@ -333,41 +458,36 @@ function makeDb<S extends AnySchema>(theory: S, state: DbState): Db<S> {
333
458
  },
334
459
  apply(changes, options) {
335
460
  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"))
341
- }
342
- if (internal.schemaId !== state.schemaId) {
343
- return yield* Effect.fail(refusal("Db.apply", "InvalidArgument"))
344
- }
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 }
461
+ const input = yield* writeInputs(state, changes, options, "Db.apply")
349
462
  return yield* nativeOperationWith(
350
463
  "Db.apply",
351
- (callback) =>
352
- dbNative.runtimeDbApply(state.db, policyWire(options, "Db.apply"), internal.handle, expected, callback),
464
+ (callback) => dbNative.runtimeDbApply(state.db, input.handle, input.expected, callback),
353
465
  dbNative.runtimeApplyTake,
354
466
  outcomeOf
355
467
  )
356
468
  })
357
469
  },
358
- inspect(work) {
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
+ },
481
+ inspect() {
359
482
  return nativeOperationWith(
360
483
  "Db.inspect",
361
- (callback) => dbNative.runtimeDbInspect(state.db, policyWire(work, "Db.inspect"), callback),
484
+ (callback) => dbNative.runtimeDbInspect(state.db, callback),
362
485
  dbNative.runtimeDbInspectTake,
363
486
  (wire: DbInspectionWire): DbInspection =>
364
487
  Object.freeze({
365
488
  schemaId: state.schemaId,
366
489
  generation: wire.generation,
367
- mapBytes: wire.mapBytes,
368
- populatedBytes: wire.populatedBytes,
369
- diskBytes: wire.diskBytes,
370
- residentEstimateBytes: wire.residentEstimateBytes,
490
+ storage: Object.freeze(wire.storage),
371
491
  retainedOperations: wire.retainedOperations
372
492
  })
373
493
  )
@@ -402,23 +522,18 @@ function openDatabase<S extends AnySchema>(
402
522
  operation: "Db.create" | "Db.open",
403
523
  path: string,
404
524
  schema: S,
405
- work: ExecutionPolicy,
406
525
  create: boolean
407
526
  ): Effect.Effect<Db<S>, DbError, NativeRuntime | Scope.Scope> {
408
527
  return Effect.gen(function* () {
409
528
  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")
415
- })
529
+ const compiled: CompiledSchema<S> = yield* CoreSchema.compile(schema)
530
+ const spec = lower(compiled.schema)
416
531
  // Compound acquisition: register the directory owner and
417
532
  // its finalizer BEFORE any interruptible child-open step.
418
533
  const directory = yield* Effect.acquireRelease(
419
534
  nativeOperationWith(
420
535
  operation,
421
- (callback) => runtimeNative.runtimeDirectoryAcquire(runtime, wire, path, callback),
536
+ (callback) => runtimeNative.runtimeDirectoryAcquire(runtime, path, callback),
422
537
  runtimeNative.runtimeDirectoryTake,
423
538
  (value) => value
424
539
  ),
@@ -432,7 +547,7 @@ function openDatabase<S extends AnySchema>(
432
547
  Effect.gen(function* () {
433
548
  const outcome = yield* nativeOperationWith(
434
549
  operation,
435
- (callback) => runtimeNative.runtimeDirectoryDbOpen(directory, wire, CHILD, spec, create, callback),
550
+ (callback) => runtimeNative.runtimeDirectoryDbOpen(directory, CHILD, spec, create, callback),
436
551
  runtimeNative.runtimeDbTake,
437
552
  (value) => value
438
553
  ).pipe(
@@ -448,8 +563,8 @@ function openDatabase<S extends AnySchema>(
448
563
  )
449
564
  return yield* Effect.fail(refusal(operation, "InvalidArgument"))
450
565
  }
451
- const state: DbState = { theory: schema, db: outcome.db, directory, schemaId: compiled.schemaId }
452
- 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)
453
568
  dbStates.set(db, state)
454
569
  return db
455
570
  }),
@@ -481,11 +596,11 @@ const dbStates = new WeakMap<object, DbState>()
481
596
  * implementation as `Schema.compile` — prior compilation is optional.
482
597
  */
483
598
  const Db = Object.freeze({
484
- create<S extends AnySchema>(path: string, schema: S, work: ExecutionPolicy) {
485
- return openDatabase("Db.create", path, schema, work, true)
599
+ create<S extends AnySchema>(path: string, schema: S) {
600
+ return openDatabase("Db.create", path, schema, true)
486
601
  },
487
- open<S extends AnySchema>(path: string, schema: S, work: ExecutionPolicy) {
488
- return openDatabase("Db.open", path, schema, work, false)
602
+ open<S extends AnySchema>(path: string, schema: S) {
603
+ return openDatabase("Db.open", path, schema, false)
489
604
  }
490
605
  })
491
606
 
@@ -506,44 +621,32 @@ function internalPublishedReader<S extends AnySchema>(
506
621
  ): {
507
622
  readonly get: QueryReader<S>["get"]
508
623
  readonly execute: QueryReader<S>["execute"]
509
- readonly session: (work: ExecutionPolicy) => Effect.Effect<ExecutionSession<S>, DbError, Scope.Scope>
624
+ readonly prepare: QueryReader<S>["prepare"]
510
625
  } {
511
- const state: SnapshotState<S> = { theory, handle: core as SnapshotHandle }
626
+ const state: SnapshotState<S> = { theory: schemaDescriptor(theory), handle: core as SnapshotHandle }
512
627
  return Object.freeze({
513
- get<R extends Rel<S>>(relation: R, key: Key<R>, work: ExecutionPolicy) {
514
- return getOn(state, relation, key, work)
628
+ get<K extends KeyStatement<Rel<S>, readonly string[]>>(key: K, value: NoInfer<Key<K>>) {
629
+ return getOn(state, key, value)
515
630
  },
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)
631
+ execute<P extends ParamsRecord, A>(queryValue: QueryTemplate<S, P, A>, params: P) {
632
+ return executeOn<S, A>(state, queryValue as AnyQuery, params)
518
633
  },
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
- )
634
+ prepare<P extends ParamsRecord, A>(queryValue: QueryTemplate<S, P, A>) {
635
+ return prepareOn(state, queryValue)
535
636
  }
536
637
  })
537
638
  }
538
639
 
539
640
  export type {
540
- ApplyExpected,
541
- ApplyOptions,
542
641
  ApplyOutcome,
543
642
  CoreWitness,
544
643
  DbInspection,
545
- ExecutionSession,
644
+ JudgeOutcome,
645
+ PreparedQuery,
546
646
  QueryReader,
547
- Snapshot
647
+ Snapshot,
648
+ StorageInspection,
649
+ WriteExpected,
650
+ WriteOptions
548
651
  }
549
652
  export { Db, internalPublishedReader }