space-data-module-sdk 0.8.21 → 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 +88 -0
- package/docs/isomorphic-pthreads.md +290 -0
- package/package.json +7 -2
- package/src/browser.js +6 -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/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/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 +18 -11
- package/src/host/wasiThreadHost.js +258 -41
- package/src/host/wasiThreadWorker.mjs +24 -8
- package/src/host/wasiThreadWorkerRuntime.js +54 -0
- package/src/index.d.ts +347 -0
|
@@ -122,6 +122,23 @@ const OP = {
|
|
|
122
122
|
i64_mul: 0x7e,
|
|
123
123
|
i64_xor: 0x85,
|
|
124
124
|
void: 0x40,
|
|
125
|
+
unreachable: 0x00,
|
|
126
|
+
drop: 0x1a,
|
|
127
|
+
i32_eq: 0x46,
|
|
128
|
+
i32_ne: 0x47,
|
|
129
|
+
i32_gt_s: 0x4a,
|
|
130
|
+
i32_ge_s: 0x4e,
|
|
131
|
+
i64_eqz: 0x50,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Threads-proposal opcodes (0xFE prefix) used by shim v2.
|
|
135
|
+
const ATOMIC = {
|
|
136
|
+
notify: [0xfe, 0x00],
|
|
137
|
+
wait32: [0xfe, 0x01],
|
|
138
|
+
i32_load: [0xfe, 0x10],
|
|
139
|
+
i32_store: [0xfe, 0x17],
|
|
140
|
+
i32_add: [0xfe, 0x1e],
|
|
141
|
+
i32_cmpxchg: [0xfe, 0x48],
|
|
125
142
|
};
|
|
126
143
|
|
|
127
144
|
const FNV_OFFSET_BASIS = 1469598103934665603n; // deployed decision-gate basis
|
|
@@ -302,3 +319,281 @@ export function readEngineRefEntry(view, base) {
|
|
|
302
319
|
used: view.getUint32(base + 36, true),
|
|
303
320
|
};
|
|
304
321
|
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
// Shim v2 (FlatSQL partition store T9, design §18 T9 and §20): a mailbox over
|
|
326
|
+
// IMPORTED LANE MEMORY.
|
|
327
|
+
//
|
|
328
|
+
// In the partition store a linked flow no longer calls engine functions: the
|
|
329
|
+
// engine is a set of multithreaded instances, and a query runs on a reader
|
|
330
|
+
// lane thread that serves a mailbox in the reader instance's shared memory.
|
|
331
|
+
// v2 imports that shared memory and gives the flow the mailbox protocol as
|
|
332
|
+
// direct in-wasm calls:
|
|
333
|
+
//
|
|
334
|
+
// mb_submit(mailbox, op, req_ptr, req_len) -> seq | -1 (mailbox busy)
|
|
335
|
+
// mb_poll(mailbox, seq) -> 1 when complete, else 0
|
|
336
|
+
// mb_wait(mailbox, seq, poll_ns, max_polls) -> the lane's status, or
|
|
337
|
+
// FLATSQL_LINK_PENDING (-110) after
|
|
338
|
+
// max_polls bounded waits
|
|
339
|
+
// mb_release(mailbox, seq) -> 0, or -1 when not complete
|
|
340
|
+
// mb_cancel(mailbox, seq) -> 0 (the lane polls the cancel word)
|
|
341
|
+
// load32_acquire(addr) / store32_release(addr, value)
|
|
342
|
+
// plus v1's peek8/peek32/peek64/poke8/poke32/fnv1a64/count_frames over the
|
|
343
|
+
// lane memory.
|
|
344
|
+
//
|
|
345
|
+
// POLLING-BOUNDED WAITS, NO RELIANCE ON CROSS-EXECUTOR NOTIFY. WasmEdge keeps
|
|
346
|
+
// atomic waiters per executor, and the flow and the reader instance run on
|
|
347
|
+
// different executors, so a lane's notify may never reach the flow's wait.
|
|
348
|
+
// mb_wait therefore never waits unboundedly: each wait is `poll_ns` long and
|
|
349
|
+
// every wake (notified or timed out) re-reads the mailbox, so a lost notify
|
|
350
|
+
// costs at most one poll interval and can never lose a completion. poll_ns = 0
|
|
351
|
+
// polls without executing memory.atomic.wait at all (contexts that may not
|
|
352
|
+
// block, such as a browser main thread). The shim still notifies the lane's
|
|
353
|
+
// doorbell on submit and cancel; that is only an accelerator.
|
|
354
|
+
//
|
|
355
|
+
// Mailbox (64 bytes, 8-aligned, in lane memory; little-endian):
|
|
356
|
+
// +0 state (IDLE 0, SUBMITTED 1, CLAIMED 2, DONE 3, SUBMITTING 4)
|
|
357
|
+
// +4 seq +8 done_seq +12 op +16 req_ptr +20 req_len
|
|
358
|
+
// +24 status +28 resp_ptr +32 resp_len +36 doorbell +40 cancel
|
|
359
|
+
// +44 flags +48 generation (u64) +56 reserved
|
|
360
|
+
// Client: IDLE -> SUBMITTING -> SUBMITTED (publishes the fields), waits for
|
|
361
|
+
// DONE with done_seq == seq, reads status/resp, then DONE -> IDLE (release).
|
|
362
|
+
// Lane: waits (bounded) on the doorbell, CAS SUBMITTED -> CLAIMED, runs the
|
|
363
|
+
// request, writes status/resp_ptr/resp_len/generation, stores done_seq = seq,
|
|
364
|
+
// stores DONE, and notifies the state word. A lane checks `cancel == seq`
|
|
365
|
+
// while it runs.
|
|
366
|
+
|
|
367
|
+
export const FLATSQL_LINK_MAILBOX_BYTES = 64;
|
|
368
|
+
export const FLATSQL_LINK_MAILBOX = Object.freeze({
|
|
369
|
+
STATE: 0,
|
|
370
|
+
SEQ: 4,
|
|
371
|
+
DONE_SEQ: 8,
|
|
372
|
+
OP: 12,
|
|
373
|
+
REQ_PTR: 16,
|
|
374
|
+
REQ_LEN: 20,
|
|
375
|
+
STATUS: 24,
|
|
376
|
+
RESP_PTR: 28,
|
|
377
|
+
RESP_LEN: 32,
|
|
378
|
+
DOORBELL: 36,
|
|
379
|
+
CANCEL: 40,
|
|
380
|
+
FLAGS: 44,
|
|
381
|
+
GENERATION: 48,
|
|
382
|
+
});
|
|
383
|
+
export const FLATSQL_LINK_STATE = Object.freeze({
|
|
384
|
+
IDLE: 0,
|
|
385
|
+
SUBMITTED: 1,
|
|
386
|
+
CLAIMED: 2,
|
|
387
|
+
DONE: 3,
|
|
388
|
+
SUBMITTING: 4,
|
|
389
|
+
});
|
|
390
|
+
/** mb_wait's "not complete yet" result: retryable, the request stays live. */
|
|
391
|
+
export const FLATSQL_LINK_PENDING = -110;
|
|
392
|
+
/** mb_submit's result when the mailbox holds another request. */
|
|
393
|
+
export const FLATSQL_LINK_BUSY = -1;
|
|
394
|
+
|
|
395
|
+
/** Assemble the v2 shim bytes (deterministic). */
|
|
396
|
+
export function buildFlatsqlLinkShimV2Wasm() {
|
|
397
|
+
const types = [
|
|
398
|
+
[0x60, ...vec([[I32]]), ...vec([[I32]])], // t0: (i32)->i32
|
|
399
|
+
[0x60, ...vec([[I32]]), ...vec([[I64]])], // t1: (i32)->i64
|
|
400
|
+
[0x60, ...vec([[I32], [I32]]), ...vec([])], // t2: (i32,i32)->()
|
|
401
|
+
[0x60, ...vec([[I32], [I32]]), ...vec([[I64]])], // t3: (i32,i32)->i64
|
|
402
|
+
[0x60, ...vec([[I32], [I32]]), ...vec([[I32]])], // t4: (i32,i32)->i32
|
|
403
|
+
[0x60, ...vec([[I32], [I32], [I32], [I32]]), ...vec([[I32]])], // t5: submit
|
|
404
|
+
[0x60, ...vec([[I32], [I32], [I64], [I32]]), ...vec([[I32]])], // t6: wait
|
|
405
|
+
];
|
|
406
|
+
|
|
407
|
+
// (import "flatsql" "memory" (memory 0 65536 shared))
|
|
408
|
+
const imports = [
|
|
409
|
+
[
|
|
410
|
+
...lebU(7), ...utf8("flatsql"),
|
|
411
|
+
...lebU(6), ...utf8("memory"),
|
|
412
|
+
0x02,
|
|
413
|
+
0x03, ...lebU(0), ...lebU(65536),
|
|
414
|
+
],
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
const names = [
|
|
418
|
+
["peek8", 0], ["peek32", 0], ["peek64", 1], ["poke8", 2], ["poke32", 2],
|
|
419
|
+
["fnv1a64", 3], ["count_frames", 4],
|
|
420
|
+
["mb_submit", 5], ["mb_poll", 4], ["mb_wait", 6], ["mb_release", 4],
|
|
421
|
+
["mb_cancel", 4], ["load32_acquire", 0], ["store32_release", 2],
|
|
422
|
+
];
|
|
423
|
+
const funcTypes = names.map(([, type]) => type);
|
|
424
|
+
const exports = names.map(([name], index) => [
|
|
425
|
+
...lebU(name.length), ...utf8(name), 0x00, ...lebU(index),
|
|
426
|
+
]);
|
|
427
|
+
|
|
428
|
+
const m = FLATSQL_LINK_MAILBOX;
|
|
429
|
+
const S = FLATSQL_LINK_STATE;
|
|
430
|
+
const aload = (offset) => [...ATOMIC.i32_load, ...lebU(2), ...lebU(offset)];
|
|
431
|
+
const astore = (offset) => [...ATOMIC.i32_store, ...lebU(2), ...lebU(offset)];
|
|
432
|
+
const store = (offset) => [OP.i32_store, ...lebU(2), ...lebU(offset)];
|
|
433
|
+
const load = (offset) => [OP.i32_load, ...lebU(2), ...lebU(offset)];
|
|
434
|
+
|
|
435
|
+
const peek8 = func([], [OP.local_get, 0, OP.i32_load8_u, ...memarg(0)]);
|
|
436
|
+
const peek32 = func([], [OP.local_get, 0, OP.i32_load, ...memarg(0)]);
|
|
437
|
+
const peek64 = func([], [OP.local_get, 0, OP.i64_load, ...memarg(0)]);
|
|
438
|
+
const poke8 = func([], [OP.local_get, 0, OP.local_get, 1, OP.i32_store8, ...memarg(0)]);
|
|
439
|
+
const poke32 = func([], [OP.local_get, 0, OP.local_get, 1, OP.i32_store, ...memarg(0)]);
|
|
440
|
+
const v1 = buildFlatsqlLinkShimWasm();
|
|
441
|
+
// fnv1a64 and count_frames are byte-for-byte v1's bodies (same algorithm,
|
|
442
|
+
// same code); lift them from the v1 code section.
|
|
443
|
+
const v1Bodies = extractCodeBodies(v1);
|
|
444
|
+
const fnv = v1Bodies[5];
|
|
445
|
+
const countFrames = v1Bodies[6];
|
|
446
|
+
|
|
447
|
+
// mb_submit(mb, op, req_ptr, req_len) -> seq | -1 ; local 4 = seq
|
|
448
|
+
const submit = func(
|
|
449
|
+
[[1, I32]],
|
|
450
|
+
[
|
|
451
|
+
OP.local_get, 0, OP.i32_const, S.IDLE, OP.i32_const, S.SUBMITTING,
|
|
452
|
+
...ATOMIC.i32_cmpxchg, ...lebU(2), ...lebU(m.STATE),
|
|
453
|
+
OP.if, OP.void, OP.i32_const, ...lebS32(FLATSQL_LINK_BUSY), OP.return, OP.end,
|
|
454
|
+
OP.local_get, 0, ...aload(m.SEQ), OP.i32_const, 1, OP.i32_add, OP.local_set, 4,
|
|
455
|
+
OP.local_get, 4, OP.i32_eqz, OP.if, OP.void, OP.i32_const, 1, OP.local_set, 4, OP.end,
|
|
456
|
+
OP.local_get, 0, OP.local_get, 1, ...store(m.OP),
|
|
457
|
+
OP.local_get, 0, OP.local_get, 2, ...store(m.REQ_PTR),
|
|
458
|
+
OP.local_get, 0, OP.local_get, 3, ...store(m.REQ_LEN),
|
|
459
|
+
OP.local_get, 0, OP.i32_const, 0, ...store(m.STATUS),
|
|
460
|
+
OP.local_get, 0, OP.i32_const, 0, ...store(m.RESP_PTR),
|
|
461
|
+
OP.local_get, 0, OP.i32_const, 0, ...store(m.RESP_LEN),
|
|
462
|
+
OP.local_get, 0, OP.i32_const, 0, ...astore(m.CANCEL),
|
|
463
|
+
OP.local_get, 0, OP.local_get, 4, ...astore(m.SEQ),
|
|
464
|
+
OP.local_get, 0, OP.i32_const, S.SUBMITTED, ...astore(m.STATE),
|
|
465
|
+
OP.local_get, 0, OP.i32_const, 1, ...ATOMIC.i32_add, ...lebU(2), ...lebU(m.DOORBELL), OP.drop,
|
|
466
|
+
OP.local_get, 0, OP.i32_const, 1, ...ATOMIC.notify, ...lebU(2), ...lebU(m.DOORBELL), OP.drop,
|
|
467
|
+
OP.local_get, 4,
|
|
468
|
+
],
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
// mb_poll(mb, seq) -> 1 | 0
|
|
472
|
+
const poll = func(
|
|
473
|
+
[],
|
|
474
|
+
[
|
|
475
|
+
OP.local_get, 0, ...aload(m.STATE), OP.i32_const, S.DONE, OP.i32_eq,
|
|
476
|
+
OP.local_get, 0, ...aload(m.DONE_SEQ), OP.local_get, 1, OP.i32_eq,
|
|
477
|
+
OP.i32_and,
|
|
478
|
+
],
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
// mb_wait(mb, seq, poll_ns, max_polls) -> status | PENDING
|
|
482
|
+
// locals: 4 polls, 5 state
|
|
483
|
+
const wait = func(
|
|
484
|
+
[[2, I32]],
|
|
485
|
+
[
|
|
486
|
+
OP.loop, OP.void,
|
|
487
|
+
OP.local_get, 0, ...aload(m.STATE), OP.local_set, 5,
|
|
488
|
+
OP.local_get, 5, OP.i32_const, S.DONE, OP.i32_eq,
|
|
489
|
+
OP.local_get, 0, ...aload(m.DONE_SEQ), OP.local_get, 1, OP.i32_eq,
|
|
490
|
+
OP.i32_and,
|
|
491
|
+
OP.if, OP.void, OP.local_get, 0, ...load(m.STATUS), OP.return, OP.end,
|
|
492
|
+
OP.local_get, 3, OP.i32_const, 0, OP.i32_gt_s,
|
|
493
|
+
OP.local_get, 4, OP.local_get, 3, OP.i32_ge_s,
|
|
494
|
+
OP.i32_and,
|
|
495
|
+
OP.if, OP.void, OP.i32_const, ...lebS32(FLATSQL_LINK_PENDING), OP.return, OP.end,
|
|
496
|
+
OP.local_get, 4, OP.i32_const, 1, OP.i32_add, OP.local_set, 4,
|
|
497
|
+
OP.local_get, 2, OP.i64_eqz, OP.i32_eqz,
|
|
498
|
+
OP.if, OP.void,
|
|
499
|
+
OP.local_get, 0, OP.local_get, 5, OP.local_get, 2,
|
|
500
|
+
...ATOMIC.wait32, ...lebU(2), ...lebU(m.STATE), OP.drop,
|
|
501
|
+
OP.end,
|
|
502
|
+
OP.br, 0,
|
|
503
|
+
OP.end,
|
|
504
|
+
OP.unreachable,
|
|
505
|
+
],
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
// mb_release(mb, seq) -> 0 | -1
|
|
509
|
+
const release = func(
|
|
510
|
+
[],
|
|
511
|
+
[
|
|
512
|
+
OP.local_get, 0, ...aload(m.DONE_SEQ), OP.local_get, 1, OP.i32_ne,
|
|
513
|
+
OP.if, OP.void, OP.i32_const, ...lebS32(-1), OP.return, OP.end,
|
|
514
|
+
OP.local_get, 0, OP.i32_const, S.DONE, OP.i32_const, S.IDLE,
|
|
515
|
+
...ATOMIC.i32_cmpxchg, ...lebU(2), ...lebU(m.STATE),
|
|
516
|
+
OP.i32_const, S.DONE, OP.i32_eq,
|
|
517
|
+
OP.if, I32, OP.i32_const, 0, 0x05 /* else */, OP.i32_const, ...lebS32(-1), OP.end,
|
|
518
|
+
],
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
// mb_cancel(mb, seq) -> 0
|
|
522
|
+
const cancel = func(
|
|
523
|
+
[],
|
|
524
|
+
[
|
|
525
|
+
OP.local_get, 0, OP.local_get, 1, ...astore(m.CANCEL),
|
|
526
|
+
OP.local_get, 0, OP.i32_const, 1, ...ATOMIC.i32_add, ...lebU(2), ...lebU(m.DOORBELL), OP.drop,
|
|
527
|
+
OP.local_get, 0, OP.i32_const, 1, ...ATOMIC.notify, ...lebU(2), ...lebU(m.DOORBELL), OP.drop,
|
|
528
|
+
OP.i32_const, 0,
|
|
529
|
+
],
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
const loadAcquire = func([], [OP.local_get, 0, ...aload(0)]);
|
|
533
|
+
const storeRelease = func([], [OP.local_get, 0, OP.local_get, 1, ...astore(0)]);
|
|
534
|
+
|
|
535
|
+
const bytes = [
|
|
536
|
+
0x00, 0x61, 0x73, 0x6d,
|
|
537
|
+
0x01, 0x00, 0x00, 0x00,
|
|
538
|
+
...section(1, vec(types)),
|
|
539
|
+
...section(2, vec(imports)),
|
|
540
|
+
...section(3, vec(funcTypes.map((t) => lebU(t)))),
|
|
541
|
+
...section(7, vec(exports)),
|
|
542
|
+
...section(
|
|
543
|
+
10,
|
|
544
|
+
vec([
|
|
545
|
+
peek8, peek32, peek64, poke8, poke32, fnv, countFrames,
|
|
546
|
+
submit, poll, wait, release, cancel, loadAcquire, storeRelease,
|
|
547
|
+
]),
|
|
548
|
+
),
|
|
549
|
+
];
|
|
550
|
+
return new Uint8Array(bytes);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function readLebU(bytes, offset) {
|
|
554
|
+
let result = 0;
|
|
555
|
+
let shift = 0;
|
|
556
|
+
let at = offset;
|
|
557
|
+
for (;;) {
|
|
558
|
+
const byte = bytes[at];
|
|
559
|
+
at += 1;
|
|
560
|
+
result |= (byte & 0x7f) << shift;
|
|
561
|
+
if ((byte & 0x80) === 0) return [result >>> 0, at];
|
|
562
|
+
shift += 7;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// The code section's function bodies, each as its size-prefixed byte array.
|
|
567
|
+
function extractCodeBodies(wasm) {
|
|
568
|
+
let at = 8;
|
|
569
|
+
while (at < wasm.length) {
|
|
570
|
+
const id = wasm[at];
|
|
571
|
+
const [size, payload] = readLebU(wasm, at + 1);
|
|
572
|
+
if (id === 10) {
|
|
573
|
+
let [count, cursor] = readLebU(wasm, payload);
|
|
574
|
+
const bodies = [];
|
|
575
|
+
for (let i = 0; i < count; i += 1) {
|
|
576
|
+
const [bodySize, bodyStart] = readLebU(wasm, cursor);
|
|
577
|
+
bodies.push(Array.from(wasm.subarray(cursor, bodyStart + bodySize)));
|
|
578
|
+
cursor = bodyStart + bodySize;
|
|
579
|
+
}
|
|
580
|
+
return bodies;
|
|
581
|
+
}
|
|
582
|
+
at = payload + size;
|
|
583
|
+
}
|
|
584
|
+
throw new Error("no code section");
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/** The v2 shim module bytes (assembled once at import). */
|
|
588
|
+
export const FLATSQL_LINK_SHIM_V2_WASM = buildFlatsqlLinkShimV2Wasm();
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Instantiate shim v2 against a lane's shared memory (the reader instance's
|
|
592
|
+
* `WebAssembly.Memory`, shared).
|
|
593
|
+
*/
|
|
594
|
+
export async function instantiateFlatsqlLinkShimV2(laneMemory) {
|
|
595
|
+
const { instance } = await WebAssembly.instantiate(FLATSQL_LINK_SHIM_V2_WASM.slice().buffer, {
|
|
596
|
+
[FLATSQL_ENGINE_IMPORT_MODULE]: { memory: laneMemory },
|
|
597
|
+
});
|
|
598
|
+
return instance;
|
|
599
|
+
}
|
package/src/flow/index.d.ts
CHANGED
|
@@ -103,6 +103,37 @@ export function instantiateFlatsqlLinkShim(
|
|
|
103
103
|
export function isEngineBodyRefToken(
|
|
104
104
|
token: bigint | number | string,
|
|
105
105
|
): boolean;
|
|
106
|
+
/** Link shim v2: a mailbox over imported (shared) lane memory. */
|
|
107
|
+
export const FLATSQL_LINK_SHIM_V2_WASM: Uint8Array;
|
|
108
|
+
export function buildFlatsqlLinkShimV2Wasm(): Uint8Array;
|
|
109
|
+
export function instantiateFlatsqlLinkShimV2(
|
|
110
|
+
laneMemory: WebAssembly.Memory,
|
|
111
|
+
): Promise<WebAssembly.Instance>;
|
|
112
|
+
export const FLATSQL_LINK_MAILBOX_BYTES: number;
|
|
113
|
+
export const FLATSQL_LINK_MAILBOX: Readonly<{
|
|
114
|
+
STATE: number;
|
|
115
|
+
SEQ: number;
|
|
116
|
+
DONE_SEQ: number;
|
|
117
|
+
OP: number;
|
|
118
|
+
REQ_PTR: number;
|
|
119
|
+
REQ_LEN: number;
|
|
120
|
+
STATUS: number;
|
|
121
|
+
RESP_PTR: number;
|
|
122
|
+
RESP_LEN: number;
|
|
123
|
+
DOORBELL: number;
|
|
124
|
+
CANCEL: number;
|
|
125
|
+
FLAGS: number;
|
|
126
|
+
GENERATION: number;
|
|
127
|
+
}>;
|
|
128
|
+
export const FLATSQL_LINK_STATE: Readonly<{
|
|
129
|
+
IDLE: number;
|
|
130
|
+
SUBMITTED: number;
|
|
131
|
+
CLAIMED: number;
|
|
132
|
+
DONE: number;
|
|
133
|
+
SUBMITTING: number;
|
|
134
|
+
}>;
|
|
135
|
+
export const FLATSQL_LINK_PENDING: number;
|
|
136
|
+
export const FLATSQL_LINK_BUSY: number;
|
|
106
137
|
export function readEngineRefEntry(
|
|
107
138
|
view: DataView,
|
|
108
139
|
base: number,
|
package/src/flow/index.js
CHANGED
|
@@ -25,6 +25,14 @@ export {
|
|
|
25
25
|
instantiateFlatsqlLinkShim,
|
|
26
26
|
isEngineBodyRefToken,
|
|
27
27
|
readEngineRefEntry,
|
|
28
|
+
FLATSQL_LINK_SHIM_V2_WASM,
|
|
29
|
+
buildFlatsqlLinkShimV2Wasm,
|
|
30
|
+
instantiateFlatsqlLinkShimV2,
|
|
31
|
+
FLATSQL_LINK_MAILBOX,
|
|
32
|
+
FLATSQL_LINK_MAILBOX_BYTES,
|
|
33
|
+
FLATSQL_LINK_STATE,
|
|
34
|
+
FLATSQL_LINK_PENDING,
|
|
35
|
+
FLATSQL_LINK_BUSY,
|
|
28
36
|
} from "./flatsqlLinkShim.js";
|
|
29
37
|
// The flow compiler (flow check/compile) is node-only (emception + fs) and
|
|
30
38
|
// ships on the dedicated "./flow/compiler" subpath to keep this barrel
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// Browser capability matrix probe (docs/architecture/flatsql-partition-store.md
|
|
2
|
+
// §18 T9 #4, §20 "browser matrix", A13, A37, §22.4-6).
|
|
3
|
+
//
|
|
4
|
+
// The local partition store needs, in the context that runs it:
|
|
5
|
+
// - cross-origin isolation and SharedArrayBuffer (ruling 6);
|
|
6
|
+
// - OPFS with FileSystemSyncAccessHandle in a worker (A13 gate: a successful
|
|
7
|
+
// createSyncAccessHandle probe);
|
|
8
|
+
// - SHARED sync-handle modes ("readwrite-unsafe"): a reader I/O worker must be
|
|
9
|
+
// able to read files the writer I/O worker holds (A7). The owner ruled that
|
|
10
|
+
// a browser without them gets no local store and reads from the network
|
|
11
|
+
// (§22.4-6);
|
|
12
|
+
// and it adapts to:
|
|
13
|
+
// - Atomics.waitAsync (else the MessagePort/BroadcastChannel doorbell);
|
|
14
|
+
// - SharedArrayBuffer views in sync-handle read/write (else a scratch copy).
|
|
15
|
+
//
|
|
16
|
+
// The worker half is a self-contained function, serialized into a blob: worker
|
|
17
|
+
// so it runs under the dashboard's `worker-src 'self' blob:` CSP (A39).
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Worker-side probe. Self-contained on purpose: it is stringified into a blob
|
|
21
|
+
* worker, so it may not reference anything outside its own body.
|
|
22
|
+
*/
|
|
23
|
+
export async function probeWorkerCapabilities() {
|
|
24
|
+
const result = {
|
|
25
|
+
context: "worker",
|
|
26
|
+
crossOriginIsolated: self.crossOriginIsolated === true,
|
|
27
|
+
sharedArrayBuffer: typeof SharedArrayBuffer === "function",
|
|
28
|
+
atomicsWaitAsync: typeof Atomics.waitAsync === "function",
|
|
29
|
+
atomicsWait: false,
|
|
30
|
+
broadcastChannel: typeof BroadcastChannel === "function",
|
|
31
|
+
webLocks: !!(self.navigator && self.navigator.locks),
|
|
32
|
+
hardwareConcurrency: self.navigator ? self.navigator.hardwareConcurrency ?? null : null,
|
|
33
|
+
opfs: false,
|
|
34
|
+
opfsError: null,
|
|
35
|
+
syncAccessHandle: false,
|
|
36
|
+
syncAccessHandleError: null,
|
|
37
|
+
sharedHandleModes: false,
|
|
38
|
+
sharedHandleModesDetail: null,
|
|
39
|
+
secondHandleError: null,
|
|
40
|
+
sharedViewRead: false,
|
|
41
|
+
sharedViewWrite: false,
|
|
42
|
+
wasmSharedMemoryView: false,
|
|
43
|
+
removeWhileOpenError: null,
|
|
44
|
+
nestedBlobWorker: null,
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
const word = new Int32Array(new SharedArrayBuffer(4));
|
|
48
|
+
result.atomicsWait = Atomics.wait(word, 0, 1, 0) === "not-equal";
|
|
49
|
+
} catch (error) {
|
|
50
|
+
result.atomicsWait = false;
|
|
51
|
+
}
|
|
52
|
+
let root = null;
|
|
53
|
+
try {
|
|
54
|
+
root = await navigator.storage.getDirectory();
|
|
55
|
+
result.opfs = true;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
result.opfsError = `${error && error.name}: ${error && error.message}`;
|
|
58
|
+
}
|
|
59
|
+
if (root) {
|
|
60
|
+
const dir = await root.getDirectoryHandle(".sdm-capability-probe", { create: true });
|
|
61
|
+
let handle = null;
|
|
62
|
+
try {
|
|
63
|
+
const file = await dir.getFileHandle("probe.bin", { create: true });
|
|
64
|
+
handle = await file.createSyncAccessHandle();
|
|
65
|
+
result.syncAccessHandle = true;
|
|
66
|
+
try {
|
|
67
|
+
const second = await file.createSyncAccessHandle();
|
|
68
|
+
second.close();
|
|
69
|
+
result.secondHandleError = null;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
result.secondHandleError = error && error.name;
|
|
72
|
+
}
|
|
73
|
+
const shared = new Uint8Array(new SharedArrayBuffer(64));
|
|
74
|
+
shared.fill(0x3c);
|
|
75
|
+
try {
|
|
76
|
+
result.sharedViewWrite = handle.write(shared, { at: 0 }) === 64;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
result.sharedViewWrite = false;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const back = new Uint8Array(new SharedArrayBuffer(64));
|
|
82
|
+
result.sharedViewRead = handle.read(back, { at: 0 }) === 64 && back[63] === 0x3c;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
result.sharedViewRead = false;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const memory = new WebAssembly.Memory({ initial: 1, maximum: 2, shared: true });
|
|
88
|
+
const view = new Uint8Array(memory.buffer, 128, 64);
|
|
89
|
+
result.wasmSharedMemoryView = handle.read(view, { at: 0 }) === 64 && view[0] === 0x3c;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
result.wasmSharedMemoryView = false;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
await dir.removeEntry("probe.bin");
|
|
95
|
+
result.removeWhileOpenError = null;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
result.removeWhileOpenError = error && error.name;
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
result.syncAccessHandleError = `${error && error.name}: ${error && error.message}`;
|
|
101
|
+
} finally {
|
|
102
|
+
try {
|
|
103
|
+
if (handle) handle.close();
|
|
104
|
+
} catch (error) {
|
|
105
|
+
// ignore
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
let first = null;
|
|
109
|
+
let second = null;
|
|
110
|
+
try {
|
|
111
|
+
const file = await dir.getFileHandle("modes.bin", { create: true });
|
|
112
|
+
first = await file.createSyncAccessHandle({ mode: "readwrite-unsafe" });
|
|
113
|
+
if (first.mode !== "readwrite-unsafe") {
|
|
114
|
+
result.sharedHandleModesDetail = "mode option ignored";
|
|
115
|
+
} else {
|
|
116
|
+
second = await file.createSyncAccessHandle({ mode: "readwrite-unsafe" });
|
|
117
|
+
result.sharedHandleModes = true;
|
|
118
|
+
result.sharedHandleModesDetail = "two readwrite-unsafe handles";
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
result.sharedHandleModesDetail = `${error && error.name}: ${error && error.message}`;
|
|
122
|
+
} finally {
|
|
123
|
+
try {
|
|
124
|
+
if (second) second.close();
|
|
125
|
+
} catch (error) {
|
|
126
|
+
// ignore
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
if (first) first.close();
|
|
130
|
+
} catch (error) {
|
|
131
|
+
// ignore
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
await root.removeEntry(".sdm-capability-probe", { recursive: true });
|
|
136
|
+
} catch (error) {
|
|
137
|
+
// ignore
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const url = URL.createObjectURL(new Blob(["self.postMessage(1)"], { type: "text/javascript" }));
|
|
142
|
+
const nested = new Worker(url);
|
|
143
|
+
result.nestedBlobWorker = await new Promise((resolve) => {
|
|
144
|
+
const timer = setTimeout(() => resolve(false), 3000);
|
|
145
|
+
nested.onmessage = () => {
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
resolve(true);
|
|
148
|
+
};
|
|
149
|
+
nested.onerror = () => {
|
|
150
|
+
clearTimeout(timer);
|
|
151
|
+
resolve(false);
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
nested.terminate();
|
|
155
|
+
URL.revokeObjectURL(url);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
result.nestedBlobWorker = false;
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The worker probe as a classic script source (for a blob: URL). */
|
|
163
|
+
export function workerCapabilityProbeSource() {
|
|
164
|
+
return `(${probeWorkerCapabilities.toString()})().then((r) => self.postMessage(r), (e) => self.postMessage({ error: String(e && e.message || e) }));`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function pageCapabilities() {
|
|
168
|
+
const nav = typeof navigator !== "undefined" ? navigator : {};
|
|
169
|
+
let sharedWasmMemory = false;
|
|
170
|
+
try {
|
|
171
|
+
sharedWasmMemory =
|
|
172
|
+
new WebAssembly.Memory({ initial: 1, maximum: 1, shared: true }).buffer instanceof
|
|
173
|
+
SharedArrayBuffer;
|
|
174
|
+
} catch {
|
|
175
|
+
sharedWasmMemory = false;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
context: typeof window !== "undefined" ? "window" : "worker",
|
|
179
|
+
crossOriginIsolated: globalThis.crossOriginIsolated === true,
|
|
180
|
+
sharedArrayBuffer: typeof SharedArrayBuffer === "function",
|
|
181
|
+
sharedWasmMemory,
|
|
182
|
+
atomicsWaitAsync: typeof Atomics.waitAsync === "function",
|
|
183
|
+
broadcastChannel: typeof BroadcastChannel === "function",
|
|
184
|
+
webLocks: !!nav.locks,
|
|
185
|
+
opfsApi: !!(nav.storage && nav.storage.getDirectory),
|
|
186
|
+
storagePersist: !!(nav.storage && nav.storage.persist),
|
|
187
|
+
storageEstimate: !!(nav.storage && nav.storage.estimate),
|
|
188
|
+
hardwareConcurrency: nav.hardwareConcurrency ?? null,
|
|
189
|
+
deviceMemory: nav.deviceMemory ?? null,
|
|
190
|
+
userAgent: nav.userAgent ?? null,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Probe the current context and (from a window or worker) a blob: worker.
|
|
196
|
+
* Resolves to `{ page, worker, localStore }`; `localStore` is the gate
|
|
197
|
+
* decision with its reasons.
|
|
198
|
+
*/
|
|
199
|
+
export async function probeBrowserCapabilities({ timeoutMs = 10_000 } = {}) {
|
|
200
|
+
const page = pageCapabilities();
|
|
201
|
+
let worker = null;
|
|
202
|
+
if (typeof Worker === "function" && typeof Blob === "function") {
|
|
203
|
+
try {
|
|
204
|
+
const url = URL.createObjectURL(
|
|
205
|
+
new Blob([workerCapabilityProbeSource()], { type: "text/javascript" }),
|
|
206
|
+
);
|
|
207
|
+
const probe = new Worker(url);
|
|
208
|
+
worker = await new Promise((resolve) => {
|
|
209
|
+
const timer = setTimeout(() => resolve({ error: "probe timed out" }), timeoutMs);
|
|
210
|
+
probe.onmessage = (event) => {
|
|
211
|
+
clearTimeout(timer);
|
|
212
|
+
resolve(event.data);
|
|
213
|
+
};
|
|
214
|
+
probe.onerror = (event) => {
|
|
215
|
+
clearTimeout(timer);
|
|
216
|
+
resolve({ error: String(event && event.message) });
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
probe.terminate();
|
|
220
|
+
URL.revokeObjectURL(url);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
worker = { error: String(error && error.message) };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { page, worker, localStore: flatsqlLocalStoreGate({ page, worker }) };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The local-store gate (ruling 6, A13, §22.4-6): all of cross-origin
|
|
230
|
+
* isolation, shared memory, OPFS sync access handles in a worker, and shared
|
|
231
|
+
* sync-handle modes. Anything missing means no local store; reads go to the
|
|
232
|
+
* network. Returns `{ supported, missing: string[], doorbell, transfer }`.
|
|
233
|
+
*/
|
|
234
|
+
export function flatsqlLocalStoreGate({ page, worker }) {
|
|
235
|
+
const missing = [];
|
|
236
|
+
if (!page?.crossOriginIsolated) missing.push("cross-origin isolation");
|
|
237
|
+
if (!page?.sharedArrayBuffer || !page?.sharedWasmMemory) missing.push("shared memory");
|
|
238
|
+
if (!worker || worker.error) missing.push("worker probe");
|
|
239
|
+
else {
|
|
240
|
+
if (!worker.opfs) missing.push("OPFS");
|
|
241
|
+
if (!worker.syncAccessHandle) missing.push("sync access handles");
|
|
242
|
+
if (!worker.sharedHandleModes) missing.push("shared sync-handle modes");
|
|
243
|
+
if (!worker.atomicsWait) missing.push("Atomics.wait in workers");
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
supported: missing.length === 0,
|
|
247
|
+
missing,
|
|
248
|
+
doorbell: worker?.atomicsWaitAsync ? "atomics" : "message",
|
|
249
|
+
transfer: worker?.sharedViewRead && worker?.sharedViewWrite ? "direct" : "scratch",
|
|
250
|
+
};
|
|
251
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// FlatSQL host I/O for browsers and Node (design docs/architecture/
|
|
2
|
+
// flatsql-partition-store.md §5.5, §5.6; SDK T9). Browser-safe barrel: the
|
|
3
|
+
// Node sync-fs provider lives on its own subpath
|
|
4
|
+
// (space-data-module-sdk/host/flatsql-io/node).
|
|
5
|
+
export * from "./flatsqlIoContract.js";
|
|
6
|
+
export * from "./sabIoChannel.js";
|
|
7
|
+
export * from "./sabIoMirror.js";
|
|
8
|
+
export * from "./flatsqlIoImports.js";
|
|
9
|
+
export * from "./flatsqlIoServer.js";
|
|
10
|
+
export * from "./flatsqlIoMemoryBackend.js";
|
|
11
|
+
export * from "./opfsIoBackend.js";
|
|
12
|
+
export * from "./flatsqlIoWorkers.js";
|
|
13
|
+
export * from "./flatsqlIoConformance.js";
|
|
14
|
+
export * from "./browserCapabilityProbe.js";
|