@bytecodealliance/preview2-shim 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +220 -0
  2. package/README.md +4 -6
  3. package/lib/browser/cli.js +79 -12
  4. package/lib/browser/filesystem.js +168 -198
  5. package/lib/browser/index.js +1 -5
  6. package/lib/browser/io.js +173 -29
  7. package/lib/common/io.js +6 -4
  8. package/lib/{http → common}/make-request.js +1 -1
  9. package/lib/nodejs/cli.js +5 -4
  10. package/lib/nodejs/filesystem.js +106 -73
  11. package/lib/nodejs/http.js +309 -3
  12. package/lib/nodejs/index.js +0 -5
  13. package/package.json +1 -1
  14. package/types/interfaces/wasi-cli-stderr.d.ts +1 -1
  15. package/types/interfaces/wasi-cli-stdin.d.ts +1 -1
  16. package/types/interfaces/wasi-cli-stdout.d.ts +1 -1
  17. package/types/interfaces/wasi-cli-terminal-stderr.d.ts +1 -1
  18. package/types/interfaces/wasi-cli-terminal-stdin.d.ts +1 -1
  19. package/types/interfaces/wasi-cli-terminal-stdout.d.ts +1 -1
  20. package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +1 -1
  21. package/types/interfaces/wasi-clocks-timezone.d.ts +1 -1
  22. package/types/interfaces/wasi-filesystem-preopens.d.ts +1 -1
  23. package/types/interfaces/wasi-filesystem-types.d.ts +8 -8
  24. package/types/interfaces/wasi-http-incoming-handler.d.ts +19 -0
  25. package/types/interfaces/wasi-http-outgoing-handler.d.ts +23 -0
  26. package/types/interfaces/wasi-http-types.d.ts +371 -0
  27. package/types/interfaces/wasi-io-streams.d.ts +13 -21
  28. package/types/interfaces/wasi-sockets-instance-network.d.ts +1 -1
  29. package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +5 -5
  30. package/types/interfaces/wasi-sockets-tcp-create-socket.d.ts +4 -4
  31. package/types/interfaces/wasi-sockets-tcp.d.ts +7 -7
  32. package/types/interfaces/wasi-sockets-udp-create-socket.d.ts +4 -4
  33. package/types/interfaces/wasi-sockets-udp.d.ts +5 -5
  34. package/types/wasi-cli-command.d.ts +3 -3
  35. package/types/wasi-http-proxy.d.ts +13 -0
  36. package/lib/browser/poll.js +0 -53
  37. package/lib/http/wasi-http.js +0 -382
  38. package/lib/nodejs/poll.js +0 -9
  39. /package/lib/{http/synckit → synckit}/index.d.ts +0 -0
  40. /package/lib/{http/synckit → synckit}/index.js +0 -0
package/lib/browser/io.js CHANGED
@@ -1,39 +1,183 @@
1
- import { Io } from '../common/io.js';
1
+ let id = 0;
2
2
 
3
- // buffer until the next newline
4
- export class NewlineBufferStream {
3
+ const symbolDispose = Symbol.dispose || Symbol.for('dispose');
4
+
5
+ class Error {
6
+ constructor (msg) {
7
+ this.msg = msg;
8
+ }
9
+ toDebugString () {
10
+ return this.msg;
11
+ }
12
+ }
13
+
14
+ /**
15
+ * @typedef {{
16
+ * read?: (len: BigInt) => Uint8Array,
17
+ * blockingRead: (len: BigInt) => Uint8Array,
18
+ * skip?: (len: BigInt) => BigInt,
19
+ * blockingSkip?: (len: BigInt) => BigInt,
20
+ * subscribe: () => void,
21
+ * drop?: () => void,
22
+ * }} InputStreamHandler
23
+ *
24
+ * @typedef {{
25
+ * checkWrite?: () -> BigInt,
26
+ * write: (buf: Uint8Array) => BigInt,
27
+ * blockingWriteAndFlush?: (buf: Uint8Array) => void,
28
+ * flush?: () => void,
29
+ * blockingFlush: () => void,
30
+ * writeZeroes?: (len: BigInt) => void,
31
+ * blockingWriteZeroes?: (len: BigInt) => void,
32
+ * blockingWriteZeroesAndFlush?: (len: BigInt) => void,
33
+ * splice?: (src: InputStream, len: BigInt) => BigInt,
34
+ * blockingSplice?: (src: InputStream, len: BigInt) => BigInt,
35
+ * forward?: (src: InputStream) => void,
36
+ * subscribe?: () => void,
37
+ * drop?: () => void,
38
+ * }} OutputStreamHandler
39
+ *
40
+ **/
41
+
42
+ class InputStream {
43
+ /**
44
+ * @param {InputStreamHandler} handler
45
+ */
5
46
  constructor (handler) {
6
- this.bufferLen = 0;
7
- this.bufferCapacity = 1024;
8
- this.buffer = new Uint8Array(1024);
47
+ if (!handler)
48
+ console.trace('no handler');
49
+ this.id = ++id;
9
50
  this.handler = handler;
10
51
  }
11
- write (bytes) {
12
- const newlineIdx = bytes.lastIndexOf(10);
13
- if (newlineIdx === -1) {
14
- this.#addToBuffer(bytes);
15
- } else {
16
- this.#addToBuffer(bytes.slice(0, newlineIdx + 1));
17
- this.handler(new TextDecoder().decode(this.buffer.slice(0, this.bufferLen)));
18
- this.bufferLen = 0;
19
- this.#addToBuffer(bytes.slice(newlineIdx + 1));
20
- }
52
+ read(len) {
53
+ if (this.handler.read)
54
+ return this.handler.read(len);
55
+ return this.handler.blockingRead.call(this, len);
21
56
  }
22
- #addToBuffer (bytes) {
23
- if (bytes.byteLength + this.bufferLen > this.bufferCapacity) {
24
- this.bufferCapacity *= 2;
25
- const buffer = new Uint8Array(this.bufferCapacity);
26
- buffer.set(this.buffer);
27
- this.buffer = buffer;
57
+ blockingRead(len) {
58
+ return this.handler.blockingRead.call(this, len);
59
+ }
60
+ skip(len) {
61
+ if (this.handler.skip)
62
+ return this.handler.skip.call(this, len);
63
+ if (this.handler.read) {
64
+ const bytes = this.handler.read.call(this, len);
65
+ return BigInt(bytes.byteLength);
28
66
  }
29
- this.buffer.set(bytes, this.bufferLen);
30
- this.bufferLen += bytes.byteLength;
67
+ return this.blockingSkip.call(this, len);
68
+ }
69
+ blockingSkip(len) {
70
+ if (this.handler.blockingSkip)
71
+ return this.handler.blockingSkip.call(this, len);
72
+ const bytes = this.handler.blockingRead.call(this, len);
73
+ return BigInt(bytes.byteLength);
74
+ }
75
+ subscribe() {
76
+ console.log(`[streams] Subscribe to input stream ${this.id}`);
77
+ }
78
+ [symbolDispose] () {
79
+ if (this.handler.drop)
80
+ this.handler.drop.call(this);
81
+ }
82
+ }
83
+
84
+ class OutputStream {
85
+ /**
86
+ * @param {OutputStreamHandler} handler
87
+ */
88
+ constructor (handler) {
89
+ if (!handler)
90
+ console.trace('no handler');
91
+ this.id = ++id;
92
+ this.open = true;
93
+ this.handler = handler;
94
+ }
95
+ checkWrite(len) {
96
+ if (!this.open)
97
+ return 0n;
98
+ if (this.handler.checkWrite)
99
+ return this.handler.checkWrite.call(this, len);
100
+ return 1_000_000n;
31
101
  }
102
+ write(buf) {
103
+ this.handler.write.call(this, buf);
104
+ }
105
+ blockingWriteAndFlush(buf) {
106
+ /// Perform a write of up to 4096 bytes, and then flush the stream. Block
107
+ /// until all of these operations are complete, or an error occurs.
108
+ ///
109
+ /// This is a convenience wrapper around the use of `check-write`,
110
+ /// `subscribe`, `write`, and `flush`, and is implemented with the
111
+ /// following pseudo-code:
112
+ ///
113
+ /// ```text
114
+ /// let pollable = this.subscribe();
115
+ /// while !contents.is_empty() {
116
+ /// // Wait for the stream to become writable
117
+ /// poll-one(pollable);
118
+ /// let Ok(n) = this.check-write(); // eliding error handling
119
+ /// let len = min(n, contents.len());
120
+ /// let (chunk, rest) = contents.split_at(len);
121
+ /// this.write(chunk ); // eliding error handling
122
+ /// contents = rest;
123
+ /// }
124
+ /// this.flush();
125
+ /// // Wait for completion of `flush`
126
+ /// poll-one(pollable);
127
+ /// // Check for any errors that arose during `flush`
128
+ /// let _ = this.check-write(); // eliding error handling
129
+ /// ```
130
+ this.handler.write.call(this, buf);
131
+ }
132
+ flush() {
133
+ if (this.handler.flush)
134
+ this.handler.flush.call(this);
135
+ }
136
+ blockingFlush() {
137
+ this.open = true;
138
+ }
139
+ writeZeroes(len) {
140
+ this.write.call(this, new Uint8Array(Number(len)));
141
+ }
142
+ blockingWriteZeroes(len) {
143
+ this.blockingWrite.call(this, new Uint8Array(Number(len)));
144
+ }
145
+ blockingWriteZeroesAndFlush(len) {
146
+ this.blockingWriteAndFlush.call(this, new Uint8Array(Number(len)));
147
+ }
148
+ splice(src, len) {
149
+ const spliceLen = Math.min(len, this.checkWrite.call(this));
150
+ const bytes = src.read(spliceLen);
151
+ this.write.call(this, bytes);
152
+ return bytes.byteLength;
153
+ }
154
+ blockingSplice(_src, _len) {
155
+ console.log(`[streams] Blocking splice ${this.id}`);
156
+ }
157
+ forward(_src) {
158
+ console.log(`[streams] Forward ${this.id}`);
159
+ }
160
+ subscribe() {
161
+ console.log(`[streams] Subscribe to output stream ${this.id}`);
162
+ }
163
+ [symbolDispose]() {
164
+ }
165
+ }
166
+
167
+ export const streams = { Error, InputStream, OutputStream };
168
+
169
+ class Pollable {}
170
+
171
+ function pollList (_list) {
172
+ // TODO
32
173
  }
33
174
 
34
- export const _io = new Io(
35
- new NewlineBufferStream(console.log.bind(console)),
36
- new NewlineBufferStream(console.error.bind(console))
37
- );
175
+ function pollOne (_poll) {
176
+ // TODO
177
+ }
38
178
 
39
- export const streams = _io.streams;
179
+ export const poll = {
180
+ Pollable,
181
+ pollList,
182
+ pollOne
183
+ };
package/lib/common/io.js CHANGED
@@ -1,5 +1,7 @@
1
1
  let id = 0;
2
2
 
3
+ const symbolDispose = Symbol.dispose || Symbol.for('dispose');
4
+
3
5
  class Error {
4
6
  constructor (msg) {
5
7
  this.msg = msg;
@@ -16,7 +18,7 @@ class Error {
16
18
  * skip?: (len: BigInt) => BigInt,
17
19
  * blockingSkip?: (len: BigInt) => BigInt,
18
20
  * subscribe: () => void,
19
- * drop: () => void,
21
+ * drop?: () => void,
20
22
  * }} InputStreamHandler
21
23
  *
22
24
  * @typedef {{
@@ -32,7 +34,7 @@ class Error {
32
34
  * blockingSplice?: (src: InputStream, len: BigInt) => BigInt,
33
35
  * forward?: (src: InputStream) => void,
34
36
  * subscribe?: () => void,
35
- * drop: () => void,
37
+ * drop?: () => void,
36
38
  * }} OutputStreamHandler
37
39
  *
38
40
  **/
@@ -73,7 +75,7 @@ class InputStream {
73
75
  subscribe() {
74
76
  console.log(`[streams] Subscribe to input stream ${this.id}`);
75
77
  }
76
- drop () {
78
+ [symbolDispose] () {
77
79
  if (this.handler.drop)
78
80
  this.handler.drop.call(this);
79
81
  }
@@ -158,7 +160,7 @@ class OutputStream {
158
160
  subscribe() {
159
161
  console.log(`[streams] Subscribe to output stream ${this.id}`);
160
162
  }
161
- drop() {
163
+ [symbolDispose]() {
162
164
  }
163
165
  }
164
166
 
@@ -1,4 +1,4 @@
1
- import { runAsWorker } from "./synckit/index.js";
1
+ import { runAsWorker } from "../synckit/index.js";
2
2
 
3
3
  /**
4
4
  * @param {import("../../types/interfaces/wasi-http-types").Request} req
package/lib/nodejs/cli.js CHANGED
@@ -2,7 +2,8 @@ import { argv, env, cwd } from 'node:process';
2
2
  import { streams } from '../common/io.js';
3
3
  const { InputStream, OutputStream } = streams;
4
4
 
5
- let _env = Object.entries(env), _args = argv, _cwd = cwd();
5
+ let _env = Object.entries(env), _args = argv.slice(1), _cwd = cwd();
6
+ const symbolDispose = Symbol.dispose || Symbol.for('dispose');
6
7
 
7
8
  export const environment = {
8
9
  getEnvironment () {
@@ -29,7 +30,7 @@ const stdinStream = new InputStream({
29
30
  subscribe () {
30
31
  // TODO
31
32
  },
32
- drop () {
33
+ [symbolDispose] () {
33
34
  // TODO
34
35
  }
35
36
  });
@@ -39,7 +40,7 @@ const stdoutStream = new OutputStream({
39
40
  },
40
41
  blockingFlush () {
41
42
  },
42
- drop () {
43
+ [symbolDispose] () {
43
44
  }
44
45
  });
45
46
  const stderrStream = new OutputStream({
@@ -49,7 +50,7 @@ const stderrStream = new OutputStream({
49
50
  blockingFlush () {
50
51
 
51
52
  },
52
- drop () {
53
+ [symbolDispose] () {
53
54
 
54
55
  }
55
56
  });
@@ -5,6 +5,8 @@ import { platform } from 'node:process';
5
5
 
6
6
  const { InputStream, OutputStream, Error: StreamError } = streams;
7
7
 
8
+ const symbolDispose = Symbol.dispose || Symbol.for('dispose');
9
+
8
10
  const isWindows = platform === 'win32';
9
11
 
10
12
  const nsMagnitude = 1_000_000_000_000n;
@@ -39,35 +41,6 @@ function lookupType (obj) {
39
41
  * } DescriptorProps
40
42
  */
41
43
  export class FileSystem {
42
- // Note: This should implement per-segment semantics of openAt, but we cannot currently
43
- // due to the lack of support for openat() in Node.js.
44
- // Tracking issue: https://github.com/libuv/libuv/issues/4167
45
-
46
- // TODO: support followSymlinks
47
- getFullPath (descriptor, subpath, _followSymlinks) {
48
- if (subpath.indexOf('\\') !== -1)
49
- subpath = subpath.replace(/\\/g, '/');
50
- if (subpath[0] === '/') {
51
- let bestPreopenMatch = '';
52
- for (const preopenEntry of this.preopenEntries) {
53
- if (subpath.startsWith(preopenEntry[1]) && (!bestPreopenMatch || bestPreopenMatch.length < preopenEntry[1].length)) {
54
- bestPreopenMatch = preopenEntry;
55
- }
56
- }
57
- if (!bestPreopenMatch)
58
- throw 'no-entry';
59
- descriptor = bestPreopenMatch[0];
60
- subpath = subpath.slice(bestPreopenMatch[1]);
61
- if (subpath[0] === '/')
62
- subpath = subpath.slice(1);
63
- }
64
- if (subpath.startsWith('.'))
65
- subpath = subpath.slice(subpath[1] === '/' ? 2 : 1);
66
- if (descriptor.hostPreopen)
67
- return descriptor.hostPreopen + (descriptor.hostPreopen.endsWith('/') ? '' : '/') + subpath;
68
- return descriptor.fullPath + '/' + subpath;
69
- }
70
-
71
44
  /**
72
45
  *
73
46
  * @param {[string, string][]} preopens
@@ -79,16 +52,18 @@ export class FileSystem {
79
52
  this.cwd = environment.initialCwd();
80
53
 
81
54
  class FileInputStream extends InputStream {
55
+ #hostFd;
56
+ #position;
82
57
  constructor (hostFd, position) {
83
58
  super({
84
59
  blockingRead (len) {
85
60
  const buf = new Uint8Array(Number(len));
86
61
  try {
87
- var bytesRead = readSync(this.hostFd, buf, 0, buf.byteLength, this.position);
62
+ var bytesRead = readSync(self.#hostFd, buf, 0, buf.byteLength, self.#position);
88
63
  } catch (e) {
89
64
  throw { tag: 'last-operation-failed', val: new StreamError(e.message) };
90
65
  }
91
- this.position += bytesRead;
66
+ self.#position += bytesRead;
92
67
  if (bytesRead < buf.byteLength) {
93
68
  if (bytesRead === 0)
94
69
  throw { tag: 'closed' };
@@ -99,26 +74,29 @@ export class FileSystem {
99
74
  subscribe () {
100
75
  // TODO
101
76
  },
102
- drop () {
77
+ [symbolDispose] () {
103
78
  // TODO
104
79
  }
105
80
  });
106
- this.hostFd = hostFd;
107
- this.position = Number(position);
81
+ const self = this;
82
+ this.#hostFd = hostFd;
83
+ this.#position = Number(position);
108
84
  }
109
85
  }
110
86
 
111
87
  class FileOutputStream extends OutputStream {
88
+ #hostFd;
89
+ #position;
112
90
  constructor (hostFd, position) {
113
91
  super({
114
92
  write (contents) {
115
93
  let totalWritten = 0;
116
94
  while (totalWritten !== contents.byteLength) {
117
- const bytesWritten = writeSync(this.hostFd, contents, null, null, this.position);
95
+ const bytesWritten = writeSync(self.#hostFd, contents, null, null, self.#position);
118
96
  totalWritten += bytesWritten;
119
97
  contents = new Uint8Array(contents.buffer, bytesWritten);
120
98
  }
121
- this.position += contents.byteLength;
99
+ self.#position += contents.byteLength;
122
100
  },
123
101
  blockingFlush () {
124
102
 
@@ -127,19 +105,18 @@ export class FileSystem {
127
105
 
128
106
  }
129
107
  });
130
- this.hostFd = hostFd;
131
- this.position = Number(position);
108
+ const self = this;
109
+ this.#hostFd = hostFd;
110
+ this.#position = Number(position);
132
111
  }
133
112
  }
134
113
 
135
114
  class DirectoryEntryStream {
136
- constructor (dir) {
137
- this.dir = dir;
138
- }
115
+ #dir;
139
116
  readDirectoryEntry () {
140
117
  let entry;
141
118
  try {
142
- entry = this.dir.readSync();
119
+ entry = this.#dir.readSync();
143
120
  } catch (e) {
144
121
  throw convertFsError(e);
145
122
  }
@@ -150,27 +127,54 @@ export class FileSystem {
150
127
  const type = lookupType(entry);
151
128
  return { name, type };
152
129
  }
153
- drop () {
154
- this.dir.closeSync();
130
+ [symbolDispose] () {
131
+ this.#dir.closeSync();
132
+ }
133
+
134
+ static _create (dir) {
135
+ const dirStream = new DirectoryEntryStream();
136
+ dirStream.#dir = dir;
137
+ return dirStream;
155
138
  }
156
139
  }
140
+ const directoryEntryStreamCreate = DirectoryEntryStream._create;
141
+ delete DirectoryEntryStream._create;
157
142
 
143
+ // Note: This should implement per-segment semantics of openAt, but we cannot currently
144
+ // due to the lack of support for openat() in Node.js.
145
+ // Tracking issue: https://github.com/libuv/libuv/issues/4167
158
146
  /**
159
147
  * @implements {DescriptorProps}
160
148
  */
161
149
  class Descriptor {
150
+ #hostPreopen;
151
+ #fd;
152
+ #fullPath;
153
+
154
+ static _createPreopen (hostPreopen) {
155
+ const descriptor = new Descriptor();
156
+ descriptor.#hostPreopen = hostPreopen;
157
+ return descriptor;
158
+ }
159
+ static _create (fd, fullPath) {
160
+ const descriptor = new Descriptor();
161
+ descriptor.#fd = fd;
162
+ descriptor.#fullPath = fullPath;
163
+ return descriptor;
164
+ }
165
+
162
166
  constructor () {
163
167
  this.id = fs.descriptorCnt++;
164
168
  }
165
169
  readViaStream(offset) {
166
- if (this.hostPreopen)
170
+ if (this.#hostPreopen)
167
171
  throw { tag: 'last-operation-failed', val: new StreamError };
168
- return new FileInputStream(this.fd, offset);
172
+ return new FileInputStream(this.#fd, offset);
169
173
  }
170
174
  writeViaStream(offset) {
171
- if (this.hostPreopen)
175
+ if (this.#hostPreopen)
172
176
  throw 'is-directory';
173
- return new FileOutputStream(this.fd, offset);
177
+ return new FileOutputStream(this.#fd, offset);
174
178
  }
175
179
 
176
180
  appendViaStream() {
@@ -190,8 +194,8 @@ export class FileSystem {
190
194
  }
191
195
 
192
196
  getType() {
193
- if (this.hostPreopen) return 'directory';
194
- const stats = fstatSync(this.fd);
197
+ if (this.#hostPreopen) return 'directory';
198
+ const stats = fstatSync(this.#fd);
195
199
  return lookupType(stats);
196
200
  }
197
201
 
@@ -208,23 +212,23 @@ export class FileSystem {
208
212
  }
209
213
 
210
214
  read(length, offset) {
211
- if (!this.fullPath) throw 'bad-descriptor';
215
+ if (!this.#fullPath) throw 'bad-descriptor';
212
216
  const buf = new Uint8Array(length);
213
- const bytesRead = readSync(this.fd, buf, Number(offset), length, 0);
217
+ const bytesRead = readSync(this.#fd, buf, Number(offset), length, 0);
214
218
  const out = new Uint8Array(buf.buffer, 0, bytesRead);
215
219
  return [out, bytesRead === 0 ? 'ended' : 'open'];
216
220
  }
217
221
 
218
222
  write(buffer, offset) {
219
- if (!this.fullPath) throw 'bad-descriptor';
220
- return BigInt(writeSync(this.fd, buffer, Number(offset), buffer.byteLength - offset, 0));
223
+ if (!this.#fullPath) throw 'bad-descriptor';
224
+ return BigInt(writeSync(this.#fd, buffer, Number(offset), buffer.byteLength - offset, 0));
221
225
  }
222
226
 
223
227
  readDirectory() {
224
- if (!this.fullPath) throw 'bad-descriptor';
228
+ if (!this.#fullPath) throw 'bad-descriptor';
225
229
  try {
226
- const dir = opendirSync(isWindows ? this.fullPath.slice(1) : this.fullPath);
227
- return new DirectoryEntryStream(dir);
230
+ const dir = opendirSync(isWindows ? this.#fullPath.slice(1) : this.#fullPath);
231
+ return directoryEntryStreamCreate(dir);
228
232
  }
229
233
  catch (e) {
230
234
  throw convertFsError(e);
@@ -236,7 +240,7 @@ export class FileSystem {
236
240
  }
237
241
 
238
242
  createDirectoryAt(path) {
239
- const fullPath = fs.getFullPath(this, path);
243
+ const fullPath = this.#getFullPath(path);
240
244
  try {
241
245
  mkdirSync(fullPath);
242
246
  }
@@ -246,10 +250,10 @@ export class FileSystem {
246
250
  }
247
251
 
248
252
  stat() {
249
- if (this.hostPreopen) throw 'invalid';
253
+ if (this.#hostPreopen) throw 'invalid';
250
254
  let stats;
251
255
  try {
252
- stats = fstatSync(this.fd, { bigint: true });
256
+ stats = fstatSync(this.#fd, { bigint: true });
253
257
  }
254
258
  catch (e) {
255
259
  convertFsError(e);
@@ -266,7 +270,7 @@ export class FileSystem {
266
270
  }
267
271
 
268
272
  statAt(pathFlags, path) {
269
- const fullPath = fs.getFullPath(this, path, false);
273
+ const fullPath = this.#getFullPath(path, false);
270
274
  let stats;
271
275
  try {
272
276
  stats = (pathFlags.symlinkFollow ? statSync : lstatSync)(isWindows ? fullPath.slice(1) : fullPath, { bigint: true });
@@ -294,7 +298,7 @@ export class FileSystem {
294
298
  }
295
299
 
296
300
  openAt(pathFlags, path, openFlags, descriptorFlags, modes) {
297
- const fullPath = fs.getFullPath(this, path, pathFlags.symlinkFollow);
301
+ const fullPath = this.#getFullPath(path, pathFlags.symlinkFollow);
298
302
  let fsOpenFlags = 0x0;
299
303
  if (openFlags.create)
300
304
  fsOpenFlags |= constants.O_CREAT;
@@ -325,7 +329,7 @@ export class FileSystem {
325
329
 
326
330
  try {
327
331
  const fd = openSync(isWindows ? fullPath.slice(1) : fullPath, fsOpenFlags, fsMode);
328
- return Object.assign(new Descriptor(), { fullPath, fd });
332
+ return descriptorCreate(fd, fullPath);
329
333
  }
330
334
  catch (e) {
331
335
  throw convertFsError(e);
@@ -380,16 +384,16 @@ export class FileSystem {
380
384
  console.log(`[filesystem] UNLOCK`, this.id);
381
385
  }
382
386
 
383
- drop() {
384
- if (this.fd)
385
- closeSync(this.fd);
387
+ [symbolDispose]() {
388
+ if (this.#fd)
389
+ closeSync(this.#fd);
386
390
  }
387
391
 
388
392
  metadataHash() {
389
- if (this.hostPreopen)
393
+ if (this.#hostPreopen)
390
394
  return { upper: 0n, lower: BigInt(this.id) };
391
395
  try {
392
- const stats = fstatSync(this.fd, { bigint: true });
396
+ const stats = fstatSync(this.#fd, { bigint: true });
393
397
  return { upper: stats.mtimeNs, lower: stats.ino };
394
398
  }
395
399
  catch (e) {
@@ -398,7 +402,7 @@ export class FileSystem {
398
402
  }
399
403
 
400
404
  metadataHashAt(pathFlags, path) {
401
- const fullPath = fs.getFullPath(this, path, false);
405
+ const fullPath = this.#getFullPath(path, false);
402
406
  try {
403
407
  const stats = (pathFlags.symlinkFollow ? statSync : lstatSync)(isWindows ? fullPath.slice(1) : fullPath, { bigint: true });
404
408
  return { upper: stats.mtimeNs, lower: stats.ino };
@@ -407,12 +411,43 @@ export class FileSystem {
407
411
  convertFsError(e);
408
412
  }
409
413
  }
414
+
415
+ // TODO: support followSymlinks
416
+ #getFullPath (subpath, _followSymlinks) {
417
+ let descriptor = this;
418
+ if (subpath.indexOf('\\') !== -1)
419
+ subpath = subpath.replace(/\\/g, '/');
420
+ if (subpath[0] === '/') {
421
+ let bestPreopenMatch = '';
422
+ for (const preopenEntry of fs.preopenEntries) {
423
+ if (subpath.startsWith(preopenEntry[1]) && (!bestPreopenMatch || bestPreopenMatch.length < preopenEntry[1].length)) {
424
+ bestPreopenMatch = preopenEntry;
425
+ }
426
+ }
427
+ if (!bestPreopenMatch)
428
+ throw 'no-entry';
429
+ descriptor = bestPreopenMatch[0];
430
+ subpath = subpath.slice(bestPreopenMatch[1]);
431
+ if (subpath[0] === '/')
432
+ subpath = subpath.slice(1);
433
+ }
434
+ if (subpath.startsWith('.'))
435
+ subpath = subpath.slice(subpath[1] === '/' ? 2 : 1);
436
+ if (descriptor.#hostPreopen)
437
+ return descriptor.#hostPreopen + (descriptor.#hostPreopen.endsWith('/') ? '' : '/') + subpath;
438
+ return descriptor.#fullPath + '/' + subpath;
439
+ }
410
440
  }
411
441
 
442
+ const descriptorCreatePreopen = Descriptor._createPreopen;
443
+ delete Descriptor._createPreopen;
444
+ const descriptorCreate = Descriptor._create;
445
+ delete Descriptor._create;
446
+
412
447
  this.descriptorCnt = 3;
413
448
  this.preopenEntries = [];
414
449
  for (const [virtualPath, hostPreopen] of Object.entries(preopens)) {
415
- const preopenEntry = [Object.assign(new Descriptor(), { hostPreopen }), virtualPath];
450
+ const preopenEntry = [descriptorCreatePreopen(hostPreopen), virtualPath];
416
451
  this.preopenEntries.push(preopenEntry);
417
452
  }
418
453
  this.preopens = {
@@ -428,9 +463,7 @@ export class FileSystem {
428
463
  }
429
464
  }
430
465
 
431
- const _fs = new FileSystem({ '/': '/' }, environment);
432
-
433
- export const { preopens, types } = _fs;
466
+ export const { preopens, types } = new FileSystem({ '/': '/' }, environment);
434
467
 
435
468
  function convertFsError (e) {
436
469
  switch (e.code) {