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.
Files changed (35) hide show
  1. package/docs/isomorphic-pthreads.html +100 -0
  2. package/docs/isomorphic-pthreads.md +335 -0
  3. package/package.json +7 -2
  4. package/src/browser.js +6 -0
  5. package/src/compiler/invokeGlue.js +38 -0
  6. package/src/flow/flatsqlLinkShim.js +295 -0
  7. package/src/flow/index.d.ts +31 -0
  8. package/src/flow/index.js +8 -0
  9. package/src/host/browserCapabilityProbe.js +251 -0
  10. package/src/host/browserModuleHarness.js +24 -5
  11. package/src/host/flatsqlIo.js +14 -0
  12. package/src/host/flatsqlIoConformance.js +349 -0
  13. package/src/host/flatsqlIoContract.js +240 -0
  14. package/src/host/flatsqlIoImports.js +335 -0
  15. package/src/host/flatsqlIoMemoryBackend.js +178 -0
  16. package/src/host/flatsqlIoServer.js +960 -0
  17. package/src/host/flatsqlIoWorkers.js +323 -0
  18. package/src/host/hostWorkerBundleSources.js +11 -0
  19. package/src/host/hostWorkerBundles.js +79 -0
  20. package/src/host/index.js +9 -0
  21. package/src/host/isomorphicLoader.js +10 -2
  22. package/src/host/nodeSyncFsIo.js +633 -0
  23. package/src/host/opfsIoBackend.js +280 -0
  24. package/src/host/opfsIoWorker.mjs +223 -0
  25. package/src/host/sabIoChannel.js +848 -0
  26. package/src/host/sabIoMirror.js +306 -0
  27. package/src/host/wasiThreadBrowserWorker.mjs +21 -11
  28. package/src/host/wasiThreadHost.js +261 -43
  29. package/src/host/wasiThreadWorker.mjs +46 -8
  30. package/src/host/wasiThreadWorkerRuntime.js +54 -0
  31. package/src/index.d.ts +349 -0
  32. package/src/runtime/constants.js +6 -0
  33. package/src/testing/native/wasmedge_wasi_threads_runner.c +13 -5
  34. package/src/testing/parityHarness.js +7 -1
  35. package/src/testing/parityLanes.js +19 -0
@@ -1,4 +1,5 @@
1
1
  import { createHostcallBridge, DEFAULT_HOSTCALL_IMPORT_MODULE } from "./abi.js";
2
+ import { mergeImportFragments, resolveExtraImports } from "./flatsqlIoImports.js";
2
3
  import {
3
4
  createSabHostcallBuffer,
4
5
  createSabHostcallClientDispatch,
@@ -54,11 +55,27 @@ function createThreadHostcallDispatch(options) {
54
55
  };
55
56
  }
56
57
 
58
+ /**
59
+ * The per-worker half of the wasi-threads host: the import object one guest
60
+ * thread instantiates the shared module with.
61
+ *
62
+ * Every pool thread gets WASI, the shared `env.memory`, a `wasi.thread-spawn`
63
+ * stub, the hostcall bridge when the module imports it, and the `extraImports`
64
+ * entries (T9): per-worker import objects such as FlatSQL's `env.flatsql_io_*`.
65
+ * Each entry is a factory `(ctx) => importObject` (or `{ imports, close }`), or
66
+ * a built-in descriptor such as `{ provider: "flatsql-io", instanceId, channels }`
67
+ * (flatsqlIoImports.js). Factories run once per worker, so each worker owns its
68
+ * own resources (for flatsql-io: its own request-ring slot). `ctx` is
69
+ * `{ memory, getMemory, tid, workerIndex }`.
70
+ */
57
71
  export function createWasiThreadWorkerRuntime({
58
72
  wasmModule,
59
73
  memory,
60
74
  hostcallChannel,
61
75
  processState,
76
+ extraImports,
77
+ workerIndex,
78
+ tid,
62
79
  } = {}) {
63
80
  const wasi = createBrowserWasiShim({ processState });
64
81
  wasi.setMemory(memory);
@@ -79,7 +96,18 @@ export function createWasiThreadWorkerRuntime({
79
96
  Object.assign(imports, bridge.imports);
80
97
  }
81
98
 
99
+ const extras = resolveExtraImports(extraImports, {
100
+ memory,
101
+ getMemory: () => instance?.exports?.memory ?? memory,
102
+ workerIndex: workerIndex ?? null,
103
+ tid: tid ?? null,
104
+ });
105
+ mergeImportFragments(imports, extras.fragments);
106
+ // The shared memory is the one contract every import object must agree on.
107
+ imports.env.memory = memory;
108
+
82
109
  return {
110
+ imports,
83
111
  instantiate() {
84
112
  instance = new WebAssembly.Instance(wasmModule, imports);
85
113
  wasi.setMemory(instance.exports.memory ?? memory);
@@ -87,8 +115,34 @@ export function createWasiThreadWorkerRuntime({
87
115
  },
88
116
  close() {
89
117
  hostcalls?.close();
118
+ extras.close();
90
119
  },
91
120
  };
92
121
  }
93
122
 
123
+ /**
124
+ * Resolve `{ moduleUrl, exportName?, config? }` entries by importing their
125
+ * factory module (module workers and Node; a classic blob worker cannot). The
126
+ * resolved entries are factories that receive `{ ...ctx, config }`. Other
127
+ * entries pass through unchanged.
128
+ */
129
+ export async function resolveModuleExtraImports(extraImports) {
130
+ const out = [];
131
+ for (const entry of extraImports ?? []) {
132
+ if (entry && typeof entry.moduleUrl === "string") {
133
+ const mod = await import(/* @vite-ignore */ /* webpackIgnore: true */ entry.moduleUrl);
134
+ const factory = mod[entry.exportName ?? "default"];
135
+ if (typeof factory !== "function") {
136
+ throw new TypeError(
137
+ `extraImports module ${entry.moduleUrl} exports no factory "${entry.exportName ?? "default"}".`,
138
+ );
139
+ }
140
+ out.push({ factory, config: entry.config ?? null });
141
+ } else {
142
+ out.push(entry);
143
+ }
144
+ }
145
+ return out;
146
+ }
147
+
94
148
  export const WASI_THREAD_HOSTCALL_MESSAGE = THREAD_HOSTCALL_MESSAGE;
package/src/index.d.ts CHANGED
@@ -2063,6 +2063,353 @@ export function createSabHostcallServer(options: {
2063
2063
  dispatch: (operation: string, params: unknown) => Promise<unknown> | unknown;
2064
2064
  }): { handleRequest(request: { operation: string; params?: unknown }): Promise<void> };
2065
2065
 
2066
+ // --- FlatSQL partition store host I/O (T9; docs/isomorphic-pthreads.md) ---
2067
+ export const DEFAULT_FLATSQL_IO_CHUNK_BYTES: number;
2068
+ export const DEFAULT_FLATSQL_IO_SPIN_MICROS: number;
2069
+ export const DEFAULT_SAB_IO_CLIENT_SPIN_MICROS: number;
2070
+ export const DEFAULT_SAB_IO_DATA_BYTES: number;
2071
+ export const DEFAULT_SAB_IO_MAX_INSTANCES: number;
2072
+ export const DEFAULT_SAB_IO_SLOTS: number;
2073
+ export const FLATSQL_IO_CREATE: number;
2074
+ export const FLATSQL_IO_CREATE_PARENTS: number;
2075
+ export const FLATSQL_IO_DELETE_ON_CLOSE: number;
2076
+ export const FLATSQL_IO_ERR_ACCESS: number;
2077
+ export const FLATSQL_IO_ERR_BADHANDLE: number;
2078
+ export const FLATSQL_IO_ERR_BUSY: number;
2079
+ export const FLATSQL_IO_ERR_GENERIC: number;
2080
+ export const FLATSQL_IO_ERR_IO: number;
2081
+ export const FLATSQL_IO_ERR_NOENT: number;
2082
+ export const FLATSQL_IO_ERR_NOSPACE: number;
2083
+ export const FLATSQL_IO_EXCL: number;
2084
+ export const FLATSQL_IO_MAX_PATH_BYTES: number;
2085
+ export const FLATSQL_IO_OK: number;
2086
+ export const FLATSQL_IO_OPEN_DEFERRED: number;
2087
+ export const FLATSQL_IO_PROBE: number;
2088
+ export const FLATSQL_IO_READ: number;
2089
+ export const FLATSQL_IO_SERVER_CREATE_FLAGS: number;
2090
+ export const FLATSQL_IO_TRUNC: number;
2091
+ export const FLATSQL_IO_UNLINK: number;
2092
+ export const FLATSQL_IO_UNLINK_IF_UNUSED: number;
2093
+ export const FLATSQL_IO_WRITE: number;
2094
+ export const NODE_SYNC_FS_ANONYMOUS_INSTANCE: number;
2095
+ export const SAB_IO_DATA_MEMORY: number;
2096
+ export const SAB_IO_DATA_SLOT: number;
2097
+ export const SAB_IO_DOORBELL_ATOMICS: number;
2098
+ export const SAB_IO_DOORBELL_MESSAGE: number;
2099
+ export const SAB_IO_H_CHANNEL_ID: number;
2100
+ export const SAB_IO_H_CLAIM_HINT: number;
2101
+ export const SAB_IO_H_DATA_BYTES: number;
2102
+ export const SAB_IO_H_DOORBELL: number;
2103
+ export const SAB_IO_H_DOORBELL_MODE: number;
2104
+ export const SAB_IO_H_MAGIC: number;
2105
+ export const SAB_IO_H_MAX_INSTANCES: number;
2106
+ export const SAB_IO_H_SERVED: number;
2107
+ export const SAB_IO_H_SERVER_EPOCH: number;
2108
+ export const SAB_IO_H_SERVER_STATE: number;
2109
+ export const SAB_IO_H_SLOT_BYTES: number;
2110
+ export const SAB_IO_H_SLOT_COUNT: number;
2111
+ export const SAB_IO_H_SLOT_RELEASES: number;
2112
+ export const SAB_IO_H_VERSION: number;
2113
+ export const SAB_IO_INSTANCE_ATTACHED: number;
2114
+ export const SAB_IO_INSTANCE_NONE: number;
2115
+ export const SAB_IO_INSTANCE_REVOKED: number;
2116
+ export const SAB_IO_INSTANCE_REVOKING: number;
2117
+ export const SAB_IO_MAGIC: number;
2118
+ export const SAB_IO_NOTIFY_ATOMICS: number;
2119
+ export const SAB_IO_NOTIFY_MESSAGE: number;
2120
+ export const SAB_IO_OP_CLOSE: number;
2121
+ export const SAB_IO_OP_OPEN: number;
2122
+ export const SAB_IO_OP_READ: number;
2123
+ export const SAB_IO_OP_SIZE: number;
2124
+ export const SAB_IO_OP_SYNC: number;
2125
+ export const SAB_IO_OP_TRUNCATE: number;
2126
+ export const SAB_IO_OP_WRITE: number;
2127
+ export const SAB_IO_SCRATCH_INSTANCE: number;
2128
+ export const SAB_IO_SERVER_DEAD: number;
2129
+ export const SAB_IO_SERVER_NOT_STARTED: number;
2130
+ export const SAB_IO_SERVER_RUNNING: number;
2131
+ export const SAB_IO_SERVER_STOPPED: number;
2132
+ export const SAB_IO_SLOT_DONE: number;
2133
+ export const SAB_IO_SLOT_FREE: number;
2134
+ export const SAB_IO_SLOT_IDLE: number;
2135
+ export const SAB_IO_SLOT_PENDING: number;
2136
+ export const SAB_IO_SLOT_SERVICING: number;
2137
+ export const SAB_IO_STATUS_DEAD: number;
2138
+ export const SAB_IO_STATUS_REVOKED: number;
2139
+ export const SAB_IO_S_DATA_MODE: number;
2140
+ export const SAB_IO_S_DONE_SEQ: number;
2141
+ export const SAB_IO_S_F64_OFFSET: number;
2142
+ export const SAB_IO_S_F64_RESULT: number;
2143
+ export const SAB_IO_S_FLAGS: number;
2144
+ export const SAB_IO_S_HANDLE: number;
2145
+ export const SAB_IO_S_INSTANCE: number;
2146
+ export const SAB_IO_S_LEN: number;
2147
+ export const SAB_IO_S_NOTIFY: number;
2148
+ export const SAB_IO_S_OP: number;
2149
+ export const SAB_IO_S_OWNER: number;
2150
+ export const SAB_IO_S_PATH_LEN: number;
2151
+ export const SAB_IO_S_PTR: number;
2152
+ export const SAB_IO_S_SEQ: number;
2153
+ export const SAB_IO_S_STATE: number;
2154
+ export const SAB_IO_S_STATUS: number;
2155
+ export const SAB_IO_VERSION: number;
2156
+ export const SAB_IO_WAIT_SLICE_MS: number;
2157
+ export const FLATSQL_IO_IMPORT_MODULE: string;
2158
+ export const FLATSQL_IO_PROVIDER_DESCRIPTOR: string;
2159
+ export const FLATSQL_IO_WORKER_FILENAME: string;
2160
+ export const NODE_SYNC_FS_PROVIDER_DESCRIPTOR: string;
2161
+ export const OPFS_SHARED_HANDLE_MODE: string;
2162
+ export const FLATSQL_IO_FLAGS: Readonly<Record<string, number>>;
2163
+ export const FLATSQL_IO_STATUS: Readonly<Record<string, number>>;
2164
+ export const FLATSQL_IO_IMPORT_NAMES: readonly string[];
2165
+ export const FLATSQL_IO_TRACE_OPS: Readonly<Record<string, number>>;
2166
+
2167
+ /** Synchronous provider behind the seven env.flatsql_io_* imports. */
2168
+ export interface FlatsqlIoProvider {
2169
+ open(path: Uint8Array | string, flags: number): number;
2170
+ read(handle: number, ptr: number, len: number, offset: number): number;
2171
+ write(handle: number, ptr: number, len: number, offset: number): number;
2172
+ truncate(handle: number, size: number): number;
2173
+ sync(handle: number): number;
2174
+ size(handle: number): number;
2175
+ close(handle: number): number;
2176
+ }
2177
+ export interface FlatsqlIoSyncClient extends FlatsqlIoProvider {
2178
+ readonly instanceId: number;
2179
+ readonly requests: number;
2180
+ readInto(handle: number, view: Uint8Array, offset: number): number;
2181
+ writeFrom(handle: number, view: Uint8Array, offset: number): number;
2182
+ release(): void;
2183
+ }
2184
+ export interface FlatsqlIoAsyncClient {
2185
+ readonly instanceId: number;
2186
+ open(path: Uint8Array | string, flags: number): Promise<number>;
2187
+ read(handle: number, length: number, offset: number): Promise<Uint8Array | number>;
2188
+ write(handle: number, bytes: Uint8Array, offset: number): Promise<number>;
2189
+ truncate(handle: number, size: number): Promise<number>;
2190
+ sync(handle: number): Promise<number>;
2191
+ size(handle: number): Promise<number>;
2192
+ close(handle: number): Promise<number>;
2193
+ close$(): void;
2194
+ }
2195
+ export interface FlatsqlIoBackendFile {
2196
+ read(view: Uint8Array, options?: { at?: number }): number;
2197
+ write(view: Uint8Array, options?: { at?: number }): number;
2198
+ truncate(size: number): void;
2199
+ flush(): void;
2200
+ getSize(): number;
2201
+ close(): void;
2202
+ }
2203
+ export interface FlatsqlIoBackend {
2204
+ kind: string;
2205
+ init?(): Promise<Record<string, unknown>>;
2206
+ openFile(path: string, components: string[], flags: number): FlatsqlIoBackendFile | Promise<FlatsqlIoBackendFile>;
2207
+ exists(path: string, components: string[]): boolean | Promise<boolean>;
2208
+ remove(path: string, components: string[]): void | Promise<void>;
2209
+ }
2210
+ export interface FlatsqlIoMirrorOptions {
2211
+ buffer: SharedArrayBuffer;
2212
+ suffixes?: string[];
2213
+ write?: boolean;
2214
+ }
2215
+ export type FlatsqlIoExtraImport =
2216
+ | { provider: "flatsql-io"; instanceId?: number; channels: SharedArrayBuffer[]; mirror?: FlatsqlIoMirrorOptions; trace?: { buffer: SharedArrayBuffer } }
2217
+ | { provider: "flatsql-io-node"; root: string; table: SharedArrayBuffer; instanceId?: number; mirror?: FlatsqlIoMirrorOptions; trace?: { buffer: SharedArrayBuffer } }
2218
+ | { moduleUrl: string; exportName?: string; config?: unknown };
2219
+
2220
+ export class FlatsqlIoStatusError extends Error {
2221
+ constructor(status: number, message?: string);
2222
+ status: number;
2223
+ }
2224
+ export function flatsqlIoStatusForError(error: unknown, options?: { during?: "open" }): number;
2225
+ export function flatsqlIoStatusOf(error: unknown, options?: { during?: "open" }): number;
2226
+ export function splitFlatsqlIoPath(path: string): string[] | number;
2227
+ export function decodeFlatsqlIoPath(bytes: Uint8Array): string | number;
2228
+ export function hashFlatsqlIoPath(bytes: Uint8Array): number;
2229
+
2230
+ export function createSabIoChannelBuffer(options?: { slots?: number; dataBytes?: number; maxInstances?: number }): SharedArrayBuffer;
2231
+ export function describeSabIoChannel(buffer: SharedArrayBuffer): Record<string, unknown>;
2232
+ export function sabIoDoorbellChannelName(channelId: number): string;
2233
+ export function sabIoCompletionChannelName(channelId: number): string;
2234
+ export function createSabIoClient(options: { buffer: SharedArrayBuffer; instanceId?: number; getMemory?: () => WebAssembly.Memory | null; spinMicros?: number }): FlatsqlIoSyncClient;
2235
+ export function createSabIoAsyncClient(options: { buffer: SharedArrayBuffer; instanceId?: number; forceMessages?: boolean }): FlatsqlIoAsyncClient;
2236
+ export function sabIoInstanceState(buffer: SharedArrayBuffer, instanceId: number): number;
2237
+ export function revokeSabIoInstance(buffer: SharedArrayBuffer, instanceId: number, options?: { pollMs?: number }): Promise<void>;
2238
+ export function resetSabIoInstance(buffer: SharedArrayBuffer, instanceId: number): void;
2239
+ export function failPendingSabIoRequests(buffer: SharedArrayBuffer, status?: number): number;
2240
+ export function reclaimSabIoSlots(buffer: SharedArrayBuffer, instanceId: number): number;
2241
+ export function completeSabIoSlot(layout: unknown, slot: unknown, status: number, f64Result?: number): void;
2242
+
2243
+ export function createSabIoMirrorBuffer(options?: { entries?: number; entryBytes?: number; maxPathBytes?: number }): SharedArrayBuffer;
2244
+ export function sabIoMirrorEntryBytes(buffer: SharedArrayBuffer): number;
2245
+ export function findSabIoMirrorEntry(buffer: SharedArrayBuffer, path: string | Uint8Array): number;
2246
+ export function updateSabIoMirror(buffer: SharedArrayBuffer, path: string | Uint8Array, offset: number, bytes: Uint8Array, options?: { truncateTo?: number }): boolean;
2247
+ export function clearSabIoMirror(buffer: SharedArrayBuffer, path: string | Uint8Array): void;
2248
+ export function readSabIoMirror(buffer: SharedArrayBuffer, path: string | Uint8Array, dst: Uint8Array, offset: number): number;
2249
+ export function readSabIoMirrorEntry(buffer: SharedArrayBuffer, index: number, dst: Uint8Array, offset: number): number;
2250
+ export function createSabIoMirrorMatcher(suffixes?: string[]): (path: string) => boolean;
2251
+
2252
+ export function createFlatsqlIoImports(options: {
2253
+ getMemory: () => WebAssembly.Memory;
2254
+ provider: FlatsqlIoProvider | FlatsqlIoProvider[];
2255
+ mirror?: FlatsqlIoMirrorOptions;
2256
+ trace?: { buffer: SharedArrayBuffer };
2257
+ }): { env: Record<string, (...args: number[]) => number> };
2258
+ export function resolveFlatsqlIoDescriptor(descriptor: FlatsqlIoExtraImport, ctx: { getMemory: () => WebAssembly.Memory }): { imports: WebAssembly.Imports; close(): void };
2259
+ export function resolveExtraImports(extraImports: Array<FlatsqlIoExtraImport | ((ctx: unknown) => unknown)>, ctx: { memory: WebAssembly.Memory; getMemory: () => WebAssembly.Memory; tid?: number | null; workerIndex?: number | null }): { fragments: WebAssembly.Imports[]; close(): void };
2260
+ export function mergeImportFragments(imports: WebAssembly.Imports, fragments: WebAssembly.Imports[]): WebAssembly.Imports;
2261
+ export function createFlatsqlIoTraceBuffer(capacity?: number): SharedArrayBuffer;
2262
+ export function readFlatsqlIoTrace(buffer: SharedArrayBuffer): { recorded: number; capacity: number; samples: Array<{ op: number; micros: number }> };
2263
+
2264
+ export interface FlatsqlIoServer {
2265
+ readonly doorbellMode: number;
2266
+ readonly stats: Record<string, number | boolean>;
2267
+ start(): Promise<void>;
2268
+ stop(): Promise<void>;
2269
+ attachMemory(instanceId: number, memory: WebAssembly.Memory | SharedArrayBuffer): boolean;
2270
+ detachMemory(instanceId: number): void;
2271
+ preopen(paths: string[], flags?: number): Promise<number[]>;
2272
+ releasePreopen(paths: string[]): void;
2273
+ inventory(): { files: Array<{ path: string; refs: number; pins: number; opening: boolean; openStatus: number }>; handles: number };
2274
+ }
2275
+ export function createFlatsqlIoServer(options: {
2276
+ buffer: SharedArrayBuffer;
2277
+ backend: FlatsqlIoBackend;
2278
+ chunkBytes?: number;
2279
+ doorbell?: "auto" | "atomics" | "message";
2280
+ mirror?: { buffer: SharedArrayBuffer; match(path: string): boolean; write?: boolean };
2281
+ spinMicros?: number;
2282
+ onEvent?: (event: Record<string, unknown>) => void;
2283
+ }): FlatsqlIoServer;
2284
+ export function createFlatsqlIoMemoryBackend(options?: { maxBytes?: number }): FlatsqlIoBackend & {
2285
+ reset(): void;
2286
+ usage(): { files: number; allocatedBytes: number };
2287
+ list(): string[];
2288
+ };
2289
+ export function createOpfsIoBackend(options?: {
2290
+ rootDirectory?: string;
2291
+ handleMode?: "auto" | "shared" | "exclusive";
2292
+ openDelayMs?: (path: string) => number;
2293
+ root?: unknown;
2294
+ busyRetry?: { attempts: number; baseMs?: number };
2295
+ }): FlatsqlIoBackend & { readonly sharedModes: boolean; readonly sharedViews: boolean | null; clear(): Promise<void> };
2296
+ export function probeOpfsSharedHandleModes(root: unknown, name?: string): Promise<{ supported: boolean; detail: string }>;
2297
+ export function probeOpfsSharedViews(root: unknown, name?: string): Promise<boolean>;
2298
+
2299
+ export class FlatsqlIoWorkerStartError extends Error {
2300
+ lockUnavailable?: boolean;
2301
+ }
2302
+ export interface FlatsqlIoWorkerController {
2303
+ readonly buffer: SharedArrayBuffer;
2304
+ readonly info: Record<string, unknown> | null;
2305
+ readonly dead: boolean;
2306
+ readonly restarts: number;
2307
+ readonly worker: unknown;
2308
+ attachMemory(instanceId: number, memory: WebAssembly.Memory | SharedArrayBuffer): Promise<boolean>;
2309
+ detachMemory(instanceId: number): void;
2310
+ revoke(instanceId: number): Promise<void>;
2311
+ preopen(paths: string[], flags?: number): Promise<number[]>;
2312
+ releasePreopen(paths: string[]): void;
2313
+ stats(): Promise<Record<string, unknown>>;
2314
+ reset(): Promise<void>;
2315
+ clear(): Promise<unknown>;
2316
+ stop(): Promise<void>;
2317
+ kill(): void;
2318
+ }
2319
+ export function createFlatsqlIoWorker(options?: {
2320
+ buffer?: SharedArrayBuffer;
2321
+ channel?: { slots?: number; dataBytes?: number; maxInstances?: number };
2322
+ backend?: "opfs" | "memory";
2323
+ role?: "writer" | "reader";
2324
+ chunkBytes?: number;
2325
+ doorbell?: "auto" | "atomics" | "message";
2326
+ mirror?: FlatsqlIoMirrorOptions;
2327
+ rootDirectory?: string;
2328
+ handleMode?: "auto" | "shared" | "exclusive";
2329
+ memoryMaxBytes?: number;
2330
+ openDelay?: { pattern: string; ms: number } | null;
2331
+ lock?: { name: string; ifAvailable?: boolean } | null;
2332
+ busyRetry?: { attempts: number; baseMs?: number } | null;
2333
+ workerUrl?: string | URL;
2334
+ workerType?: "module" | "classic";
2335
+ onError?: (error: unknown, info: { restarted: boolean; failedRequests: number }) => void;
2336
+ restart?: boolean;
2337
+ }): Promise<FlatsqlIoWorkerController>;
2338
+
2339
+ export const FLATSQL_IO_CONFORMANCE_CASES: ReadonlyArray<{ name: string; run(io: unknown, dir: string): Promise<void> }>;
2340
+ export function runFlatsqlIoConformance(io: unknown, options?: { prefix?: string; skip?: string[] }): Promise<{ passed: string[]; failed: Array<{ name: string; error: string }>; skipped: string[] }>;
2341
+ export function conformanceAdapterForAsyncClient(client: FlatsqlIoAsyncClient): unknown;
2342
+
2343
+ export function probeWorkerCapabilities(): Promise<Record<string, unknown>>;
2344
+ export function workerCapabilityProbeSource(): string;
2345
+ export function probeBrowserCapabilities(options?: { timeoutMs?: number }): Promise<{
2346
+ page: Record<string, unknown>;
2347
+ worker: Record<string, unknown> | null;
2348
+ localStore: { supported: boolean; missing: string[]; doorbell: "atomics" | "message"; transfer: "direct" | "scratch" };
2349
+ }>;
2350
+ export function flatsqlLocalStoreGate(matrix: { page: Record<string, unknown>; worker: Record<string, unknown> | null }): {
2351
+ supported: boolean;
2352
+ missing: string[];
2353
+ doorbell: "atomics" | "message";
2354
+ transfer: "direct" | "scratch";
2355
+ };
2356
+
2357
+ export function createNodeSyncFsIoTable(options?: { maxHandles?: number; maxPathBytes?: number; maxInstances?: number }): SharedArrayBuffer;
2358
+ export function createNodeSyncFsIo(options: {
2359
+ root: string;
2360
+ table: SharedArrayBuffer;
2361
+ instanceId?: number;
2362
+ getMemory?: () => WebAssembly.Memory | null;
2363
+ interpose?: (op: string, fn: (...args: unknown[]) => unknown, args: unknown[]) => unknown;
2364
+ }): FlatsqlIoProvider & {
2365
+ readInto(handle: number, view: Uint8Array, offset: number): number;
2366
+ writeFrom(handle: number, view: Uint8Array, offset: number): number;
2367
+ closeLocalFds(): void;
2368
+ liveHandles(): number;
2369
+ localFdCount(): number;
2370
+ };
2371
+ export function revokeNodeSyncFsIoInstance(table: SharedArrayBuffer, instanceId: number): void;
2372
+ export function resetNodeSyncFsIoInstance(table: SharedArrayBuffer, instanceId: number): void;
2373
+ export function resolveNodeFlatsqlIoDescriptors(entries: unknown[]): unknown[];
2374
+
2375
+ export interface WasiThreadSpawnReport {
2376
+ poolSize: number | null;
2377
+ armed: number;
2378
+ failedToArm: number;
2379
+ spawned: number;
2380
+ declined: number;
2381
+ declinedByReason: Record<string, number>;
2382
+ lastDeclineReason: string | null;
2383
+ active: number;
2384
+ idle?: number;
2385
+ }
2386
+ export function isWasiThreadsModule(wasmModule: WebAssembly.Module): boolean;
2387
+ export function createWasiThreadSpawn(options: {
2388
+ wasmModule: WebAssembly.Module;
2389
+ memory: WebAssembly.Memory;
2390
+ requestedThreads?: number;
2391
+ poolSize?: number;
2392
+ extraImports?: FlatsqlIoExtraImport[];
2393
+ instanceId?: number;
2394
+ onGuestError?: (instanceId: number | null, tid: number | null, error: unknown) => void;
2395
+ onSpawnDeclined?: (event: { reason: string; poolSize: number | null; declined: number }) => void;
2396
+ probeTimeoutMs?: number;
2397
+ hostcallChannel?: unknown;
2398
+ processState?: unknown;
2399
+ requiresHostcalls?: boolean;
2400
+ enableBrowserThreads?: boolean;
2401
+ browserWorkerBaseUrl?: string | URL;
2402
+ browserWorkerUrl?: string | URL;
2403
+ browserWorkerType?: "module" | "classic";
2404
+ }): Promise<{
2405
+ threadSpawn(startArg: number): number;
2406
+ activeThreadCount(): number;
2407
+ spawnCount(): number;
2408
+ distinctOsThreadCount(): number;
2409
+ spawnReport(): WasiThreadSpawnReport;
2410
+ terminateAll(): Promise<void>;
2411
+ }>;
2412
+
2066
2413
  // --- Browser cron/timer driver (WS6.3) ---
2067
2414
  export interface ModuleTimerRunRecord {
2068
2415
  timerId: string;
@@ -2790,6 +3137,8 @@ export const DefaultInvokeExports: {
2790
3137
  allocSymbol: string;
2791
3138
  freeSymbol: string;
2792
3139
  commandSymbol: string;
3140
+ reactorInitializeSymbol: string;
3141
+ constructorsSymbol: string;
2793
3142
  };
2794
3143
 
2795
3144
  export const DrainPolicy: {
@@ -74,4 +74,10 @@ export const DefaultInvokeExports = Object.freeze({
74
74
  allocSymbol: "plugin_alloc",
75
75
  freeSymbol: "plugin_free",
76
76
  commandSymbol: "_start",
77
+ // Direct-surface initialization. A reactor exports `_initialize`; a command
78
+ // artifact that also serves the direct surface exports `__wasm_call_ctors`
79
+ // (its `_start` would run main). A direct host calls the first one present,
80
+ // once per instance, before the first direct call.
81
+ reactorInitializeSymbol: "_initialize",
82
+ constructorsSymbol: "__wasm_call_ctors",
77
83
  });
@@ -150,9 +150,12 @@ static WasmEdge_ModuleInstanceContext *memory_import(Threads *g) {
150
150
  return env;
151
151
  }
152
152
 
153
- // Reactor artifacts use the same PIV request/response bytes as commands. Stage
154
- // the request through the public allocator, then monitor the direct call with
155
- // the same worker-group lifecycle as _start. No guest ABI is reimplemented.
153
+ // Direct calls use the same PIV request/response bytes as commands. Stage the
154
+ // request through the public allocator, then monitor the direct call with the
155
+ // same worker-group lifecycle as _start. No guest ABI is reimplemented. The
156
+ // module initializes first, once: a reactor through _initialize, a command
157
+ // artifact serving the direct surface through __wasm_call_ctors (its _start
158
+ // would run main).
156
159
  static uint32_t stage_direct_request(Threads *g,
157
160
  WasmEdge_ModuleInstanceContext *instance,
158
161
  WasmEdge_MemoryInstanceContext *memory, WasmEdge_Value args[3]) {
@@ -171,6 +174,7 @@ static uint32_t stage_direct_request(Threads *g,
171
174
  }
172
175
  const WasmEdge_FunctionInstanceContext *init =
173
176
  WasmEdge_ModuleInstanceFindFunction(instance, name("_initialize"));
177
+ if (!init) init = WasmEdge_ModuleInstanceFindFunction(instance, name("__wasm_call_ctors"));
174
178
  if (init) require_result("initialize", WasmEdge_ExecutorInvoke(g->executor, init, NULL, 0, NULL, 0));
175
179
  const WasmEdge_FunctionInstanceContext *alloc =
176
180
  WasmEdge_ModuleInstanceFindFunction(instance, name("plugin_alloc"));
@@ -224,10 +228,12 @@ int main(int argc, char **argv) {
224
228
  if (!envs) return 1;
225
229
  uint32_t env_count = 0;
226
230
  bool stats = false;
231
+ bool force_direct = false;
227
232
  int first = 1;
228
233
  for (; first < argc; ++first) {
229
234
  if (strcmp(argv[first], "--enable-threads") == 0) continue;
230
235
  if (strcmp(argv[first], "--sdm-thread-stats") == 0) { stats = true; continue; }
236
+ if (strcmp(argv[first], "--sdm-direct") == 0) { force_direct = true; continue; }
231
237
  if (strcmp(argv[first], "--env") == 0 && first + 1 < argc) {
232
238
  envs[env_count++] = argv[++first];
233
239
  continue;
@@ -235,7 +241,7 @@ int main(int argc, char **argv) {
235
241
  break;
236
242
  }
237
243
  if (first >= argc || argv[first][0] == '-') {
238
- fprintf(stderr, "usage: %s [--env NAME=VALUE] module.wasm [args...]\n", argv[0]);
244
+ fprintf(stderr, "usage: %s [--sdm-direct] [--env NAME=VALUE] module.wasm [args...]\n", argv[0]);
239
245
  free(envs);
240
246
  return 2;
241
247
  }
@@ -269,7 +275,9 @@ int main(int argc, char **argv) {
269
275
  WasmEdge_ModuleInstanceContext *instance = NULL;
270
276
  require_result("instantiate", WasmEdge_ExecutorInstantiate(
271
277
  g.executor, &instance, g.store, g.ast));
272
- const WasmEdge_FunctionInstanceContext *start =
278
+ // --sdm-direct serves the direct surface of an artifact that also has a
279
+ // command entry; a reactor (no _start) is always served directly.
280
+ const WasmEdge_FunctionInstanceContext *start = force_direct ? NULL :
273
281
  WasmEdge_ModuleInstanceFindFunction(instance, name("_start"));
274
282
  bool direct = start == NULL;
275
283
  WasmEdge_Value direct_args[3], response_pointer;
@@ -499,6 +499,11 @@ function applyInjectedDivergence(runs, injectDivergence) {
499
499
  * @param {string[]} [options.lanes] - subset of PARITY_LANES. Defaults to ALL
500
500
  * THREE. Narrowing is always an explicit caller decision — lanes are never
501
501
  * skipped silently, and a single-lane run can never claim parity.
502
+ * @param {string} [options.surface] - "direct" runs EVERY lane on the direct
503
+ * surface: the browser harness, and the wasi-threads runner with
504
+ * --sdm-direct (the WasmEdge CLI has no direct surface, so a single-thread
505
+ * artifact cannot be compared on it). Omitted: the browser lane follows
506
+ * browserSurface and the WasmEdge lanes run the artifact's default surface.
502
507
  * @param {Object} [options.laneRunners] - {laneName: async (context) => runs[]}
503
508
  * override for tests. Default runners come from parityLanes.js.
504
509
  * @param {string} [options.injectDivergence] - fire-drill: XOR one output byte
@@ -555,7 +560,8 @@ export async function runParityHarness(options = {}) {
555
560
  chromeBinary: options.chromeBinary,
556
561
  wasmedgeBinary: options.wasmedgeBinary,
557
562
  wasmEdgeRunnerBinary: options.wasmEdgeRunnerBinary,
558
- browserSurface: options.browserSurface ??
563
+ surface: options.surface ?? null,
564
+ browserSurface: options.browserSurface ?? options.surface ??
559
565
  (WebAssembly.Module.exports(await WebAssembly.compile(loadableBytes)).some((entry) => entry.name === "_start") ? "command" : "direct"),
560
566
  dockerBinary: options.dockerBinary,
561
567
  dockerPlatform: options.dockerPlatform,
@@ -130,6 +130,21 @@ function wasmedgeInvocationArgs(planCase, plan, threadCount) {
130
130
  return args;
131
131
  }
132
132
 
133
+ // Runner flags that select the surface. Only the wasi-threads runner serves the
134
+ // direct surface of an artifact with a command entry; the WasmEdge CLI runs
135
+ // `_start` or nothing, so a direct-surface comparison on it would silently
136
+ // compare two different surfaces.
137
+ function surfaceArgs(context, threaded, lane) {
138
+ if (context.surface !== "direct") return [];
139
+ if (!threaded) {
140
+ throw new Error(
141
+ `${lane}: the WasmEdge CLI serves only the command surface of a single-thread artifact; ` +
142
+ "it cannot take part in a direct-surface parity run.",
143
+ );
144
+ }
145
+ return ["--sdm-direct"];
146
+ }
147
+
133
148
  async function stageModuleWorkdir(context, label) {
134
149
  const workdir = await mkdtemp(path.join(os.tmpdir(), `sdm-parity-${label}-`));
135
150
  // Stage the canonical loadable bytes (publication records stripped by the
@@ -183,6 +198,7 @@ export async function runNativeWasmEdgeLane(context) {
183
198
  `native binary ${binary}`,
184
199
  );
185
200
 
201
+ const directArgs = surfaceArgs(context, threaded, "native WasmEdge lane");
186
202
  const workdir = await stageModuleWorkdir(context, "native");
187
203
  const runs = [];
188
204
  try {
@@ -191,6 +207,7 @@ export async function runNativeWasmEdgeLane(context) {
191
207
  const outcome = await spawnWithStdin(
192
208
  binary,
193
209
  [...(threaded ? ["--sdm-thread-stats"] : []),
210
+ ...directArgs,
194
211
  ...wasmedgeInvocationArgs(planCase, context.plan, threadCount)],
195
212
  {
196
213
  cwd: workdir,
@@ -288,6 +305,7 @@ export async function runDockerWasmEdgeLane(context) {
288
305
  `docker WasmEdge lane: cannot execute "${dockerBinary}" (${error.message}).`,
289
306
  );
290
307
  }
308
+ const directArgs = surfaceArgs(context, threaded, "docker WasmEdge lane");
291
309
  const image = await ensureDockerParityImage(context);
292
310
 
293
311
  const workdir = await stageModuleWorkdir(context, "docker");
@@ -313,6 +331,7 @@ export async function runDockerWasmEdgeLane(context) {
313
331
  dockerArgs.push(
314
332
  image,
315
333
  ...(threaded ? ["--sdm-thread-stats"] : []),
334
+ ...directArgs,
316
335
  ...wasmedgeInvocationArgs(planCase, context.plan, threadCount),
317
336
  );
318
337
  const outcome = await spawnWithStdin(dockerBinary, dockerArgs, {