@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,274 @@
|
|
|
1
|
+
export const VOLATILE_DATABASE_FILES = new Set(['postmaster.opts', 'postmaster.pid']);
|
|
2
|
+
export function splitStorageDeltaDeletes(delta) {
|
|
3
|
+
const upserts = new Set([...delta.directories, ...delta.files.map(({ path }) => path)]);
|
|
4
|
+
return {
|
|
5
|
+
replacements: delta.deleted.filter((path) => upserts.has(path)),
|
|
6
|
+
removals: delta.deleted.filter((path) => !upserts.has(path)),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export async function snapshotStorageDirectory(directory, filter = {}) {
|
|
10
|
+
const directories = [];
|
|
11
|
+
const files = [];
|
|
12
|
+
const walk = async (parent) => {
|
|
13
|
+
const entries = [...(await directory.readDir(parent))].sort((left, right) => left.name.localeCompare(right.name));
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
validateDirectoryEntryName(entry.name);
|
|
16
|
+
const path = parent.length === 0 ? entry.name : `${parent}/${entry.name}`;
|
|
17
|
+
if (parent.length === 0 && VOLATILE_DATABASE_FILES.has(entry.name))
|
|
18
|
+
continue;
|
|
19
|
+
if (entry.type === 'dir') {
|
|
20
|
+
directories.push(path);
|
|
21
|
+
if (filter.skipDirectoryContents?.(path) !== true)
|
|
22
|
+
await walk(path);
|
|
23
|
+
}
|
|
24
|
+
else if (entry.type === 'file') {
|
|
25
|
+
if (filter.skipFile?.(path) === true)
|
|
26
|
+
continue;
|
|
27
|
+
files.push({ path, bytes: (await directory.readFile(path)).slice() });
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
throw new Error(`Wasmer cannot snapshot PGDATA entry ${JSON.stringify(path)} of unknown type`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
await walk('');
|
|
35
|
+
return {
|
|
36
|
+
schema: 'oliphaunt-wasix-directory-snapshot-v1',
|
|
37
|
+
directories,
|
|
38
|
+
files,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Read only journaled PGDATA paths, falling back to a complete snapshot when
|
|
43
|
+
* the host does not expose mutation tracking or no durable generation exists.
|
|
44
|
+
*/
|
|
45
|
+
export async function snapshotStorageDelta(directory, persistedEntries, forceFull = false) {
|
|
46
|
+
if (forceFull || directory.changedPaths === undefined || directory.entryType === undefined) {
|
|
47
|
+
const snapshot = await snapshotStorageDirectory(directory);
|
|
48
|
+
const current = new Map([
|
|
49
|
+
...snapshot.directories.map((path) => [path, 'dir']),
|
|
50
|
+
...snapshot.files.map(({ path }) => [path, 'file']),
|
|
51
|
+
]);
|
|
52
|
+
return {
|
|
53
|
+
directories: snapshot.directories,
|
|
54
|
+
files: snapshot.files,
|
|
55
|
+
deleted: [...persistedEntries]
|
|
56
|
+
.filter(([path, type]) => current.get(path) !== type)
|
|
57
|
+
.map(([path]) => path)
|
|
58
|
+
.sort(comparePathDepthDescending),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const touched = collapseTouchedPaths(await directory.changedPaths());
|
|
62
|
+
if (touched.length === 0) {
|
|
63
|
+
return { directories: [], files: [], deleted: [] };
|
|
64
|
+
}
|
|
65
|
+
const directories = new Map();
|
|
66
|
+
const files = new Map();
|
|
67
|
+
const current = new Map();
|
|
68
|
+
const inspect = async (path) => {
|
|
69
|
+
if (isVolatilePath(path))
|
|
70
|
+
return;
|
|
71
|
+
const type = await directory.entryType?.(path);
|
|
72
|
+
if (type === 'missing')
|
|
73
|
+
return;
|
|
74
|
+
if (type !== 'dir' && type !== 'file') {
|
|
75
|
+
throw new Error(`Wasmer cannot persist PGDATA entry ${JSON.stringify(path)} of unknown type`);
|
|
76
|
+
}
|
|
77
|
+
if (path.length > 0)
|
|
78
|
+
current.set(path, type);
|
|
79
|
+
if (type === 'file') {
|
|
80
|
+
files.set(path, {
|
|
81
|
+
path,
|
|
82
|
+
bytes: (await directory.readFile(path)).slice(),
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (path.length > 0)
|
|
87
|
+
directories.set(path, path);
|
|
88
|
+
const entries = [...(await directory.readDir(path))].sort((left, right) => left.name.localeCompare(right.name));
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
validateDirectoryEntryName(entry.name);
|
|
91
|
+
const child = path.length === 0 ? entry.name : `${path}/${entry.name}`;
|
|
92
|
+
await inspect(child);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
for (const path of touched)
|
|
96
|
+
await inspect(path);
|
|
97
|
+
const deleted = [...persistedEntries]
|
|
98
|
+
.filter(([path, type]) => touched.some((root) => pathIsWithin(path, root)) && current.get(path) !== type)
|
|
99
|
+
.map(([path]) => path)
|
|
100
|
+
.sort(comparePathDepthDescending);
|
|
101
|
+
return {
|
|
102
|
+
directories: [...directories.keys()].sort(compareDirectoryDepth),
|
|
103
|
+
files: [...files.values()].sort((left, right) => left.path.localeCompare(right.path)),
|
|
104
|
+
deleted,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export function applyStorageDeltaToEntries(persistedEntries, delta) {
|
|
108
|
+
for (const path of delta.deleted)
|
|
109
|
+
persistedEntries.delete(path);
|
|
110
|
+
for (const path of delta.directories)
|
|
111
|
+
persistedEntries.set(path, 'dir');
|
|
112
|
+
for (const { path } of delta.files)
|
|
113
|
+
persistedEntries.set(path, 'file');
|
|
114
|
+
}
|
|
115
|
+
export function validateStoredSnapshot(value, expectedPostgresMajor, context) {
|
|
116
|
+
let snapshot;
|
|
117
|
+
try {
|
|
118
|
+
snapshot = requireRecord(value, `${context.label} snapshot`);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
throw context.corrupt(`has a malformed directory snapshot: ${describeError(error)}`, error);
|
|
122
|
+
}
|
|
123
|
+
if (snapshot.schema !== 'oliphaunt-wasix-directory-snapshot-v1') {
|
|
124
|
+
throw context.corrupt('has an unsupported directory snapshot');
|
|
125
|
+
}
|
|
126
|
+
if (!Array.isArray(snapshot.directories) || !Array.isArray(snapshot.files)) {
|
|
127
|
+
throw context.corrupt('has malformed directory or file rows');
|
|
128
|
+
}
|
|
129
|
+
const directories = [];
|
|
130
|
+
const files = [];
|
|
131
|
+
const paths = new Set();
|
|
132
|
+
for (const path of snapshot.directories) {
|
|
133
|
+
validateStoredPath(path, context);
|
|
134
|
+
if (paths.has(path))
|
|
135
|
+
throw context.corrupt(`repeats snapshot path ${JSON.stringify(path)}`);
|
|
136
|
+
paths.add(path);
|
|
137
|
+
directories.push(path);
|
|
138
|
+
}
|
|
139
|
+
for (const value of snapshot.files) {
|
|
140
|
+
let file;
|
|
141
|
+
try {
|
|
142
|
+
file = requireRecord(value, `${context.label} file row`);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
throw context.corrupt(`has a malformed file row: ${describeError(error)}`, error);
|
|
146
|
+
}
|
|
147
|
+
validateStoredPath(file.path, context);
|
|
148
|
+
if (!(file.bytes instanceof Uint8Array)) {
|
|
149
|
+
throw context.corrupt(`has non-binary contents for ${JSON.stringify(file.path)}`);
|
|
150
|
+
}
|
|
151
|
+
if (paths.has(file.path)) {
|
|
152
|
+
throw context.corrupt(`repeats snapshot path ${JSON.stringify(file.path)}`);
|
|
153
|
+
}
|
|
154
|
+
paths.add(file.path);
|
|
155
|
+
files.push({ path: file.path, bytes: file.bytes.slice() });
|
|
156
|
+
}
|
|
157
|
+
validateSnapshotSemantics(directories, files, paths, expectedPostgresMajor, context);
|
|
158
|
+
return {
|
|
159
|
+
schema: 'oliphaunt-wasix-directory-snapshot-v1',
|
|
160
|
+
directories: directories.sort(compareDirectoryDepth),
|
|
161
|
+
files: files.sort((left, right) => left.path.localeCompare(right.path)),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export function snapshotToMount(snapshot) {
|
|
165
|
+
return {
|
|
166
|
+
files: Object.fromEntries(snapshot.files.map((file) => [file.path, file.bytes])),
|
|
167
|
+
directories: [...snapshot.directories],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export function requireRecord(value, label) {
|
|
171
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
172
|
+
throw new TypeError(`${label} must be an object`);
|
|
173
|
+
}
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
function validateSnapshotSemantics(directories, files, paths, expectedPostgresMajor, context) {
|
|
177
|
+
for (const volatile of VOLATILE_DATABASE_FILES) {
|
|
178
|
+
if (paths.has(volatile)) {
|
|
179
|
+
throw context.corrupt(`contains transient PostgreSQL process state ${JSON.stringify(volatile)}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const directoryPaths = new Set(directories);
|
|
183
|
+
const fileByPath = new Map(files.map((file) => [file.path, file.bytes]));
|
|
184
|
+
for (const path of paths) {
|
|
185
|
+
for (const parent of parentPaths(path)) {
|
|
186
|
+
if (fileByPath.has(parent)) {
|
|
187
|
+
throw context.corrupt(`contains path ${JSON.stringify(path)} below file ${JSON.stringify(parent)}`);
|
|
188
|
+
}
|
|
189
|
+
if (!directoryPaths.has(parent)) {
|
|
190
|
+
throw context.corrupt(`contains path ${JSON.stringify(path)} without parent directory ${JSON.stringify(parent)}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const pgVersion = fileByPath.get('PG_VERSION');
|
|
195
|
+
const pgControl = fileByPath.get('global/pg_control');
|
|
196
|
+
if (pgVersion === undefined || pgControl === undefined || !directoryPaths.has('pg_wal')) {
|
|
197
|
+
throw context.corrupt('is missing PG_VERSION, global/pg_control, or pg_wal');
|
|
198
|
+
}
|
|
199
|
+
if (pgControl.length === 0)
|
|
200
|
+
throw context.corrupt('contains an empty global/pg_control');
|
|
201
|
+
let actualPostgresMajor;
|
|
202
|
+
try {
|
|
203
|
+
actualPostgresMajor = new TextDecoder('utf-8', { fatal: true }).decode(pgVersion).trim();
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
throw context.corrupt(`contains a non-UTF-8 PG_VERSION: ${describeError(error)}`, error);
|
|
207
|
+
}
|
|
208
|
+
if (actualPostgresMajor !== expectedPostgresMajor) {
|
|
209
|
+
throw context.corrupt(`contains PG_VERSION ${JSON.stringify(actualPostgresMajor)}, expected ${JSON.stringify(expectedPostgresMajor)}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export function validateDirectoryEntryName(name) {
|
|
213
|
+
if (name.length === 0 ||
|
|
214
|
+
name === '.' ||
|
|
215
|
+
name === '..' ||
|
|
216
|
+
name.includes('/') ||
|
|
217
|
+
name.includes('\\') ||
|
|
218
|
+
name.includes('\0')) {
|
|
219
|
+
throw new Error(`Wasmer returned an unsafe PGDATA entry name ${JSON.stringify(name)}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function validateStoredPath(value, context) {
|
|
223
|
+
if (typeof value !== 'string' || value.length === 0 || value.startsWith('/')) {
|
|
224
|
+
throw context.corrupt(`contains invalid snapshot path ${JSON.stringify(value)}`);
|
|
225
|
+
}
|
|
226
|
+
const segments = value.split('/');
|
|
227
|
+
if (value.includes('\\') ||
|
|
228
|
+
value.includes('\0') ||
|
|
229
|
+
segments.some((segment) => segment.length === 0 || segment === '.' || segment === '..')) {
|
|
230
|
+
throw context.corrupt(`contains unsafe snapshot path ${JSON.stringify(value)}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function parentPaths(path) {
|
|
234
|
+
const segments = path.split('/');
|
|
235
|
+
const parents = [];
|
|
236
|
+
for (let length = 1; length < segments.length; length += 1) {
|
|
237
|
+
parents.push(segments.slice(0, length).join('/'));
|
|
238
|
+
}
|
|
239
|
+
return parents;
|
|
240
|
+
}
|
|
241
|
+
function compareDirectoryDepth(left, right) {
|
|
242
|
+
return left.split('/').length - right.split('/').length || left.localeCompare(right);
|
|
243
|
+
}
|
|
244
|
+
function collapseTouchedPaths(values) {
|
|
245
|
+
const paths = [...new Set(values.map(validateTouchedPath))].sort(compareDirectoryDepth);
|
|
246
|
+
return paths.filter((path, index) => !paths.slice(0, index).some((parent) => pathIsWithin(path, parent)));
|
|
247
|
+
}
|
|
248
|
+
function validateTouchedPath(path) {
|
|
249
|
+
if (path === '')
|
|
250
|
+
return path;
|
|
251
|
+
if (typeof path !== 'string' || path.startsWith('/')) {
|
|
252
|
+
throw new Error(`Wasmer returned an unsafe changed PGDATA path ${JSON.stringify(path)}`);
|
|
253
|
+
}
|
|
254
|
+
const segments = path.split('/');
|
|
255
|
+
if (path.includes('\\') ||
|
|
256
|
+
path.includes('\0') ||
|
|
257
|
+
segments.some((segment) => segment.length === 0 || segment === '.' || segment === '..')) {
|
|
258
|
+
throw new Error(`Wasmer returned an unsafe changed PGDATA path ${JSON.stringify(path)}`);
|
|
259
|
+
}
|
|
260
|
+
return path;
|
|
261
|
+
}
|
|
262
|
+
function pathIsWithin(path, root) {
|
|
263
|
+
return root.length === 0 || path === root || path.startsWith(`${root}/`);
|
|
264
|
+
}
|
|
265
|
+
function isVolatilePath(path) {
|
|
266
|
+
const root = path.split('/')[0];
|
|
267
|
+
return root !== undefined && VOLATILE_DATABASE_FILES.has(root);
|
|
268
|
+
}
|
|
269
|
+
function comparePathDepthDescending(left, right) {
|
|
270
|
+
return right.split('/').length - left.split('/').length || right.localeCompare(left);
|
|
271
|
+
}
|
|
272
|
+
function describeError(error) {
|
|
273
|
+
return error instanceof Error ? error.message : String(error);
|
|
274
|
+
}
|
package/lib/storage.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
declare const storageDescriptorBrand: unique symbol;
|
|
2
|
+
declare const persistentStorageDescriptorBrand: unique symbol;
|
|
3
|
+
/**
|
|
4
|
+
* An opaque storage selection created by this package's storage factories.
|
|
5
|
+
* The descriptor is deliberately not a bag of user-authored paths or assets.
|
|
6
|
+
*/
|
|
7
|
+
export type WasixStorage = Readonly<{
|
|
8
|
+
[storageDescriptorBrand]: 'oliphaunt-wasix-storage';
|
|
9
|
+
}>;
|
|
10
|
+
/** Opaque persistent storage accepted by static physical restore. */
|
|
11
|
+
export type PersistentWasixStorage = WasixStorage & Readonly<{
|
|
12
|
+
[persistentStorageDescriptorBrand]: 'oliphaunt-wasix-persistent-storage';
|
|
13
|
+
}>;
|
|
14
|
+
export type SerializedWasixStorage = Readonly<{
|
|
15
|
+
schema: 'oliphaunt-wasix-storage-v1';
|
|
16
|
+
kind: 'memory';
|
|
17
|
+
}> | Readonly<{
|
|
18
|
+
schema: 'oliphaunt-wasix-storage-v1';
|
|
19
|
+
kind: 'indexed-db';
|
|
20
|
+
name: string;
|
|
21
|
+
}> | Readonly<{
|
|
22
|
+
schema: 'oliphaunt-wasix-storage-v1';
|
|
23
|
+
kind: 'opfs';
|
|
24
|
+
name: string;
|
|
25
|
+
}> | Readonly<{
|
|
26
|
+
schema: 'oliphaunt-wasix-storage-v1';
|
|
27
|
+
kind: 'directory';
|
|
28
|
+
path: string;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Select a fresh in-memory database. This is also the default when `storage`
|
|
32
|
+
* is omitted. Reusing the descriptor does not preserve data.
|
|
33
|
+
*/
|
|
34
|
+
export declare function memory(): WasixStorage;
|
|
35
|
+
/** @internal Used by the selectively imported IndexedDB adapter. */
|
|
36
|
+
export declare function defineIndexedDbStorage(name: string): PersistentWasixStorage;
|
|
37
|
+
/** @internal Used by the selectively imported OPFS adapter. */
|
|
38
|
+
export declare function defineOpfsStorage(name: string): PersistentWasixStorage;
|
|
39
|
+
/** @internal Used by the selectively imported Node directory adapter. */
|
|
40
|
+
export declare function defineDirectoryStorage(path: string): PersistentWasixStorage;
|
|
41
|
+
/** @internal Validate and project the opaque main-thread value for the worker. */
|
|
42
|
+
export declare function serializeWasixStorage(storage: WasixStorage | undefined): SerializedWasixStorage;
|
|
43
|
+
export declare function validateIndexedDbDatabaseName(name: unknown): asserts name is string;
|
|
44
|
+
export declare function validateOpfsDatabaseName(name: unknown): asserts name is string;
|
|
45
|
+
export declare function validateHostDirectoryPath(path: unknown): asserts path is string;
|
|
46
|
+
export {};
|
package/lib/storage.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const descriptorValues = new WeakMap();
|
|
2
|
+
/**
|
|
3
|
+
* Select a fresh in-memory database. This is also the default when `storage`
|
|
4
|
+
* is omitted. Reusing the descriptor does not preserve data.
|
|
5
|
+
*/
|
|
6
|
+
export function memory() {
|
|
7
|
+
return defineStorage({
|
|
8
|
+
schema: 'oliphaunt-wasix-storage-v1',
|
|
9
|
+
kind: 'memory',
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/** @internal Used by the selectively imported IndexedDB adapter. */
|
|
13
|
+
export function defineIndexedDbStorage(name) {
|
|
14
|
+
validateIndexedDbDatabaseName(name);
|
|
15
|
+
return defineStorage({
|
|
16
|
+
schema: 'oliphaunt-wasix-storage-v1',
|
|
17
|
+
kind: 'indexed-db',
|
|
18
|
+
name,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/** @internal Used by the selectively imported OPFS adapter. */
|
|
22
|
+
export function defineOpfsStorage(name) {
|
|
23
|
+
validateOpfsDatabaseName(name);
|
|
24
|
+
return defineStorage({
|
|
25
|
+
schema: 'oliphaunt-wasix-storage-v1',
|
|
26
|
+
kind: 'opfs',
|
|
27
|
+
name,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** @internal Used by the selectively imported Node directory adapter. */
|
|
31
|
+
export function defineDirectoryStorage(path) {
|
|
32
|
+
validateHostDirectoryPath(path);
|
|
33
|
+
return defineStorage({
|
|
34
|
+
schema: 'oliphaunt-wasix-storage-v1',
|
|
35
|
+
kind: 'directory',
|
|
36
|
+
path,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** @internal Validate and project the opaque main-thread value for the worker. */
|
|
40
|
+
export function serializeWasixStorage(storage) {
|
|
41
|
+
if (storage === undefined) {
|
|
42
|
+
return { schema: 'oliphaunt-wasix-storage-v1', kind: 'memory' };
|
|
43
|
+
}
|
|
44
|
+
const value = descriptorValues.get(storage);
|
|
45
|
+
if (value === undefined) {
|
|
46
|
+
throw new TypeError('storage must come from @oliphaunt/wasix-ts or one of its storage adapter subpaths');
|
|
47
|
+
}
|
|
48
|
+
switch (value.kind) {
|
|
49
|
+
case 'memory':
|
|
50
|
+
return { ...value };
|
|
51
|
+
case 'indexed-db':
|
|
52
|
+
return { ...value, name: value.name };
|
|
53
|
+
case 'opfs':
|
|
54
|
+
return { ...value, name: value.name };
|
|
55
|
+
case 'directory':
|
|
56
|
+
return { ...value, path: value.path };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function validateIndexedDbDatabaseName(name) {
|
|
60
|
+
if (typeof name !== 'string' || name.length === 0 || name.length > 200 || name.includes('\0')) {
|
|
61
|
+
throw new TypeError('IndexedDB storage name must be 1-200 characters without NUL bytes');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function validateOpfsDatabaseName(name) {
|
|
65
|
+
if (typeof name !== 'string' ||
|
|
66
|
+
name.length === 0 ||
|
|
67
|
+
name.length > 100 ||
|
|
68
|
+
name === '.' ||
|
|
69
|
+
name === '..' ||
|
|
70
|
+
!/^[A-Za-z0-9._-]+$/.test(name)) {
|
|
71
|
+
throw new TypeError('OPFS storage name must be 1-100 ASCII letters, digits, dot, dash, or underscore');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function validateHostDirectoryPath(path) {
|
|
75
|
+
if (typeof path !== 'string' || path.length === 0 || path.includes('\0')) {
|
|
76
|
+
throw new TypeError('host directory storage path must be a non-empty string without NUL bytes');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function defineStorage(value) {
|
|
80
|
+
const descriptor = Object.freeze({});
|
|
81
|
+
descriptorValues.set(descriptor, Object.freeze(value));
|
|
82
|
+
return descriptor;
|
|
83
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Directory, RunWasixOptions } from './host/index.mjs';
|
|
2
|
+
import type { SerializedAssetSource } from './rpc.js';
|
|
3
|
+
export type WasixToolDescriptor = Readonly<{
|
|
4
|
+
name: 'pg_dump' | 'psql';
|
|
5
|
+
sha256: string;
|
|
6
|
+
size: number;
|
|
7
|
+
source: SerializedAssetSource;
|
|
8
|
+
}>;
|
|
9
|
+
export type WasixToolProcessOptions = Readonly<{
|
|
10
|
+
runtimeVersion: string;
|
|
11
|
+
tool: WasixToolDescriptor;
|
|
12
|
+
args: readonly string[];
|
|
13
|
+
/** @internal An exact ArrayBuffer-backed view is transferred and consumed. */
|
|
14
|
+
stdin?: Uint8Array;
|
|
15
|
+
}>;
|
|
16
|
+
export type WasixToolProcessResult = Readonly<{
|
|
17
|
+
exitCode: number;
|
|
18
|
+
stdout: Uint8Array;
|
|
19
|
+
stderr: Uint8Array;
|
|
20
|
+
}>;
|
|
21
|
+
/** @internal The already-open database worker does not need carrier runtime metadata again. */
|
|
22
|
+
export type WasixPgDumpProcessOptions = Readonly<{
|
|
23
|
+
tool: WasixToolDescriptor;
|
|
24
|
+
args: readonly string[];
|
|
25
|
+
}>;
|
|
26
|
+
export type PreparedWasixToolAsset = Readonly<{
|
|
27
|
+
name: 'pg_dump' | 'psql';
|
|
28
|
+
bytes: Uint8Array;
|
|
29
|
+
module: WebAssembly.Module;
|
|
30
|
+
}>;
|
|
31
|
+
/** @internal Verify and compile one immutable frontend program once per owning worker. */
|
|
32
|
+
export declare function prepareWasixToolAsset(descriptor: WasixToolDescriptor): Promise<PreparedWasixToolAsset>;
|
|
33
|
+
/** @internal Create only the fresh mutable paths a standalone frontend program uses. */
|
|
34
|
+
export declare function materializeWasixToolMounts(DirectoryConstructor: typeof Directory, tool: PreparedWasixToolAsset): Promise<Record<string, Directory>>;
|
|
35
|
+
/** @internal Release every mount and retain both execution and cleanup failures. */
|
|
36
|
+
export declare function releaseWasixToolMounts(mounts: Record<string, Directory>): void;
|
|
37
|
+
export declare function releaseWasixToolMounts(mounts: Record<string, Directory>, failure: Readonly<{
|
|
38
|
+
primary: unknown;
|
|
39
|
+
}>): never;
|
|
40
|
+
/** @internal Build the identical fresh-process contract for both execution topologies. */
|
|
41
|
+
export declare function wasixToolRunOptions(tool: PreparedWasixToolAsset, args: readonly string[], mounts: Record<string, Directory>, stdin?: Uint8Array): RunWasixOptions;
|
|
42
|
+
export declare function validateWasixToolDescriptor(tool: WasixToolDescriptor): void;
|
|
43
|
+
export declare function wasixToolAssetIdentity(descriptor: WasixToolDescriptor): string;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { assertSha256 } from './extensions.js';
|
|
2
|
+
import { loadAsset } from './archive.js';
|
|
3
|
+
/** @internal Verify and compile one immutable frontend program once per owning worker. */
|
|
4
|
+
export async function prepareWasixToolAsset(descriptor) {
|
|
5
|
+
validateWasixToolDescriptor(descriptor);
|
|
6
|
+
const loaded = await loadAsset(descriptor.source, `WASIX ${descriptor.name} module`);
|
|
7
|
+
const bytes = standaloneBytes(loaded);
|
|
8
|
+
if (bytes.length !== descriptor.size) {
|
|
9
|
+
throw new Error(`WASIX ${descriptor.name} module size mismatch`);
|
|
10
|
+
}
|
|
11
|
+
await assertSha256(bytes, descriptor.sha256, `WASIX ${descriptor.name} module`);
|
|
12
|
+
return {
|
|
13
|
+
name: descriptor.name,
|
|
14
|
+
bytes,
|
|
15
|
+
module: await WebAssembly.compile(bytes),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** @internal Create only the fresh mutable paths a standalone frontend program uses. */
|
|
19
|
+
export async function materializeWasixToolMounts(DirectoryConstructor, tool) {
|
|
20
|
+
const mounts = {};
|
|
21
|
+
try {
|
|
22
|
+
mounts['/bin'] = new DirectoryConstructor({ [tool.name]: tool.bytes });
|
|
23
|
+
const home = new DirectoryConstructor();
|
|
24
|
+
mounts['/home'] = home;
|
|
25
|
+
await home.createDir('postgres');
|
|
26
|
+
mounts['/tmp'] = new DirectoryConstructor();
|
|
27
|
+
return mounts;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
releaseWasixToolMounts(mounts, { primary: error });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function releaseWasixToolMounts(mounts, failure) {
|
|
34
|
+
const cleanupFailures = [];
|
|
35
|
+
for (const directory of Object.values(mounts)) {
|
|
36
|
+
try {
|
|
37
|
+
directory.free();
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
cleanupFailures.push(error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (failure !== undefined) {
|
|
44
|
+
if (cleanupFailures.length === 0)
|
|
45
|
+
throw failure.primary;
|
|
46
|
+
throw new AggregateError([failure.primary, ...cleanupFailures], 'WASIX tool execution and mount cleanup both failed', { cause: failure.primary });
|
|
47
|
+
}
|
|
48
|
+
if (cleanupFailures.length !== 0) {
|
|
49
|
+
throw new AggregateError(cleanupFailures, 'WASIX tool mount cleanup failed');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** @internal Build the identical fresh-process contract for both execution topologies. */
|
|
53
|
+
export function wasixToolRunOptions(tool, args, mounts, stdin) {
|
|
54
|
+
return {
|
|
55
|
+
program: `/bin/${tool.name}`,
|
|
56
|
+
args: [...args],
|
|
57
|
+
cwd: '/',
|
|
58
|
+
env: {
|
|
59
|
+
PGUSER: 'postgres',
|
|
60
|
+
PGPASSWORD: 'password',
|
|
61
|
+
PGSSLMODE: 'disable',
|
|
62
|
+
PGCLIENTENCODING: 'UTF8',
|
|
63
|
+
...(tool.name === 'psql' ? { OLIPHAUNT_PSQL_NONINTERACTIVE: '1' } : {}),
|
|
64
|
+
HOME: '/home/postgres',
|
|
65
|
+
PATH: '/bin',
|
|
66
|
+
LC_CTYPE: 'C.UTF-8',
|
|
67
|
+
TZ: 'UTC',
|
|
68
|
+
},
|
|
69
|
+
mount: mounts,
|
|
70
|
+
stdin,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function validateWasixToolDescriptor(tool) {
|
|
74
|
+
if (tool.name !== 'pg_dump' && tool.name !== 'psql') {
|
|
75
|
+
throw new TypeError('unsupported Oliphaunt WASIX tool');
|
|
76
|
+
}
|
|
77
|
+
if (!/^[0-9a-f]{64}$/u.test(tool.sha256)) {
|
|
78
|
+
throw new TypeError(`WASIX ${tool.name} SHA-256 is invalid`);
|
|
79
|
+
}
|
|
80
|
+
if (!Number.isSafeInteger(tool.size) || tool.size <= 0) {
|
|
81
|
+
throw new TypeError(`WASIX ${tool.name} size is invalid`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function wasixToolAssetIdentity(descriptor) {
|
|
85
|
+
return `${descriptor.name}:${descriptor.sha256}:${descriptor.size}`;
|
|
86
|
+
}
|
|
87
|
+
function standaloneBytes(bytes) {
|
|
88
|
+
return bytes.buffer instanceof ArrayBuffer &&
|
|
89
|
+
bytes.byteOffset === 0 &&
|
|
90
|
+
bytes.byteLength === bytes.buffer.byteLength
|
|
91
|
+
? bytes
|
|
92
|
+
: Uint8Array.from(bytes);
|
|
93
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type WasixByteChannel } from './byte-channel.js';
|
|
2
|
+
import type { Directory, OliphauntPreparedTool, OliphauntToolOutput, RunWasixOptions } from './host/index.mjs';
|
|
3
|
+
import { type WasixToolDescriptor } from './tool-runtime.js';
|
|
4
|
+
export type WasixToolWorkerRequest = WasixToolPrepareRequest | WasixToolRunRequest;
|
|
5
|
+
export type WasixToolPrepareRequest = Readonly<{
|
|
6
|
+
id: number;
|
|
7
|
+
kind: 'prepare';
|
|
8
|
+
tool: WasixToolDescriptor & Readonly<{
|
|
9
|
+
name: 'psql';
|
|
10
|
+
}>;
|
|
11
|
+
}>;
|
|
12
|
+
export type WasixToolRunRequest = Readonly<{
|
|
13
|
+
id: number;
|
|
14
|
+
kind: 'run';
|
|
15
|
+
tool: 'psql';
|
|
16
|
+
args: string[];
|
|
17
|
+
stdin?: Uint8Array;
|
|
18
|
+
frontend: WasixByteChannel;
|
|
19
|
+
backend: WasixByteChannel;
|
|
20
|
+
}>;
|
|
21
|
+
export type WasixToolWorkerResponse = Readonly<{
|
|
22
|
+
id: number;
|
|
23
|
+
ok: true;
|
|
24
|
+
kind: 'prepared';
|
|
25
|
+
}> | Readonly<{
|
|
26
|
+
id: number;
|
|
27
|
+
ok: true;
|
|
28
|
+
kind: 'completed';
|
|
29
|
+
exitCode: number;
|
|
30
|
+
stdout: Uint8Array;
|
|
31
|
+
stderr: Uint8Array;
|
|
32
|
+
}> | Readonly<{
|
|
33
|
+
id: number;
|
|
34
|
+
ok: false;
|
|
35
|
+
message: string;
|
|
36
|
+
}>;
|
|
37
|
+
export type WasixToolHost = Readonly<{
|
|
38
|
+
Directory: typeof Directory;
|
|
39
|
+
init(): Promise<unknown>;
|
|
40
|
+
prepareOliphauntTool(module: WebAssembly.Module, moduleBytes: Uint8Array): OliphauntPreparedTool;
|
|
41
|
+
runOliphauntToolDirect(prepared: OliphauntPreparedTool, options: RunWasixOptions, protocolRead: (maximumBytes: number) => Uint8Array,
|
|
42
|
+
/** Borrowed bytes: synchronously copy; never mutate or retain this view. */
|
|
43
|
+
protocolWrite: (chunk: Uint8Array) => void): Promise<OliphauntToolOutput>;
|
|
44
|
+
}>;
|
|
45
|
+
/** @internal Stateful dispatcher owned by one persistent outer tool worker. */
|
|
46
|
+
export declare function createWasixToolWorkerDispatcher(host: WasixToolHost): (request: WasixToolWorkerRequest) => Promise<WasixToolWorkerResponse>;
|
|
47
|
+
/** @internal Transfer owned tool outputs without another table-sized structured clone. */
|
|
48
|
+
export declare function toolWorkerResponseTransfers(response: WasixToolWorkerResponse): ArrayBuffer[];
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { closeWasixByteChannel, failWasixByteChannel, markWasixByteChannelProtocolComplete, markWasixByteChannelProtocolStarted, readWasixByteChannelSync, wasixByteChannelProtocolOutcomeUnknown, writeWasixByteChannelSync, } from './byte-channel.js';
|
|
2
|
+
import { materializeWasixToolMounts, prepareWasixToolAsset, releaseWasixToolMounts, wasixToolAssetIdentity, wasixToolRunOptions, } from './tool-runtime.js';
|
|
3
|
+
/** @internal Stateful dispatcher owned by one persistent outer tool worker. */
|
|
4
|
+
export function createWasixToolWorkerDispatcher(host) {
|
|
5
|
+
let hostInitialization;
|
|
6
|
+
let tool;
|
|
7
|
+
const dispatch = async (request) => {
|
|
8
|
+
try {
|
|
9
|
+
hostInitialization ??= host.init();
|
|
10
|
+
await hostInitialization;
|
|
11
|
+
if (request.kind === 'prepare') {
|
|
12
|
+
const toolIdentity = wasixToolAssetIdentity(request.tool);
|
|
13
|
+
if (tool === undefined) {
|
|
14
|
+
const asset = await prepareWasixToolAsset(request.tool);
|
|
15
|
+
tool = {
|
|
16
|
+
identity: toolIdentity,
|
|
17
|
+
asset,
|
|
18
|
+
prepared: host.prepareOliphauntTool(asset.module, asset.bytes),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
else if (tool.identity !== toolIdentity) {
|
|
22
|
+
throw new Error('Oliphaunt WASIX tool worker cannot replace psql');
|
|
23
|
+
}
|
|
24
|
+
return { id: request.id, ok: true, kind: 'prepared' };
|
|
25
|
+
}
|
|
26
|
+
if (tool === undefined)
|
|
27
|
+
throw new Error(`WASIX ${request.tool} module is not prepared`);
|
|
28
|
+
return await runPreparedTool(request, host, tool);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (request.kind === 'run') {
|
|
32
|
+
finishFailedToolChannels(request);
|
|
33
|
+
}
|
|
34
|
+
return { id: request.id, ok: false, message: describeWorkerError(error) };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
return dispatch;
|
|
38
|
+
}
|
|
39
|
+
/** @internal Transfer owned tool outputs without another table-sized structured clone. */
|
|
40
|
+
export function toolWorkerResponseTransfers(response) {
|
|
41
|
+
if (!response.ok || response.kind !== 'completed')
|
|
42
|
+
return [];
|
|
43
|
+
const buffers = new Set();
|
|
44
|
+
for (const bytes of [response.stdout, response.stderr]) {
|
|
45
|
+
if (bytes.buffer instanceof ArrayBuffer)
|
|
46
|
+
buffers.add(bytes.buffer);
|
|
47
|
+
}
|
|
48
|
+
return [...buffers];
|
|
49
|
+
}
|
|
50
|
+
async function runPreparedTool(request, host, tool) {
|
|
51
|
+
const mounts = await materializeWasixToolMounts(host.Directory, tool.asset);
|
|
52
|
+
let response;
|
|
53
|
+
let failure;
|
|
54
|
+
try {
|
|
55
|
+
const output = await host.runOliphauntToolDirect(tool.prepared, wasixToolRunOptions(tool.asset, request.args, mounts, request.stdin), (maximumBytes) => {
|
|
56
|
+
markWasixByteChannelProtocolStarted(request.frontend);
|
|
57
|
+
return readWasixByteChannelSync(request.backend, maximumBytes);
|
|
58
|
+
}, (chunk) => {
|
|
59
|
+
markWasixByteChannelProtocolStarted(request.frontend);
|
|
60
|
+
writeWasixByteChannelSync(request.frontend, chunk);
|
|
61
|
+
});
|
|
62
|
+
markWasixByteChannelProtocolComplete(request.frontend);
|
|
63
|
+
closeWasixByteChannel(request.frontend);
|
|
64
|
+
response = {
|
|
65
|
+
id: request.id,
|
|
66
|
+
ok: true,
|
|
67
|
+
kind: 'completed',
|
|
68
|
+
exitCode: output.code,
|
|
69
|
+
stdout: output.stdoutBytes,
|
|
70
|
+
stderr: output.stderrBytes,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
failure = { primary: error };
|
|
75
|
+
}
|
|
76
|
+
if (failure !== undefined)
|
|
77
|
+
releaseWasixToolMounts(mounts, failure);
|
|
78
|
+
releaseWasixToolMounts(mounts);
|
|
79
|
+
if (response === undefined)
|
|
80
|
+
throw new Error('WASIX psql completed without a response');
|
|
81
|
+
return response;
|
|
82
|
+
}
|
|
83
|
+
function finishFailedToolChannels(request) {
|
|
84
|
+
if (wasixByteChannelProtocolOutcomeUnknown(request.frontend)) {
|
|
85
|
+
// Mark uncertainty before EOF so the database cannot observe a clean
|
|
86
|
+
// connection close and reset while a later public tool failure escapes.
|
|
87
|
+
failWasixByteChannel(request.frontend);
|
|
88
|
+
failWasixByteChannel(request.backend);
|
|
89
|
+
}
|
|
90
|
+
closeWasixByteChannel(request.frontend);
|
|
91
|
+
}
|
|
92
|
+
function describeWorkerError(error) {
|
|
93
|
+
if (!(error instanceof Error))
|
|
94
|
+
return String(error);
|
|
95
|
+
const cause = 'cause' in error ? error.cause : undefined;
|
|
96
|
+
return cause === undefined ? error.message : `${error.message}: ${describeWorkerError(cause)}`;
|
|
97
|
+
}
|