@bytecodealliance/preview2-shim 0.23.0 → 0.24.1

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.
@@ -1,339 +1,173 @@
1
- import { inputStreamCreate, outputStreamCreate } from "./io.js";
2
1
  import { environment } from "./environment.js";
3
- import { _setCwd, _getCwd } from "./config.js";
2
+ import { InMemoryFilesystemAdapter } from "./in-memory-filesystem.js";
3
+ import { _setCwd } from "./config.js";
4
4
  export { _setCwd } from "./config.js";
5
- export function _setFileData(fileData) {
6
- _fileData = fileData;
7
- _rootPreopen[0] = descriptorCreate(fileData);
8
- const cwd = environment.initialCwd();
9
- _setCwd(cwd || "/");
10
- }
11
- export function _getFileData() {
12
- return JSON.stringify(_fileData);
13
- }
14
- let _fileData = { dir: {} };
15
- const timeZero = {
16
- seconds: 0n,
17
- nanoseconds: 0,
18
- };
19
- /** Coerce the given object to a safe integer */
20
- function coerceToSafeIntegerNumber(obj) {
21
- let n;
22
- if (typeof obj === "number") {
23
- n = obj;
24
- }
25
- else if (typeof obj == "bigint") {
26
- n = Number(obj);
27
- }
28
- else {
29
- throw new TypeError(`unexpected non-numeric type: ${obj}`);
30
- }
31
- if (n > Number.MAX_SAFE_INTEGER) {
32
- throw new TypeError(`excessively large number: ${n}`);
33
- }
34
- return n;
35
- }
36
- function getChildEntry(parentEntry, subpath, openFlags) {
37
- if (subpath === "." && _rootPreopen && descriptorGetEntry(_rootPreopen[0]) === parentEntry) {
38
- subpath = _getCwd();
39
- if (subpath.startsWith("/") && subpath !== "/") {
40
- subpath = subpath.slice(1);
41
- }
42
- }
43
- let entry = parentEntry;
44
- let segmentIdx;
45
- do {
46
- if (!entry?.dir) {
47
- throw "not-directory";
48
- }
49
- segmentIdx = subpath.indexOf("/");
50
- const segment = segmentIdx === -1 ? subpath : subpath.slice(0, segmentIdx);
51
- if (segment === "..") {
52
- throw "no-entry";
53
- }
54
- if (segment === "." || segment === "") {
55
- }
56
- else if (!entry.dir[segment] && openFlags.create) {
57
- entry = entry.dir[segment] = openFlags.directory
58
- ? { dir: {} }
59
- : { source: new Uint8Array([]) };
60
- }
61
- else {
62
- entry = entry.dir[segment];
63
- }
64
- subpath = subpath.slice(segmentIdx + 1);
65
- } while (segmentIdx !== -1);
66
- if (!entry) {
67
- throw "no-entry";
68
- }
69
- return entry;
70
- }
71
- function getSource(fileEntry) {
72
- if (typeof fileEntry.source === "string") {
73
- fileEntry.source = new TextEncoder().encode(fileEntry.source);
74
- }
75
- return fileEntry.source;
76
- }
77
- // Keep spare capacity separate so FileDataEntry.source always reflects the logical file size.
78
- const fileWriteBuffers = new WeakMap();
79
- function getFileWriteBuffer(entry, source, requiredLength) {
80
- let buffer = fileWriteBuffers.get(entry);
81
- if (!buffer || buffer.buffer !== source.buffer || buffer.byteOffset !== source.byteOffset) {
82
- buffer = source;
83
- }
84
- if (requiredLength <= buffer.byteLength) {
85
- return buffer;
86
- }
87
- const newBuffer = new Uint8Array(Math.max(requiredLength, source.byteLength * 2));
88
- newBuffer.set(source);
89
- fileWriteBuffers.set(entry, newBuffer);
90
- return newBuffer;
91
- }
5
+ export { InMemoryFilesystemAdapter } from "./in-memory-filesystem.js";
92
6
  class DirectoryEntryStream {
93
- idx = 0;
94
- entries = [];
95
- static _create(entries) {
7
+ #implementation;
8
+ static _create(implementation) {
96
9
  const stream = new DirectoryEntryStream();
97
- stream.entries = entries;
10
+ stream.#implementation = implementation;
98
11
  return stream;
99
12
  }
100
13
  readDirectoryEntry() {
101
- if (this.idx === this.entries.length) {
102
- return undefined;
103
- }
104
- const [name, entry] = this.entries[this.idx];
105
- this.idx += 1;
106
- return {
107
- name,
108
- type: entry.dir ? "directory" : "regular-file",
109
- };
14
+ return this.#implementation.readDirectoryEntry();
110
15
  }
111
16
  }
112
- const descriptorEntryStreamCreate = DirectoryEntryStream._create;
17
+ const directoryEntryStreamCreate = DirectoryEntryStream._create;
113
18
  // @ts-expect-error - Deleting static method
114
19
  delete DirectoryEntryStream._create;
115
20
  class Descriptor {
116
- #stream;
117
- #entry;
118
- #mtime = 0;
119
- _getEntry(descriptor) {
120
- return descriptor.#entry;
21
+ #implementation;
22
+ _getImplementation(descriptor) {
23
+ return descriptor.#implementation;
121
24
  }
122
- static _create(entry, isStream) {
25
+ static _create(implementation) {
123
26
  const descriptor = new Descriptor();
124
- if (isStream) {
125
- descriptor.#stream = entry;
126
- }
127
- else {
128
- descriptor.#entry = entry;
129
- }
27
+ descriptor.#implementation = implementation;
130
28
  return descriptor;
131
29
  }
132
- readViaStream(_offset) {
133
- const source = getSource(this.#entry);
134
- let offset = Number(_offset);
135
- return inputStreamCreate({
136
- blockingRead(len) {
137
- if (offset === source.byteLength) {
138
- throw { tag: "closed" };
139
- }
140
- const bytes = source.slice(offset, offset + Number(len));
141
- offset += bytes.byteLength;
142
- return bytes;
143
- },
144
- });
145
- }
146
- writeViaStream(_offset) {
147
- const entry = this.#entry;
148
- let offset = coerceToSafeIntegerNumber(_offset);
149
- return outputStreamCreate({
150
- write(buf) {
151
- if (buf.byteLength === 0) {
152
- return;
153
- }
154
- const source = getSource(entry);
155
- const end = offset + buf.byteLength;
156
- if (!Number.isSafeInteger(end)) {
157
- throw new TypeError(`excessively large number: ${end}`);
158
- }
159
- const buffer = getFileWriteBuffer(entry, source, end);
160
- if (offset > source.byteLength) {
161
- buffer.fill(0, source.byteLength, offset);
162
- }
163
- buffer.set(buf, offset);
164
- entry.source = buffer.subarray(0, Math.max(source.byteLength, end));
165
- offset = end;
166
- },
167
- });
30
+ readViaStream(offset) {
31
+ return this.#implementation.readViaStream(offset);
32
+ }
33
+ writeViaStream(offset) {
34
+ return this.#implementation.writeViaStream(offset);
168
35
  }
169
36
  appendViaStream() {
170
- console.log(`[filesystem] APPEND STREAM`);
171
- return {};
37
+ return this.#implementation.appendViaStream();
172
38
  }
173
39
  advise(offset, length, advice) {
174
- console.log(`[filesystem] ADVISE`, offset, length, advice);
40
+ return this.#implementation.advise(offset, length, advice);
175
41
  }
176
42
  syncData() {
177
- console.log(`[filesystem] SYNC DATA`);
43
+ return this.#implementation.syncData();
178
44
  }
179
45
  getFlags() {
180
- console.log(`[filesystem] FLAGS FOR`);
181
- return {};
46
+ return this.#implementation.getFlags();
182
47
  }
183
48
  getType() {
184
- if (this.#stream) {
185
- return "fifo";
186
- }
187
- if (this.#entry.dir) {
188
- return "directory";
189
- }
190
- if (this.#entry.source) {
191
- return "regular-file";
192
- }
193
- return "unknown";
49
+ return this.#implementation.getType();
194
50
  }
195
51
  setSize(size) {
196
- console.log(`[filesystem] SET SIZE`, size);
52
+ return this.#implementation.setSize(size);
197
53
  }
198
54
  setTimes(dataAccessTimestamp, dataModificationTimestamp) {
199
- console.log(`[filesystem] SET TIMES`, dataAccessTimestamp, dataModificationTimestamp);
55
+ return this.#implementation.setTimes(dataAccessTimestamp, dataModificationTimestamp);
200
56
  }
201
57
  read(length, offset) {
202
- const source = getSource(this.#entry);
203
- const off = coerceToSafeIntegerNumber(offset);
204
- const len = coerceToSafeIntegerNumber(length);
205
- const result = [
206
- source.slice(off, off + len),
207
- off + len >= source.byteLength,
208
- ];
209
- return result;
58
+ return this.#implementation.read(length, offset);
210
59
  }
211
60
  write(buffer, offset) {
212
- if (offset !== 0n) {
213
- throw "invalid-seek";
214
- }
215
- this.#entry.source = buffer;
216
- return BigInt(buffer.byteLength);
61
+ return this.#implementation.write(buffer, offset);
217
62
  }
218
63
  readDirectory() {
219
- if (!this.#entry?.dir) {
220
- throw "bad-descriptor";
221
- }
222
- return descriptorEntryStreamCreate(Object.entries(this.#entry.dir).sort(([a], [b]) => (a > b ? 1 : -1)));
64
+ return directoryEntryStreamCreate(this.#implementation.readDirectory());
223
65
  }
224
66
  sync() {
225
- console.log(`[filesystem] SYNC`);
67
+ return this.#implementation.sync();
226
68
  }
227
69
  createDirectoryAt(path) {
228
- const entry = getChildEntry(this.#entry, path, {
229
- create: true,
230
- directory: true,
231
- });
232
- if (entry.source) {
233
- throw "exist";
234
- }
70
+ return this.#implementation.createDirectoryAt(path);
235
71
  }
236
72
  stat() {
237
- let type = "unknown";
238
- let size = 0n;
239
- if (this.#entry.source) {
240
- type = "regular-file";
241
- const source = getSource(this.#entry);
242
- size = BigInt(source.byteLength);
243
- }
244
- else if (this.#entry.dir) {
245
- type = "directory";
246
- }
247
- return {
248
- type,
249
- linkCount: 0n,
250
- size,
251
- dataAccessTimestamp: timeZero,
252
- dataModificationTimestamp: timeZero,
253
- statusChangeTimestamp: timeZero,
254
- };
255
- }
256
- statAt(_pathFlags, path) {
257
- const entry = getChildEntry(this.#entry, path, {
258
- create: false,
259
- directory: false,
260
- });
261
- let type = "unknown";
262
- let size = 0n;
263
- if (entry.source) {
264
- type = "regular-file";
265
- const source = getSource(entry);
266
- size = BigInt(source.byteLength);
267
- }
268
- else if (entry.dir) {
269
- type = "directory";
270
- }
271
- return {
272
- type,
273
- linkCount: 0n,
274
- size,
275
- dataAccessTimestamp: timeZero,
276
- dataModificationTimestamp: timeZero,
277
- statusChangeTimestamp: timeZero,
278
- };
73
+ return this.#implementation.stat();
74
+ }
75
+ statAt(pathFlags, path) {
76
+ return this.#implementation.statAt(pathFlags, path);
279
77
  }
280
- setTimesAt() {
281
- console.log(`[filesystem] SET TIMES AT`);
78
+ setTimesAt(pathFlags, path, dataAccessTimestamp, dataModificationTimestamp) {
79
+ return this.#implementation.setTimesAt(pathFlags, path, dataAccessTimestamp, dataModificationTimestamp);
282
80
  }
283
- linkAt() {
284
- console.log(`[filesystem] LINK AT`);
81
+ linkAt(oldPathFlags, oldPath, newDescriptor, newPath) {
82
+ return this.#implementation.linkAt(oldPathFlags, oldPath, descriptorGetImplementation(newDescriptor), newPath);
285
83
  }
286
- openAt(_pathFlags, path, openFlags, _flags) {
287
- const childEntry = getChildEntry(this.#entry, path, openFlags);
288
- return descriptorCreate(childEntry);
84
+ openAt(pathFlags, path, openFlags, flags) {
85
+ return descriptorCreate(this.#implementation.openAt(pathFlags, path, openFlags, flags));
289
86
  }
290
- readlinkAt(_path) {
291
- console.log(`[filesystem] READLINK AT`);
292
- return "";
87
+ readlinkAt(path) {
88
+ return this.#implementation.readlinkAt(path);
293
89
  }
294
- removeDirectoryAt() {
295
- console.log(`[filesystem] REMOVE DIR AT`);
90
+ removeDirectoryAt(path) {
91
+ return this.#implementation.removeDirectoryAt(path);
296
92
  }
297
- renameAt() {
298
- console.log(`[filesystem] RENAME AT`);
93
+ renameAt(oldPath, newDescriptor, newPath) {
94
+ return this.#implementation.renameAt(oldPath, descriptorGetImplementation(newDescriptor), newPath);
299
95
  }
300
- symlinkAt() {
301
- console.log(`[filesystem] SYMLINK AT`);
96
+ symlinkAt(oldPath, newPath) {
97
+ return this.#implementation.symlinkAt(oldPath, newPath);
302
98
  }
303
- unlinkFileAt() {
304
- console.log(`[filesystem] UNLINK FILE AT`);
99
+ unlinkFileAt(path) {
100
+ return this.#implementation.unlinkFileAt(path);
305
101
  }
306
102
  isSameObject(other) {
307
- return other === this;
103
+ return this.#implementation.isSameObject(descriptorGetImplementation(other));
308
104
  }
309
105
  metadataHash() {
310
- let upper = 0n;
311
- upper += BigInt(this.#mtime);
312
- return { upper, lower: 0n };
106
+ return this.#implementation.metadataHash();
313
107
  }
314
- metadataHashAt(_pathFlags, _path) {
315
- return this.metadataHash();
108
+ metadataHashAt(pathFlags, path) {
109
+ return this.#implementation.metadataHashAt(pathFlags, path);
316
110
  }
317
111
  }
318
- const descriptorGetEntry = Descriptor.prototype._getEntry;
112
+ const descriptorGetImplementation = Descriptor.prototype._getImplementation;
319
113
  // @ts-expect-error - Deleting prototype method
320
- delete Descriptor.prototype._getEntry;
114
+ delete Descriptor.prototype._getImplementation;
321
115
  const descriptorCreate = Descriptor._create;
322
116
  // @ts-expect-error - Deleting static method
323
117
  delete Descriptor._create;
324
- let _preopens = [[descriptorCreate(_fileData), "/"]];
325
- let _rootPreopen = _preopens[0];
118
+ const defaultAdapter = new InMemoryFilesystemAdapter();
119
+ let _fileData = { dir: {} };
120
+ let _preopens = [];
121
+ let _rootPreopen = null;
326
122
  export const preopens = {
327
123
  getDirectories() {
328
124
  return _preopens;
329
125
  },
330
126
  };
127
+ /** Create isolated filesystem namespaces backed by an application-selected adapter. */
128
+ export function createFilesystem({ adapter, preopens: configuredPreopens, }) {
129
+ const entries = Object.entries(configuredPreopens).map(([guestPath, capability]) => [descriptorCreate(adapter.getRoot(capability)), guestPath]);
130
+ let disposed = false;
131
+ return {
132
+ types,
133
+ preopens: {
134
+ getDirectories() {
135
+ if (disposed) {
136
+ throw new Error("filesystem adapter has been disposed");
137
+ }
138
+ return [...entries];
139
+ },
140
+ },
141
+ dispose() {
142
+ if (disposed) {
143
+ return;
144
+ }
145
+ disposed = true;
146
+ adapter.dispose?.();
147
+ },
148
+ };
149
+ }
150
+ export function _setFileData(fileData) {
151
+ _fileData = fileData;
152
+ if (_rootPreopen) {
153
+ _rootPreopen[0] = descriptorCreate(defaultAdapter.getRoot(fileData));
154
+ }
155
+ else {
156
+ _setPreopens({ "/": fileData });
157
+ }
158
+ const cwd = environment.initialCwd();
159
+ _setCwd(cwd || "/");
160
+ }
161
+ export function _getFileData() {
162
+ return JSON.stringify(_fileData);
163
+ }
331
164
  /**
332
165
  * Replace all preopens with the given set.
333
166
  * @param preopensConfig - Map of virtual paths to file data entries
334
167
  */
335
168
  export function _setPreopens(preopensConfig) {
336
169
  _preopens = [];
170
+ _rootPreopen = null;
337
171
  for (const [virtualPath, fileData] of Object.entries(preopensConfig)) {
338
172
  _addPreopen(virtualPath, fileData);
339
173
  }
@@ -344,40 +178,25 @@ export function _setPreopens(preopensConfig) {
344
178
  * @param fileData - The file data object representing the directory
345
179
  */
346
180
  export function _addPreopen(virtualPath, fileData) {
347
- const descriptor = descriptorCreate(fileData);
348
- _preopens.push([descriptor, virtualPath]);
181
+ const descriptor = descriptorCreate(defaultAdapter.getRoot(fileData));
182
+ const entry = [descriptor, virtualPath];
183
+ _preopens.push(entry);
349
184
  if (virtualPath === "/") {
350
- _rootPreopen = [descriptor, virtualPath];
185
+ _rootPreopen = entry;
351
186
  }
352
187
  }
353
- /**
354
- * Clear all preopens, giving the guest no filesystem access.
355
- *
356
- * This functionality exists mostly to maintain backwards compatibility. Prefer setting preopens
357
- * via `WASIShim` rather than making top level changes to preopens using these functions.
358
- */
188
+ /** Clear all preopens, giving the guest no filesystem access. */
359
189
  export function _clearPreopens() {
360
190
  _preopens = [];
361
191
  _rootPreopen = null;
362
192
  }
363
- /**
364
- * Get current preopens configuration.
365
- * @returns Array of [descriptor, virtualPath] pairs
366
- */
193
+ /** Get current preopens configuration. */
367
194
  export function _getPreopens() {
368
195
  return [..._preopens];
369
196
  }
370
- /**
371
- * Create a preopen descriptor for a host path.
372
- * This is used internally to create isolated preopen instances.
373
- * @param hostPreopen - The host filesystem path
374
- * @returns A preopen descriptor
375
- */
197
+ /** Reject host paths because browser filesystems require explicit capabilities. */
376
198
  export function _createPreopenDescriptor(hostPreopen) {
377
- _fileData.dir = {
378
- [hostPreopen]: {},
379
- };
380
- return descriptorCreate(_fileData);
199
+ throw new TypeError(`browser preopen ${JSON.stringify(hostPreopen)} is a host path; configure browser file data or an adapter instead`);
381
200
  }
382
201
  export const types = {
383
202
  Descriptor,
@@ -1,4 +1,13 @@
1
1
  import type { incomingHandler as IncomingHandlerNamespace, outgoingHandler as OutgoingHandlerNamespace, types as TypesNamespace } from "../../types/http.js";
2
+ export { InMemoryHttpClient } from "./in-memory-http.js";
3
+ /** Enable or disable Fetch `ReadableStream` request bodies. Disabled by default. */
4
+ export declare function _setRequestStreaming(enabled: boolean): void;
2
5
  export declare const outgoingHandler: typeof OutgoingHandlerNamespace;
3
6
  export declare const incomingHandler: typeof IncomingHandlerNamespace;
7
+ export type WasiIncomingHandler = (request: TypesNamespace.IncomingRequest, responseOut: TypesNamespace.ResponseOutparam) => void | Promise<void>;
8
+ export type WebIncomingHandler = (request: Request) => Response | Promise<Response>;
9
+ /** Create a `wasi:http/incoming-handler` namespace backed by a Web Request handler. */
10
+ export declare function createIncomingHandler(handler: WebIncomingHandler): typeof IncomingHandlerNamespace;
11
+ /** Translate a browser Request through a WASI handler with a synchronously readable body. */
12
+ export declare function handleIncomingRequest(request: Request, handler: WasiIncomingHandler): Promise<Response>;
4
13
  export declare const types: typeof TypesNamespace;