@bjornpagen/bumbledb 0.20.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/COOKBOOK.md +283 -1611
  2. package/README.md +156 -118
  3. package/dist/capacity.d.ts +16 -12
  4. package/dist/capacity.d.ts.map +1 -1
  5. package/dist/capacity.js +16 -48
  6. package/dist/capacity.js.map +1 -1
  7. package/dist/changes.d.ts +54 -0
  8. package/dist/changes.d.ts.map +1 -0
  9. package/dist/changes.js +245 -0
  10. package/dist/changes.js.map +1 -0
  11. package/dist/close.d.ts +24 -0
  12. package/dist/close.d.ts.map +1 -0
  13. package/dist/close.js +34 -0
  14. package/dist/close.js.map +1 -0
  15. package/dist/closed.d.ts +1 -3
  16. package/dist/closed.d.ts.map +1 -1
  17. package/dist/closed.js +24 -12
  18. package/dist/closed.js.map +1 -1
  19. package/dist/codec.d.ts +56 -0
  20. package/dist/codec.d.ts.map +1 -0
  21. package/dist/codec.js +289 -0
  22. package/dist/codec.js.map +1 -0
  23. package/dist/compile.d.ts +68 -0
  24. package/dist/compile.d.ts.map +1 -0
  25. package/dist/compile.js +75 -0
  26. package/dist/compile.js.map +1 -0
  27. package/dist/db-native.d.ts +170 -0
  28. package/dist/db-native.d.ts.map +1 -0
  29. package/dist/db-native.js +5 -0
  30. package/dist/db-native.js.map +1 -0
  31. package/dist/db.d.ts +101 -233
  32. package/dist/db.d.ts.map +1 -1
  33. package/dist/db.js +232 -1084
  34. package/dist/db.js.map +1 -1
  35. package/dist/errors.d.ts +57 -0
  36. package/dist/errors.d.ts.map +1 -0
  37. package/dist/errors.js +32 -0
  38. package/dist/errors.js.map +1 -0
  39. package/dist/face.d.ts.map +1 -1
  40. package/dist/face.js +2 -2
  41. package/dist/face.js.map +1 -1
  42. package/dist/fields.d.ts +65 -24
  43. package/dist/fields.d.ts.map +1 -1
  44. package/dist/fields.js +94 -24
  45. package/dist/fields.js.map +1 -1
  46. package/dist/index.d.ts +37 -32
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +17 -21
  49. package/dist/index.js.map +1 -1
  50. package/dist/internal/log.d.ts +24 -0
  51. package/dist/internal/log.d.ts.map +1 -0
  52. package/dist/internal/log.js +10 -0
  53. package/dist/internal/log.js.map +1 -0
  54. package/dist/law.d.ts +8 -13
  55. package/dist/law.d.ts.map +1 -1
  56. package/dist/law.js +6 -59
  57. package/dist/law.js.map +1 -1
  58. package/dist/lower.d.ts +0 -13
  59. package/dist/lower.d.ts.map +1 -1
  60. package/dist/lower.js +9 -17
  61. package/dist/lower.js.map +1 -1
  62. package/dist/migration.d.ts +33 -0
  63. package/dist/migration.d.ts.map +1 -0
  64. package/dist/migration.js +48 -0
  65. package/dist/migration.js.map +1 -0
  66. package/dist/native.d.ts +256 -301
  67. package/dist/native.d.ts.map +1 -1
  68. package/dist/native.js +65 -108
  69. package/dist/native.js.map +1 -1
  70. package/dist/query/atom.d.ts +24 -13
  71. package/dist/query/atom.d.ts.map +1 -1
  72. package/dist/query/atom.js +7 -3
  73. package/dist/query/atom.js.map +1 -1
  74. package/dist/query/compute.d.ts +68 -0
  75. package/dist/query/compute.d.ts.map +1 -0
  76. package/dist/query/compute.js +141 -0
  77. package/dist/query/compute.js.map +1 -0
  78. package/dist/query/find.d.ts +15 -8
  79. package/dist/query/find.d.ts.map +1 -1
  80. package/dist/query/find.js +6 -2
  81. package/dist/query/find.js.map +1 -1
  82. package/dist/query/lower.d.ts +23 -2
  83. package/dist/query/lower.d.ts.map +1 -1
  84. package/dist/query/lower.js +433 -80
  85. package/dist/query/lower.js.map +1 -1
  86. package/dist/query/parse-ir.d.ts.map +1 -1
  87. package/dist/query/parse-ir.js +17 -8
  88. package/dist/query/parse-ir.js.map +1 -1
  89. package/dist/query/run.d.ts +12 -2
  90. package/dist/query/run.d.ts.map +1 -1
  91. package/dist/query/run.js +26 -9
  92. package/dist/query/run.js.map +1 -1
  93. package/dist/query/scope.d.ts +47 -3
  94. package/dist/query/scope.d.ts.map +1 -1
  95. package/dist/query/scope.js +83 -19
  96. package/dist/query/scope.js.map +1 -1
  97. package/dist/relation.d.ts +3 -8
  98. package/dist/relation.d.ts.map +1 -1
  99. package/dist/relation.js +16 -10
  100. package/dist/relation.js.map +1 -1
  101. package/dist/result.d.ts +47 -0
  102. package/dist/result.d.ts.map +1 -0
  103. package/dist/result.js +50 -0
  104. package/dist/result.js.map +1 -0
  105. package/dist/rows.d.ts +69 -0
  106. package/dist/rows.d.ts.map +1 -0
  107. package/dist/rows.js +328 -0
  108. package/dist/rows.js.map +1 -0
  109. package/dist/runtime-codes.d.ts +2 -0
  110. package/dist/runtime-codes.d.ts.map +1 -0
  111. package/dist/runtime-codes.js +19 -0
  112. package/dist/runtime-codes.js.map +1 -0
  113. package/dist/runtime-errors.d.ts +106 -0
  114. package/dist/runtime-errors.d.ts.map +1 -0
  115. package/dist/runtime-errors.js +62 -0
  116. package/dist/runtime-errors.js.map +1 -0
  117. package/dist/runtime-native.d.ts +171 -0
  118. package/dist/runtime-native.d.ts.map +1 -0
  119. package/dist/runtime-native.js +5 -0
  120. package/dist/runtime-native.js.map +1 -0
  121. package/dist/runtime.d.ts +73 -0
  122. package/dist/runtime.d.ts.map +1 -0
  123. package/dist/runtime.js +293 -0
  124. package/dist/runtime.js.map +1 -0
  125. package/dist/scalar.d.ts +165 -0
  126. package/dist/scalar.d.ts.map +1 -0
  127. package/dist/scalar.js +344 -0
  128. package/dist/scalar.js.map +1 -0
  129. package/dist/schema.d.ts +1 -1
  130. package/dist/schema.d.ts.map +1 -1
  131. package/dist/schema.js +34 -37
  132. package/dist/schema.js.map +1 -1
  133. package/dist/shape.d.ts +28 -0
  134. package/dist/shape.d.ts.map +1 -0
  135. package/dist/shape.js +2 -0
  136. package/dist/shape.js.map +1 -0
  137. package/dist/spec.d.ts +27 -3
  138. package/dist/spec.d.ts.map +1 -1
  139. package/dist/spec.js +22 -1
  140. package/dist/spec.js.map +1 -1
  141. package/dist/statements.d.ts +16 -3
  142. package/dist/statements.d.ts.map +1 -1
  143. package/dist/statements.js +54 -28
  144. package/dist/statements.js.map +1 -1
  145. package/dist/uuid.d.ts +23 -0
  146. package/dist/uuid.d.ts.map +1 -0
  147. package/dist/uuid.js +54 -0
  148. package/dist/uuid.js.map +1 -0
  149. package/pack-provenance.json +7 -0
  150. package/package.json +12 -16
  151. package/src/capacity.ts +34 -54
  152. package/src/changes.ts +361 -0
  153. package/src/close.ts +53 -0
  154. package/src/closed.ts +25 -19
  155. package/src/codec.ts +379 -0
  156. package/src/compile.ts +149 -0
  157. package/src/db-native.ts +247 -0
  158. package/src/db.ts +461 -1632
  159. package/src/errors.ts +50 -0
  160. package/src/face.ts +2 -2
  161. package/src/fields.ts +182 -55
  162. package/src/index.ts +62 -126
  163. package/src/internal/log.ts +51 -0
  164. package/src/law.ts +13 -28
  165. package/src/lower.ts +9 -4
  166. package/src/migration.ts +69 -0
  167. package/src/native.ts +326 -487
  168. package/src/query/atom.ts +61 -25
  169. package/src/query/compute.ts +237 -0
  170. package/src/query/find.ts +31 -12
  171. package/src/query/lower.ts +531 -143
  172. package/src/query/parse-ir.ts +18 -13
  173. package/src/query/run.ts +32 -16
  174. package/src/query/scope.ts +140 -8
  175. package/src/relation.ts +17 -21
  176. package/src/result.ts +130 -0
  177. package/src/rows.ts +404 -0
  178. package/src/runtime-codes.ts +18 -0
  179. package/src/runtime-errors.ts +69 -0
  180. package/src/runtime-native.ts +242 -0
  181. package/src/runtime.ts +440 -0
  182. package/src/scalar.ts +560 -0
  183. package/src/schema.ts +35 -37
  184. package/src/shape.ts +31 -0
  185. package/src/spec.ts +33 -3
  186. package/src/statements.ts +75 -68
  187. package/src/uuid.ts +69 -0
  188. package/dist/marshal.d.ts +0 -44
  189. package/dist/marshal.d.ts.map +0 -1
  190. package/dist/marshal.js +0 -165
  191. package/dist/marshal.js.map +0 -1
  192. package/src/marshal.ts +0 -200
@@ -0,0 +1,242 @@
1
+ /** Exact-version internal bridge, shared by core and log. Not a public API. */
2
+
3
+ import type {
4
+ DbHandle,
5
+ LogChainKind,
6
+ LogCommandKind,
7
+ LogCommandMetadata,
8
+ LogCommandRef,
9
+ LogCondition,
10
+ LogDecisionStamp,
11
+ LogIdentity,
12
+ LogLimits,
13
+ LogOutcomeWire,
14
+ LogSchemaHandle,
15
+ LogStateStamp,
16
+ Violation
17
+ } from "#native.ts"
18
+ import { native } from "#native.ts"
19
+ import type { SchemaSpec } from "#spec.ts"
20
+
21
+ export interface RuntimeHandle {
22
+ readonly __runtime: unique symbol
23
+ }
24
+ export interface OperationHandle {
25
+ readonly __operation: unique symbol
26
+ }
27
+ export interface DirectoryHandle {
28
+ readonly __directory: unique symbol
29
+ }
30
+
31
+ /** Stamped `NativeKind::RepositoryLock` mint — not a directory-owner twin. */
32
+ export interface RepositoryLockHandle {
33
+ readonly __repositoryLock: unique symbol
34
+ }
35
+
36
+ /** F0 C7 kind roster for worker-table resources. */
37
+ export type NativeKind = "snapshot" | "result" | "cursor" | "draft" | "changes" | "repository-lock"
38
+
39
+ /** Worker-routed capability header (C7). Never holds payload bytes. */
40
+ export interface ResourceHeader {
41
+ readonly runtime: bigint
42
+ readonly worker: number
43
+ readonly kind: NativeKind
44
+ readonly id: bigint
45
+ readonly generation: bigint
46
+ }
47
+
48
+ /**
49
+ * Checked capability into the native registry (C7). JS tokens validate
50
+ * these five fields; native re-judges kind/generation/owner on every verb.
51
+ */
52
+ export interface Capability {
53
+ readonly runtime: bigint
54
+ readonly worker: number
55
+ readonly kind: NativeKind
56
+ readonly id: bigint
57
+ readonly generation: bigint
58
+ }
59
+
60
+ /** Coalesced close obligation — already-owned drain, not rejectable work. */
61
+ export interface CloseDrain {
62
+ readonly header: ResourceHeader
63
+ }
64
+
65
+ export type ManagedDbOutcome =
66
+ | { readonly tag: "accepted"; readonly db: DbHandle }
67
+ | { readonly tag: "rejected"; readonly violations: readonly Violation[] }
68
+ | {
69
+ readonly tag: "refused"
70
+ readonly kind: "schemaError" | "newtypeMismatch" | "fingerprintMismatch" | "destinationExists"
71
+ readonly message: string
72
+ }
73
+
74
+ // The 0.x five-verb JS filesystem transport (`runtimeFs`/`runtimeFsTake`,
75
+ // FsRequestWire/FsResultWire) is DELETED with the TS CAS authority: all
76
+ // object-store work is native (C07, P05's store rewrite); the log machine
77
+ // drives FsStore/S3Store inside `ts/crate` and no JS layer holds a
78
+ // conditional-store verb anymore.
79
+
80
+ export type LogTakeWire =
81
+ | { readonly ok: false; readonly kind: LogCommandKind | LogChainKind; readonly message: string }
82
+ | {
83
+ /** Command sealed: envelope bytes + reference. */
84
+ readonly ok: true
85
+ readonly bytes: Uint8Array
86
+ readonly ref: LogCommandRef
87
+ }
88
+ | {
89
+ /** Command parsed. */
90
+ readonly ok: true
91
+ readonly identity: LogIdentity
92
+ readonly receiptEpoch: bigint
93
+ readonly requestId: string
94
+ readonly condition: LogCondition
95
+ readonly changes: Uint8Array
96
+ readonly result: Uint8Array
97
+ readonly ref: LogCommandRef
98
+ }
99
+ | {
100
+ /** Decision framed. */
101
+ readonly ok: true
102
+ readonly bytes: Uint8Array
103
+ readonly digest: Uint8Array
104
+ }
105
+ | {
106
+ /** Decision decoded (and chain-verified when a parent was given). */
107
+ readonly ok: true
108
+ readonly identity: LogIdentity
109
+ readonly seq: bigint
110
+ readonly parent: LogDecisionStamp
111
+ readonly beforeState: LogStateStamp
112
+ readonly afterState: LogStateStamp
113
+ readonly commandBytes: Uint8Array
114
+ readonly command: LogCommandRef
115
+ readonly outcome: LogOutcomeWire
116
+ readonly digest: Uint8Array
117
+ }
118
+
119
+ export interface PolicyWire {
120
+ readonly inputBytes: bigint
121
+ readonly workingBytes: bigint
122
+ readonly scratchBytes: bigint
123
+ readonly resultBytes: bigint
124
+ readonly rows: bigint
125
+ readonly workUnits: bigint
126
+ readonly timeoutMs: number
127
+ }
128
+
129
+ export interface OptionsWire {
130
+ readonly workers: number
131
+ readonly queueCapacity: number
132
+ readonly cleanupCapacity: number
133
+ readonly ownerCapacity: number
134
+ readonly nativeHandleCapacity: number
135
+ readonly inputBytes: bigint
136
+ readonly workingBytes: bigint
137
+ readonly scratchBytes: bigint
138
+ readonly resultBytes: bigint
139
+ readonly chunkBytes: bigint
140
+ readonly cleanupTimeoutMs: number
141
+ }
142
+
143
+ export interface InspectionWire {
144
+ readonly phase: "open" | "closing" | "closed"
145
+ readonly queued: bigint
146
+ readonly active: bigint
147
+ readonly retained: bigint
148
+ readonly owners: bigint
149
+ readonly databases: bigint
150
+ readonly natives: bigint
151
+ readonly inputBytes: bigint
152
+ readonly workingBytes: bigint
153
+ readonly scratchBytes: bigint
154
+ readonly resultBytes: bigint
155
+ }
156
+
157
+ export type CloseWire =
158
+ | { readonly kind: "closed" }
159
+ | { readonly kind: "incomplete"; readonly outstanding: InspectionWire }
160
+ | { readonly kind: "failed" }
161
+
162
+ interface RuntimeNative {
163
+ runtimeErrorCodes(): readonly string[]
164
+ runtimeOpen(options: OptionsWire): RuntimeHandle
165
+ runtimeReady(runtime: RuntimeHandle, policy: PolicyWire, callback: () => void): OperationHandle
166
+ runtimeHash(runtime: RuntimeHandle, policy: PolicyWire, bytes: Uint8Array, callback: () => void): OperationHandle
167
+ runtimeTake(operation: OperationHandle): Uint8Array | null
168
+ runtimeCancel(operation: OperationHandle, callback: (report: CloseWire) => void): void
169
+ runtimeClose(runtime: RuntimeHandle, callback: (report: CloseWire) => void): void
170
+ runtimeInspect(runtime: RuntimeHandle): InspectionWire
171
+ /**
172
+ * L12 request (D12/D25): one-shot arm. Next `dispatch_payload_message`
173
+ * must cancel after `work()` returns and the post-work `checkpoint()`
174
+ * (`runtime.rs` ~860–862), before `complete_operation` writes
175
+ * `operation.output`. A local `QueuedOutput` is not registration.
176
+ * Predelivery: no JS take; cursor stays on row1 so retry does not skip.
177
+ */
178
+ runtimeArmPublicationCancel(runtime: RuntimeHandle): void
179
+ runtimeDirectoryAcquire(
180
+ runtime: RuntimeHandle,
181
+ policy: PolicyWire,
182
+ path: string,
183
+ callback: () => void
184
+ ): OperationHandle
185
+ runtimeDirectoryTake(operation: OperationHandle): DirectoryHandle
186
+ runtimeDirectoryBegin(owner: DirectoryHandle, policy: PolicyWire): OperationHandle
187
+ runtimeDirectoryCheck(operation: OperationHandle): void
188
+ runtimeDirectoryEnd(operation: OperationHandle): void
189
+ runtimeDirectoryClose(owner: DirectoryHandle, remove: boolean, callback: (report: CloseWire) => void): void
190
+ runtimeDirectoryDbOpen(
191
+ owner: DirectoryHandle,
192
+ policy: PolicyWire,
193
+ childName: string,
194
+ spec: SchemaSpec,
195
+ create: boolean,
196
+ callback: () => void
197
+ ): OperationHandle
198
+ runtimeDbTake(operation: OperationHandle): ManagedDbOutcome
199
+ runtimeManagedDbClose(db: DbHandle, callback: (report: CloseWire) => void): void
200
+
201
+ // --- successor log grammar on the executor (charged hashing over
202
+ // whole canonical change payloads; C06 through C09) ---
203
+ runtimeLogCommandSeal(
204
+ runtime: RuntimeHandle,
205
+ schema: LogSchemaHandle,
206
+ policy: PolicyWire,
207
+ metadata: LogCommandMetadata,
208
+ changes: Uint8Array,
209
+ result: Uint8Array | null,
210
+ limits: LogLimits,
211
+ callback: () => void
212
+ ): OperationHandle
213
+ runtimeLogCommandParse(
214
+ runtime: RuntimeHandle,
215
+ schema: LogSchemaHandle,
216
+ policy: PolicyWire,
217
+ bytes: Uint8Array,
218
+ limits: LogLimits,
219
+ callback: () => void
220
+ ): OperationHandle
221
+ runtimeLogDecisionDecode(
222
+ runtime: RuntimeHandle,
223
+ policy: PolicyWire,
224
+ bytes: Uint8Array,
225
+ parent: LogDecisionStamp | null,
226
+ limits: LogLimits,
227
+ callback: () => void
228
+ ): OperationHandle
229
+ runtimeLogTake(operation: OperationHandle): LogTakeWire
230
+ /** L14 mint: `Runtime::mint_repository_lock` stamps `NativeKind::RepositoryLock` at take. */
231
+ logRepositoryLockAcquire(
232
+ runtime: RuntimeHandle,
233
+ policy: PolicyWire,
234
+ directory: string,
235
+ callback: () => void
236
+ ): OperationHandle
237
+ logRepositoryLockTake(operation: OperationHandle): RepositoryLockHandle
238
+ logRepositoryLockRelease(owner: RepositoryLockHandle, callback: (report: CloseWire) => void): void
239
+ }
240
+
241
+ // The checked source/fresh-addon roster test pins this private declaration.
242
+ export const runtimeNative = native as typeof native & RuntimeNative
package/src/runtime.ts ADDED
@@ -0,0 +1,440 @@
1
+ import { Context, Duration, Effect, Exit, Layer } from "effect"
2
+ import type { CloseReport, OutstandingWork } from "#runtime-errors.ts"
3
+ import { CloseFailure, DbError, dbError } from "#runtime-errors.ts"
4
+ import type {
5
+ CloseWire,
6
+ OperationHandle,
7
+ OptionsWire,
8
+ PolicyWire,
9
+ RepositoryLockHandle,
10
+ RuntimeHandle
11
+ } from "#runtime-native.ts"
12
+ import { runtimeNative } from "#runtime-native.ts"
13
+
14
+ export interface ExecutionPolicy {
15
+ readonly inputBytes: bigint
16
+ readonly workingBytes: bigint
17
+ readonly scratchBytes: bigint
18
+ readonly resultBytes: bigint
19
+ readonly rows: bigint
20
+ readonly workUnits: bigint
21
+ readonly timeout: Duration.Input
22
+ }
23
+
24
+ export interface NativeRuntimeOptions {
25
+ readonly workers: number
26
+ readonly queueCapacity: number
27
+ readonly cleanupCapacity: number
28
+ readonly ownerCapacity: number
29
+ readonly nativeHandleCapacity: number
30
+ readonly inputBytes: bigint
31
+ readonly workingBytes: bigint
32
+ readonly scratchBytes: bigint
33
+ readonly resultBytes: bigint
34
+ readonly chunkBytes: bigint
35
+ readonly cleanupTimeout: Duration.Input
36
+ }
37
+
38
+ interface RuntimeService {
39
+ readonly close: () => Effect.Effect<CloseReport>
40
+ readonly inspect: (work: ExecutionPolicy) => Effect.Effect<OutstandingWork, DbError>
41
+ }
42
+
43
+ const owners = new WeakMap<RuntimeService, RuntimeHandle>()
44
+
45
+ function invalid(operation: string): DbError {
46
+ return new DbError({ operation, reason: { _tag: "InvalidArgument" } })
47
+ }
48
+
49
+ function count(value: number, operation: string): number {
50
+ if (!Number.isSafeInteger(value) || value <= 0 || value > 0xffffffff) throw invalid(operation)
51
+ return value
52
+ }
53
+
54
+ function bytes(value: bigint, operation: string): bigint {
55
+ if (typeof value !== "bigint" || value < 0n || value > 0xffffffffffffffffn) throw invalid(operation)
56
+ return value
57
+ }
58
+
59
+ function millis(value: Duration.Input, operation: string): number {
60
+ const duration = Duration.toMillis(value)
61
+ if (!Number.isFinite(duration) || duration < 0 || duration > 0xffffffff) throw invalid(operation)
62
+ return Math.ceil(duration)
63
+ }
64
+
65
+ export function policyWire(work: ExecutionPolicy, operation: string): PolicyWire {
66
+ return {
67
+ inputBytes: bytes(work.inputBytes, operation),
68
+ workingBytes: bytes(work.workingBytes, operation),
69
+ scratchBytes: bytes(work.scratchBytes, operation),
70
+ resultBytes: bytes(work.resultBytes, operation),
71
+ rows: bytes(work.rows, operation),
72
+ workUnits: bytes(work.workUnits, operation),
73
+ timeoutMs: millis(work.timeout, operation)
74
+ }
75
+ }
76
+
77
+ function options(value: NativeRuntimeOptions): OptionsWire {
78
+ const operation = "NativeRuntime.acquire"
79
+ return {
80
+ workers: count(value.workers, operation),
81
+ queueCapacity: count(value.queueCapacity, operation),
82
+ cleanupCapacity: count(value.cleanupCapacity, operation),
83
+ ownerCapacity: count(value.ownerCapacity, operation),
84
+ nativeHandleCapacity: count(value.nativeHandleCapacity, operation),
85
+ inputBytes: bytes(value.inputBytes, operation),
86
+ workingBytes: bytes(value.workingBytes, operation),
87
+ scratchBytes: bytes(value.scratchBytes, operation),
88
+ resultBytes: bytes(value.resultBytes, operation),
89
+ chunkBytes: bytes(value.chunkBytes, operation),
90
+ cleanupTimeoutMs: count(millis(value.cleanupTimeout, operation), operation)
91
+ }
92
+ }
93
+
94
+ function failure(operation: string, cause: unknown): DbError {
95
+ return cause instanceof DbError ? cause : dbError(operation, cause)
96
+ }
97
+
98
+ function closeReport(operation: string, report: CloseWire): CloseReport {
99
+ // A payload-less native "failed" drain (cleanup capacity exhausted, or
100
+ // owner teardown failed) is bounded diagnostic data per chapter 35's
101
+ // CloseReport policy: it decodes as the core `Internal` reason — the
102
+ // same mapping the log bridge applies, so one wire arm has one meaning.
103
+ // It is deliberately NOT `QueueFull`: a failed drain is never retryable
104
+ // submit backpressure.
105
+ return report.kind === "failed" ? { kind: "failed", error: dbError(operation, { _tag: "Internal" }) } : report
106
+ }
107
+
108
+ function drain(operation: string, start: (callback: (report: CloseWire) => void) => void): Effect.Effect<CloseReport> {
109
+ return Effect.callback<CloseReport>((resume) => {
110
+ try {
111
+ start((report) => resume(Effect.succeed(closeReport(operation, report))))
112
+ } catch (cause) {
113
+ resume(Effect.succeed({ kind: "failed", error: failure(operation, cause) }))
114
+ }
115
+ }).pipe(Effect.uninterruptible)
116
+ }
117
+
118
+ export function finalizeClose(operation: string, report: CloseReport): Effect.Effect<void> {
119
+ return report.kind === "closed" ? Effect.void : Effect.die(new CloseFailure({ operation, report }))
120
+ }
121
+
122
+ /**
123
+ * Effect.callback's returned finalizer is interruption cleanup only
124
+ * (`asyncFinalizer` runs solely on interrupt Cause). If that finalizer
125
+ * dies, the original interrupt is dropped. Join the native drain here and
126
+ * always succeed so the interrupt Cause is kept; attach CloseFailure via
127
+ * `onExit` so both appear in the final Cause.
128
+ */
129
+ function joinInterruptDrain(
130
+ operation: string,
131
+ start: (callback: (report: CloseWire) => void) => void,
132
+ stash: { report?: CloseReport }
133
+ ): Effect.Effect<void> {
134
+ return drain(operation, start).pipe(
135
+ Effect.tap((report) => {
136
+ stash.report = report
137
+ return Effect.void
138
+ }),
139
+ Effect.asVoid
140
+ )
141
+ }
142
+
143
+ function attachInterruptClose(
144
+ operation: string,
145
+ stash: { report?: CloseReport }
146
+ ): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> {
147
+ return (effect) =>
148
+ effect.pipe(
149
+ Effect.onExit((exit) => {
150
+ if (!Exit.hasInterrupts(exit)) {
151
+ return Effect.void
152
+ }
153
+ const report = stash.report
154
+ if (report === undefined || report.kind === "closed") {
155
+ return Effect.void
156
+ }
157
+ return Effect.die(new CloseFailure({ operation, report }))
158
+ })
159
+ )
160
+ }
161
+
162
+ function afterClose<A, E, R>(
163
+ operation: string,
164
+ report: CloseReport,
165
+ effect: Effect.Effect<A, E, R>
166
+ ): Effect.Effect<A, E, R> {
167
+ if (report.kind === "closed") {
168
+ return effect
169
+ }
170
+ return effect.pipe(Effect.onExit(() => Effect.die(new CloseFailure({ operation, report }))))
171
+ }
172
+
173
+ /** Independent caps intersect: a delivery request cannot enlarge `work.resultBytes`. */
174
+ export function deliveryResultBytes(requested: bigint, work: ExecutionPolicy): bigint {
175
+ if (typeof requested !== "bigint" || requested < 0n) {
176
+ throw invalid("deliveryResultBytes")
177
+ }
178
+ return requested < work.resultBytes ? requested : work.resultBytes
179
+ }
180
+
181
+ function close(handle: RuntimeHandle): Effect.Effect<CloseReport> {
182
+ return drain("NativeRuntime.close", (callback) => runtimeNative.runtimeClose(handle, callback))
183
+ }
184
+
185
+ /** Kick `runtimeCancel` without joining. Used when the completion
186
+ * callback arrives after abort: JS must not `take`; L12 drain reclaims
187
+ * queued Page/Rows. A second call is native-idempotent (Committed
188
+ * mutations stay; only unpublished delivery is dropped).
189
+ */
190
+ function startCancelDrain(operation: string, lease: OperationHandle, stash: { report?: CloseReport }): void {
191
+ try {
192
+ runtimeNative.runtimeCancel(lease, (report) => {
193
+ stash.report = closeReport(operation, report)
194
+ })
195
+ } catch (cause) {
196
+ stash.report = { kind: "failed", error: failure(operation, cause) }
197
+ }
198
+ }
199
+
200
+ /** Registration returns the native lease before any completion can run in JS.
201
+ * Interruption cancels and joins that lease, including a late successful result.
202
+ * No Promise or libuv job is created for ordinary native work.
203
+ */
204
+ export function nativeOperation<A>(
205
+ operation: string,
206
+ start: (callback: () => void) => OperationHandle,
207
+ accept: (value: Uint8Array | null) => A
208
+ ): Effect.Effect<A, DbError> {
209
+ return nativeOperationWith(operation, start, runtimeNative.runtimeTake, accept)
210
+ }
211
+
212
+ export function nativeOperationWith<A, Value>(
213
+ operation: string,
214
+ start: (callback: () => void) => OperationHandle,
215
+ take: (operation: OperationHandle) => Value,
216
+ accept: (value: Value) => A
217
+ ): Effect.Effect<A, DbError> {
218
+ const stash: { report?: CloseReport } = {}
219
+ const cancelOp = `${operation}.cancel`
220
+ return attachInterruptClose(
221
+ cancelOp,
222
+ stash
223
+ )(
224
+ Effect.callback((resume, signal) => {
225
+ let lease: OperationHandle
226
+ try {
227
+ lease = start(() => {
228
+ if (signal.aborted) {
229
+ startCancelDrain(cancelOp, lease, stash)
230
+ return
231
+ }
232
+ try {
233
+ resume(Effect.succeed(accept(take(lease))))
234
+ } catch (cause) {
235
+ resume(Effect.fail(failure(operation, cause)))
236
+ }
237
+ })
238
+ } catch (cause) {
239
+ resume(Effect.fail(failure(operation, cause)))
240
+ return
241
+ }
242
+ return joinInterruptDrain(cancelOp, (callback) => runtimeNative.runtimeCancel(lease, callback), stash)
243
+ })
244
+ )
245
+ }
246
+
247
+ const acquire = Effect.fn("NativeRuntime.acquire")(function* (configuration: NativeRuntimeOptions) {
248
+ return yield* Effect.acquireRelease(
249
+ Effect.suspend(() => {
250
+ const stash: { report?: CloseReport } = {}
251
+ return attachInterruptClose(
252
+ "NativeRuntime.acquire.cancel",
253
+ stash
254
+ )(
255
+ Effect.callback<RuntimeService, DbError>((resume, signal) => {
256
+ let handle: RuntimeHandle | undefined
257
+ try {
258
+ const wire = options(configuration)
259
+ handle = runtimeNative.runtimeOpen(wire)
260
+ const owner = handle
261
+ const lease = runtimeNative.runtimeReady(
262
+ owner,
263
+ {
264
+ inputBytes: 0n,
265
+ workingBytes: 0n,
266
+ scratchBytes: 0n,
267
+ resultBytes: 0n,
268
+ rows: 0n,
269
+ workUnits: 1n,
270
+ timeoutMs: wire.cleanupTimeoutMs
271
+ },
272
+ () => {
273
+ if (signal.aborted) {
274
+ try {
275
+ runtimeNative.runtimeClose(owner, (report) => {
276
+ stash.report = closeReport("NativeRuntime.acquire.cancel", report)
277
+ })
278
+ } catch (cause) {
279
+ stash.report = { kind: "failed", error: failure("NativeRuntime.acquire.cancel", cause) }
280
+ }
281
+ return
282
+ }
283
+ try {
284
+ runtimeNative.runtimeTake(lease)
285
+ const service: RuntimeService = {
286
+ close: () => close(owner),
287
+ inspect: Effect.fn("NativeRuntime.inspect")(function* (work) {
288
+ yield* nativeOperation(
289
+ "NativeRuntime.inspect",
290
+ (callback) =>
291
+ runtimeNative.runtimeReady(owner, policyWire(work, "NativeRuntime.inspect"), callback),
292
+ () => undefined
293
+ )
294
+ return runtimeNative.runtimeInspect(owner)
295
+ })
296
+ }
297
+ owners.set(service, owner)
298
+ resume(Effect.succeed(service))
299
+ } catch (cause) {
300
+ const error = failure("NativeRuntime.acquire", cause)
301
+ resume(
302
+ close(owner).pipe(
303
+ Effect.flatMap((report) => afterClose("NativeRuntime.acquire", report, Effect.fail(error)))
304
+ )
305
+ )
306
+ }
307
+ }
308
+ )
309
+ } catch (cause) {
310
+ const error = failure("NativeRuntime.acquire", cause)
311
+ resume(
312
+ handle === undefined
313
+ ? Effect.fail(error)
314
+ : close(handle).pipe(
315
+ Effect.flatMap((report) => afterClose("NativeRuntime.acquire", report, Effect.fail(error)))
316
+ )
317
+ )
318
+ }
319
+ return handle === undefined
320
+ ? Effect.void
321
+ : joinInterruptDrain(
322
+ "NativeRuntime.acquire.cancel",
323
+ (callback) => runtimeNative.runtimeClose(handle, callback),
324
+ stash
325
+ )
326
+ })
327
+ )
328
+ }),
329
+ (service) => service.close().pipe(Effect.flatMap((report) => finalizeClose("NativeRuntime.release", report))),
330
+ { interruptible: true }
331
+ )
332
+ })
333
+
334
+ export class NativeRuntime extends Context.Service<NativeRuntime, RuntimeService>()(
335
+ "@bjornpagen/bumbledb/NativeRuntime"
336
+ ) {
337
+ static layer(options: NativeRuntimeOptions): Layer.Layer<NativeRuntime, DbError> {
338
+ return Layer.effect(NativeRuntime, acquire(options))
339
+ }
340
+ }
341
+
342
+ /** Private core/log integration: captures the already acquired shared service. */
343
+ export const runtimeHandle = Effect.fn("NativeRuntime.handle")(function* () {
344
+ const runtime = yield* NativeRuntime
345
+ const handle = owners.get(runtime)
346
+ if (handle === undefined) return yield* Effect.fail(invalid("NativeRuntime.handle"))
347
+ return handle
348
+ })
349
+
350
+ /**
351
+ * Opaque kernel fence (C8). Backed by L14 `log_repository_lock_*` and
352
+ * L12 `mint_repository_lock` (`NativeKind::RepositoryLock`). Native
353
+ * exclusion is the owner — there is no JS occupancy table. `release`
354
+ * is the one close: it joins `logRepositoryLockRelease` then clears
355
+ * `slot.owner` so the Scope finalizer is a no-op. Callers join host
356
+ * I/O first (`joinPendingIo.pipe(Effect.andThen(lock.release))`).
357
+ */
358
+ export interface RepositoryLock {
359
+ readonly directory: string
360
+ readonly release: Effect.Effect<void>
361
+ }
362
+
363
+ function joinLockRelease(operation: string, owner: RepositoryLockHandle): Effect.Effect<void> {
364
+ return drain(operation, (callback) => runtimeNative.logRepositoryLockRelease(owner, callback)).pipe(
365
+ Effect.flatMap((report) => finalizeClose(operation, report))
366
+ )
367
+ }
368
+
369
+ /**
370
+ * Internal log seam: stamped mint only. Cleanup is registered before
371
+ * the interruptible acquire (TS-003): `acquireRelease({ interruptible })`
372
+ * installs the finalizer only after acquire succeeds. Interrupt, defect,
373
+ * and acquire failure all run the same slot finalizer — empty slot is
374
+ * a no-op. No `runtimeDirectoryAcquire`, no core `Db`, no JS bookkeeping.
375
+ */
376
+ export const internalAcquireRepositoryLock = Effect.fn("internalAcquireRepositoryLock")(function* (
377
+ operation: string,
378
+ directory: string,
379
+ work: ExecutionPolicy
380
+ ) {
381
+ if (directory.length === 0) {
382
+ return yield* Effect.fail(invalid(operation))
383
+ }
384
+ const runtime = yield* runtimeHandle()
385
+ const wire = yield* Effect.try({
386
+ try: () => policyWire(work, operation),
387
+ catch: () => invalid(operation)
388
+ })
389
+ const closeOp = `${operation}.repositoryLock`
390
+ return yield* Effect.uninterruptibleMask((restore) =>
391
+ Effect.gen(function* () {
392
+ const slot: { owner: RepositoryLockHandle | undefined } = { owner: undefined }
393
+ const release = Effect.suspend(() => {
394
+ const held = slot.owner
395
+ if (held === undefined) {
396
+ return Effect.void
397
+ }
398
+ return joinLockRelease(closeOp, held).pipe(
399
+ Effect.ensuring(
400
+ Effect.sync(() => {
401
+ slot.owner = undefined
402
+ })
403
+ )
404
+ )
405
+ })
406
+ yield* Effect.addFinalizer(() => release)
407
+ const owner = yield* restore(
408
+ nativeOperationWith(
409
+ operation,
410
+ (callback) => runtimeNative.logRepositoryLockAcquire(runtime, wire, directory, callback),
411
+ runtimeNative.logRepositoryLockTake,
412
+ (value) => value
413
+ )
414
+ )
415
+ slot.owner = owner
416
+ return Object.freeze({
417
+ directory,
418
+ release
419
+ })
420
+ })
421
+ )
422
+ })
423
+
424
+ /** Internal first executor consumer; not a replacement row codec or public hash API. */
425
+ export const hashChunk = Effect.fn("bumbledb.hashChunk")(function* (input: Uint8Array, work: ExecutionPolicy) {
426
+ const runtime = yield* NativeRuntime
427
+ const handle = owners.get(runtime)
428
+ if (handle === undefined) return yield* Effect.fail(invalid("hashChunk"))
429
+ return yield* nativeOperation(
430
+ "hashChunk",
431
+ (callback) => {
432
+ if (!(input instanceof Uint8Array) || !(input.buffer instanceof ArrayBuffer)) throw invalid("hashChunk")
433
+ return runtimeNative.runtimeHash(handle, policyWire(work, "hashChunk"), input, callback)
434
+ },
435
+ (value) => {
436
+ if (value === null) throw dbError("hashChunk", { _tag: "Internal" })
437
+ return value
438
+ }
439
+ )
440
+ })