@bjornpagen/bumbledb 1.3.0 → 2.0.0-rc.1

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 (290) hide show
  1. package/COOKBOOK.md +224 -95
  2. package/README.md +114 -45
  3. package/dist/alternatives.d.ts +5 -6
  4. package/dist/alternatives.d.ts.map +1 -1
  5. package/dist/alternatives.js +5 -6
  6. package/dist/alternatives.js.map +1 -1
  7. package/dist/bin.d.ts +3 -0
  8. package/dist/bin.d.ts.map +1 -0
  9. package/dist/bin.js +85 -0
  10. package/dist/bin.js.map +1 -0
  11. package/dist/capacity.d.ts +14 -17
  12. package/dist/capacity.d.ts.map +1 -1
  13. package/dist/capacity.js +7 -7
  14. package/dist/capacity.js.map +1 -1
  15. package/dist/changes.d.ts +32 -41
  16. package/dist/changes.d.ts.map +1 -1
  17. package/dist/changes.js +86 -110
  18. package/dist/changes.js.map +1 -1
  19. package/dist/closed.d.ts +2 -2
  20. package/dist/closed.d.ts.map +1 -1
  21. package/dist/closed.js +5 -5
  22. package/dist/closed.js.map +1 -1
  23. package/dist/codec.d.ts +20 -27
  24. package/dist/codec.d.ts.map +1 -1
  25. package/dist/codec.js +48 -22
  26. package/dist/codec.js.map +1 -1
  27. package/dist/compile.d.ts +26 -50
  28. package/dist/compile.d.ts.map +1 -1
  29. package/dist/compile.js +75 -64
  30. package/dist/compile.js.map +1 -1
  31. package/dist/database/database.d.ts +98 -0
  32. package/dist/database/database.d.ts.map +1 -0
  33. package/dist/database/database.js +249 -0
  34. package/dist/database/database.js.map +1 -0
  35. package/dist/database/driver.d.ts +40 -0
  36. package/dist/database/driver.d.ts.map +1 -0
  37. package/dist/database/driver.js +68 -0
  38. package/dist/database/driver.js.map +1 -0
  39. package/dist/database/fs.d.ts +12 -0
  40. package/dist/database/fs.d.ts.map +1 -0
  41. package/dist/database/fs.js +120 -0
  42. package/dist/database/fs.js.map +1 -0
  43. package/dist/database/generate.d.ts +26 -0
  44. package/dist/database/generate.d.ts.map +1 -0
  45. package/dist/database/generate.js +102 -0
  46. package/dist/database/generate.js.map +1 -0
  47. package/dist/database/hosted.d.ts +32 -0
  48. package/dist/database/hosted.d.ts.map +1 -0
  49. package/dist/database/hosted.js +44 -0
  50. package/dist/database/hosted.js.map +1 -0
  51. package/dist/database/io.d.ts +94 -0
  52. package/dist/database/io.d.ts.map +1 -0
  53. package/dist/database/io.js +35 -0
  54. package/dist/database/io.js.map +1 -0
  55. package/dist/database/mem.d.ts +26 -0
  56. package/dist/database/mem.d.ts.map +1 -0
  57. package/dist/database/mem.js +67 -0
  58. package/dist/database/mem.js.map +1 -0
  59. package/dist/database/migration.d.ts +55 -0
  60. package/dist/database/migration.d.ts.map +1 -0
  61. package/dist/database/migration.js +46 -0
  62. package/dist/database/migration.js.map +1 -0
  63. package/dist/database/s3.d.ts +36 -0
  64. package/dist/database/s3.d.ts.map +1 -0
  65. package/dist/database/s3.js +141 -0
  66. package/dist/database/s3.js.map +1 -0
  67. package/dist/db.d.ts +44 -128
  68. package/dist/db.d.ts.map +1 -1
  69. package/dist/db.js +163 -287
  70. package/dist/db.js.map +1 -1
  71. package/dist/engine.d.ts +7 -0
  72. package/dist/engine.d.ts.map +1 -0
  73. package/dist/engine.js +2 -0
  74. package/dist/engine.js.map +1 -0
  75. package/dist/errors.d.ts +152 -46
  76. package/dist/errors.d.ts.map +1 -1
  77. package/dist/errors.js +107 -19
  78. package/dist/errors.js.map +1 -1
  79. package/dist/face.d.ts +5 -5
  80. package/dist/face.d.ts.map +1 -1
  81. package/dist/face.js +6 -6
  82. package/dist/face.js.map +1 -1
  83. package/dist/fields.d.ts +4 -5
  84. package/dist/fields.d.ts.map +1 -1
  85. package/dist/fields.js +3 -3
  86. package/dist/fields.js.map +1 -1
  87. package/dist/index.d.ts +60 -58
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +27 -29
  90. package/dist/index.js.map +1 -1
  91. package/dist/judgment.d.ts +1 -1
  92. package/dist/judgment.js +1 -1
  93. package/dist/law.d.ts +16 -68
  94. package/dist/law.d.ts.map +1 -1
  95. package/dist/law.js +3 -3
  96. package/dist/law.js.map +1 -1
  97. package/dist/lower.d.ts +3 -3
  98. package/dist/lower.d.ts.map +1 -1
  99. package/dist/lower.js +35 -39
  100. package/dist/lower.js.map +1 -1
  101. package/dist/native/addon.d.ts +23 -0
  102. package/dist/native/addon.d.ts.map +1 -0
  103. package/dist/native/addon.js +15 -0
  104. package/dist/native/addon.js.map +1 -0
  105. package/dist/native/binding.d.ts +996 -0
  106. package/dist/native/json.d.ts +14 -0
  107. package/dist/native/json.d.ts.map +1 -0
  108. package/dist/native/json.js +73 -0
  109. package/dist/native/json.js.map +1 -0
  110. package/dist/native/load.d.ts +9 -0
  111. package/dist/native/load.d.ts.map +1 -0
  112. package/dist/native/load.js +30 -0
  113. package/dist/native/load.js.map +1 -0
  114. package/dist/native/op.d.ts +29 -0
  115. package/dist/native/op.d.ts.map +1 -0
  116. package/dist/native/op.js +50 -0
  117. package/dist/native/op.js.map +1 -0
  118. package/dist/query/atom.d.ts +13 -14
  119. package/dist/query/atom.d.ts.map +1 -1
  120. package/dist/query/atom.js +2 -2
  121. package/dist/query/atom.js.map +1 -1
  122. package/dist/query/compute.d.ts +7 -7
  123. package/dist/query/compute.d.ts.map +1 -1
  124. package/dist/query/compute.js +7 -16
  125. package/dist/query/compute.js.map +1 -1
  126. package/dist/query/find.d.ts +8 -8
  127. package/dist/query/find.d.ts.map +1 -1
  128. package/dist/query/find.js +1 -1
  129. package/dist/query/lower.d.ts +31 -17
  130. package/dist/query/lower.d.ts.map +1 -1
  131. package/dist/query/lower.js +260 -368
  132. package/dist/query/lower.js.map +1 -1
  133. package/dist/query/run.d.ts +5 -5
  134. package/dist/query/run.d.ts.map +1 -1
  135. package/dist/query/run.js +32 -15
  136. package/dist/query/run.js.map +1 -1
  137. package/dist/query/scope.d.ts +20 -28
  138. package/dist/query/scope.d.ts.map +1 -1
  139. package/dist/query/scope.js +13 -8
  140. package/dist/query/scope.js.map +1 -1
  141. package/dist/query/segments.d.ts +2 -2
  142. package/dist/query/segments.d.ts.map +1 -1
  143. package/dist/query/segments.js +3 -3
  144. package/dist/query/segments.js.map +1 -1
  145. package/dist/relation.d.ts +1 -1
  146. package/dist/relation.d.ts.map +1 -1
  147. package/dist/relation.js +4 -4
  148. package/dist/relation.js.map +1 -1
  149. package/dist/result.d.ts +15 -22
  150. package/dist/result.d.ts.map +1 -1
  151. package/dist/result.js +30 -39
  152. package/dist/result.js.map +1 -1
  153. package/dist/rows.d.ts +9 -33
  154. package/dist/rows.d.ts.map +1 -1
  155. package/dist/rows.js +8 -12
  156. package/dist/rows.js.map +1 -1
  157. package/dist/runtime.d.ts +17 -43
  158. package/dist/runtime.d.ts.map +1 -1
  159. package/dist/runtime.js +41 -241
  160. package/dist/runtime.js.map +1 -1
  161. package/dist/scalar.d.ts +36 -106
  162. package/dist/scalar.d.ts.map +1 -1
  163. package/dist/scalar.js +36 -185
  164. package/dist/scalar.js.map +1 -1
  165. package/dist/schema.d.ts +7 -18
  166. package/dist/schema.d.ts.map +1 -1
  167. package/dist/schema.js +18 -228
  168. package/dist/schema.js.map +1 -1
  169. package/dist/selection.d.ts +4 -4
  170. package/dist/selection.d.ts.map +1 -1
  171. package/dist/selection.js +5 -5
  172. package/dist/selection.js.map +1 -1
  173. package/dist/shape.d.ts +5 -5
  174. package/dist/shape.d.ts.map +1 -1
  175. package/dist/spec.d.ts +7 -79
  176. package/dist/spec.d.ts.map +1 -1
  177. package/dist/spec.js.map +1 -1
  178. package/dist/statements.d.ts +3 -3
  179. package/dist/statements.d.ts.map +1 -1
  180. package/dist/statements.js +10 -10
  181. package/dist/statements.js.map +1 -1
  182. package/dist/uuid.d.ts +1 -1
  183. package/dist/uuid.d.ts.map +1 -1
  184. package/dist/uuid.js +1 -1
  185. package/dist/uuid.js.map +1 -1
  186. package/dist/values.d.ts +2 -2
  187. package/dist/values.d.ts.map +1 -1
  188. package/dist/values.js +2 -2
  189. package/dist/values.js.map +1 -1
  190. package/package.json +18 -18
  191. package/src/alternatives.ts +7 -8
  192. package/src/bin.ts +82 -0
  193. package/src/capacity.ts +20 -23
  194. package/src/changes.ts +134 -197
  195. package/src/closed.ts +5 -5
  196. package/src/codec.ts +77 -51
  197. package/src/compile.ts +91 -113
  198. package/src/database/database.ts +413 -0
  199. package/src/database/driver.ts +134 -0
  200. package/src/database/fs.ts +119 -0
  201. package/src/database/generate.ts +116 -0
  202. package/src/database/hosted.ts +102 -0
  203. package/src/database/io.ts +134 -0
  204. package/src/database/mem.ts +113 -0
  205. package/src/database/migration.ts +107 -0
  206. package/src/database/s3.ts +231 -0
  207. package/src/db.ts +290 -547
  208. package/src/engine.ts +14 -0
  209. package/src/errors.ts +118 -39
  210. package/src/face.ts +9 -9
  211. package/src/fields.ts +6 -7
  212. package/src/index.ts +102 -109
  213. package/src/judgment.ts +1 -1
  214. package/src/law.ts +18 -70
  215. package/src/lower.ts +61 -72
  216. package/src/native/addon.ts +54 -0
  217. package/src/native/binding.d.ts +996 -0
  218. package/src/native/json.ts +100 -0
  219. package/src/native/load.ts +31 -0
  220. package/src/native/op.ts +73 -0
  221. package/src/query/atom.ts +15 -16
  222. package/src/query/compute.ts +13 -22
  223. package/src/query/find.ts +8 -8
  224. package/src/query/lower.ts +323 -414
  225. package/src/query/run.ts +42 -21
  226. package/src/query/scope.ts +37 -38
  227. package/src/query/segments.ts +4 -4
  228. package/src/relation.ts +4 -4
  229. package/src/result.ts +53 -80
  230. package/src/rows.ts +18 -46
  231. package/src/runtime.ts +50 -346
  232. package/src/scalar.ts +76 -336
  233. package/src/schema.ts +23 -284
  234. package/src/selection.ts +6 -6
  235. package/src/shape.ts +5 -5
  236. package/src/spec.ts +7 -85
  237. package/src/statements.ts +11 -11
  238. package/src/uuid.ts +1 -1
  239. package/src/values.ts +4 -4
  240. package/dist/close.d.ts +0 -24
  241. package/dist/close.d.ts.map +0 -1
  242. package/dist/close.js +0 -34
  243. package/dist/close.js.map +0 -1
  244. package/dist/db-native.d.ts +0 -196
  245. package/dist/db-native.d.ts.map +0 -1
  246. package/dist/db-native.js +0 -13
  247. package/dist/db-native.js.map +0 -1
  248. package/dist/internal/log.d.ts +0 -26
  249. package/dist/internal/log.d.ts.map +0 -1
  250. package/dist/internal/log.js +0 -12
  251. package/dist/internal/log.js.map +0 -1
  252. package/dist/migration.d.ts +0 -31
  253. package/dist/migration.d.ts.map +0 -1
  254. package/dist/migration.js +0 -47
  255. package/dist/migration.js.map +0 -1
  256. package/dist/native.d.ts +0 -562
  257. package/dist/native.d.ts.map +0 -1
  258. package/dist/native.js +0 -123
  259. package/dist/native.js.map +0 -1
  260. package/dist/query/description.d.ts +0 -41
  261. package/dist/query/description.d.ts.map +0 -1
  262. package/dist/query/description.js +0 -311
  263. package/dist/query/description.js.map +0 -1
  264. package/dist/query/parse-ir.d.ts +0 -10
  265. package/dist/query/parse-ir.d.ts.map +0 -1
  266. package/dist/query/parse-ir.js +0 -280
  267. package/dist/query/parse-ir.js.map +0 -1
  268. package/dist/runtime-codes.d.ts +0 -2
  269. package/dist/runtime-codes.d.ts.map +0 -1
  270. package/dist/runtime-codes.js +0 -18
  271. package/dist/runtime-codes.js.map +0 -1
  272. package/dist/runtime-errors.d.ts +0 -118
  273. package/dist/runtime-errors.d.ts.map +0 -1
  274. package/dist/runtime-errors.js +0 -98
  275. package/dist/runtime-errors.js.map +0 -1
  276. package/dist/runtime-native.d.ts +0 -141
  277. package/dist/runtime-native.d.ts.map +0 -1
  278. package/dist/runtime-native.js +0 -5
  279. package/dist/runtime-native.js.map +0 -1
  280. package/pack-provenance.json +0 -7
  281. package/src/close.ts +0 -53
  282. package/src/db-native.ts +0 -243
  283. package/src/internal/log.ts +0 -48
  284. package/src/migration.ts +0 -61
  285. package/src/native.ts +0 -736
  286. package/src/query/description.ts +0 -424
  287. package/src/query/parse-ir.ts +0 -291
  288. package/src/runtime-codes.ts +0 -17
  289. package/src/runtime-errors.ts +0 -109
  290. package/src/runtime-native.ts +0 -189
package/src/db.ts CHANGED
@@ -1,117 +1,52 @@
1
+ /**
2
+ * The embedded engine surface. `Db.create`/`Db.open` own a database directory for the current
3
+ * scope; snapshots, prepared queries and results are scoped the same way, so every native
4
+ * resource is released by its scope and a release that does not drain cleanly is a
5
+ * `CloseFailure` defect. Every verb is a lazy Effect that runs on the shared native executor.
6
+ */
1
7
  import type { Scope } from "effect"
2
8
  import { Effect, Option } from "effect"
3
- import type { ChangeCounts, ChangeSet } from "#changes.ts"
4
- import { internalChanges } from "#changes.ts"
5
- import { drainClose, releaseOwner } from "#close.ts"
6
- import { membersAgree } from "#closed.ts"
7
- import type { CompiledSchema, SchemaId } from "#compile.ts"
8
- import { Schema as CoreSchema, declaredKey, schemaTables } from "#compile.ts"
9
- import type {
10
- ApplyOutcomeWire,
11
- DbInspectionWire,
12
- ExpectedWire,
13
- JudgeOutcomeWire,
14
- PreparedHandle,
15
- SnapshotHandle,
16
- WitnessWire
17
- } from "#db-native.ts"
18
- import { dbNative } from "#db-native.ts"
19
- import { lower } from "#lower.ts"
20
- import type { DbHandle, Violation } from "#native.ts"
21
- import type { AnyQuery } from "#query/lower.ts"
22
- import { lowerQuery } from "#query/lower.ts"
23
- import { wireParams } from "#query/run.ts"
24
- import type { ParamsRecord } from "#query/scope.ts"
25
- import type { Fact } from "#relation.ts"
26
- import type { CompleteResult } from "#result.ts"
27
- import { internalResult, makeCompleteResult } from "#result.ts"
28
- import type { CellValue } from "#rows.ts"
29
- import { factOfCells, keyCellsOf } from "#rows.ts"
30
- import type { NativeRuntime } from "#runtime.ts"
31
- import { nativeOperationWith, runtimeHandle } from "#runtime.ts"
32
- import type { CloseReport } from "#runtime-errors.ts"
33
- import { argumentError, DbError } from "#runtime-errors.ts"
34
- import type { DirectoryHandle } from "#runtime-native.ts"
35
- import { runtimeNative } from "#runtime-native.ts"
36
- import type { AnySchema } from "#schema.ts"
37
- import { schemaDescriptor, schemasAgree } from "#schema.ts"
38
- import type { Key, QueryTemplate, Rel } from "#shape.ts"
39
- import type { KeyStatement } from "#statements.ts"
40
- import { integerValue, recordValue } from "#values.ts"
9
+ import type { ChangeSet } from "./changes.ts"
10
+ import { ChangeSetLive } from "./changes.ts"
11
+ import { membersAgree } from "./closed.ts"
12
+ import type { SchemaId } from "./compile.ts"
13
+ import { compiledOf, declaredKey, schemaTables } from "./compile.ts"
14
+ import { argumentError, DbError } from "./errors.ts"
15
+ import type { DbRef, DirectoryRef, PreparedRef, SnapshotRef, WitnessRef } from "./native/addon.ts"
16
+ import { addon } from "./native/addon.ts"
17
+ import type { ApplyOutcome, DbInspection as DbInspectionOut, DbOpened, JudgeOutcome } from "./native/binding.d.ts"
18
+ import type { Start } from "./native/op.ts"
19
+ import { call, drain, scoped } from "./native/op.ts"
20
+ import type { AnyQuery } from "./query/lower.ts"
21
+ import { queryHandleOf } from "./query/lower.ts"
22
+ import { wireParams } from "./query/run.ts"
23
+ import type { ParamsRecord } from "./query/scope.ts"
24
+ import type { Fact } from "./relation.ts"
25
+ import type { CompleteResult } from "./result.ts"
26
+ import { CompleteResultLive } from "./result.ts"
27
+ import type { CellValue } from "./rows.ts"
28
+ import { factOfCells, keyCellsOf } from "./rows.ts"
29
+ import type { Bumble } from "./runtime.ts"
30
+ import { runtimeHandle } from "./runtime.ts"
31
+ import type { AnySchema } from "./schema.ts"
32
+ import { schemaDescriptor, schemasAgree } from "./schema.ts"
33
+ import type { Key, QueryTemplate, Rel } from "./shape.ts"
34
+ import type { KeyStatement } from "./statements.ts"
41
35
 
42
36
  /**
43
- * The core surface: `Db.create`/`Db.open`, scoped coherent
44
- * `Snapshot`s, reusable `PreparedQuery`s, the shared `QueryReader`
45
- * capability, one immutable final-state `apply`, bounded `inspect` and
46
- * honest `close`. Effect-only: every method constructs a lazy effect; all
47
- * native work runs on the shared executor with cooperative cancellation.
48
- * Resources are scoped with `CloseFailure`-defect
49
- * finalizers. There is no Promise/sync/disposal twin, no transaction
50
- * callback, no per-row fiber and no Proxy row anywhere.
51
- *
52
- * Database open is the managed two-step handshake: acquire the kernel-held
53
- * directory owner and register its finalizer before the interruptible
54
- * child-open step. `Db.open` NEVER creates on missing/error; `Db.create`
55
- * refuses existing authority. Published log snapshots use this same
56
- * `QueryReader` literally — no second reader or adapter.
57
- */
58
-
59
- /** The core-local witness: catalog/store identity plus generation — never a log StateStamp. */
60
- interface CoreWitness {
61
- readonly store: string
62
- readonly generation: bigint
63
- }
64
-
65
- type WriteExpected = { readonly kind: "any" } | { readonly kind: "exact"; readonly at: CoreWitness }
66
-
67
- /** Expected-state intent shared by judgment and application. */
68
- type WriteOptions = { readonly expected: WriteExpected }
69
-
70
- type ApplyOutcome =
71
- | { readonly kind: "accepted"; readonly witness: CoreWitness }
72
- | { readonly kind: "no-change"; readonly witness: CoreWitness }
73
- | { readonly kind: "invariant-rejected"; readonly violations: readonly Violation[] }
74
- | { readonly kind: "moved"; readonly witnessed: CoreWitness; readonly current: CoreWitness }
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.
37
+ * The state one snapshot observed, as an opaque proof: a write given it applies only while that
38
+ * state is current. Only a snapshot mints one, and it is valid only for the database that did.
78
39
  */
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
- }
40
+ type Witness = WitnessRef
100
41
 
101
42
  /** Database diagnostics: measurements, never retained rows. */
102
- interface DbInspection {
43
+ interface DbInspection extends DbInspectionOut {
103
44
  readonly schemaId: SchemaId
104
- readonly generation: bigint
105
- readonly storage: StorageInspection
106
- readonly retainedOperations: bigint
107
45
  }
108
46
 
109
47
  /**
110
- * The shared read capability: the same typed `get` and
111
- * `execute` on a core snapshot and on a log published snapshot, so a
112
- * cross-package read helper takes this interface with no adapter. Missing
113
- * key is `Option.none`, never a fake I/O error or nullable row. It carries
114
- * no writable authority — never a `Db`, an `apply`, or a raw transaction.
48
+ * Typed reads over one coherent snapshot. A missing key is `Option.none`. It carries no write
49
+ * authority.
115
50
  */
116
51
  interface QueryReader<S extends AnySchema> {
117
52
  get<K extends KeyStatement<Rel<S>, readonly string[]>>(
@@ -128,472 +63,323 @@ interface QueryReader<S extends AnySchema> {
128
63
  }
129
64
 
130
65
  interface Snapshot<S extends AnySchema> extends QueryReader<S> {
131
- readonly witness: CoreWitness
132
- close(): Effect.Effect<CloseReport>
66
+ readonly witness: Witness
67
+ /** The committed generation the snapshot observed. */
68
+ readonly generation: bigint
133
69
  }
134
70
 
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
- */
71
+ /** One compiled plan and its reusable buffers, pinned to the snapshot that prepared it. */
138
72
  interface PreparedQuery<P extends ParamsRecord, A> {
139
73
  execute(params: P): Effect.Effect<CompleteResult<A>, DbError, Scope.Scope>
140
- /** Drop reusable execution buffers, keeping the compiled query and snapshot. */
74
+ /** Drops the reusable execution buffers, keeping the compiled plan. */
141
75
  releaseMemory(): Effect.Effect<void, DbError>
142
- close(): Effect.Effect<CloseReport>
143
76
  }
144
77
 
145
78
  interface Db<S extends AnySchema> {
146
79
  readonly schemaId: SchemaId
147
80
  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>
81
+ /** Applies `changes` atomically; with `expected`, only if the database is still at that state. */
82
+ apply(changes: ChangeSet<S>, expected?: Witness): Effect.Effect<ApplyOutcome, DbError>
83
+ /** Judges `changes` against the current state (or `expected`) and stores nothing. */
84
+ judge(changes: ChangeSet<S>, expected?: Witness): Effect.Effect<JudgeOutcome, DbError>
151
85
  inspect(): Effect.Effect<DbInspection, DbError>
152
- /** Clear shared query caches without invalidating live snapshots or results. */
86
+ /** Clears shared query caches without invalidating live snapshots or results. */
153
87
  clearCache(): Effect.Effect<void, DbError>
154
- close(): Effect.Effect<CloseReport>
155
88
  }
156
89
 
157
- function refusal(operation: string, reason: "InvalidArgument" | "ClosedHandle"): DbError {
158
- return new DbError({ operation, reason: { _tag: reason } })
90
+ function invalid(operation: string): DbError {
91
+ return new DbError({ operation, reason: { _tag: "InvalidArgument" } })
159
92
  }
160
93
 
161
- function witnessOf(wire: WitnessWire): CoreWitness {
162
- return Object.freeze({ store: wire.store, generation: wire.generation })
94
+ /** A database the engine would not open: its refusal, as an engine error. */
95
+ function refused(operation: string, opened: Exclude<DbOpened, { _tag: "Opened" }>): DbError {
96
+ const message =
97
+ opened._tag === "Rejected" ? opened.violations.map((violation) => violation.spelling).join("; ") : opened.message
98
+ return new DbError({ operation, reason: { _tag: "Engine", kind: opened._tag, message } })
163
99
  }
164
100
 
165
- function outcomeOf(wire: ApplyOutcomeWire): ApplyOutcome {
166
- switch (wire.tag) {
167
- case "accepted":
168
- return Object.freeze({ kind: "accepted", witness: witnessOf(wire.witness) })
169
- case "no-change":
170
- return Object.freeze({ kind: "no-change", witness: witnessOf(wire.witness) })
171
- case "invariant-rejected":
172
- return Object.freeze({ kind: "invariant-rejected", violations: wire.violations })
173
- case "moved":
174
- return Object.freeze({ kind: "moved", witnessed: witnessOf(wire.witnessed), current: witnessOf(wire.current) })
175
- }
101
+ function changesOf(schemaId: SchemaId, changes: object, operation: string) {
102
+ return Effect.suspend(() => {
103
+ const handle = ChangeSetLive.handle(changes, schemaId)
104
+ return handle === undefined ? Effect.fail(invalid(operation)) : Effect.succeed(handle)
105
+ })
176
106
  }
177
107
 
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
- }
108
+ /** A completed result owned by the current scope. */
109
+ function scopedResult<A>(
110
+ operation: string,
111
+ start: Start,
112
+ finds: AnyQuery["data"]["finds"]
113
+ ): Effect.Effect<CompleteResult<A>, DbError, Scope.Scope> {
114
+ return Effect.map(
115
+ scoped(
116
+ "CompleteResult.release",
117
+ call(operation, start, addon.runtimeResultTake),
118
+ (handle) => (done) => addon.runtimeResultClose(handle, done)
119
+ ),
120
+ (handle) => new CompleteResultLive<A>(handle, finds)
121
+ )
192
122
  }
193
123
 
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)
124
+ const getOn = Effect.fn("QueryReader.get")(function* <
125
+ S extends AnySchema,
126
+ K extends KeyStatement<Rel<S>, readonly string[]>
127
+ >(theory: S, handle: SnapshotRef, key: K, value: Key<K>) {
128
+ const resolved = yield* Effect.try({
129
+ try: () => declaredKey(theory, key),
130
+ catch: (cause) => argumentError("QueryReader.get", cause)
226
131
  })
227
- }
228
-
229
- /**
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.
233
- */
234
- function preparedOf<S extends AnySchema>(
235
- theory: S,
236
- query: AnyQuery,
237
- params: Readonly<Record<string, unknown>>
238
- ): {
239
- readonly ir: ReturnType<typeof lowerQuery>
240
- readonly wire: ReturnType<typeof wireParams>
241
- readonly finds: AnyQuery["data"]["finds"]
242
- } {
243
- if (!schemasAgree(query.schema, theory)) {
244
- throw refusal("QueryReader.execute", "InvalidArgument")
132
+ if (resolved === undefined || key.kind !== "key") return yield* Effect.fail(invalid("QueryReader.get"))
133
+ const relation = resolved.key.owner
134
+ const relationId = schemaTables(theory).relationIds.get(relation.name)
135
+ if (!membersAgree(theory.relations[relation.name], relation) || relationId === undefined) {
136
+ return yield* Effect.fail(invalid("QueryReader.get"))
245
137
  }
246
- const ir = lowerQuery(query)
247
- const wire = wireParams(query.data.params, params)
248
- return { ir, wire, finds: query.data.finds }
249
- }
250
-
251
- interface SnapshotState<S extends AnySchema> {
252
- readonly theory: S
253
- readonly handle: SnapshotHandle
254
- }
138
+ const cells = yield* Effect.try({
139
+ try: () => keyCellsOf(relation, resolved.key.projection, value),
140
+ catch: (cause) => argumentError("QueryReader.get", cause)
141
+ })
142
+ const row = yield* call(
143
+ "QueryReader.get",
144
+ (done) => addon.runtimeSnapshotGet(handle, relationId, resolved.statementId, cells, done),
145
+ addon.runtimeRowTake
146
+ )
147
+ return row === null
148
+ ? Option.none<Fact<K["owner"]>>()
149
+ : Option.some(factOfCells(relation, row as readonly CellValue[]) as Fact<K["owner"]>)
150
+ })
255
151
 
256
152
  function executeOn<S extends AnySchema, A>(
257
- state: SnapshotState<S>,
153
+ theory: S,
154
+ handle: SnapshotRef,
258
155
  query: AnyQuery,
259
156
  params: Readonly<Record<string, unknown>>
260
157
  ): Effect.Effect<CompleteResult<A>, DbError, Scope.Scope> {
261
- return scopedResult(
262
- Effect.gen(function* () {
263
- const prepared = yield* Effect.try({
264
- try: () => preparedOf(state.theory, query, params),
265
- catch: (cause) => argumentError("QueryReader.execute", cause)
266
- })
267
- const handle = yield* nativeOperationWith(
268
- "QueryReader.execute",
269
- (callback) => dbNative.runtimeSnapshotExecute(state.handle, prepared.ir, prepared.wire, callback),
270
- dbNative.runtimeResultTake,
271
- (value) => value
272
- )
273
- return makeCompleteResult<A>(handle, prepared.finds)
274
- })
275
- )
276
- }
277
-
278
- function scopedResult<A>(acquire: Effect.Effect<CompleteResult<A>, DbError>) {
279
- return Effect.acquireRelease(
280
- acquire,
281
- (result) =>
282
- Effect.suspend(() => {
283
- const internal = internalResultHandle(result)
284
- if (internal === undefined) {
285
- return Effect.void
286
- }
287
- return releaseOwner("CompleteResult.close", (callback) => dbNative.runtimeResultClose(internal, callback))
288
- }),
289
- { interruptible: true }
290
- )
291
- }
292
-
293
- function internalResultHandle(result: object) {
294
- return internalResult(result)?.handle
295
- }
296
-
297
- function getOn<S extends AnySchema, K extends KeyStatement<Rel<S>, readonly string[]>>(
298
- state: SnapshotState<S>,
299
- key: K,
300
- value: Key<K>
301
- ): Effect.Effect<Option.Option<Fact<K["owner"]>>, DbError> {
302
158
  return Effect.gen(function* () {
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)) {
314
- return yield* Effect.fail(refusal("QueryReader.get", "InvalidArgument"))
315
- }
316
- const relationId = tables.relationIds.get(relation.name)
317
- if (relationId === undefined) {
318
- return yield* Effect.fail(refusal("QueryReader.get", "InvalidArgument"))
319
- }
320
- const cells = yield* Effect.try({
321
- try: () => keyCellsOf(relation, resolved.key.projection, value),
322
- catch: (cause) => argumentError("QueryReader.get", cause)
159
+ const prepared = yield* Effect.try({
160
+ try: () => {
161
+ if (!schemasAgree(query.schema, theory)) throw invalid("QueryReader.execute")
162
+ return { query: queryHandleOf(query), wire: wireParams(query.data.params, params) }
163
+ },
164
+ catch: (cause) => argumentError("QueryReader.execute", cause)
323
165
  })
324
- const row = yield* nativeOperationWith(
325
- "QueryReader.get",
326
- (callback) => dbNative.runtimeSnapshotGet(state.handle, relationId, statementId, cells, callback),
327
- dbNative.runtimeRowTake,
328
- (value) => value
166
+ return yield* scopedResult<A>(
167
+ "QueryReader.execute",
168
+ (done) => addon.runtimeSnapshotExecute(handle, prepared.query, prepared.wire, done),
169
+ query.data.finds
329
170
  )
330
- if (row === null) {
331
- return Option.none<Fact<K["owner"]>>()
332
- }
333
- return Option.some(factOfCells(relation, row as readonly CellValue[]))
334
- })
171
+ }).pipe(Effect.withSpan("QueryReader.execute"))
335
172
  }
336
173
 
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
- })
174
+ class PreparedQueryLive<P extends ParamsRecord, A> implements PreparedQuery<P, A> {
175
+ readonly #handle: PreparedRef
176
+ readonly #query: AnyQuery
177
+
178
+ constructor(handle: PreparedRef, query: AnyQuery) {
179
+ this.#handle = handle
180
+ this.#query = query
181
+ }
182
+
183
+ execute(params: P): Effect.Effect<CompleteResult<A>, DbError, Scope.Scope> {
184
+ const handle = this.#handle
185
+ const query = this.#query
186
+ return Effect.gen(function* () {
187
+ const args = yield* Effect.try({
188
+ try: () => wireParams(query.data.params, params),
189
+ catch: (cause) => argumentError("PreparedQuery.execute", cause)
190
+ })
191
+ return yield* scopedResult<A>(
192
+ "PreparedQuery.execute",
193
+ (done) => addon.runtimePreparedExecute(handle, args, done),
194
+ query.data.finds
366
195
  )
367
- },
368
- close() {
369
- return drainClose("PreparedQuery.close", (callback) => dbNative.runtimePreparedClose(handle, callback))
370
- }
371
- })
196
+ }).pipe(Effect.withSpan("PreparedQuery.execute"))
197
+ }
198
+
199
+ releaseMemory(): Effect.Effect<void, DbError> {
200
+ const handle = this.#handle
201
+ return call(
202
+ "PreparedQuery.releaseMemory",
203
+ (done) => addon.runtimePreparedReleaseMemory(handle, done),
204
+ addon.runtimeTake
205
+ ).pipe(Effect.withSpan("PreparedQuery.releaseMemory"))
206
+ }
372
207
  }
373
208
 
374
209
  function prepareOn<S extends AnySchema, P extends ParamsRecord, A>(
375
- state: SnapshotState<S>,
210
+ theory: S,
211
+ handle: SnapshotRef,
376
212
  query: QueryTemplate<S, P, A>
377
213
  ): Effect.Effect<PreparedQuery<P, A>, DbError, Scope.Scope> {
378
214
  return Effect.gen(function* () {
379
215
  const ir = yield* Effect.try({
380
216
  try: () => {
381
- if (!schemasAgree(query.schema, state.theory)) {
382
- throw refusal("QueryReader.prepare", "InvalidArgument")
383
- }
384
- return lowerQuery(query)
217
+ if (!schemasAgree(query.schema, theory)) throw invalid("QueryReader.prepare")
218
+ return queryHandleOf(query as AnyQuery)
385
219
  },
386
220
  catch: (cause) => argumentError("QueryReader.prepare", cause)
387
221
  })
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 }
222
+ const prepared = yield* scoped(
223
+ "PreparedQuery.release",
224
+ call("QueryReader.prepare", (done) => addon.runtimeSnapshotPrepare(handle, ir, done), addon.runtimePreparedTake),
225
+ (owned) => (done) => addon.runtimePreparedClose(owned, done)
397
226
  )
398
- return makePrepared<P, A>(handle, query.data.params, query.data.finds)
399
- })
227
+ return new PreparedQueryLive<P, A>(prepared, query)
228
+ }).pipe(Effect.withSpan("QueryReader.prepare"))
400
229
  }
401
230
 
402
- function makeSnapshot<S extends AnySchema>(theory: S, handle: SnapshotHandle, witness: CoreWitness): Snapshot<S> {
403
- const state: SnapshotState<S> = { theory, handle }
404
- const snapshot: Snapshot<S> = {
405
- witness,
406
- get(key, value) {
407
- return getOn(state, key, value)
408
- },
409
- execute(query, params) {
410
- return executeOn(state, query, params)
411
- },
412
- prepare(query) {
413
- return prepareOn(state, query)
414
- },
415
- close() {
416
- return drainClose("Snapshot.close", (callback) => dbNative.runtimeSnapshotClose(handle, callback))
417
- }
231
+ class SnapshotLive<S extends AnySchema> implements Snapshot<S> {
232
+ readonly #theory: S
233
+ readonly #handle: SnapshotRef
234
+ readonly witness: Witness
235
+ readonly generation: bigint
236
+
237
+ constructor(theory: S, handle: SnapshotRef, witness: Witness, generation: bigint) {
238
+ this.#theory = theory
239
+ this.#handle = handle
240
+ this.witness = witness
241
+ this.generation = generation
242
+ }
243
+
244
+ get<K extends KeyStatement<Rel<S>, readonly string[]>>(
245
+ key: K,
246
+ value: NoInfer<Key<K>>
247
+ ): Effect.Effect<Option.Option<Fact<K["owner"]>>, DbError> {
248
+ return getOn(this.#theory, this.#handle, key, value)
249
+ }
250
+
251
+ execute<P extends ParamsRecord, A>(
252
+ query: QueryTemplate<S, P, A>,
253
+ params: P
254
+ ): Effect.Effect<CompleteResult<A>, DbError, Scope.Scope> {
255
+ return executeOn<S, A>(this.#theory, this.#handle, query as AnyQuery, params)
256
+ }
257
+
258
+ prepare<P extends ParamsRecord, A>(
259
+ query: QueryTemplate<S, P, A>
260
+ ): Effect.Effect<PreparedQuery<P, A>, DbError, Scope.Scope> {
261
+ return prepareOn(this.#theory, this.#handle, query)
418
262
  }
419
- Object.freeze(snapshot)
420
- snapshotHandles.set(snapshot, handle)
421
- return snapshot
422
263
  }
423
264
 
424
- interface DbState {
425
- readonly theory: AnySchema
426
- readonly db: DbHandle
427
- readonly directory: DirectoryHandle
428
- readonly schemaId: SchemaId
265
+ /** A snapshot `start` pins, read through `theory` and released by the current scope. */
266
+ function scopedSnapshot<S extends AnySchema>(
267
+ operation: string,
268
+ theory: S,
269
+ start: Start
270
+ ): Effect.Effect<Snapshot<S>, DbError, Scope.Scope> {
271
+ return Effect.map(
272
+ scoped(
273
+ "Snapshot.release",
274
+ call(operation, start, addon.runtimeSnapshotTake),
275
+ (opened) => (done) => addon.runtimeSnapshotClose(opened.snapshot, done)
276
+ ),
277
+ (opened): Snapshot<S> => new SnapshotLive(theory, opened.snapshot, opened.witness, opened.generation)
278
+ )
429
279
  }
430
280
 
431
- function makeDb<S extends AnySchema>(theory: S, state: DbState): Db<S> {
432
- const value: Db<S> = {
433
- schemaId: state.schemaId,
434
- clearCache() {
435
- return nativeOperationWith(
436
- "Db.clearCache",
437
- (callback) => dbNative.runtimeDbClearCache(state.db, callback),
438
- runtimeNative.runtimeTake,
439
- () => undefined
440
- )
441
- },
442
- snapshot() {
443
- return Effect.acquireRelease(
444
- nativeOperationWith(
445
- "Db.snapshot",
446
- (callback) => dbNative.runtimeDbSnapshot(state.db, callback),
447
- dbNative.runtimeSnapshotTake,
448
- (wire) => makeSnapshot(theory, wire.snapshot, witnessOf(wire.witness))
449
- ),
450
- (snapshot) =>
451
- Effect.suspend(() =>
452
- releaseOwner("Snapshot.close", (callback) =>
453
- dbNative.runtimeSnapshotClose(snapshotHandles.get(snapshot) ?? missingSnapshot(), callback)
454
- )
455
- ),
456
- { interruptible: true }
457
- )
458
- },
459
- apply(changes, options) {
460
- return Effect.gen(function* () {
461
- const input = yield* writeInputs(state, changes, options, "Db.apply")
462
- return yield* nativeOperationWith(
463
- "Db.apply",
464
- (callback) => dbNative.runtimeDbApply(state.db, input.handle, input.expected, callback),
465
- dbNative.runtimeApplyTake,
466
- outcomeOf
467
- )
468
- })
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
- },
481
- inspect() {
482
- return nativeOperationWith(
483
- "Db.inspect",
484
- (callback) => dbNative.runtimeDbInspect(state.db, callback),
485
- dbNative.runtimeDbInspectTake,
486
- (wire: DbInspectionWire): DbInspection =>
487
- Object.freeze({
488
- schemaId: state.schemaId,
489
- generation: wire.generation,
490
- storage: Object.freeze(wire.storage),
491
- retainedOperations: wire.retainedOperations
492
- })
493
- )
494
- },
495
- close() {
496
- // The one close authority: database child first, then the
497
- // directory owner releases its kernel lock LAST. Both
498
- // joins are idempotent natively.
499
- return drainClose("Db.close", (callback) => runtimeNative.runtimeManagedDbClose(state.db, callback)).pipe(
500
- Effect.flatMap((report) =>
501
- drainClose("Db.directoryClose", (callback) =>
502
- runtimeNative.runtimeDirectoryClose(state.directory, false, callback)
503
- ).pipe(Effect.map((directoryReport) => (report.kind === "closed" ? directoryReport : report)))
504
- )
505
- )
506
- }
281
+ class DbLive<S extends AnySchema> implements Db<S> {
282
+ readonly #theory: S
283
+ readonly #handle: DbRef
284
+ readonly schemaId: SchemaId
285
+
286
+ constructor(theory: S, handle: DbRef, schemaId: SchemaId) {
287
+ this.#theory = theory
288
+ this.#handle = handle
289
+ this.schemaId = schemaId
507
290
  }
508
- Object.freeze(value)
509
- return value
510
- }
511
291
 
512
- const snapshotHandles = new WeakMap<object, SnapshotHandle>()
292
+ snapshot(): Effect.Effect<Snapshot<S>, DbError, Scope.Scope> {
293
+ const handle = this.#handle
294
+ return scopedSnapshot("Db.snapshot", this.#theory, (done) => addon.runtimeDbSnapshot(handle, done)).pipe(
295
+ Effect.withSpan("Db.snapshot")
296
+ )
297
+ }
298
+
299
+ apply(changes: ChangeSet<S>, expected?: Witness): Effect.Effect<ApplyOutcome, DbError> {
300
+ const handle = this.#handle
301
+ return changesOf(this.schemaId, changes, "Db.apply").pipe(
302
+ Effect.flatMap((owned) =>
303
+ call("Db.apply", (done) => addon.runtimeDbApply(handle, owned, expected ?? null, done), addon.runtimeApplyTake)
304
+ ),
305
+ Effect.withSpan("Db.apply")
306
+ )
307
+ }
513
308
 
514
- function missingSnapshot(): never {
515
- throw new DbError({ operation: "Snapshot.close", reason: { _tag: "Internal" } })
309
+ judge(changes: ChangeSet<S>, expected?: Witness): Effect.Effect<JudgeOutcome, DbError> {
310
+ const handle = this.#handle
311
+ return changesOf(this.schemaId, changes, "Db.judge").pipe(
312
+ Effect.flatMap((owned) =>
313
+ call("Db.judge", (done) => addon.runtimeDbJudge(handle, owned, expected ?? null, done), addon.runtimeJudgeTake)
314
+ ),
315
+ Effect.withSpan("Db.judge")
316
+ )
317
+ }
318
+
319
+ inspect(): Effect.Effect<DbInspection, DbError> {
320
+ const handle = this.#handle
321
+ const schemaId = this.schemaId
322
+ return call(
323
+ "Db.inspect",
324
+ (done) => addon.runtimeDbInspect(handle, done),
325
+ (lease): DbInspection => Object.freeze({ ...addon.runtimeDbInspectTake(lease), schemaId })
326
+ ).pipe(Effect.withSpan("Db.inspect"))
327
+ }
328
+
329
+ clearCache(): Effect.Effect<void, DbError> {
330
+ const handle = this.#handle
331
+ return call("Db.clearCache", (done) => addon.runtimeDbClearCache(handle, done), addon.runtimeTake).pipe(
332
+ Effect.withSpan("Db.clearCache")
333
+ )
334
+ }
516
335
  }
517
336
 
518
- /** The managed child name under the database directory owner. */
519
- const CHILD = "store"
337
+ /** The database's child directory inside the owned directory. */
338
+ const CHILD = "store.bdb"
339
+
340
+ const closeDirectory = (directory: DirectoryRef) => (done: Parameters<typeof addon.runtimeDirectoryClose>[2]) =>
341
+ addon.runtimeDirectoryClose(directory, false, done)
520
342
 
521
- function openDatabase<S extends AnySchema>(
343
+ /**
344
+ * Takes the directory lock, then opens the database inside it. Both are owned by the current
345
+ * scope, which releases the database before the directory. A failed open releases the
346
+ * directory at once rather than when the scope ends.
347
+ */
348
+ const openDatabase = <S extends AnySchema>(
522
349
  operation: "Db.create" | "Db.open",
523
350
  path: string,
524
351
  schema: S,
525
352
  create: boolean
526
- ): Effect.Effect<Db<S>, DbError, NativeRuntime | Scope.Scope> {
527
- return Effect.gen(function* () {
528
- const runtime = yield* runtimeHandle()
529
- const compiled: CompiledSchema<S> = yield* CoreSchema.compile(schema)
530
- const spec = lower(compiled.schema)
531
- // Compound acquisition: register the directory owner and
532
- // its finalizer BEFORE any interruptible child-open step.
533
- const directory = yield* Effect.acquireRelease(
534
- nativeOperationWith(
535
- operation,
536
- (callback) => runtimeNative.runtimeDirectoryAcquire(runtime, path, callback),
537
- runtimeNative.runtimeDirectoryTake,
538
- (value) => value
539
- ),
540
- (dir) =>
541
- releaseOwner(`${operation}.directoryClose`, (callback) =>
542
- runtimeNative.runtimeDirectoryClose(dir, false, callback)
543
- ),
544
- { interruptible: true }
353
+ ): Effect.Effect<Db<S>, DbError, Bumble | Scope.Scope> =>
354
+ Effect.gen(function* () {
355
+ const runtime = yield* runtimeHandle
356
+ const theory = yield* Effect.try({
357
+ try: () => schemaDescriptor(schema),
358
+ catch: (cause) => argumentError(operation, cause)
359
+ })
360
+ const compiled = compiledOf(theory)
361
+ const directory = yield* scoped(
362
+ "Db.directoryRelease",
363
+ call(operation, (done) => addon.runtimeDirectoryAcquire(runtime, path, done), addon.runtimeDirectoryTake),
364
+ closeDirectory
545
365
  )
546
- return yield* Effect.acquireRelease(
547
- Effect.gen(function* () {
548
- const outcome = yield* nativeOperationWith(
549
- operation,
550
- (callback) => runtimeNative.runtimeDirectoryDbOpen(directory, CHILD, spec, create, callback),
551
- runtimeNative.runtimeDbTake,
552
- (value) => value
553
- ).pipe(
554
- Effect.catch((error) =>
555
- drainClose(`${operation}.directoryClose`, (callback) =>
556
- runtimeNative.runtimeDirectoryClose(directory, false, callback)
557
- ).pipe(Effect.andThen(Effect.fail(error)))
558
- )
559
- )
560
- if (outcome.tag !== "accepted") {
561
- yield* drainClose(`${operation}.directoryClose`, (callback) =>
562
- runtimeNative.runtimeDirectoryClose(directory, false, callback)
563
- )
564
- return yield* Effect.fail(refusal(operation, "InvalidArgument"))
565
- }
566
- const state: DbState = { theory: compiled.schema, db: outcome.db, directory, schemaId: compiled.schemaId }
567
- const db = makeDb(compiled.schema, state)
568
- dbStates.set(db, state)
569
- return db
570
- }),
571
- (db) =>
572
- Effect.suspend(() => {
573
- const state = dbStates.get(db)
574
- if (state === undefined) {
575
- return Effect.void
576
- }
577
- return releaseOwner("Db.close", (callback) => runtimeNative.runtimeManagedDbClose(state.db, callback)).pipe(
578
- Effect.ensuring(
579
- releaseOwner("Db.directoryClose", (callback) =>
580
- runtimeNative.runtimeDirectoryClose(state.directory, false, callback)
581
- )
582
- )
583
- )
584
- }),
585
- { interruptible: true }
366
+ const opened = call(
367
+ operation,
368
+ (done) => addon.runtimeDirectoryDbOpen(directory, CHILD, compiled.handle, create, done),
369
+ addon.runtimeDbTake
370
+ ).pipe(
371
+ Effect.flatMap((opened) =>
372
+ opened._tag === "Opened" ? Effect.succeed(opened.db) : Effect.fail(refused(operation, opened))
373
+ ),
374
+ Effect.tapError(() => drain(closeDirectory(directory)))
586
375
  )
587
- })
588
- }
589
-
590
- const dbStates = new WeakMap<object, DbState>()
376
+ const handle = yield* scoped("Db.release", opened, (db) => (done) => addon.runtimeManagedDbClose(db, done))
377
+ return new DbLive(theory as S, handle, compiled.schemaId) as Db<S>
378
+ }).pipe(Effect.withSpan(operation))
591
379
 
592
380
  /**
593
- * `Db.create` is the explicit constructor and refuses existing authority;
594
- * `Db.open` of a missing or unreadable database never creates an empty
595
- * replacement. Both compile the schema through the same
596
- * implementation as `Schema.compile` — prior compilation is optional.
381
+ * `Db.create` makes a new database and refuses an existing one; `Db.open` opens an existing one
382
+ * and never creates. Both compile the schema first.
597
383
  */
598
384
  const Db = Object.freeze({
599
385
  create<S extends AnySchema>(path: string, schema: S) {
@@ -604,49 +390,6 @@ const Db = Object.freeze({
604
390
  }
605
391
  })
606
392
 
607
- /**
608
- * Private log integration: wraps a published core snapshot
609
- * handle — minted by the internal log machine's native open/snapshot verbs
610
- * — in the exact core `QueryReader` plus the scoped session acquisition
611
- * (the log adds identity, stamps and freshness around this capability,
612
- * never a second reader).
613
- * The argument is the log package's branded handle for the same native
614
- * registry entry, so the one cast below is a cross-package respelling of
615
- * one native capability — the native side re-judges kind/generation/owner
616
- * on every verb, so a forged object refuses there, typed.
617
- */
618
- function internalPublishedReader<S extends AnySchema>(
619
- core: object,
620
- theory: S
621
- ): {
622
- readonly get: QueryReader<S>["get"]
623
- readonly execute: QueryReader<S>["execute"]
624
- readonly prepare: QueryReader<S>["prepare"]
625
- } {
626
- const state: SnapshotState<S> = { theory: schemaDescriptor(theory), handle: core as SnapshotHandle }
627
- return Object.freeze({
628
- get<K extends KeyStatement<Rel<S>, readonly string[]>>(key: K, value: NoInfer<Key<K>>) {
629
- return getOn(state, key, value)
630
- },
631
- execute<P extends ParamsRecord, A>(queryValue: QueryTemplate<S, P, A>, params: P) {
632
- return executeOn<S, A>(state, queryValue as AnyQuery, params)
633
- },
634
- prepare<P extends ParamsRecord, A>(queryValue: QueryTemplate<S, P, A>) {
635
- return prepareOn(state, queryValue)
636
- }
637
- })
638
- }
639
-
640
- export type {
641
- ApplyOutcome,
642
- CoreWitness,
643
- DbInspection,
644
- JudgeOutcome,
645
- PreparedQuery,
646
- QueryReader,
647
- Snapshot,
648
- StorageInspection,
649
- WriteExpected,
650
- WriteOptions
651
- }
652
- export { Db, internalPublishedReader }
393
+ export type { ApplyOutcome, JudgeOutcome } from "./native/binding.d.ts"
394
+ export type { DbInspection, PreparedQuery, QueryReader, Snapshot, Witness }
395
+ export { Db, scopedSnapshot }