@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.
- package/COOKBOOK.md +283 -1611
- package/README.md +156 -118
- package/dist/capacity.d.ts +16 -12
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +16 -48
- package/dist/capacity.js.map +1 -1
- package/dist/changes.d.ts +54 -0
- package/dist/changes.d.ts.map +1 -0
- package/dist/changes.js +245 -0
- package/dist/changes.js.map +1 -0
- package/dist/close.d.ts +24 -0
- package/dist/close.d.ts.map +1 -0
- package/dist/close.js +34 -0
- package/dist/close.js.map +1 -0
- package/dist/closed.d.ts +1 -3
- package/dist/closed.d.ts.map +1 -1
- package/dist/closed.js +24 -12
- package/dist/closed.js.map +1 -1
- package/dist/codec.d.ts +56 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +289 -0
- package/dist/codec.js.map +1 -0
- package/dist/compile.d.ts +67 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +75 -0
- package/dist/compile.js.map +1 -0
- package/dist/db-native.d.ts +161 -0
- package/dist/db-native.d.ts.map +1 -0
- package/dist/db-native.js +5 -0
- package/dist/db-native.js.map +1 -0
- package/dist/db.d.ts +101 -233
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +232 -1084
- package/dist/db.js.map +1 -1
- package/dist/errors.d.ts +53 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +28 -0
- package/dist/errors.js.map +1 -0
- package/dist/face.d.ts.map +1 -1
- package/dist/face.js +2 -2
- package/dist/face.js.map +1 -1
- package/dist/fields.d.ts +64 -24
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +94 -24
- package/dist/fields.js.map +1 -1
- package/dist/index.d.ts +37 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -21
- package/dist/index.js.map +1 -1
- package/dist/internal/log.d.ts +24 -0
- package/dist/internal/log.d.ts.map +1 -0
- package/dist/internal/log.js +10 -0
- package/dist/internal/log.js.map +1 -0
- package/dist/law.d.ts +8 -13
- package/dist/law.d.ts.map +1 -1
- package/dist/law.js +6 -59
- package/dist/law.js.map +1 -1
- package/dist/lower.d.ts +0 -13
- package/dist/lower.d.ts.map +1 -1
- package/dist/lower.js +9 -17
- package/dist/lower.js.map +1 -1
- package/dist/migration.d.ts +32 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +47 -0
- package/dist/migration.js.map +1 -0
- package/dist/native.d.ts +256 -301
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +65 -108
- package/dist/native.js.map +1 -1
- package/dist/query/atom.d.ts +24 -13
- package/dist/query/atom.d.ts.map +1 -1
- package/dist/query/atom.js +7 -3
- package/dist/query/atom.js.map +1 -1
- package/dist/query/compute.d.ts +68 -0
- package/dist/query/compute.d.ts.map +1 -0
- package/dist/query/compute.js +141 -0
- package/dist/query/compute.js.map +1 -0
- package/dist/query/find.d.ts +15 -8
- package/dist/query/find.d.ts.map +1 -1
- package/dist/query/find.js +6 -2
- package/dist/query/find.js.map +1 -1
- package/dist/query/lower.d.ts +23 -2
- package/dist/query/lower.d.ts.map +1 -1
- package/dist/query/lower.js +433 -80
- package/dist/query/lower.js.map +1 -1
- package/dist/query/parse-ir.d.ts.map +1 -1
- package/dist/query/parse-ir.js +17 -8
- package/dist/query/parse-ir.js.map +1 -1
- package/dist/query/run.d.ts +12 -2
- package/dist/query/run.d.ts.map +1 -1
- package/dist/query/run.js +26 -9
- package/dist/query/run.js.map +1 -1
- package/dist/query/scope.d.ts +47 -3
- package/dist/query/scope.d.ts.map +1 -1
- package/dist/query/scope.js +83 -19
- package/dist/query/scope.js.map +1 -1
- package/dist/relation.d.ts +3 -8
- package/dist/relation.d.ts.map +1 -1
- package/dist/relation.js +16 -10
- package/dist/relation.js.map +1 -1
- package/dist/result.d.ts +47 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +50 -0
- package/dist/result.js.map +1 -0
- package/dist/rows.d.ts +69 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +325 -0
- package/dist/rows.js.map +1 -0
- package/dist/runtime-codes.d.ts +2 -0
- package/dist/runtime-codes.d.ts.map +1 -0
- package/dist/runtime-codes.js +19 -0
- package/dist/runtime-codes.js.map +1 -0
- package/dist/runtime-errors.d.ts +106 -0
- package/dist/runtime-errors.d.ts.map +1 -0
- package/dist/runtime-errors.js +62 -0
- package/dist/runtime-errors.js.map +1 -0
- package/dist/runtime-native.d.ts +159 -0
- package/dist/runtime-native.d.ts.map +1 -0
- package/dist/runtime-native.js +5 -0
- package/dist/runtime-native.js.map +1 -0
- package/dist/runtime.d.ts +73 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +292 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scalar.d.ts +165 -0
- package/dist/scalar.d.ts.map +1 -0
- package/dist/scalar.js +344 -0
- package/dist/scalar.js.map +1 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +34 -37
- package/dist/schema.js.map +1 -1
- package/dist/shape.d.ts +27 -0
- package/dist/shape.d.ts.map +1 -0
- package/dist/shape.js +2 -0
- package/dist/shape.js.map +1 -0
- package/dist/spec.d.ts +27 -3
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js +22 -1
- package/dist/spec.js.map +1 -1
- package/dist/statements.d.ts +16 -3
- package/dist/statements.d.ts.map +1 -1
- package/dist/statements.js +54 -28
- package/dist/statements.js.map +1 -1
- package/dist/uuid.d.ts +23 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +54 -0
- package/dist/uuid.js.map +1 -0
- package/pack-provenance.json +7 -0
- package/package.json +12 -16
- package/src/capacity.ts +34 -54
- package/src/changes.ts +361 -0
- package/src/close.ts +53 -0
- package/src/closed.ts +25 -19
- package/src/codec.ts +379 -0
- package/src/compile.ts +148 -0
- package/src/db-native.ts +238 -0
- package/src/db.ts +461 -1632
- package/src/errors.ts +46 -0
- package/src/face.ts +2 -2
- package/src/fields.ts +181 -55
- package/src/index.ts +62 -126
- package/src/internal/log.ts +49 -0
- package/src/law.ts +13 -28
- package/src/lower.ts +9 -4
- package/src/migration.ts +68 -0
- package/src/native.ts +326 -487
- package/src/query/atom.ts +61 -25
- package/src/query/compute.ts +237 -0
- package/src/query/find.ts +31 -12
- package/src/query/lower.ts +531 -143
- package/src/query/parse-ir.ts +18 -13
- package/src/query/run.ts +32 -16
- package/src/query/scope.ts +140 -8
- package/src/relation.ts +17 -21
- package/src/result.ts +130 -0
- package/src/rows.ts +401 -0
- package/src/runtime-codes.ts +18 -0
- package/src/runtime-errors.ts +69 -0
- package/src/runtime-native.ts +222 -0
- package/src/runtime.ts +439 -0
- package/src/scalar.ts +560 -0
- package/src/schema.ts +35 -37
- package/src/shape.ts +30 -0
- package/src/spec.ts +33 -3
- package/src/statements.ts +75 -68
- package/src/uuid.ts +69 -0
- package/dist/marshal.d.ts +0 -44
- package/dist/marshal.d.ts.map +0 -1
- package/dist/marshal.js +0 -165
- package/dist/marshal.js.map +0 -1
- 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
|
+
})
|