space-data-module-sdk 0.8.20 → 0.8.22
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/docs/isomorphic-pthreads.html +100 -0
- package/docs/isomorphic-pthreads.md +335 -0
- package/package.json +7 -2
- package/src/browser.js +6 -0
- package/src/compiler/invokeGlue.js +38 -0
- package/src/flow/flatsqlLinkShim.js +295 -0
- package/src/flow/index.d.ts +31 -0
- package/src/flow/index.js +8 -0
- package/src/host/browserCapabilityProbe.js +251 -0
- package/src/host/browserModuleHarness.js +24 -5
- package/src/host/flatsqlIo.js +14 -0
- package/src/host/flatsqlIoConformance.js +349 -0
- package/src/host/flatsqlIoContract.js +240 -0
- package/src/host/flatsqlIoImports.js +335 -0
- package/src/host/flatsqlIoMemoryBackend.js +178 -0
- package/src/host/flatsqlIoServer.js +960 -0
- package/src/host/flatsqlIoWorkers.js +323 -0
- package/src/host/hostWorkerBundleSources.js +11 -0
- package/src/host/hostWorkerBundles.js +79 -0
- package/src/host/index.js +9 -0
- package/src/host/isomorphicLoader.js +10 -2
- package/src/host/nodeSyncFsIo.js +633 -0
- package/src/host/opfsIoBackend.js +280 -0
- package/src/host/opfsIoWorker.mjs +223 -0
- package/src/host/sabIoChannel.js +848 -0
- package/src/host/sabIoMirror.js +306 -0
- package/src/host/wasiThreadBrowserWorker.mjs +21 -11
- package/src/host/wasiThreadHost.js +261 -43
- package/src/host/wasiThreadWorker.mjs +46 -8
- package/src/host/wasiThreadWorkerRuntime.js +54 -0
- package/src/index.d.ts +349 -0
- package/src/runtime/constants.js +6 -0
- package/src/testing/native/wasmedge_wasi_threads_runner.c +13 -5
- package/src/testing/parityHarness.js +7 -1
- package/src/testing/parityLanes.js +19 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
// The seven `env.flatsql_io_*` imports, built over a synchronous I/O provider.
|
|
2
|
+
//
|
|
3
|
+
// This is the guest-facing edge of every SDK FlatSQL host: the SAB I/O channel
|
|
4
|
+
// client (browser pool workers and the engine worker, sabIoChannel.js) and the
|
|
5
|
+
// Node sync-fs provider (nodeSyncFsIo.js) both plug in here. The wasm-side
|
|
6
|
+
// contract is flatsql cpp/include/flatsql/flatsql_io.h: i32/f64 only, errors as
|
|
7
|
+
// negative returns, never a throw (a throw out of an import is a trap, and a
|
|
8
|
+
// trap poisons the instance).
|
|
9
|
+
//
|
|
10
|
+
// A provider has the synchronous methods
|
|
11
|
+
// open(pathBytes: Uint8Array, flags) -> handle | status
|
|
12
|
+
// read(handle, ptr, len, offset) -> bytes | status (ptr in the guest memory)
|
|
13
|
+
// write(handle, ptr, len, offset) -> bytes | status
|
|
14
|
+
// truncate(handle, size) / sync(handle) / close(handle) -> status
|
|
15
|
+
// size(handle) -> f64 | status
|
|
16
|
+
//
|
|
17
|
+
// Several providers (sharded reader I/O workers, A7) are routed by a hash of the
|
|
18
|
+
// path at open; the provider index rides in bits 24..30 of the returned handle.
|
|
19
|
+
//
|
|
20
|
+
// Built-in `extraImports` descriptors (structured-cloneable, so they reach pool
|
|
21
|
+
// workers, including the blob-bundled ones of A39):
|
|
22
|
+
// { provider: "flatsql-io", instanceId, channels: [SharedArrayBuffer, ...],
|
|
23
|
+
// mirror?: { buffer, suffixes }, trace?: { buffer } }
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
FLATSQL_IO_ERR_BADHANDLE,
|
|
27
|
+
FLATSQL_IO_ERR_GENERIC,
|
|
28
|
+
FLATSQL_IO_ERR_IO,
|
|
29
|
+
FLATSQL_IO_MAX_PATH_BYTES,
|
|
30
|
+
hashFlatsqlIoPath,
|
|
31
|
+
} from "./flatsqlIoContract.js";
|
|
32
|
+
import { createSabIoClient } from "./sabIoChannel.js";
|
|
33
|
+
import {
|
|
34
|
+
createSabIoMirrorMatcher,
|
|
35
|
+
findSabIoMirrorEntry,
|
|
36
|
+
readSabIoMirrorEntry,
|
|
37
|
+
} from "./sabIoMirror.js";
|
|
38
|
+
|
|
39
|
+
export const FLATSQL_IO_PROVIDER_DESCRIPTOR = "flatsql-io";
|
|
40
|
+
|
|
41
|
+
/** Trace op codes recorded per import call. */
|
|
42
|
+
export const FLATSQL_IO_TRACE_OPS = Object.freeze({
|
|
43
|
+
open: 1,
|
|
44
|
+
read: 2,
|
|
45
|
+
write: 3,
|
|
46
|
+
truncate: 4,
|
|
47
|
+
sync: 5,
|
|
48
|
+
size: 6,
|
|
49
|
+
close: 7,
|
|
50
|
+
mirrorRead: 8,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const ROUTE_SHIFT = 24;
|
|
54
|
+
const LOCAL_HANDLE_MASK = 0xffffff;
|
|
55
|
+
|
|
56
|
+
function nowMicros() {
|
|
57
|
+
return typeof performance !== "undefined" && performance.now
|
|
58
|
+
? performance.now() * 1000
|
|
59
|
+
: Date.now() * 1000;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A shared latency trace: every import call appends (op, microseconds) when a
|
|
64
|
+
* trace is configured. Diagnostic; the browser acceptance suite reads it to
|
|
65
|
+
* compute guest-observed I/O percentiles.
|
|
66
|
+
*/
|
|
67
|
+
export function createFlatsqlIoTraceBuffer(capacity = 65536) {
|
|
68
|
+
const buffer = new SharedArrayBuffer(16 + capacity * 8 + capacity);
|
|
69
|
+
const header = new Int32Array(buffer, 0, 4);
|
|
70
|
+
header[1] = capacity;
|
|
71
|
+
return buffer;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function readFlatsqlIoTrace(buffer) {
|
|
75
|
+
const header = new Int32Array(buffer, 0, 4);
|
|
76
|
+
const capacity = header[1];
|
|
77
|
+
const count = Math.min(Atomics.load(header, 0), capacity);
|
|
78
|
+
const micros = new Float64Array(buffer, 16, capacity);
|
|
79
|
+
const ops = new Uint8Array(buffer, 16 + capacity * 8, capacity);
|
|
80
|
+
const out = [];
|
|
81
|
+
for (let i = 0; i < count; i += 1) {
|
|
82
|
+
out.push({ op: ops[i], micros: micros[i] });
|
|
83
|
+
}
|
|
84
|
+
return { recorded: Atomics.load(header, 0), capacity, samples: out };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createTracer(trace) {
|
|
88
|
+
if (!trace?.buffer) return null;
|
|
89
|
+
const header = new Int32Array(trace.buffer, 0, 4);
|
|
90
|
+
const capacity = header[1];
|
|
91
|
+
const micros = new Float64Array(trace.buffer, 16, capacity);
|
|
92
|
+
const ops = new Uint8Array(trace.buffer, 16 + capacity * 8, capacity);
|
|
93
|
+
return (op, started) => {
|
|
94
|
+
const index = Atomics.add(header, 0, 1);
|
|
95
|
+
if (index < capacity) {
|
|
96
|
+
micros[index] = nowMicros() - started;
|
|
97
|
+
ops[index] = op;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function copyPath(memory, ptr, len) {
|
|
103
|
+
if (!memory || len <= 0 || len > FLATSQL_IO_MAX_PATH_BYTES || ptr < 0) return null;
|
|
104
|
+
const buffer = memory.buffer ?? memory;
|
|
105
|
+
if (ptr + len > buffer.byteLength) return null;
|
|
106
|
+
// slice() copies out of the (possibly shared) memory before anyone decodes.
|
|
107
|
+
return new Uint8Array(buffer, ptr, len).slice();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Build the import object fragment `{ env: { flatsql_io_* } }`.
|
|
112
|
+
*
|
|
113
|
+
* @param {object} options
|
|
114
|
+
* @param {() => WebAssembly.Memory} options.getMemory the instance's memory.
|
|
115
|
+
* Re-read on every call: a grown memory has a new buffer.
|
|
116
|
+
* @param {object|object[]} options.provider one provider, or an array routed
|
|
117
|
+
* by path hash.
|
|
118
|
+
* @param {{ buffer: SharedArrayBuffer, suffixes?: string[] }} [options.mirror]
|
|
119
|
+
* serve reads of matching paths (heads) from the SAB mirror (A7).
|
|
120
|
+
* @param {{ buffer: SharedArrayBuffer }} [options.trace] latency trace.
|
|
121
|
+
*/
|
|
122
|
+
export function createFlatsqlIoImports(options = {}) {
|
|
123
|
+
const getMemory = options.getMemory;
|
|
124
|
+
if (typeof getMemory !== "function") {
|
|
125
|
+
throw new TypeError("createFlatsqlIoImports requires getMemory().");
|
|
126
|
+
}
|
|
127
|
+
const providers = Array.isArray(options.provider) ? options.provider : [options.provider];
|
|
128
|
+
if (providers.length === 0 || providers.some((p) => !p || typeof p.open !== "function")) {
|
|
129
|
+
throw new TypeError("createFlatsqlIoImports requires at least one provider.");
|
|
130
|
+
}
|
|
131
|
+
if (providers.length > 127) {
|
|
132
|
+
throw new RangeError("At most 127 providers can be routed.");
|
|
133
|
+
}
|
|
134
|
+
const routed = providers.length > 1;
|
|
135
|
+
const trace = createTracer(options.trace);
|
|
136
|
+
const mirrorBuffer = options.mirror?.buffer ?? null;
|
|
137
|
+
const mirrorMatch = mirrorBuffer
|
|
138
|
+
? createSabIoMirrorMatcher(options.mirror.suffixes ?? ["/h.fsh"])
|
|
139
|
+
: null;
|
|
140
|
+
const mirroredHandles = new Map();
|
|
141
|
+
const decoder = new TextDecoder();
|
|
142
|
+
|
|
143
|
+
function route(handle) {
|
|
144
|
+
if (!routed) return { provider: providers[0], local: handle };
|
|
145
|
+
const index = handle >>> ROUTE_SHIFT;
|
|
146
|
+
const provider = providers[index];
|
|
147
|
+
return provider ? { provider, local: handle & LOCAL_HANDLE_MASK } : null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function guard(op, fallback, fn) {
|
|
151
|
+
const started = trace ? nowMicros() : 0;
|
|
152
|
+
let result;
|
|
153
|
+
try {
|
|
154
|
+
result = fn();
|
|
155
|
+
} catch {
|
|
156
|
+
result = fallback;
|
|
157
|
+
}
|
|
158
|
+
if (trace) trace(op, started);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const env = {
|
|
163
|
+
flatsql_io_open(pathPtr, pathLen, flags) {
|
|
164
|
+
return guard(FLATSQL_IO_TRACE_OPS.open, FLATSQL_IO_ERR_GENERIC, () => {
|
|
165
|
+
const bytes = copyPath(getMemory(), pathPtr, pathLen);
|
|
166
|
+
if (!bytes) return FLATSQL_IO_ERR_GENERIC;
|
|
167
|
+
const index = routed ? hashFlatsqlIoPath(bytes) % providers.length : 0;
|
|
168
|
+
const handle = providers[index].open(bytes, flags | 0);
|
|
169
|
+
if (handle < 0) return handle;
|
|
170
|
+
if (routed && handle > LOCAL_HANDLE_MASK) return FLATSQL_IO_ERR_IO;
|
|
171
|
+
const exposed = routed ? (index << ROUTE_SHIFT) | handle : handle;
|
|
172
|
+
if (mirrorMatch) {
|
|
173
|
+
const path = decoder.decode(bytes);
|
|
174
|
+
if (mirrorMatch(path)) mirroredHandles.set(exposed, path);
|
|
175
|
+
}
|
|
176
|
+
return exposed;
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
flatsql_io_read(handle, dstPtr, len, offset) {
|
|
180
|
+
if (mirrorBuffer && mirroredHandles.has(handle)) {
|
|
181
|
+
const started = trace ? nowMicros() : 0;
|
|
182
|
+
try {
|
|
183
|
+
const memory = getMemory();
|
|
184
|
+
const buffer = memory.buffer ?? memory;
|
|
185
|
+
if (dstPtr >= 0 && len >= 0 && dstPtr + len <= buffer.byteLength) {
|
|
186
|
+
const index = findSabIoMirrorEntry(mirrorBuffer, mirroredHandles.get(handle));
|
|
187
|
+
if (index >= 0) {
|
|
188
|
+
const n = readSabIoMirrorEntry(
|
|
189
|
+
mirrorBuffer,
|
|
190
|
+
index,
|
|
191
|
+
new Uint8Array(buffer, dstPtr, len),
|
|
192
|
+
offset,
|
|
193
|
+
);
|
|
194
|
+
if (n >= 0) {
|
|
195
|
+
if (trace) trace(FLATSQL_IO_TRACE_OPS.mirrorRead, started);
|
|
196
|
+
return n;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// fall through to the file
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return guard(FLATSQL_IO_TRACE_OPS.read, FLATSQL_IO_ERR_IO, () => {
|
|
205
|
+
const target = route(handle);
|
|
206
|
+
if (!target) return FLATSQL_IO_ERR_BADHANDLE;
|
|
207
|
+
return target.provider.read(target.local, dstPtr, len, offset);
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
flatsql_io_write(handle, srcPtr, len, offset) {
|
|
211
|
+
return guard(FLATSQL_IO_TRACE_OPS.write, FLATSQL_IO_ERR_IO, () => {
|
|
212
|
+
const target = route(handle);
|
|
213
|
+
if (!target) return FLATSQL_IO_ERR_BADHANDLE;
|
|
214
|
+
return target.provider.write(target.local, srcPtr, len, offset);
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
flatsql_io_truncate(handle, size) {
|
|
218
|
+
return guard(FLATSQL_IO_TRACE_OPS.truncate, FLATSQL_IO_ERR_IO, () => {
|
|
219
|
+
const target = route(handle);
|
|
220
|
+
if (!target) return FLATSQL_IO_ERR_BADHANDLE;
|
|
221
|
+
return target.provider.truncate(target.local, size);
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
flatsql_io_sync(handle) {
|
|
225
|
+
return guard(FLATSQL_IO_TRACE_OPS.sync, FLATSQL_IO_ERR_IO, () => {
|
|
226
|
+
const target = route(handle);
|
|
227
|
+
if (!target) return FLATSQL_IO_ERR_BADHANDLE;
|
|
228
|
+
return target.provider.sync(target.local);
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
flatsql_io_size(handle) {
|
|
232
|
+
return guard(FLATSQL_IO_TRACE_OPS.size, FLATSQL_IO_ERR_IO, () => {
|
|
233
|
+
const target = route(handle);
|
|
234
|
+
if (!target) return FLATSQL_IO_ERR_BADHANDLE;
|
|
235
|
+
return target.provider.size(target.local);
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
flatsql_io_close(handle) {
|
|
239
|
+
return guard(FLATSQL_IO_TRACE_OPS.close, FLATSQL_IO_ERR_IO, () => {
|
|
240
|
+
mirroredHandles.delete(handle);
|
|
241
|
+
const target = route(handle);
|
|
242
|
+
if (!target) return FLATSQL_IO_ERR_BADHANDLE;
|
|
243
|
+
return target.provider.close(target.local);
|
|
244
|
+
});
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
return { env };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Resolve a built-in `{ provider: "flatsql-io" }` descriptor into imports for
|
|
252
|
+
* one thread: one blocking channel client (its own slot) per channel.
|
|
253
|
+
*
|
|
254
|
+
* @returns {{ imports: object, close: () => void }}
|
|
255
|
+
*/
|
|
256
|
+
export function resolveFlatsqlIoDescriptor(descriptor, { getMemory }) {
|
|
257
|
+
const channels = Array.isArray(descriptor.channels)
|
|
258
|
+
? descriptor.channels
|
|
259
|
+
: [descriptor.channel ?? descriptor.buffer];
|
|
260
|
+
if (channels.length === 0 || channels.some((c) => !(c instanceof SharedArrayBuffer))) {
|
|
261
|
+
throw new TypeError("A flatsql-io descriptor needs channels: SharedArrayBuffer[].");
|
|
262
|
+
}
|
|
263
|
+
const clients = channels.map((buffer) =>
|
|
264
|
+
createSabIoClient({ buffer, instanceId: descriptor.instanceId, getMemory }),
|
|
265
|
+
);
|
|
266
|
+
const imports = createFlatsqlIoImports({
|
|
267
|
+
getMemory,
|
|
268
|
+
provider: clients,
|
|
269
|
+
mirror: descriptor.mirror,
|
|
270
|
+
trace: descriptor.trace,
|
|
271
|
+
});
|
|
272
|
+
return {
|
|
273
|
+
imports,
|
|
274
|
+
close() {
|
|
275
|
+
for (const client of clients) client.release();
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Resolve `extraImports` entries for one thread. Each entry is a function
|
|
282
|
+
* `(ctx) => importObject | { imports, close }`, or a built-in descriptor.
|
|
283
|
+
* Returns the merged import fragments and a close() for their resources.
|
|
284
|
+
*
|
|
285
|
+
* @param {Array<Function|object>} extraImports
|
|
286
|
+
* @param {{ memory: WebAssembly.Memory, getMemory: Function, tid?: number }} ctx
|
|
287
|
+
*/
|
|
288
|
+
export function resolveExtraImports(extraImports, ctx) {
|
|
289
|
+
const fragments = [];
|
|
290
|
+
const closers = [];
|
|
291
|
+
for (const entry of extraImports ?? []) {
|
|
292
|
+
let resolved;
|
|
293
|
+
if (typeof entry === "function") {
|
|
294
|
+
resolved = entry(ctx);
|
|
295
|
+
} else if (entry && entry.provider === FLATSQL_IO_PROVIDER_DESCRIPTOR) {
|
|
296
|
+
resolved = resolveFlatsqlIoDescriptor(entry, ctx);
|
|
297
|
+
} else if (entry && typeof entry.factory === "function") {
|
|
298
|
+
resolved = entry.factory({ ...ctx, config: entry.config });
|
|
299
|
+
} else {
|
|
300
|
+
throw new TypeError(
|
|
301
|
+
`Unresolvable extraImports entry ${JSON.stringify(entry?.provider ?? entry?.moduleUrl ?? typeof entry)}; ` +
|
|
302
|
+
"pass a factory function, a { provider: \"flatsql-io\" } descriptor, or (module workers) " +
|
|
303
|
+
"a { moduleUrl } descriptor.",
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
if (resolved && resolved.imports && typeof resolved.close === "function") {
|
|
307
|
+
fragments.push(resolved.imports);
|
|
308
|
+
closers.push(resolved.close);
|
|
309
|
+
} else if (resolved) {
|
|
310
|
+
fragments.push(resolved);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
fragments,
|
|
315
|
+
close() {
|
|
316
|
+
for (const close of closers) {
|
|
317
|
+
try {
|
|
318
|
+
close();
|
|
319
|
+
} catch {
|
|
320
|
+
// best effort
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Merge import fragments into `imports`, module by module. */
|
|
328
|
+
export function mergeImportFragments(imports, fragments) {
|
|
329
|
+
for (const fragment of fragments) {
|
|
330
|
+
for (const [moduleName, values] of Object.entries(fragment ?? {})) {
|
|
331
|
+
imports[moduleName] = { ...(imports[moduleName] ?? {}), ...values };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return imports;
|
|
335
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// In-memory backend for the FlatSQL I/O server: the dashboard window store
|
|
2
|
+
// (docs/architecture/flatsql-partition-store.md §5.5 "Window store", 22.3a-8).
|
|
3
|
+
//
|
|
4
|
+
// The dashboard's ephemeral window store runs the same artifact over this
|
|
5
|
+
// backend inside its I/O worker. It writes nothing to OPFS and is dropped on
|
|
6
|
+
// every UI-state change; there is never a full local mirror. It is also the
|
|
7
|
+
// backend the Node channel tests serve.
|
|
8
|
+
//
|
|
9
|
+
// Files follow FileSystemSyncAccessHandle's synchronous shape (read/write with
|
|
10
|
+
// {at}, truncate, flush, getSize, close), so the server treats this backend and
|
|
11
|
+
// OPFS identically. Directories are explicit: creating a file under a missing
|
|
12
|
+
// directory is NOENT unless CREATE_PARENTS is set, as on every other host.
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
FLATSQL_IO_CREATE,
|
|
16
|
+
FLATSQL_IO_CREATE_PARENTS,
|
|
17
|
+
FLATSQL_IO_ERR_ACCESS,
|
|
18
|
+
FLATSQL_IO_ERR_GENERIC,
|
|
19
|
+
FLATSQL_IO_ERR_NOENT,
|
|
20
|
+
FLATSQL_IO_ERR_NOSPACE,
|
|
21
|
+
FLATSQL_IO_EXCL,
|
|
22
|
+
FLATSQL_IO_TRUNC,
|
|
23
|
+
FlatsqlIoStatusError,
|
|
24
|
+
} from "./flatsqlIoContract.js";
|
|
25
|
+
|
|
26
|
+
class MemoryFile {
|
|
27
|
+
constructor() {
|
|
28
|
+
this.bytes = new Uint8Array(0);
|
|
29
|
+
this.length = 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ensureCapacity(size, budget) {
|
|
33
|
+
if (size <= this.bytes.length) return;
|
|
34
|
+
let capacity = Math.max(64, this.bytes.length);
|
|
35
|
+
while (capacity < size) capacity *= 2;
|
|
36
|
+
budget.reserve(capacity - this.bytes.length);
|
|
37
|
+
const next = new Uint8Array(capacity);
|
|
38
|
+
next.set(this.bytes.subarray(0, this.length));
|
|
39
|
+
this.bytes = next;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {object} [options]
|
|
45
|
+
* @param {number} [options.maxBytes=Infinity] total capacity budget; a write
|
|
46
|
+
* past it fails with NOSPACE (the per-tab budget row, 22.3a-8).
|
|
47
|
+
*/
|
|
48
|
+
export function createFlatsqlIoMemoryBackend(options = {}) {
|
|
49
|
+
const maxBytes = Number.isFinite(options.maxBytes) ? options.maxBytes : Infinity;
|
|
50
|
+
const files = new Map();
|
|
51
|
+
const directories = new Set([""]);
|
|
52
|
+
let allocated = 0;
|
|
53
|
+
|
|
54
|
+
const budget = {
|
|
55
|
+
reserve(bytes) {
|
|
56
|
+
if (allocated + bytes > maxBytes) {
|
|
57
|
+
throw new FlatsqlIoStatusError(FLATSQL_IO_ERR_NOSPACE, "memory store budget exhausted");
|
|
58
|
+
}
|
|
59
|
+
allocated += bytes;
|
|
60
|
+
},
|
|
61
|
+
release(bytes) {
|
|
62
|
+
allocated = Math.max(0, allocated - bytes);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function parentOf(components) {
|
|
67
|
+
return components.slice(0, -1).join("/");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function handleFor(key) {
|
|
71
|
+
let closed = false;
|
|
72
|
+
const live = () => {
|
|
73
|
+
if (closed) {
|
|
74
|
+
const error = new Error("handle closed");
|
|
75
|
+
error.name = "InvalidStateError";
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
const file = files.get(key);
|
|
79
|
+
if (!file) {
|
|
80
|
+
const error = new Error("file removed");
|
|
81
|
+
error.name = "NotFoundError";
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
return file;
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
read(view, { at = 0 } = {}) {
|
|
88
|
+
const file = live();
|
|
89
|
+
if (at >= file.length) return 0;
|
|
90
|
+
const n = Math.min(view.length, file.length - at);
|
|
91
|
+
view.set(file.bytes.subarray(at, at + n), 0);
|
|
92
|
+
return n;
|
|
93
|
+
},
|
|
94
|
+
write(view, { at = 0 } = {}) {
|
|
95
|
+
const file = live();
|
|
96
|
+
const end = at + view.length;
|
|
97
|
+
file.ensureCapacity(end, budget);
|
|
98
|
+
if (at > file.length) file.bytes.fill(0, file.length, at);
|
|
99
|
+
file.bytes.set(view, at);
|
|
100
|
+
if (end > file.length) file.length = end;
|
|
101
|
+
return view.length;
|
|
102
|
+
},
|
|
103
|
+
truncate(size) {
|
|
104
|
+
const file = live();
|
|
105
|
+
if (size > file.length) {
|
|
106
|
+
file.ensureCapacity(size, budget);
|
|
107
|
+
file.bytes.fill(0, file.length, size);
|
|
108
|
+
}
|
|
109
|
+
file.length = size;
|
|
110
|
+
},
|
|
111
|
+
flush() {
|
|
112
|
+
live();
|
|
113
|
+
},
|
|
114
|
+
getSize() {
|
|
115
|
+
return live().length;
|
|
116
|
+
},
|
|
117
|
+
close() {
|
|
118
|
+
closed = true;
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
kind: "memory",
|
|
125
|
+
openFile(path, components, flags) {
|
|
126
|
+
const parent = parentOf(components);
|
|
127
|
+
if (!directories.has(parent)) {
|
|
128
|
+
if (!(flags & FLATSQL_IO_CREATE_PARENTS) || !(flags & FLATSQL_IO_CREATE)) {
|
|
129
|
+
throw new FlatsqlIoStatusError(FLATSQL_IO_ERR_NOENT, `no directory ${parent}`);
|
|
130
|
+
}
|
|
131
|
+
for (let i = 1; i < components.length; i += 1) {
|
|
132
|
+
directories.add(components.slice(0, i).join("/"));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (directories.has(path)) {
|
|
136
|
+
throw new FlatsqlIoStatusError(FLATSQL_IO_ERR_ACCESS, `${path} is a directory`);
|
|
137
|
+
}
|
|
138
|
+
let file = files.get(path);
|
|
139
|
+
if (file && flags & FLATSQL_IO_EXCL) {
|
|
140
|
+
throw new FlatsqlIoStatusError(FLATSQL_IO_ERR_GENERIC, `${path} exists`);
|
|
141
|
+
}
|
|
142
|
+
if (!file) {
|
|
143
|
+
if (!(flags & FLATSQL_IO_CREATE)) {
|
|
144
|
+
throw new FlatsqlIoStatusError(FLATSQL_IO_ERR_NOENT, `no file ${path}`);
|
|
145
|
+
}
|
|
146
|
+
file = new MemoryFile();
|
|
147
|
+
files.set(path, file);
|
|
148
|
+
} else if (flags & FLATSQL_IO_TRUNC) {
|
|
149
|
+
file.length = 0;
|
|
150
|
+
}
|
|
151
|
+
return handleFor(path);
|
|
152
|
+
},
|
|
153
|
+
exists(path) {
|
|
154
|
+
return files.has(path) || directories.has(path);
|
|
155
|
+
},
|
|
156
|
+
remove(path) {
|
|
157
|
+
const file = files.get(path);
|
|
158
|
+
if (!file) {
|
|
159
|
+
throw new FlatsqlIoStatusError(FLATSQL_IO_ERR_NOENT, `no file ${path}`);
|
|
160
|
+
}
|
|
161
|
+
budget.release(file.bytes.length);
|
|
162
|
+
files.delete(path);
|
|
163
|
+
},
|
|
164
|
+
/** Drop everything (the window store is dropped on every UI-state change). */
|
|
165
|
+
reset() {
|
|
166
|
+
files.clear();
|
|
167
|
+
directories.clear();
|
|
168
|
+
directories.add("");
|
|
169
|
+
allocated = 0;
|
|
170
|
+
},
|
|
171
|
+
usage() {
|
|
172
|
+
return { files: files.size, allocatedBytes: allocated };
|
|
173
|
+
},
|
|
174
|
+
list() {
|
|
175
|
+
return Array.from(files.keys()).sort();
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|