@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.
- 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 +68 -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 +170 -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 +57 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +32 -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 +65 -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 +33 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +48 -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 +328 -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 +171 -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 +293 -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 +28 -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 +149 -0
- package/src/db-native.ts +247 -0
- package/src/db.ts +461 -1632
- package/src/errors.ts +50 -0
- package/src/face.ts +2 -2
- package/src/fields.ts +182 -55
- package/src/index.ts +62 -126
- package/src/internal/log.ts +51 -0
- package/src/law.ts +13 -28
- package/src/lower.ts +9 -4
- package/src/migration.ts +69 -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 +404 -0
- package/src/runtime-codes.ts +18 -0
- package/src/runtime-errors.ts +69 -0
- package/src/runtime-native.ts +242 -0
- package/src/runtime.ts +440 -0
- package/src/scalar.ts +560 -0
- package/src/schema.ts +35 -37
- package/src/shape.ts +31 -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
|
@@ -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
|
+
})
|