@oliphaunt/wasix-ts 0.1.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/ARCHITECTURE.md +655 -0
- package/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +404 -0
- package/THIRD_PARTY_NOTICES.md +20 -0
- package/lib/archive.d.ts +21 -0
- package/lib/archive.js +336 -0
- package/lib/asset-source.d.ts +4 -0
- package/lib/asset-source.js +15 -0
- package/lib/byte-channel.d.ts +27 -0
- package/lib/byte-channel.js +170 -0
- package/lib/client-common.d.ts +6 -0
- package/lib/client-common.js +36 -0
- package/lib/client.d.ts +7 -0
- package/lib/client.js +24 -0
- package/lib/database-root.d.ts +19 -0
- package/lib/database-root.js +139 -0
- package/lib/database.d.ts +135 -0
- package/lib/database.js +1039 -0
- package/lib/descriptor-validation.d.ts +10 -0
- package/lib/descriptor-validation.js +75 -0
- package/lib/direct-client-common.d.ts +50 -0
- package/lib/direct-client-common.js +717 -0
- package/lib/direct-client.d.ts +4 -0
- package/lib/direct-client.js +13 -0
- package/lib/direct.node.d.ts +2 -0
- package/lib/direct.node.js +2 -0
- package/lib/errors.d.ts +25 -0
- package/lib/errors.js +34 -0
- package/lib/extension-descriptor.d.ts +14 -0
- package/lib/extension-descriptor.js +419 -0
- package/lib/extensions.d.ts +68 -0
- package/lib/extensions.js +769 -0
- package/lib/host/LICENSE +21 -0
- package/lib/host/index.d.mts +123 -0
- package/lib/host/index.mjs +11 -0
- package/lib/host/provenance.json +16 -0
- package/lib/host/wasmer_js_bg.wasm +0 -0
- package/lib/host/worker.mjs +11 -0
- package/lib/host-runtime.d.ts +5 -0
- package/lib/host-runtime.js +13 -0
- package/lib/icu-descriptor.d.ts +3 -0
- package/lib/icu-descriptor.js +92 -0
- package/lib/index.bun.d.ts +2 -0
- package/lib/index.bun.js +2 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.deno.d.ts +2 -0
- package/lib/index.deno.js +2 -0
- package/lib/index.js +2 -0
- package/lib/index.node.d.ts +2 -0
- package/lib/index.node.js +2 -0
- package/lib/internal-common.d.ts +12 -0
- package/lib/internal-common.js +274 -0
- package/lib/internal.d.ts +5 -0
- package/lib/internal.js +42 -0
- package/lib/internal.node.d.ts +5 -0
- package/lib/internal.node.js +8 -0
- package/lib/native-addon.d.ts +111 -0
- package/lib/native-addon.js +223 -0
- package/lib/native-server.d.ts +21 -0
- package/lib/native-server.js +109 -0
- package/lib/native-session.d.ts +60 -0
- package/lib/native-session.js +565 -0
- package/lib/node-actor.d.ts +7 -0
- package/lib/node-actor.js +10 -0
- package/lib/node-client-common.d.ts +9 -0
- package/lib/node-client-common.js +35 -0
- package/lib/node-client.d.ts +4 -0
- package/lib/node-client.js +13 -0
- package/lib/node-direct.d.ts +7 -0
- package/lib/node-direct.js +10 -0
- package/lib/node-worker-options.d.ts +5 -0
- package/lib/node-worker-options.js +65 -0
- package/lib/node-worker-port.d.ts +4 -0
- package/lib/node-worker-port.js +116 -0
- package/lib/node-worker.d.ts +1 -0
- package/lib/node-worker.js +38 -0
- package/lib/pgwire-connection.d.ts +60 -0
- package/lib/pgwire-connection.js +528 -0
- package/lib/pgwire.d.ts +3 -0
- package/lib/pgwire.js +105 -0
- package/lib/physical-archive.d.ts +29 -0
- package/lib/physical-archive.js +527 -0
- package/lib/protocol.d.ts +1 -0
- package/lib/protocol.js +1 -0
- package/lib/public.d.ts +4 -0
- package/lib/public.js +3 -0
- package/lib/query.d.ts +1 -0
- package/lib/query.js +1 -0
- package/lib/rpc.d.ts +203 -0
- package/lib/rpc.js +84 -0
- package/lib/runtime-descriptor.d.ts +3 -0
- package/lib/runtime-descriptor.js +79 -0
- package/lib/server.node.d.ts +1 -0
- package/lib/server.node.js +1 -0
- package/lib/startup-config.d.ts +2 -0
- package/lib/startup-config.js +19 -0
- package/lib/storage/bun.d.ts +6 -0
- package/lib/storage/bun.js +6 -0
- package/lib/storage/deno.d.ts +7 -0
- package/lib/storage/deno.js +7 -0
- package/lib/storage/incremental-storage.d.ts +25 -0
- package/lib/storage/incremental-storage.js +154 -0
- package/lib/storage/indexed-db-provider.d.ts +40 -0
- package/lib/storage/indexed-db-provider.js +259 -0
- package/lib/storage/indexed-db.d.ts +9 -0
- package/lib/storage/indexed-db.js +11 -0
- package/lib/storage/node.d.ts +10 -0
- package/lib/storage/node.js +13 -0
- package/lib/storage/opfs-pool.d.ts +31 -0
- package/lib/storage/opfs-pool.js +1271 -0
- package/lib/storage/opfs-provider.d.ts +4 -0
- package/lib/storage/opfs-provider.js +257 -0
- package/lib/storage/opfs.d.ts +8 -0
- package/lib/storage/opfs.js +10 -0
- package/lib/storage/restore-cleanup.d.ts +4 -0
- package/lib/storage/restore-cleanup.js +22 -0
- package/lib/storage/web-lock.d.ts +2 -0
- package/lib/storage/web-lock.js +64 -0
- package/lib/storage-provider.d.ts +47 -0
- package/lib/storage-provider.js +141 -0
- package/lib/storage-snapshot.d.ts +44 -0
- package/lib/storage-snapshot.js +274 -0
- package/lib/storage.d.ts +46 -0
- package/lib/storage.js +83 -0
- package/lib/tool-runtime.d.ts +43 -0
- package/lib/tool-runtime.js +93 -0
- package/lib/tool-worker-common.d.ts +48 -0
- package/lib/tool-worker-common.js +97 -0
- package/lib/tool-worker.d.ts +1 -0
- package/lib/tool-worker.js +10 -0
- package/lib/types.d.ts +230 -0
- package/lib/types.js +1 -0
- package/lib/wasix-runtime.d.ts +24 -0
- package/lib/wasix-runtime.js +186 -0
- package/lib/worker-client.d.ts +4 -0
- package/lib/worker-client.js +45 -0
- package/lib/worker-dispatch.d.ts +11 -0
- package/lib/worker-dispatch.js +174 -0
- package/lib/worker-entry.bun.d.ts +2 -0
- package/lib/worker-entry.bun.js +2 -0
- package/lib/worker-entry.d.ts +2 -0
- package/lib/worker-entry.deno.d.ts +2 -0
- package/lib/worker-entry.deno.js +2 -0
- package/lib/worker-entry.js +2 -0
- package/lib/worker-entry.node.d.ts +2 -0
- package/lib/worker-entry.node.js +2 -0
- package/lib/worker-node-client.d.ts +4 -0
- package/lib/worker-node-client.js +55 -0
- package/lib/worker-rpc.d.ts +36 -0
- package/lib/worker-rpc.js +422 -0
- package/lib/worker-transfer.d.ts +6 -0
- package/lib/worker-transfer.js +11 -0
- package/lib/worker.d.ts +1 -0
- package/lib/worker.js +21 -0
- package/lib/zstd.d.ts +4 -0
- package/lib/zstd.js +12 -0
- package/node_modules/@oliphaunt/js-core/README.md +7 -0
- package/node_modules/@oliphaunt/js-core/dist/commonjs/protocol.d.ts +1 -0
- package/node_modules/@oliphaunt/js-core/dist/commonjs/protocol.js +22 -0
- package/node_modules/@oliphaunt/js-core/dist/commonjs/query.d.ts +255 -0
- package/node_modules/@oliphaunt/js-core/dist/commonjs/query.js +2068 -0
- package/node_modules/@oliphaunt/js-core/dist/module/package.json +3 -0
- package/node_modules/@oliphaunt/js-core/dist/module/protocol.d.ts +1 -0
- package/node_modules/@oliphaunt/js-core/dist/module/protocol.js +19 -0
- package/node_modules/@oliphaunt/js-core/dist/module/query.d.ts +255 -0
- package/node_modules/@oliphaunt/js-core/dist/module/query.js +2039 -0
- package/node_modules/@oliphaunt/js-core/package.json +21 -0
- package/package.json +122 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { WASIX_PROTOCOL_CALLBACK_CHUNK_BYTES, WasixDatabaseImpl, createWasixDeferred, normalizeWasixDatabaseIdentity, } from './database.js';
|
|
2
|
+
import { serializeOpenConfig } from './client-common.js';
|
|
3
|
+
import { toUint8Array } from './query.js';
|
|
4
|
+
import { deserializeWorkerError } from './rpc.js';
|
|
5
|
+
const STREAM_ACK = 0;
|
|
6
|
+
const STREAM_FAILED = 1;
|
|
7
|
+
/** @internal Open through a package-owned isolated host without burdening the direct graph. */
|
|
8
|
+
export async function openWasixWithWorker(createWorker, openOptions, validate, transferAssets = true) {
|
|
9
|
+
validate?.(openOptions);
|
|
10
|
+
return openWorkerDatabase(createWorker(openOptions), openOptions, transferAssets ? assetTransfers(openOptions) : []);
|
|
11
|
+
}
|
|
12
|
+
/** @internal Restore through a temporary package-owned isolated host. */
|
|
13
|
+
export async function restoreWasixWithWorker(createWorker, storage, bytes, validate) {
|
|
14
|
+
if (storage === undefined)
|
|
15
|
+
throw new TypeError('WASIX restore requires persistent storage');
|
|
16
|
+
const openOptions = serializeOpenConfig({ storage });
|
|
17
|
+
validate?.(openOptions);
|
|
18
|
+
const input = toUint8Array(bytes).slice();
|
|
19
|
+
const rpc = new WorkerRpc(createWorker(openOptions));
|
|
20
|
+
try {
|
|
21
|
+
await rpc.request({ method: 'restore', storage: openOptions.storage, bytes: input }, [
|
|
22
|
+
input.buffer,
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return rethrowAfterWorkerTermination(rpc, error, 'Oliphaunt WASIX worker restore and termination both failed');
|
|
27
|
+
}
|
|
28
|
+
await rpc.terminate();
|
|
29
|
+
}
|
|
30
|
+
/** @internal Correlates worker requests and fails them as one unit if the worker exits. */
|
|
31
|
+
export class WorkerRpc {
|
|
32
|
+
terminalState;
|
|
33
|
+
#worker;
|
|
34
|
+
#terminalState = new WorkerTerminalState();
|
|
35
|
+
#pending = new Map();
|
|
36
|
+
#nextId = 1;
|
|
37
|
+
#fatal;
|
|
38
|
+
#terminationRequested = false;
|
|
39
|
+
#termination;
|
|
40
|
+
constructor(worker) {
|
|
41
|
+
this.#worker = worker;
|
|
42
|
+
this.terminalState = this.#terminalState;
|
|
43
|
+
worker.onMessage((message) => {
|
|
44
|
+
const pending = this.#pending.get(message.id);
|
|
45
|
+
if (pending === undefined) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if ('kind' in message) {
|
|
49
|
+
if (pending.control === undefined || Atomics.load(pending.control, STREAM_FAILED) === 0) {
|
|
50
|
+
try {
|
|
51
|
+
pending.onChunk?.(message.value);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
if (pending.control !== undefined) {
|
|
55
|
+
Atomics.store(pending.control, STREAM_FAILED, 1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (pending.control !== undefined) {
|
|
60
|
+
Atomics.store(pending.control, STREAM_ACK, message.sequence);
|
|
61
|
+
Atomics.notify(pending.control, STREAM_ACK);
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this.#pending.delete(message.id);
|
|
66
|
+
if (message.ok) {
|
|
67
|
+
if (pending.stream) {
|
|
68
|
+
if (!('streamOutcome' in message)) {
|
|
69
|
+
pending.reject(new Error('Oliphaunt WASIX worker omitted its protocol stream outcome'));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
pending.resolve(message.streamOutcome);
|
|
73
|
+
}
|
|
74
|
+
else if ('streamOutcome' in message) {
|
|
75
|
+
pending.reject(new Error('Oliphaunt WASIX worker returned a stream outcome for a non-stream request'));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
pending.resolve(message.value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
pending.reject(deserializeWorkerError(message.error));
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
worker.onFatal((error) => this.#fail(error));
|
|
86
|
+
}
|
|
87
|
+
get supportsSharedMemory() {
|
|
88
|
+
return this.#worker.supportsSharedMemory !== false;
|
|
89
|
+
}
|
|
90
|
+
request(request, transfer = []) {
|
|
91
|
+
if (this.#fatal !== undefined) {
|
|
92
|
+
return Promise.reject(this.#fatal);
|
|
93
|
+
}
|
|
94
|
+
const id = this.#nextId++;
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
this.#pending.set(id, { resolve, reject });
|
|
97
|
+
try {
|
|
98
|
+
this.#worker.postMessage({ ...request, id }, transfer);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
this.#pending.delete(id);
|
|
102
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
stream(input, persistence, onChunk) {
|
|
107
|
+
if (this.#fatal !== undefined) {
|
|
108
|
+
return Promise.reject(this.#fatal);
|
|
109
|
+
}
|
|
110
|
+
const id = this.#nextId++;
|
|
111
|
+
const control = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 2));
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
this.#pending.set(id, {
|
|
114
|
+
resolve: (value) => {
|
|
115
|
+
if (value === 'complete' || value === 'callbackAborted') {
|
|
116
|
+
resolve(value);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
reject(new Error('Oliphaunt WASIX worker returned an invalid protocol stream outcome'));
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
reject,
|
|
123
|
+
onChunk,
|
|
124
|
+
control,
|
|
125
|
+
stream: true,
|
|
126
|
+
});
|
|
127
|
+
const request = {
|
|
128
|
+
id,
|
|
129
|
+
method: 'execStream',
|
|
130
|
+
input,
|
|
131
|
+
persistence,
|
|
132
|
+
control: control.buffer,
|
|
133
|
+
};
|
|
134
|
+
try {
|
|
135
|
+
this.#worker.postMessage(request, [input.buffer]);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
this.#pending.delete(id);
|
|
139
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
terminate() {
|
|
144
|
+
this.#stop(this.#fatal ?? new Error('Oliphaunt WASIX worker was terminated'));
|
|
145
|
+
return this.#termination ?? Promise.resolve();
|
|
146
|
+
}
|
|
147
|
+
expectSelfExit() {
|
|
148
|
+
if (this.#fatal !== undefined)
|
|
149
|
+
return Promise.reject(this.#fatal);
|
|
150
|
+
try {
|
|
151
|
+
return this.#worker.expectSelfExit?.();
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
#fail(error) {
|
|
158
|
+
// A package-requested terminate is expected ownership teardown, not an
|
|
159
|
+
// independently observed crash. The public close state remains governed
|
|
160
|
+
// by its memoized close attempt in that case.
|
|
161
|
+
if (!this.#terminationRequested) {
|
|
162
|
+
this.#terminalState.fail(error);
|
|
163
|
+
}
|
|
164
|
+
this.#stop(error);
|
|
165
|
+
}
|
|
166
|
+
#stop(error) {
|
|
167
|
+
if (this.#fatal === undefined) {
|
|
168
|
+
this.#fatal = error;
|
|
169
|
+
this.#rejectAll(error);
|
|
170
|
+
}
|
|
171
|
+
if (this.#termination !== undefined) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.#terminationRequested = true;
|
|
175
|
+
try {
|
|
176
|
+
this.#termination = Promise.resolve(this.#worker.terminate());
|
|
177
|
+
}
|
|
178
|
+
catch (terminationError) {
|
|
179
|
+
this.#termination = Promise.reject(terminationError instanceof Error ? terminationError : new Error(String(terminationError)));
|
|
180
|
+
}
|
|
181
|
+
// Fatal-event cleanup has no awaiting caller. Keep the original promise
|
|
182
|
+
// available to close/open while preventing an unhandled rejection.
|
|
183
|
+
void this.#termination.catch(() => undefined);
|
|
184
|
+
}
|
|
185
|
+
#rejectAll(error) {
|
|
186
|
+
for (const pending of this.#pending.values()) {
|
|
187
|
+
pending.reject(error);
|
|
188
|
+
}
|
|
189
|
+
this.#pending.clear();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** @internal Opens exactly one database and guarantees worker cleanup on failure. */
|
|
193
|
+
export async function openWorkerDatabase(worker, options, transfer = []) {
|
|
194
|
+
const rpc = new WorkerRpc(worker);
|
|
195
|
+
try {
|
|
196
|
+
await rpc.request({ method: 'open', options }, transfer);
|
|
197
|
+
return new WasixDatabaseImpl(new WorkerDatabaseSession(rpc, options));
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
return rethrowAfterWorkerTermination(rpc, error, 'Oliphaunt WASIX worker open and termination both failed');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function rethrowAfterWorkerTermination(rpc, primaryFailure, aggregateMessage) {
|
|
204
|
+
try {
|
|
205
|
+
await rpc.terminate();
|
|
206
|
+
}
|
|
207
|
+
catch (terminationFailure) {
|
|
208
|
+
throw new AggregateError([primaryFailure, terminationFailure], aggregateMessage);
|
|
209
|
+
}
|
|
210
|
+
throw primaryFailure;
|
|
211
|
+
}
|
|
212
|
+
function assetTransfers(options) {
|
|
213
|
+
const transfer = [];
|
|
214
|
+
const seen = new Set();
|
|
215
|
+
appendAssetTransfer(options.runtime.runtimeArchive.source, transfer, seen);
|
|
216
|
+
appendAssetTransfer(options.runtime.manifest.source, transfer, seen);
|
|
217
|
+
if (options.icu !== undefined) {
|
|
218
|
+
appendAssetTransfer(options.icu.dataArchive.source, transfer, seen);
|
|
219
|
+
appendAssetTransfer(options.icu.clusterSeedArchive.source, transfer, seen);
|
|
220
|
+
appendAssetTransfer(options.icu.clusterSeedManifest.source, transfer, seen);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
appendAssetTransfer(options.runtime.standardSeedArchive.source, transfer, seen);
|
|
224
|
+
appendAssetTransfer(options.runtime.standardSeedManifest.source, transfer, seen);
|
|
225
|
+
}
|
|
226
|
+
for (const carrier of Object.values(options.extensionCarriers)) {
|
|
227
|
+
appendAssetTransfer(carrier.source, transfer, seen);
|
|
228
|
+
}
|
|
229
|
+
return transfer;
|
|
230
|
+
}
|
|
231
|
+
function appendAssetTransfer(source, transfer, seen) {
|
|
232
|
+
if (!(source instanceof Uint8Array) || !(source.buffer instanceof ArrayBuffer)) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (!seen.has(source.buffer)) {
|
|
236
|
+
seen.add(source.buffer);
|
|
237
|
+
transfer.push(source.buffer);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
class WorkerDatabaseSession {
|
|
241
|
+
supportsProtocolConnections;
|
|
242
|
+
identity;
|
|
243
|
+
terminalState;
|
|
244
|
+
#rpc;
|
|
245
|
+
#closed = false;
|
|
246
|
+
#closeAttempt;
|
|
247
|
+
constructor(rpc, options) {
|
|
248
|
+
this.#rpc = rpc;
|
|
249
|
+
this.supportsProtocolConnections = rpc.supportsSharedMemory;
|
|
250
|
+
this.terminalState = rpc.terminalState;
|
|
251
|
+
this.identity = normalizeWasixDatabaseIdentity(options.username, options.database);
|
|
252
|
+
}
|
|
253
|
+
async exec(input, persistence = 'sync') {
|
|
254
|
+
this.#assertOpen();
|
|
255
|
+
const response = await this.#rpc.request({ method: 'exec', input, persistence }, [
|
|
256
|
+
input.buffer,
|
|
257
|
+
]);
|
|
258
|
+
if (!(response instanceof Uint8Array)) {
|
|
259
|
+
throw new Error('Oliphaunt WASIX worker returned an invalid protocol response');
|
|
260
|
+
}
|
|
261
|
+
return response;
|
|
262
|
+
}
|
|
263
|
+
execStream(input, onChunk, persistence = 'sync') {
|
|
264
|
+
this.#assertOpen();
|
|
265
|
+
if (!this.#rpc.supportsSharedMemory) {
|
|
266
|
+
return this.#execBufferedStream(input, onChunk, persistence);
|
|
267
|
+
}
|
|
268
|
+
return this.#rpc.stream(input, persistence, onChunk);
|
|
269
|
+
}
|
|
270
|
+
async #execBufferedStream(input, onChunk, persistence) {
|
|
271
|
+
const response = await this.exec(input, persistence);
|
|
272
|
+
try {
|
|
273
|
+
for (let offset = 0; offset < response.length; offset += WASIX_PROTOCOL_CALLBACK_CHUNK_BYTES) {
|
|
274
|
+
onChunk(response.slice(offset, offset + WASIX_PROTOCOL_CALLBACK_CHUNK_BYTES));
|
|
275
|
+
}
|
|
276
|
+
return 'complete';
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
// The complete response is already owned by the parent, so stopping
|
|
280
|
+
// callback delivery cannot leave the database protocol state unknown.
|
|
281
|
+
return 'callbackAborted';
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async sync(boundary) {
|
|
285
|
+
this.#assertOpen();
|
|
286
|
+
await this.#rpc.request({ method: 'sync', boundary });
|
|
287
|
+
}
|
|
288
|
+
async backup() {
|
|
289
|
+
this.#assertOpen();
|
|
290
|
+
const response = await this.#rpc.request({ method: 'backup' });
|
|
291
|
+
if (!(response instanceof Uint8Array)) {
|
|
292
|
+
throw new Error('Oliphaunt WASIX worker returned an invalid physical archive');
|
|
293
|
+
}
|
|
294
|
+
return response;
|
|
295
|
+
}
|
|
296
|
+
async runPgDump(options) {
|
|
297
|
+
this.#assertOpen();
|
|
298
|
+
const response = await this.#rpc.request({
|
|
299
|
+
method: 'runPgDump',
|
|
300
|
+
tool: options.tool,
|
|
301
|
+
args: [...options.args],
|
|
302
|
+
});
|
|
303
|
+
if (!isWasixToolProcessResult(response)) {
|
|
304
|
+
throw new Error('Oliphaunt WASIX worker returned an invalid pg_dump result');
|
|
305
|
+
}
|
|
306
|
+
return response;
|
|
307
|
+
}
|
|
308
|
+
async runTool(options) {
|
|
309
|
+
this.#assertOpen();
|
|
310
|
+
const stdin = options.stdin;
|
|
311
|
+
const transferableStdin = stdin !== undefined &&
|
|
312
|
+
stdin.buffer instanceof ArrayBuffer &&
|
|
313
|
+
stdin.byteOffset === 0 &&
|
|
314
|
+
stdin.byteLength === stdin.buffer.byteLength;
|
|
315
|
+
const response = await this.#rpc.request({
|
|
316
|
+
method: 'runTool',
|
|
317
|
+
options: {
|
|
318
|
+
...options,
|
|
319
|
+
// The release addon owns the verified tool payload. Preserve only
|
|
320
|
+
// the descriptor identity across the Worker boundary.
|
|
321
|
+
tool: { ...options.tool, source: 'oliphaunt:wasix-napi-embedded' },
|
|
322
|
+
args: [...options.args],
|
|
323
|
+
...(stdin === undefined ? {} : { stdin }),
|
|
324
|
+
},
|
|
325
|
+
}, transferableStdin && stdin !== undefined ? [stdin.buffer] : []);
|
|
326
|
+
if (!isWasixToolProcessResult(response)) {
|
|
327
|
+
throw new Error('Oliphaunt WASIX worker returned an invalid native tool result');
|
|
328
|
+
}
|
|
329
|
+
return response;
|
|
330
|
+
}
|
|
331
|
+
async serve(connection, mode) {
|
|
332
|
+
this.#assertOpen();
|
|
333
|
+
if (!this.#rpc.supportsSharedMemory) {
|
|
334
|
+
throw new Error('Oliphaunt WASIX worker execution does not support shared-memory protocol connections');
|
|
335
|
+
}
|
|
336
|
+
await this.#rpc.request({ method: 'serve', connection, mode });
|
|
337
|
+
}
|
|
338
|
+
close() {
|
|
339
|
+
if (this.#closeAttempt !== undefined)
|
|
340
|
+
return this.#closeAttempt;
|
|
341
|
+
this.#closed = true;
|
|
342
|
+
const attempt = createWasixDeferred();
|
|
343
|
+
this.#closeAttempt = attempt.promise;
|
|
344
|
+
void this.#closeOrderly().then(attempt.resolve, attempt.reject);
|
|
345
|
+
return attempt.promise;
|
|
346
|
+
}
|
|
347
|
+
async #closeOrderly() {
|
|
348
|
+
const selfExit = this.#rpc.expectSelfExit();
|
|
349
|
+
// An early Worker failure can reject this before the close reply rejects.
|
|
350
|
+
// Observe it immediately, then preserve the same rejection for the ordered
|
|
351
|
+
// aggregation below.
|
|
352
|
+
void selfExit?.catch(() => undefined);
|
|
353
|
+
let requestFailed = false;
|
|
354
|
+
let requestFailure;
|
|
355
|
+
try {
|
|
356
|
+
await this.#rpc.request({ method: 'close' });
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
requestFailed = true;
|
|
360
|
+
requestFailure = error;
|
|
361
|
+
}
|
|
362
|
+
let terminationFailed = false;
|
|
363
|
+
let terminationFailure;
|
|
364
|
+
try {
|
|
365
|
+
if (selfExit !== undefined) {
|
|
366
|
+
// A Node-compatible Worker acknowledges close only by exiting itself
|
|
367
|
+
// after the dispatcher has unwound and released its direct native
|
|
368
|
+
// handle. Wait for that full quiescence even when native close reports
|
|
369
|
+
// an error; the error response alone is not permission to terminate a
|
|
370
|
+
// Worker which may still be finishing Node-API cleanup. The observed
|
|
371
|
+
// exit is the completed host teardown contract; Bun does not settle a
|
|
372
|
+
// redundant terminate() after that event.
|
|
373
|
+
await selfExit;
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
// Browser Workers retain their existing post-close termination path.
|
|
377
|
+
await this.#rpc.terminate();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
terminationFailed = true;
|
|
382
|
+
terminationFailure = error;
|
|
383
|
+
}
|
|
384
|
+
if (requestFailed && terminationFailed) {
|
|
385
|
+
if (requestFailure === terminationFailure) {
|
|
386
|
+
throw requestFailure;
|
|
387
|
+
}
|
|
388
|
+
throw new AggregateError([requestFailure, terminationFailure], 'Oliphaunt WASIX worker close and termination both failed');
|
|
389
|
+
}
|
|
390
|
+
if (requestFailed) {
|
|
391
|
+
throw requestFailure;
|
|
392
|
+
}
|
|
393
|
+
if (terminationFailed) {
|
|
394
|
+
throw terminationFailure;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
#assertOpen() {
|
|
398
|
+
if (this.#closed) {
|
|
399
|
+
throw new Error('Oliphaunt WASIX worker session is closed');
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
class WorkerTerminalState {
|
|
404
|
+
#failure;
|
|
405
|
+
get terminal() {
|
|
406
|
+
return this.#failure !== undefined;
|
|
407
|
+
}
|
|
408
|
+
get failure() {
|
|
409
|
+
return this.#failure;
|
|
410
|
+
}
|
|
411
|
+
fail(error) {
|
|
412
|
+
this.#failure ??= error;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function isWasixToolProcessResult(value) {
|
|
416
|
+
return (typeof value === 'object' &&
|
|
417
|
+
value !== null &&
|
|
418
|
+
!(value instanceof Uint8Array) &&
|
|
419
|
+
Number.isSafeInteger(value.exitCode) &&
|
|
420
|
+
value.stdout instanceof Uint8Array &&
|
|
421
|
+
value.stderr instanceof Uint8Array);
|
|
422
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type TransferableBytes = Readonly<{
|
|
2
|
+
value: Uint8Array;
|
|
3
|
+
transfer: readonly ArrayBuffer[];
|
|
4
|
+
}>;
|
|
5
|
+
/** @internal Never transfer unrelated bytes from a larger backing buffer. */
|
|
6
|
+
export declare function prepareTransferableBytes(value: Uint8Array): TransferableBytes;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** @internal Never transfer unrelated bytes from a larger backing buffer. */
|
|
2
|
+
export function prepareTransferableBytes(value) {
|
|
3
|
+
if (!(value.buffer instanceof ArrayBuffer)) {
|
|
4
|
+
return { value, transfer: [] };
|
|
5
|
+
}
|
|
6
|
+
if (value.byteOffset === 0 && value.byteLength === value.buffer.byteLength) {
|
|
7
|
+
return { value, transfer: [value.buffer] };
|
|
8
|
+
}
|
|
9
|
+
const exact = value.slice();
|
|
10
|
+
return { value: exact, transfer: [exact.buffer] };
|
|
11
|
+
}
|
package/lib/worker.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/worker.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/// <reference lib="webworker" />
|
|
2
|
+
import * as host from './host/index.mjs';
|
|
3
|
+
import { openBrowserWorkerSession } from './direct-client-common.js';
|
|
4
|
+
import { createWorkerSessionDispatcher } from './worker-dispatch.js';
|
|
5
|
+
const scope = globalThis;
|
|
6
|
+
const directHost = {
|
|
7
|
+
Directory: host.Directory,
|
|
8
|
+
init: host.init,
|
|
9
|
+
instantiateOliphauntDirect: host.instantiateOliphauntDirect,
|
|
10
|
+
prepareOliphauntTool: host.prepareOliphauntTool,
|
|
11
|
+
runOliphauntToolDirect: host.runOliphauntToolDirect,
|
|
12
|
+
};
|
|
13
|
+
// This package worker is the isolation boundary. Opening the direct session
|
|
14
|
+
// here keeps PostgreSQL and synchronous OPFS handles in the same realm.
|
|
15
|
+
const dispatch = createWorkerSessionDispatcher((options) => openBrowserWorkerSession(options, directHost), respond);
|
|
16
|
+
scope.addEventListener('message', (event) => {
|
|
17
|
+
void dispatch(event.data);
|
|
18
|
+
});
|
|
19
|
+
function respond(response, transfer = []) {
|
|
20
|
+
scope.postMessage(response, [...transfer]);
|
|
21
|
+
}
|
package/lib/zstd.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type ZstdDecompressor = (bytes: Uint8Array) => Uint8Array;
|
|
2
|
+
/** @internal Installed by a host realm when it has a native implementation. */
|
|
3
|
+
export declare function installZstdDecompressor(decompressor: ZstdDecompressor): void;
|
|
4
|
+
export declare function decompressZstd(bytes: Uint8Array): Uint8Array;
|
package/lib/zstd.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { decompress as decompressWithFzstd } from 'fzstd';
|
|
2
|
+
let hostZstdDecompressor;
|
|
3
|
+
/** @internal Installed by a host realm when it has a native implementation. */
|
|
4
|
+
export function installZstdDecompressor(decompressor) {
|
|
5
|
+
if (hostZstdDecompressor !== undefined) {
|
|
6
|
+
throw new Error('Oliphaunt WASIX zstd decompressor is already installed');
|
|
7
|
+
}
|
|
8
|
+
hostZstdDecompressor = decompressor;
|
|
9
|
+
}
|
|
10
|
+
export function decompressZstd(bytes) {
|
|
11
|
+
return (hostZstdDecompressor ?? decompressWithFzstd)(bytes);
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Shared JavaScript Core
|
|
2
|
+
|
|
3
|
+
Canonical TypeScript helpers shared by the JavaScript, React Native, and WASIX
|
|
4
|
+
TypeScript SDKs through the private `@oliphaunt/js-core` workspace package.
|
|
5
|
+
|
|
6
|
+
Moon builds its ESM and CommonJS exports once. Published SDKs bundle that
|
|
7
|
+
minimal private package so registry consumers do not need another dependency.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function simpleQuery(sql: string): Uint8Array;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.simpleQuery = simpleQuery;
|
|
4
|
+
const encoder = new TextEncoder();
|
|
5
|
+
function simpleQuery(sql) {
|
|
6
|
+
if (sql.includes('\0')) {
|
|
7
|
+
throw new Error('simple query SQL must not contain NUL bytes');
|
|
8
|
+
}
|
|
9
|
+
const body = encoder.encode(sql);
|
|
10
|
+
const packet = new Uint8Array(body.length + 6);
|
|
11
|
+
packet[0] = 'Q'.charCodeAt(0);
|
|
12
|
+
writeI32(packet, 1, body.length + 5);
|
|
13
|
+
packet.set(body, 5);
|
|
14
|
+
packet[packet.length - 1] = 0;
|
|
15
|
+
return packet;
|
|
16
|
+
}
|
|
17
|
+
function writeI32(bytes, offset, value) {
|
|
18
|
+
bytes[offset] = (value >>> 24) & 0xff;
|
|
19
|
+
bytes[offset + 1] = (value >>> 16) & 0xff;
|
|
20
|
+
bytes[offset + 2] = (value >>> 8) & 0xff;
|
|
21
|
+
bytes[offset + 3] = value & 0xff;
|
|
22
|
+
}
|