@bjornpagen/bumbledb 0.20.3 → 1.0.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 (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 +67 -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 +161 -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 +53 -0
  36. package/dist/errors.d.ts.map +1 -0
  37. package/dist/errors.js +28 -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 +64 -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 +32 -0
  63. package/dist/migration.d.ts.map +1 -0
  64. package/dist/migration.js +47 -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 +325 -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 +159 -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 +292 -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 +27 -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 +148 -0
  157. package/src/db-native.ts +238 -0
  158. package/src/db.ts +461 -1632
  159. package/src/errors.ts +46 -0
  160. package/src/face.ts +2 -2
  161. package/src/fields.ts +181 -55
  162. package/src/index.ts +62 -126
  163. package/src/internal/log.ts +49 -0
  164. package/src/law.ts +13 -28
  165. package/src/lower.ts +9 -4
  166. package/src/migration.ts +68 -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 +401 -0
  178. package/src/runtime-codes.ts +18 -0
  179. package/src/runtime-errors.ts +69 -0
  180. package/src/runtime-native.ts +222 -0
  181. package/src/runtime.ts +439 -0
  182. package/src/scalar.ts +560 -0
  183. package/src/schema.ts +35 -37
  184. package/src/shape.ts +30 -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
package/src/runtime.ts ADDED
@@ -0,0 +1,439 @@
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) decodes as the core `Internal` reason — the
101
+ // same mapping the log bridge applies, so one wire arm has one meaning.
102
+ // It is deliberately NOT `QueueFull`: a failed drain is never retryable
103
+ // submit backpressure.
104
+ return report.kind === "failed" ? { kind: "failed", error: dbError(operation, { _tag: "Internal" }) } : report
105
+ }
106
+
107
+ function drain(operation: string, start: (callback: (report: CloseWire) => void) => void): Effect.Effect<CloseReport> {
108
+ return Effect.callback<CloseReport>((resume) => {
109
+ try {
110
+ start((report) => resume(Effect.succeed(closeReport(operation, report))))
111
+ } catch (cause) {
112
+ resume(Effect.succeed({ kind: "failed", error: failure(operation, cause) }))
113
+ }
114
+ }).pipe(Effect.uninterruptible)
115
+ }
116
+
117
+ export function finalizeClose(operation: string, report: CloseReport): Effect.Effect<void> {
118
+ return report.kind === "closed" ? Effect.void : Effect.die(new CloseFailure({ operation, report }))
119
+ }
120
+
121
+ /**
122
+ * Effect.callback's returned finalizer is interruption cleanup only
123
+ * (`asyncFinalizer` runs solely on interrupt Cause). If that finalizer
124
+ * dies, the original interrupt is dropped. Join the native drain here and
125
+ * always succeed so the interrupt Cause is kept; attach CloseFailure via
126
+ * `onExit` so both appear in the final Cause.
127
+ */
128
+ function joinInterruptDrain(
129
+ operation: string,
130
+ start: (callback: (report: CloseWire) => void) => void,
131
+ stash: { report?: CloseReport }
132
+ ): Effect.Effect<void> {
133
+ return drain(operation, start).pipe(
134
+ Effect.tap((report) => {
135
+ stash.report = report
136
+ return Effect.void
137
+ }),
138
+ Effect.asVoid
139
+ )
140
+ }
141
+
142
+ function attachInterruptClose(
143
+ operation: string,
144
+ stash: { report?: CloseReport }
145
+ ): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> {
146
+ return (effect) =>
147
+ effect.pipe(
148
+ Effect.onExit((exit) => {
149
+ if (!Exit.hasInterrupts(exit)) {
150
+ return Effect.void
151
+ }
152
+ const report = stash.report
153
+ if (report === undefined || report.kind === "closed") {
154
+ return Effect.void
155
+ }
156
+ return Effect.die(new CloseFailure({ operation, report }))
157
+ })
158
+ )
159
+ }
160
+
161
+ function afterClose<A, E, R>(
162
+ operation: string,
163
+ report: CloseReport,
164
+ effect: Effect.Effect<A, E, R>
165
+ ): Effect.Effect<A, E, R> {
166
+ if (report.kind === "closed") {
167
+ return effect
168
+ }
169
+ return effect.pipe(Effect.onExit(() => Effect.die(new CloseFailure({ operation, report }))))
170
+ }
171
+
172
+ /** Independent caps intersect: a delivery request cannot enlarge `work.resultBytes`. */
173
+ export function deliveryResultBytes(requested: bigint, work: ExecutionPolicy): bigint {
174
+ if (typeof requested !== "bigint" || requested < 0n) {
175
+ throw invalid("deliveryResultBytes")
176
+ }
177
+ return requested < work.resultBytes ? requested : work.resultBytes
178
+ }
179
+
180
+ function close(handle: RuntimeHandle): Effect.Effect<CloseReport> {
181
+ return drain("NativeRuntime.close", (callback) => runtimeNative.runtimeClose(handle, callback))
182
+ }
183
+
184
+ /** Kick `runtimeCancel` without joining. Used when the completion
185
+ * callback arrives after abort: JS must not `take`; L12 drain reclaims
186
+ * queued Page/Rows. A second call is native-idempotent (Committed
187
+ * mutations stay; only unpublished delivery is dropped).
188
+ */
189
+ function startCancelDrain(operation: string, lease: OperationHandle, stash: { report?: CloseReport }): void {
190
+ try {
191
+ runtimeNative.runtimeCancel(lease, (report) => {
192
+ stash.report = closeReport(operation, report)
193
+ })
194
+ } catch (cause) {
195
+ stash.report = { kind: "failed", error: failure(operation, cause) }
196
+ }
197
+ }
198
+
199
+ /** Registration returns the native lease before any completion can run in JS.
200
+ * Interruption cancels and joins that lease, including a late successful result.
201
+ * No Promise or libuv job is created for ordinary native work.
202
+ */
203
+ export function nativeOperation<A>(
204
+ operation: string,
205
+ start: (callback: () => void) => OperationHandle,
206
+ accept: (value: Uint8Array | null) => A
207
+ ): Effect.Effect<A, DbError> {
208
+ return nativeOperationWith(operation, start, runtimeNative.runtimeTake, accept)
209
+ }
210
+
211
+ export function nativeOperationWith<A, Value>(
212
+ operation: string,
213
+ start: (callback: () => void) => OperationHandle,
214
+ take: (operation: OperationHandle) => Value,
215
+ accept: (value: Value) => A
216
+ ): Effect.Effect<A, DbError> {
217
+ const stash: { report?: CloseReport } = {}
218
+ const cancelOp = `${operation}.cancel`
219
+ return attachInterruptClose(
220
+ cancelOp,
221
+ stash
222
+ )(
223
+ Effect.callback((resume, signal) => {
224
+ let lease: OperationHandle
225
+ try {
226
+ lease = start(() => {
227
+ if (signal.aborted) {
228
+ startCancelDrain(cancelOp, lease, stash)
229
+ return
230
+ }
231
+ try {
232
+ resume(Effect.succeed(accept(take(lease))))
233
+ } catch (cause) {
234
+ resume(Effect.fail(failure(operation, cause)))
235
+ }
236
+ })
237
+ } catch (cause) {
238
+ resume(Effect.fail(failure(operation, cause)))
239
+ return
240
+ }
241
+ return joinInterruptDrain(cancelOp, (callback) => runtimeNative.runtimeCancel(lease, callback), stash)
242
+ })
243
+ )
244
+ }
245
+
246
+ const acquire = Effect.fn("NativeRuntime.acquire")(function* (configuration: NativeRuntimeOptions) {
247
+ return yield* Effect.acquireRelease(
248
+ Effect.suspend(() => {
249
+ const stash: { report?: CloseReport } = {}
250
+ return attachInterruptClose(
251
+ "NativeRuntime.acquire.cancel",
252
+ stash
253
+ )(
254
+ Effect.callback<RuntimeService, DbError>((resume, signal) => {
255
+ let handle: RuntimeHandle | undefined
256
+ try {
257
+ const wire = options(configuration)
258
+ handle = runtimeNative.runtimeOpen(wire)
259
+ const owner = handle
260
+ const lease = runtimeNative.runtimeReady(
261
+ owner,
262
+ {
263
+ inputBytes: 0n,
264
+ workingBytes: 0n,
265
+ scratchBytes: 0n,
266
+ resultBytes: 0n,
267
+ rows: 0n,
268
+ workUnits: 1n,
269
+ timeoutMs: wire.cleanupTimeoutMs
270
+ },
271
+ () => {
272
+ if (signal.aborted) {
273
+ try {
274
+ runtimeNative.runtimeClose(owner, (report) => {
275
+ stash.report = closeReport("NativeRuntime.acquire.cancel", report)
276
+ })
277
+ } catch (cause) {
278
+ stash.report = { kind: "failed", error: failure("NativeRuntime.acquire.cancel", cause) }
279
+ }
280
+ return
281
+ }
282
+ try {
283
+ runtimeNative.runtimeTake(lease)
284
+ const service: RuntimeService = {
285
+ close: () => close(owner),
286
+ inspect: Effect.fn("NativeRuntime.inspect")(function* (work) {
287
+ yield* nativeOperation(
288
+ "NativeRuntime.inspect",
289
+ (callback) =>
290
+ runtimeNative.runtimeReady(owner, policyWire(work, "NativeRuntime.inspect"), callback),
291
+ () => undefined
292
+ )
293
+ return runtimeNative.runtimeInspect(owner)
294
+ })
295
+ }
296
+ owners.set(service, owner)
297
+ resume(Effect.succeed(service))
298
+ } catch (cause) {
299
+ const error = failure("NativeRuntime.acquire", cause)
300
+ resume(
301
+ close(owner).pipe(
302
+ Effect.flatMap((report) => afterClose("NativeRuntime.acquire", report, Effect.fail(error)))
303
+ )
304
+ )
305
+ }
306
+ }
307
+ )
308
+ } catch (cause) {
309
+ const error = failure("NativeRuntime.acquire", cause)
310
+ resume(
311
+ handle === undefined
312
+ ? Effect.fail(error)
313
+ : close(handle).pipe(
314
+ Effect.flatMap((report) => afterClose("NativeRuntime.acquire", report, Effect.fail(error)))
315
+ )
316
+ )
317
+ }
318
+ return handle === undefined
319
+ ? Effect.void
320
+ : joinInterruptDrain(
321
+ "NativeRuntime.acquire.cancel",
322
+ (callback) => runtimeNative.runtimeClose(handle, callback),
323
+ stash
324
+ )
325
+ })
326
+ )
327
+ }),
328
+ (service) => service.close().pipe(Effect.flatMap((report) => finalizeClose("NativeRuntime.release", report))),
329
+ { interruptible: true }
330
+ )
331
+ })
332
+
333
+ export class NativeRuntime extends Context.Service<NativeRuntime, RuntimeService>()(
334
+ "@bjornpagen/bumbledb/NativeRuntime"
335
+ ) {
336
+ static layer(options: NativeRuntimeOptions): Layer.Layer<NativeRuntime, DbError> {
337
+ return Layer.effect(NativeRuntime, acquire(options))
338
+ }
339
+ }
340
+
341
+ /** Private core/log integration: captures the already acquired shared service. */
342
+ export const runtimeHandle = Effect.fn("NativeRuntime.handle")(function* () {
343
+ const runtime = yield* NativeRuntime
344
+ const handle = owners.get(runtime)
345
+ if (handle === undefined) return yield* Effect.fail(invalid("NativeRuntime.handle"))
346
+ return handle
347
+ })
348
+
349
+ /**
350
+ * Opaque kernel fence (C8). Backed by L14 `log_repository_lock_*` and
351
+ * L12 `mint_repository_lock` (`NativeKind::RepositoryLock`). Native
352
+ * exclusion is the owner — there is no JS occupancy table. `release`
353
+ * is the one close: it joins `logRepositoryLockRelease` then clears
354
+ * `slot.owner` so the Scope finalizer is a no-op. Callers join host
355
+ * I/O first (`joinPendingIo.pipe(Effect.andThen(lock.release))`).
356
+ */
357
+ export interface RepositoryLock {
358
+ readonly directory: string
359
+ readonly release: Effect.Effect<void>
360
+ }
361
+
362
+ function joinLockRelease(operation: string, owner: RepositoryLockHandle): Effect.Effect<void> {
363
+ return drain(operation, (callback) => runtimeNative.logRepositoryLockRelease(owner, callback)).pipe(
364
+ Effect.flatMap((report) => finalizeClose(operation, report))
365
+ )
366
+ }
367
+
368
+ /**
369
+ * Internal log seam: stamped mint only. Cleanup is registered before
370
+ * the interruptible acquire: `acquireRelease({ interruptible })`
371
+ * installs the finalizer only after acquire succeeds. Interrupt, defect,
372
+ * and acquire failure all run the same slot finalizer — empty slot is
373
+ * a no-op. No `runtimeDirectoryAcquire`, no core `Db`, no JS bookkeeping.
374
+ */
375
+ export const internalAcquireRepositoryLock = Effect.fn("internalAcquireRepositoryLock")(function* (
376
+ operation: string,
377
+ directory: string,
378
+ work: ExecutionPolicy
379
+ ) {
380
+ if (directory.length === 0) {
381
+ return yield* Effect.fail(invalid(operation))
382
+ }
383
+ const runtime = yield* runtimeHandle()
384
+ const wire = yield* Effect.try({
385
+ try: () => policyWire(work, operation),
386
+ catch: () => invalid(operation)
387
+ })
388
+ const closeOp = `${operation}.repositoryLock`
389
+ return yield* Effect.uninterruptibleMask((restore) =>
390
+ Effect.gen(function* () {
391
+ const slot: { owner: RepositoryLockHandle | undefined } = { owner: undefined }
392
+ const release = Effect.suspend(() => {
393
+ const held = slot.owner
394
+ if (held === undefined) {
395
+ return Effect.void
396
+ }
397
+ return joinLockRelease(closeOp, held).pipe(
398
+ Effect.ensuring(
399
+ Effect.sync(() => {
400
+ slot.owner = undefined
401
+ })
402
+ )
403
+ )
404
+ })
405
+ yield* Effect.addFinalizer(() => release)
406
+ const owner = yield* restore(
407
+ nativeOperationWith(
408
+ operation,
409
+ (callback) => runtimeNative.logRepositoryLockAcquire(runtime, wire, directory, callback),
410
+ runtimeNative.logRepositoryLockTake,
411
+ (value) => value
412
+ )
413
+ )
414
+ slot.owner = owner
415
+ return Object.freeze({
416
+ directory,
417
+ release
418
+ })
419
+ })
420
+ )
421
+ })
422
+
423
+ /** Internal first executor consumer; not a replacement row codec or public hash API. */
424
+ export const hashChunk = Effect.fn("bumbledb.hashChunk")(function* (input: Uint8Array, work: ExecutionPolicy) {
425
+ const runtime = yield* NativeRuntime
426
+ const handle = owners.get(runtime)
427
+ if (handle === undefined) return yield* Effect.fail(invalid("hashChunk"))
428
+ return yield* nativeOperation(
429
+ "hashChunk",
430
+ (callback) => {
431
+ if (!(input instanceof Uint8Array) || !(input.buffer instanceof ArrayBuffer)) throw invalid("hashChunk")
432
+ return runtimeNative.runtimeHash(handle, policyWire(work, "hashChunk"), input, callback)
433
+ },
434
+ (value) => {
435
+ if (value === null) throw dbError("hashChunk", { _tag: "Internal" })
436
+ return value
437
+ }
438
+ )
439
+ })