@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
package/lib/archive.js
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { readPackageAsset } from './asset-source.js';
|
|
2
|
+
import { decompressZstd } from './zstd.js';
|
|
3
|
+
const BLOCK_SIZE = 512;
|
|
4
|
+
const MAX_ARCHIVE_BYTES = 2 * 1024 * 1024 * 1024;
|
|
5
|
+
const MAX_ENTRY_BYTES = 512 * 1024 * 1024;
|
|
6
|
+
const MAX_ENTRIES = 65_536;
|
|
7
|
+
const MAX_PATH_BYTES = 1_024;
|
|
8
|
+
const MAX_PATH_DEPTH = 64;
|
|
9
|
+
const ZSTD_MAGIC = [0x28, 0xb5, 0x2f, 0xfd];
|
|
10
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
11
|
+
export function extractTar(archive) {
|
|
12
|
+
if (archive.length > MAX_ARCHIVE_BYTES) {
|
|
13
|
+
throw new Error(`tar archive exceeds the ${MAX_ARCHIVE_BYTES}-byte extraction limit`);
|
|
14
|
+
}
|
|
15
|
+
const files = new Map();
|
|
16
|
+
const directories = new Set();
|
|
17
|
+
const explicitPaths = new Set();
|
|
18
|
+
let offset = 0;
|
|
19
|
+
let nextPax;
|
|
20
|
+
let globalPax = {};
|
|
21
|
+
let nextLongName;
|
|
22
|
+
let entries = 0;
|
|
23
|
+
while (offset + BLOCK_SIZE <= archive.length) {
|
|
24
|
+
const header = archive.subarray(offset, offset + BLOCK_SIZE);
|
|
25
|
+
offset += BLOCK_SIZE;
|
|
26
|
+
if (isZeroBlock(header)) {
|
|
27
|
+
if (nextPax !== undefined || nextLongName !== undefined) {
|
|
28
|
+
throw new Error('tar archive ends with dangling per-entry path metadata');
|
|
29
|
+
}
|
|
30
|
+
if (offset + BLOCK_SIZE > archive.length ||
|
|
31
|
+
!isZeroBlock(archive.subarray(offset, offset + BLOCK_SIZE)) ||
|
|
32
|
+
archive.subarray(offset + BLOCK_SIZE).some((byte) => byte !== 0)) {
|
|
33
|
+
throw new Error('tar archive has an invalid terminator');
|
|
34
|
+
}
|
|
35
|
+
return { files, directories };
|
|
36
|
+
}
|
|
37
|
+
validateTarHeaderFormat(header);
|
|
38
|
+
entries += 1;
|
|
39
|
+
if (entries > MAX_ENTRIES)
|
|
40
|
+
throw new Error(`tar archive exceeds the ${MAX_ENTRIES}-entry limit`);
|
|
41
|
+
const type = String.fromCharCode(header[156] ?? 0).replace('\0', '') || '0';
|
|
42
|
+
const size = parseOctal(header.subarray(124, 136), 'tar entry size');
|
|
43
|
+
if (size > MAX_ENTRY_BYTES) {
|
|
44
|
+
throw new Error(`tar entry exceeds the ${MAX_ENTRY_BYTES}-byte size limit`);
|
|
45
|
+
}
|
|
46
|
+
const payloadEnd = offset + size;
|
|
47
|
+
if (payloadEnd > archive.length) {
|
|
48
|
+
throw new Error('tar archive ended in the middle of an entry payload');
|
|
49
|
+
}
|
|
50
|
+
const payload = archive.subarray(offset, payloadEnd);
|
|
51
|
+
offset += Math.ceil(size / BLOCK_SIZE) * BLOCK_SIZE;
|
|
52
|
+
if (offset > archive.length) {
|
|
53
|
+
throw new Error('tar archive ended in the middle of entry padding');
|
|
54
|
+
}
|
|
55
|
+
const linkName = decodeTarString(header.subarray(157, 257)).replace(/\0.*$/, '');
|
|
56
|
+
if (linkName.length > 0) {
|
|
57
|
+
throw new Error('tar archive contains a non-empty link target');
|
|
58
|
+
}
|
|
59
|
+
if (type === 'x') {
|
|
60
|
+
if (nextPax !== undefined || nextLongName !== undefined) {
|
|
61
|
+
throw new Error('tar archive repeats per-entry path metadata before an entry');
|
|
62
|
+
}
|
|
63
|
+
nextPax = parsePaxPayload(payload);
|
|
64
|
+
validatePaxSemantics(nextPax);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (type === 'g') {
|
|
68
|
+
const additions = parsePaxPayload(payload);
|
|
69
|
+
validatePaxSemantics(additions);
|
|
70
|
+
for (const key of Object.keys(additions)) {
|
|
71
|
+
if (Object.hasOwn(globalPax, key)) {
|
|
72
|
+
throw new Error(`tar archive repeats global pax key: ${key}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
globalPax = { ...globalPax, ...additions };
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (type === 'L') {
|
|
79
|
+
if (nextPax !== undefined || nextLongName !== undefined) {
|
|
80
|
+
throw new Error('tar archive repeats per-entry path metadata before an entry');
|
|
81
|
+
}
|
|
82
|
+
nextLongName = decodeTarString(payload).replace(/\0+$/, '');
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const pax = { ...globalPax, ...(nextPax ?? {}) };
|
|
86
|
+
nextPax = undefined;
|
|
87
|
+
if ((pax.linkpath ?? '').length > 0) {
|
|
88
|
+
throw new Error('tar archive contains a non-empty pax link target');
|
|
89
|
+
}
|
|
90
|
+
const path = sanitizeTarPath(pax.path ?? nextLongName ?? tarHeaderPath(header));
|
|
91
|
+
nextLongName = undefined;
|
|
92
|
+
if (path === undefined) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (explicitPaths.has(path)) {
|
|
96
|
+
throw new Error(`tar archive repeats entry path: ${path}`);
|
|
97
|
+
}
|
|
98
|
+
explicitPaths.add(path);
|
|
99
|
+
const fileAncestor = parentPaths(path).find((ancestor) => files.has(ancestor));
|
|
100
|
+
if (fileAncestor !== undefined) {
|
|
101
|
+
throw new Error(`tar entry ${path} is nested below file: ${fileAncestor}`);
|
|
102
|
+
}
|
|
103
|
+
if (type === '5') {
|
|
104
|
+
if (size !== 0) {
|
|
105
|
+
throw new Error(`tar directory entry has a non-empty payload: ${path}`);
|
|
106
|
+
}
|
|
107
|
+
if (files.has(path)) {
|
|
108
|
+
throw new Error(`tar entry repeats an existing file as a directory: ${path}`);
|
|
109
|
+
}
|
|
110
|
+
addDirectoryWithAncestors(directories, path);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (type !== '0') {
|
|
114
|
+
throw new Error(`unsupported tar entry type '${type}' for ${path}`);
|
|
115
|
+
}
|
|
116
|
+
if (directories.has(path)) {
|
|
117
|
+
throw new Error(`tar archive repeats entry path: ${path}`);
|
|
118
|
+
}
|
|
119
|
+
files.set(path, payload);
|
|
120
|
+
}
|
|
121
|
+
throw new Error('tar archive is missing its two-block terminator');
|
|
122
|
+
}
|
|
123
|
+
function validateTarHeaderFormat(header) {
|
|
124
|
+
const magic = decodeTarString(header.subarray(257, 263));
|
|
125
|
+
const version = decodeTarString(header.subarray(263, 265));
|
|
126
|
+
const posix = magic === 'ustar\0' && version === '00';
|
|
127
|
+
const gnu = magic === 'ustar ' && version === ' \0';
|
|
128
|
+
if (!posix && !gnu) {
|
|
129
|
+
throw new Error('tar entry does not use a supported ustar header');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function parentPaths(path) {
|
|
133
|
+
const segments = path.split('/');
|
|
134
|
+
const parents = [];
|
|
135
|
+
for (let length = 1; length < segments.length; length += 1) {
|
|
136
|
+
parents.push(segments.slice(0, length).join('/'));
|
|
137
|
+
}
|
|
138
|
+
return parents;
|
|
139
|
+
}
|
|
140
|
+
/** @internal Validate and project an extracted cluster seed for a `/base` mount. */
|
|
141
|
+
export function clusterSeedMount(clusterSeed) {
|
|
142
|
+
const pgdataFiles = clusterSeed.files;
|
|
143
|
+
if (!pgdataFiles.has('PG_VERSION') || !pgdataFiles.has('global/pg_control')) {
|
|
144
|
+
throw new Error('cluster seed is missing PG_VERSION or global/pg_control');
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
files: mapToDirectory(pgdataFiles),
|
|
148
|
+
directories: [...clusterSeed.directories],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** @internal Materialize runtime support mounts without loading a cluster seed. */
|
|
152
|
+
export function layoutRuntimeSupport(runtime) {
|
|
153
|
+
const runtimeFiles = runtime.files;
|
|
154
|
+
const module = runtimeFiles.get('oliphaunt/bin/postgres');
|
|
155
|
+
if (module === undefined || module.length === 0) {
|
|
156
|
+
throw new Error('runtime archive is missing oliphaunt/bin/postgres');
|
|
157
|
+
}
|
|
158
|
+
const mounts = {
|
|
159
|
+
'/home': { files: {}, directories: ['postgres'] },
|
|
160
|
+
'/tmp': { files: {}, directories: [] },
|
|
161
|
+
};
|
|
162
|
+
for (const [path, bytes] of runtimeFiles) {
|
|
163
|
+
const relative = stripPrefix(path, 'oliphaunt/');
|
|
164
|
+
const slash = relative.indexOf('/');
|
|
165
|
+
if (slash <= 0) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const root = relative.slice(0, slash);
|
|
169
|
+
const child = relative.slice(slash + 1);
|
|
170
|
+
const mountPath = `/${root}`;
|
|
171
|
+
let mount = mounts[mountPath];
|
|
172
|
+
if (mount === undefined) {
|
|
173
|
+
mount = { files: {}, directories: [] };
|
|
174
|
+
mounts[mountPath] = mount;
|
|
175
|
+
}
|
|
176
|
+
mount.files[child] = bytes;
|
|
177
|
+
}
|
|
178
|
+
for (const path of runtime.directories) {
|
|
179
|
+
if (path === 'oliphaunt') {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const relative = stripPrefix(path, 'oliphaunt/');
|
|
183
|
+
const slash = relative.indexOf('/');
|
|
184
|
+
if (slash <= 0) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const root = relative.slice(0, slash);
|
|
188
|
+
const child = relative.slice(slash + 1);
|
|
189
|
+
const mountPath = `/${root}`;
|
|
190
|
+
let mount = mounts[mountPath];
|
|
191
|
+
if (mount === undefined) {
|
|
192
|
+
mount = { files: {}, directories: [] };
|
|
193
|
+
mounts[mountPath] = mount;
|
|
194
|
+
}
|
|
195
|
+
mount.directories.push(child);
|
|
196
|
+
}
|
|
197
|
+
if (mounts['/base'] !== undefined) {
|
|
198
|
+
throw new Error('runtime archive must not provide the storage-owned /base mount');
|
|
199
|
+
}
|
|
200
|
+
for (const required of ['/bin', '/lib', '/share']) {
|
|
201
|
+
if (mounts[required] === undefined) {
|
|
202
|
+
throw new Error(`runtime archive did not produce required ${required} mount`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { module, mounts };
|
|
206
|
+
}
|
|
207
|
+
function addDirectoryWithAncestors(directories, path) {
|
|
208
|
+
const segments = path.split('/');
|
|
209
|
+
for (let index = 1; index <= segments.length; index += 1) {
|
|
210
|
+
directories.add(segments.slice(0, index).join('/'));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
export async function loadAsset(source, label) {
|
|
214
|
+
if (source instanceof Uint8Array) {
|
|
215
|
+
return source;
|
|
216
|
+
}
|
|
217
|
+
if (source.startsWith('file:')) {
|
|
218
|
+
return readPackageAsset(source, label);
|
|
219
|
+
}
|
|
220
|
+
let response;
|
|
221
|
+
try {
|
|
222
|
+
response = await fetch(source);
|
|
223
|
+
}
|
|
224
|
+
catch (cause) {
|
|
225
|
+
throw new Error(`failed to fetch ${label} from ${JSON.stringify(source)}`, { cause });
|
|
226
|
+
}
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
throw new Error(`failed to fetch ${label} (${response.status} ${response.statusText})`);
|
|
229
|
+
}
|
|
230
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
231
|
+
}
|
|
232
|
+
export function decompressIfNeeded(bytes) {
|
|
233
|
+
const isZstd = ZSTD_MAGIC.every((byte, index) => bytes[index] === byte);
|
|
234
|
+
return isZstd ? decompressZstd(bytes) : bytes;
|
|
235
|
+
}
|
|
236
|
+
function mapToDirectory(files) {
|
|
237
|
+
return Object.fromEntries(files);
|
|
238
|
+
}
|
|
239
|
+
function stripPrefix(path, prefix) {
|
|
240
|
+
if (!path.startsWith(prefix)) {
|
|
241
|
+
throw new Error(`runtime tar entry is outside ${prefix}: ${path}`);
|
|
242
|
+
}
|
|
243
|
+
return path.slice(prefix.length);
|
|
244
|
+
}
|
|
245
|
+
function isZeroBlock(block) {
|
|
246
|
+
return block.every((byte) => byte === 0);
|
|
247
|
+
}
|
|
248
|
+
function parseOctal(bytes, label) {
|
|
249
|
+
const text = decodeTarString(bytes).replace(/\0.*$/, '').trim();
|
|
250
|
+
if (text.length === 0) {
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
if (!/^[0-7]+$/.test(text)) {
|
|
254
|
+
throw new Error(`${label} is not an octal tar field`);
|
|
255
|
+
}
|
|
256
|
+
const value = Number.parseInt(text, 8);
|
|
257
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
258
|
+
throw new Error(`${label} is outside the safe integer range`);
|
|
259
|
+
}
|
|
260
|
+
return value;
|
|
261
|
+
}
|
|
262
|
+
function tarHeaderPath(header) {
|
|
263
|
+
const name = decodeTarString(header.subarray(0, 100)).replace(/\0.*$/, '');
|
|
264
|
+
const prefix = decodeTarString(header.subarray(345, 500)).replace(/\0.*$/, '');
|
|
265
|
+
return prefix.length > 0 ? `${prefix}/${name}` : name;
|
|
266
|
+
}
|
|
267
|
+
function sanitizeTarPath(path) {
|
|
268
|
+
if (path.includes('\\')) {
|
|
269
|
+
throw new Error(`tar entry path contains a backslash: ${path}`);
|
|
270
|
+
}
|
|
271
|
+
const normalized = path.replace(/^\.\/+/, '');
|
|
272
|
+
if (normalized.length === 0 || normalized === '.') {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
if (normalized.startsWith('/')) {
|
|
276
|
+
throw new Error(`tar entry path must be relative: ${path}`);
|
|
277
|
+
}
|
|
278
|
+
if (normalized.includes('\0')) {
|
|
279
|
+
throw new Error('tar entry path contains a NUL byte');
|
|
280
|
+
}
|
|
281
|
+
const segments = normalized.split('/').filter((segment) => segment.length > 0);
|
|
282
|
+
if (textByteLength(normalized) > MAX_PATH_BYTES || segments.length > MAX_PATH_DEPTH) {
|
|
283
|
+
throw new Error(`tar entry path exceeds portability limits: ${path}`);
|
|
284
|
+
}
|
|
285
|
+
if (segments.some((segment) => segment === '.' || segment === '..')) {
|
|
286
|
+
throw new Error(`tar entry path escapes the install root: ${path}`);
|
|
287
|
+
}
|
|
288
|
+
return segments.join('/');
|
|
289
|
+
}
|
|
290
|
+
function textByteLength(value) {
|
|
291
|
+
return new TextEncoder().encode(value).length;
|
|
292
|
+
}
|
|
293
|
+
function parsePaxPayload(payload) {
|
|
294
|
+
const values = {};
|
|
295
|
+
let offset = 0;
|
|
296
|
+
while (offset < payload.length) {
|
|
297
|
+
const space = payload.indexOf(0x20, offset);
|
|
298
|
+
if (space < 0) {
|
|
299
|
+
throw new Error('malformed pax header record');
|
|
300
|
+
}
|
|
301
|
+
const lengthText = decodeTarString(payload.subarray(offset, space));
|
|
302
|
+
if (!/^[0-9]+$/.test(lengthText)) {
|
|
303
|
+
throw new Error('invalid pax header record length');
|
|
304
|
+
}
|
|
305
|
+
const length = Number.parseInt(lengthText, 10);
|
|
306
|
+
if (!Number.isSafeInteger(length) || length <= 0) {
|
|
307
|
+
throw new Error('invalid pax header record length');
|
|
308
|
+
}
|
|
309
|
+
const recordEnd = offset + length;
|
|
310
|
+
if (recordEnd > payload.length || recordEnd <= space + 1 || payload[recordEnd - 1] !== 0x0a) {
|
|
311
|
+
throw new Error('malformed pax header key/value record');
|
|
312
|
+
}
|
|
313
|
+
const record = decodeTarString(payload.subarray(space + 1, recordEnd));
|
|
314
|
+
const equals = record.indexOf('=');
|
|
315
|
+
if (equals <= 0 || !record.endsWith('\n')) {
|
|
316
|
+
throw new Error('malformed pax header key/value record');
|
|
317
|
+
}
|
|
318
|
+
const key = record.slice(0, equals);
|
|
319
|
+
if (Object.hasOwn(values, key)) {
|
|
320
|
+
throw new Error(`pax header repeats key: ${key}`);
|
|
321
|
+
}
|
|
322
|
+
values[key] = record.slice(equals + 1, -1);
|
|
323
|
+
offset = recordEnd;
|
|
324
|
+
}
|
|
325
|
+
return values;
|
|
326
|
+
}
|
|
327
|
+
function validatePaxSemantics(values) {
|
|
328
|
+
for (const key of Object.keys(values)) {
|
|
329
|
+
if (key === 'size' || key === 'linkpath' || key.startsWith('GNU.sparse.')) {
|
|
330
|
+
throw new Error(`tar archive contains unsupported pax key: ${key}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function decodeTarString(bytes) {
|
|
335
|
+
return decoder.decode(bytes);
|
|
336
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type PackageAssetReader = (source: URL) => Promise<Uint8Array>;
|
|
2
|
+
/** @internal Installed by the active Node host realm for package-relative file URLs. */
|
|
3
|
+
export declare function installPackageAssetReader(reader: PackageAssetReader): void;
|
|
4
|
+
export declare function readPackageAsset(source: string, label: string): Promise<Uint8Array>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
let packageAssetReader;
|
|
2
|
+
/** @internal Installed by the active Node host realm for package-relative file URLs. */
|
|
3
|
+
export function installPackageAssetReader(reader) {
|
|
4
|
+
if (packageAssetReader !== undefined) {
|
|
5
|
+
throw new Error('Oliphaunt WASIX package asset reader is already installed');
|
|
6
|
+
}
|
|
7
|
+
packageAssetReader = reader;
|
|
8
|
+
}
|
|
9
|
+
export async function readPackageAsset(source, label) {
|
|
10
|
+
const url = new URL(source);
|
|
11
|
+
if (url.protocol !== 'file:' || packageAssetReader === undefined) {
|
|
12
|
+
throw new Error(`cannot read package-relative ${label} URL ${JSON.stringify(source)}`);
|
|
13
|
+
}
|
|
14
|
+
return packageAssetReader(url);
|
|
15
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** @internal One bounded single-producer/single-consumer byte channel. */
|
|
2
|
+
export type WasixByteChannel = Readonly<{
|
|
3
|
+
control: SharedArrayBuffer;
|
|
4
|
+
data: SharedArrayBuffer;
|
|
5
|
+
}>;
|
|
6
|
+
/** @internal Allocate the fixed-capacity transport used by tools and local sockets. */
|
|
7
|
+
export declare function createWasixByteChannel(): WasixByteChannel;
|
|
8
|
+
/** @internal Signal orderly producer EOF. */
|
|
9
|
+
export declare function closeWasixByteChannel(channel: WasixByteChannel): void;
|
|
10
|
+
/** @internal Signal an unrecoverable transport failure. */
|
|
11
|
+
export declare function failWasixByteChannel(channel: WasixByteChannel): void;
|
|
12
|
+
/** @internal Record that a tool has entered its PostgreSQL protocol callback. */
|
|
13
|
+
export declare function markWasixByteChannelProtocolStarted(channel: WasixByteChannel): void;
|
|
14
|
+
/** @internal Record successful process completion before exposing orderly EOF. */
|
|
15
|
+
export declare function markWasixByteChannelProtocolComplete(channel: WasixByteChannel): void;
|
|
16
|
+
/** @internal Observe protocol activity across the tool/database worker boundary. */
|
|
17
|
+
export declare function wasixByteChannelProtocolStarted(channel: WasixByteChannel): boolean;
|
|
18
|
+
/** @internal Whether a failed tool may have left an unobserved PostgreSQL outcome. */
|
|
19
|
+
export declare function wasixByteChannelProtocolOutcomeUnknown(channel: WasixByteChannel): boolean;
|
|
20
|
+
/** @internal Blocking read for a dedicated worker realm. Empty bytes mean EOF. */
|
|
21
|
+
export declare function readWasixByteChannelSync(channel: WasixByteChannel, maximumBytes?: number): Uint8Array;
|
|
22
|
+
/** @internal Blocking write for a dedicated worker realm. */
|
|
23
|
+
export declare function writeWasixByteChannelSync(channel: WasixByteChannel, input: Uint8Array): void;
|
|
24
|
+
/** @internal Non-blocking-realm read. Empty bytes mean EOF. */
|
|
25
|
+
export declare function readWasixByteChannel(channel: WasixByteChannel, maximumBytes?: number): Promise<Uint8Array>;
|
|
26
|
+
/** @internal Non-blocking-realm write with bounded backpressure. */
|
|
27
|
+
export declare function writeWasixByteChannel(channel: WasixByteChannel, input: Uint8Array): Promise<void>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const READ_OFFSET = 0;
|
|
2
|
+
const WRITE_OFFSET = 1;
|
|
3
|
+
const CLOSED = 2;
|
|
4
|
+
const FAILED = 3;
|
|
5
|
+
const PROTOCOL_STATE = 4;
|
|
6
|
+
const CONTROL_WORDS = 5;
|
|
7
|
+
const PROTOCOL_IDLE = 0;
|
|
8
|
+
const PROTOCOL_ACTIVE = 1;
|
|
9
|
+
const PROTOCOL_COMPLETE = 2;
|
|
10
|
+
const WASIX_BYTE_CHANNEL_CHUNK_BYTES = 64 * 1024;
|
|
11
|
+
const WASIX_CHANNEL_BYTES = 256 * 1024 + 1;
|
|
12
|
+
/** @internal Allocate the fixed-capacity transport used by tools and local sockets. */
|
|
13
|
+
export function createWasixByteChannel() {
|
|
14
|
+
return {
|
|
15
|
+
control: new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * CONTROL_WORDS),
|
|
16
|
+
data: new SharedArrayBuffer(WASIX_CHANNEL_BYTES),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** @internal Signal orderly producer EOF. */
|
|
20
|
+
export function closeWasixByteChannel(channel) {
|
|
21
|
+
const control = channelControl(channel);
|
|
22
|
+
Atomics.store(control, CLOSED, 1);
|
|
23
|
+
Atomics.notify(control, READ_OFFSET);
|
|
24
|
+
Atomics.notify(control, WRITE_OFFSET);
|
|
25
|
+
}
|
|
26
|
+
/** @internal Signal an unrecoverable transport failure. */
|
|
27
|
+
export function failWasixByteChannel(channel) {
|
|
28
|
+
const control = channelControl(channel);
|
|
29
|
+
Atomics.store(control, FAILED, 1);
|
|
30
|
+
Atomics.store(control, CLOSED, 1);
|
|
31
|
+
Atomics.notify(control, READ_OFFSET);
|
|
32
|
+
Atomics.notify(control, WRITE_OFFSET);
|
|
33
|
+
}
|
|
34
|
+
/** @internal Record that a tool has entered its PostgreSQL protocol callback. */
|
|
35
|
+
export function markWasixByteChannelProtocolStarted(channel) {
|
|
36
|
+
Atomics.store(channelControl(channel), PROTOCOL_STATE, PROTOCOL_ACTIVE);
|
|
37
|
+
}
|
|
38
|
+
/** @internal Record successful process completion before exposing orderly EOF. */
|
|
39
|
+
export function markWasixByteChannelProtocolComplete(channel) {
|
|
40
|
+
Atomics.compareExchange(channelControl(channel), PROTOCOL_STATE, PROTOCOL_ACTIVE, PROTOCOL_COMPLETE);
|
|
41
|
+
}
|
|
42
|
+
/** @internal Observe protocol activity across the tool/database worker boundary. */
|
|
43
|
+
export function wasixByteChannelProtocolStarted(channel) {
|
|
44
|
+
return Atomics.load(channelControl(channel), PROTOCOL_STATE) !== PROTOCOL_IDLE;
|
|
45
|
+
}
|
|
46
|
+
/** @internal Whether a failed tool may have left an unobserved PostgreSQL outcome. */
|
|
47
|
+
export function wasixByteChannelProtocolOutcomeUnknown(channel) {
|
|
48
|
+
return Atomics.load(channelControl(channel), PROTOCOL_STATE) === PROTOCOL_ACTIVE;
|
|
49
|
+
}
|
|
50
|
+
/** @internal Blocking read for a dedicated worker realm. Empty bytes mean EOF. */
|
|
51
|
+
export function readWasixByteChannelSync(channel, maximumBytes = WASIX_BYTE_CHANNEL_CHUNK_BYTES) {
|
|
52
|
+
const control = channelControl(channel);
|
|
53
|
+
const data = new Uint8Array(channel.data);
|
|
54
|
+
for (;;) {
|
|
55
|
+
assertChannelHealthy(control);
|
|
56
|
+
const read = Atomics.load(control, READ_OFFSET);
|
|
57
|
+
const write = Atomics.load(control, WRITE_OFFSET);
|
|
58
|
+
if (read !== write)
|
|
59
|
+
return consume(control, data, read, write, maximumBytes);
|
|
60
|
+
if (Atomics.load(control, CLOSED) !== 0)
|
|
61
|
+
return new Uint8Array();
|
|
62
|
+
Atomics.wait(control, WRITE_OFFSET, write);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** @internal Blocking write for a dedicated worker realm. */
|
|
66
|
+
export function writeWasixByteChannelSync(channel, input) {
|
|
67
|
+
const control = channelControl(channel);
|
|
68
|
+
const data = new Uint8Array(channel.data);
|
|
69
|
+
let offset = 0;
|
|
70
|
+
while (offset < input.length) {
|
|
71
|
+
assertChannelWritable(control);
|
|
72
|
+
const read = Atomics.load(control, READ_OFFSET);
|
|
73
|
+
const write = Atomics.load(control, WRITE_OFFSET);
|
|
74
|
+
const writable = writableBytes(read, write, data.length);
|
|
75
|
+
if (writable === 0) {
|
|
76
|
+
Atomics.wait(control, READ_OFFSET, read);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const copied = produce(control, data, write, input.subarray(offset), writable);
|
|
80
|
+
offset += copied;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** @internal Non-blocking-realm read. Empty bytes mean EOF. */
|
|
84
|
+
export async function readWasixByteChannel(channel, maximumBytes = WASIX_BYTE_CHANNEL_CHUNK_BYTES) {
|
|
85
|
+
const control = channelControl(channel);
|
|
86
|
+
const data = new Uint8Array(channel.data);
|
|
87
|
+
for (;;) {
|
|
88
|
+
assertChannelHealthy(control);
|
|
89
|
+
const read = Atomics.load(control, READ_OFFSET);
|
|
90
|
+
const write = Atomics.load(control, WRITE_OFFSET);
|
|
91
|
+
if (read !== write)
|
|
92
|
+
return consume(control, data, read, write, maximumBytes);
|
|
93
|
+
if (Atomics.load(control, CLOSED) !== 0)
|
|
94
|
+
return new Uint8Array();
|
|
95
|
+
await waitForChange(control, WRITE_OFFSET, write);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** @internal Non-blocking-realm write with bounded backpressure. */
|
|
99
|
+
export async function writeWasixByteChannel(channel, input) {
|
|
100
|
+
const control = channelControl(channel);
|
|
101
|
+
const data = new Uint8Array(channel.data);
|
|
102
|
+
let offset = 0;
|
|
103
|
+
while (offset < input.length) {
|
|
104
|
+
assertChannelWritable(control);
|
|
105
|
+
const read = Atomics.load(control, READ_OFFSET);
|
|
106
|
+
const write = Atomics.load(control, WRITE_OFFSET);
|
|
107
|
+
const writable = writableBytes(read, write, data.length);
|
|
108
|
+
if (writable === 0) {
|
|
109
|
+
await waitForChange(control, READ_OFFSET, read);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const copied = produce(control, data, write, input.subarray(offset), writable);
|
|
113
|
+
offset += copied;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function channelControl(channel) {
|
|
117
|
+
if (!(channel.control instanceof SharedArrayBuffer) ||
|
|
118
|
+
channel.control.byteLength !== Int32Array.BYTES_PER_ELEMENT * CONTROL_WORDS ||
|
|
119
|
+
!(channel.data instanceof SharedArrayBuffer) ||
|
|
120
|
+
channel.data.byteLength !== WASIX_CHANNEL_BYTES) {
|
|
121
|
+
throw new TypeError('invalid Oliphaunt WASIX byte channel');
|
|
122
|
+
}
|
|
123
|
+
return new Int32Array(channel.control);
|
|
124
|
+
}
|
|
125
|
+
function consume(control, data, read, write, maximumBytes) {
|
|
126
|
+
if (!Number.isSafeInteger(maximumBytes) || maximumBytes <= 0) {
|
|
127
|
+
throw new TypeError('maximum byte count must be a positive integer');
|
|
128
|
+
}
|
|
129
|
+
const available = write >= read ? write - read : data.length - read;
|
|
130
|
+
const count = Math.min(available, maximumBytes);
|
|
131
|
+
const output = data.slice(read, read + count);
|
|
132
|
+
Atomics.store(control, READ_OFFSET, (read + count) % data.length);
|
|
133
|
+
Atomics.notify(control, READ_OFFSET);
|
|
134
|
+
return output;
|
|
135
|
+
}
|
|
136
|
+
function writableBytes(read, write, capacity) {
|
|
137
|
+
if (write < read)
|
|
138
|
+
return read - write - 1;
|
|
139
|
+
if (read === 0)
|
|
140
|
+
return capacity - write - 1;
|
|
141
|
+
return capacity - write;
|
|
142
|
+
}
|
|
143
|
+
function produce(control, data, write, input, writable) {
|
|
144
|
+
const count = Math.min(writable, input.length, WASIX_BYTE_CHANNEL_CHUNK_BYTES);
|
|
145
|
+
data.set(input.subarray(0, count), write);
|
|
146
|
+
Atomics.store(control, WRITE_OFFSET, (write + count) % data.length);
|
|
147
|
+
Atomics.notify(control, WRITE_OFFSET);
|
|
148
|
+
return count;
|
|
149
|
+
}
|
|
150
|
+
function assertChannelHealthy(control) {
|
|
151
|
+
if (Atomics.load(control, FAILED) !== 0) {
|
|
152
|
+
throw new Error('Oliphaunt WASIX byte channel failed');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function assertChannelWritable(control) {
|
|
156
|
+
assertChannelHealthy(control);
|
|
157
|
+
if (Atomics.load(control, CLOSED) !== 0) {
|
|
158
|
+
throw new Error('Oliphaunt WASIX byte channel is closed');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function waitForChange(control, index, expected) {
|
|
162
|
+
const waitAsync = Atomics.waitAsync;
|
|
163
|
+
if (waitAsync !== undefined) {
|
|
164
|
+
const waiting = waitAsync(control, index, expected);
|
|
165
|
+
if (waiting.async)
|
|
166
|
+
await waiting.value;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
170
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SerializedOpenOptions } from './rpc.js';
|
|
2
|
+
import type { BinaryInput, OpenConfig, WasixRuntimeDescriptor } from './types.js';
|
|
3
|
+
export declare function serializeOpenConfig(config?: OpenConfig, runtimeDescriptor?: WasixRuntimeDescriptor): SerializedOpenOptions;
|
|
4
|
+
export declare function restoreWasix(storage: OpenConfig['storage'], bytes: BinaryInput, validate?: (options: SerializedOpenOptions) => void): Promise<void>;
|
|
5
|
+
/** @internal Restore already-owned archive bytes inside the selected realm. */
|
|
6
|
+
export declare function restoreWasixSerialized(storage: SerializedOpenOptions['storage'], bytes: Uint8Array): Promise<void>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import defaultWasixRuntime from '@oliphaunt/liboliphaunt-wasix';
|
|
2
|
+
import { serializeWasixExtensionDescriptors } from './extension-descriptor.js';
|
|
3
|
+
import { serializeWasixIcuDescriptor } from './icu-descriptor.js';
|
|
4
|
+
import { decodePhysicalArchive } from './physical-archive.js';
|
|
5
|
+
import { toUint8Array } from './query.js';
|
|
6
|
+
import { serializeWasixRuntimeDescriptor } from './runtime-descriptor.js';
|
|
7
|
+
import { serializeWasixStorage } from './storage.js';
|
|
8
|
+
import { restoreWasixStorage, WASIX_PHYSICAL_IDENTITY } from './storage-provider.js';
|
|
9
|
+
import { normalizeWasixStartupGUCs } from './startup-config.js';
|
|
10
|
+
export function serializeOpenConfig(config = {}, runtimeDescriptor = defaultWasixRuntime) {
|
|
11
|
+
const extensions = serializeWasixExtensionDescriptors(config.extensions ?? []);
|
|
12
|
+
const runtime = serializeWasixRuntimeDescriptor(runtimeDescriptor);
|
|
13
|
+
const storage = serializeWasixStorage(config.storage);
|
|
14
|
+
return {
|
|
15
|
+
runtime,
|
|
16
|
+
...(config.icu === undefined ? {} : { icu: serializeWasixIcuDescriptor(config.icu) }),
|
|
17
|
+
extensionCarriers: extensions.carriers,
|
|
18
|
+
extensions: extensions.selectedSqlNames,
|
|
19
|
+
username: config.username ?? 'postgres',
|
|
20
|
+
database: config.database ?? 'postgres',
|
|
21
|
+
startupGUCs: normalizeWasixStartupGUCs(config.startupGUCs ?? {}),
|
|
22
|
+
storage,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export async function restoreWasix(storage, bytes, validate) {
|
|
26
|
+
if (storage === undefined)
|
|
27
|
+
throw new TypeError('WASIX restore requires persistent storage');
|
|
28
|
+
const openOptions = serializeOpenConfig({ storage });
|
|
29
|
+
validate?.(openOptions);
|
|
30
|
+
await restoreWasixSerialized(openOptions.storage, toUint8Array(bytes).slice());
|
|
31
|
+
}
|
|
32
|
+
/** @internal Restore already-owned archive bytes inside the selected realm. */
|
|
33
|
+
export async function restoreWasixSerialized(storage, bytes) {
|
|
34
|
+
const snapshot = decodePhysicalArchive(bytes);
|
|
35
|
+
await restoreWasixStorage(storage, snapshot, WASIX_PHYSICAL_IDENTITY);
|
|
36
|
+
}
|
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type DirectWasixHost } from './direct-client-common.js';
|
|
2
|
+
import type { OliphauntClient, OliphauntDatabase, OpenConfig } from './types.js';
|
|
3
|
+
/** Open PostgreSQL in the importing browser realm. Guest execution may block that realm. */
|
|
4
|
+
export declare function openWasix(config?: OpenConfig): Promise<OliphauntDatabase>;
|
|
5
|
+
/** @internal Dependency seam for root-entrypoint contract qualification. */
|
|
6
|
+
export declare function openWasixWithHost(config: OpenConfig, loadHost: () => Promise<DirectWasixHost>): Promise<OliphauntDatabase>;
|
|
7
|
+
export declare const Oliphaunt: OliphauntClient;
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { restoreWasix, serializeOpenConfig } from './client-common.js';
|
|
2
|
+
import { openWasixDirect, } from './direct-client-common.js';
|
|
3
|
+
/** Open PostgreSQL in the importing browser realm. Guest execution may block that realm. */
|
|
4
|
+
export async function openWasix(config = {}) {
|
|
5
|
+
return openWasixWithHost(config, () => import('./host/index.mjs'));
|
|
6
|
+
}
|
|
7
|
+
/** @internal Dependency seam for root-entrypoint contract qualification. */
|
|
8
|
+
export async function openWasixWithHost(config, loadHost) {
|
|
9
|
+
const openOptions = serializeOpenConfig(config);
|
|
10
|
+
if (globalThis.crossOriginIsolated !== true) {
|
|
11
|
+
throw new Error('@oliphaunt/wasix-ts requires COOP: same-origin and COEP: require-corp response headers');
|
|
12
|
+
}
|
|
13
|
+
const host = await loadHost();
|
|
14
|
+
return openWasixDirect(openOptions, host, browserRealm());
|
|
15
|
+
}
|
|
16
|
+
export const Oliphaunt = {
|
|
17
|
+
open: openWasix,
|
|
18
|
+
restore: restoreWasix,
|
|
19
|
+
};
|
|
20
|
+
function browserRealm() {
|
|
21
|
+
return typeof WorkerGlobalScope !== 'undefined' && globalThis instanceof WorkerGlobalScope
|
|
22
|
+
? 'browser-worker'
|
|
23
|
+
: 'browser-main';
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const DATABASE_ROOT_DESCRIPTOR = ".oliphaunt.json";
|
|
2
|
+
export declare const DATABASE_ROOT_SCHEMA = "oliphaunt-database-root-v1";
|
|
3
|
+
export declare const DATABASE_ROOT_PGDATA = "pgdata";
|
|
4
|
+
export declare const DATABASE_ROOT_POSTGRES_MAJOR: 18;
|
|
5
|
+
export declare const WASIX_PHYSICAL_FORMAT: "wasix-pg18-v1";
|
|
6
|
+
export declare const NATIVE_PHYSICAL_FORMAT = "native-pg18-v1";
|
|
7
|
+
export type DatabaseRootDescriptor = Readonly<{
|
|
8
|
+
schema: typeof DATABASE_ROOT_SCHEMA;
|
|
9
|
+
engineFamily: 'native' | 'wasix';
|
|
10
|
+
pgdata: typeof DATABASE_ROOT_PGDATA;
|
|
11
|
+
postgresMajor: number;
|
|
12
|
+
physicalFormat: string;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function wasixDatabaseRootDescriptor(): DatabaseRootDescriptor;
|
|
15
|
+
export declare function parseDatabaseRootDescriptor(value: unknown): DatabaseRootDescriptor | undefined;
|
|
16
|
+
/** Parse the root descriptor while rejecting duplicate JSON keys. */
|
|
17
|
+
export declare function parseDatabaseRootDescriptorText(text: string): DatabaseRootDescriptor | undefined;
|
|
18
|
+
/** Parse JSON while retaining the duplicate-key rejection lost by JSON.parse(). */
|
|
19
|
+
export declare function parseJsonWithUniqueObjectKeys(text: string): unknown | undefined;
|