@bytecodealliance/preview2-shim 0.0.20 → 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.
@@ -1,4 +1,8 @@
1
1
  import { _setCwd as fsSetCwd } from './filesystem.js';
2
+ import { streams } from './io.js';
3
+ const { InputStream, OutputStream } = streams;
4
+
5
+ const symbolDispose = Symbol.dispose ?? Symbol.for('dispose');
2
6
 
3
7
  let _env = [], _args = [], _cwd = null;
4
8
  export function _setEnv (envObj) {
@@ -38,50 +42,113 @@ export const exit = {
38
42
  }
39
43
  };
40
44
 
45
+ /**
46
+ * @param {import('../common/io.js').InputStreamHandler} handler
47
+ */
48
+ export function _setStdin (handler) {
49
+ stdinStream.handler = handler;
50
+ }
51
+ /**
52
+ * @param {import('../common/io.js').OutputStreamHandler} handler
53
+ */
54
+ export function _setStderr (handler) {
55
+ stderrStream.handler = handler;
56
+ }
57
+ /**
58
+ * @param {import('../common/io.js').OutputStreamHandler} handler
59
+ */
60
+ export function _setStdout (handler) {
61
+ stdoutStream.handler = handler;
62
+ }
63
+
64
+ const stdinStream = new InputStream({
65
+ blockingRead (_len) {
66
+ // TODO
67
+ },
68
+ subscribe () {
69
+ // TODO
70
+ },
71
+ [symbolDispose] () {
72
+ // TODO
73
+ }
74
+ });
75
+ let textDecoder = new TextDecoder();
76
+ const stdoutStream = new OutputStream({
77
+ write (contents) {
78
+ console.log(textDecoder.decode(contents));
79
+ },
80
+ blockingFlush () {
81
+ },
82
+ [symbolDispose] () {
83
+ }
84
+ });
85
+ const stderrStream = new OutputStream({
86
+ write (contents) {
87
+ console.error(textDecoder.decode(contents));
88
+ },
89
+ blockingFlush () {
90
+
91
+ },
92
+ [symbolDispose] () {
93
+
94
+ }
95
+ });
96
+
41
97
  export const stdin = {
98
+ InputStream,
42
99
  getStdin () {
43
- return 0;
100
+ return stdinStream;
44
101
  }
45
102
  };
46
103
 
47
104
  export const stdout = {
105
+ OutputStream,
48
106
  getStdout () {
49
- return 1;
107
+ return stdoutStream;
50
108
  }
51
109
  };
52
110
 
53
111
  export const stderr = {
112
+ OutputStream,
54
113
  getStderr () {
55
- return 2;
114
+ return stderrStream;
56
115
  }
57
116
  };
58
117
 
59
- export const terminalInput = {
60
- dropTerminalInput () {
118
+ class TerminalInput {}
119
+ class TerminalOutput {}
61
120
 
62
- }
121
+ const terminalStdoutInstance = new TerminalOutput();
122
+ const terminalStderrInstance = new TerminalOutput();
123
+ const terminalStdinInstance = new TerminalInput();
124
+
125
+ export const terminalInput = {
126
+ TerminalInput,
127
+ dropTerminalInput () {}
63
128
  };
64
129
 
65
130
  export const terminalOutput = {
66
- dropTerminalOutput () {
67
-
68
- }
131
+ TerminalOutput,
132
+ dropTerminalOutput () {}
69
133
  };
70
134
 
71
135
  export const terminalStderr = {
136
+ TerminalOutput,
72
137
  getTerminalStderr () {
73
- return 0;
138
+ return terminalStderrInstance;
74
139
  }
75
140
  };
76
141
 
77
142
  export const terminalStdin = {
143
+ TerminalInput,
78
144
  getTerminalStdin () {
79
- return 1;
145
+ return terminalStdinInstance;
80
146
  }
81
147
  };
82
148
 
83
149
  export const terminalStdout = {
150
+ TerminalOutput,
84
151
  getTerminalStdout () {
85
- return 2;
152
+ return terminalStdoutInstance;
86
153
  }
87
154
  };
@@ -1,15 +1,7 @@
1
- import { _io } from './io.js';
1
+ import { streams } from './io.js';
2
2
  import { environment } from './cli.js';
3
3
 
4
- const { createStream, getStream, dropStream } = _io;
5
-
6
- let _preopens = [[3, '/']], _rootPreopen = _preopens[0];
7
-
8
- export function _setPreopens (preopens) {
9
- _preopens = preopens;
10
- descriptorCnt = 3 + _preopens.length;
11
- _rootPreopen = _preopens.find(preopen => preopen[1] === '/');
12
- }
4
+ const { InputStream, OutputStream } = streams;
13
5
 
14
6
  let _cwd = null;
15
7
 
@@ -19,11 +11,7 @@ export function _setCwd (cwd) {
19
11
 
20
12
  export function _setFileData (fileData) {
21
13
  _fileData = fileData;
22
- _setPreopens(Object.keys(fileData).map((key) => {
23
- const fd = descriptorCnt++;
24
- descriptorTable[fd] = { entry: fileData[key] };
25
- return [fd, key];
26
- }));
14
+ _rootPreopen[0] = new Descriptor(fileData);
27
15
  const cwd = environment.initialCwd();
28
16
  _setCwd(cwd || '/');
29
17
  }
@@ -32,34 +20,20 @@ export function _getFileData () {
32
20
  return JSON.stringify(_fileData);
33
21
  }
34
22
 
35
- let _fileData = {};
36
-
37
- let descriptorCnt = 4;
38
- const descriptorTable = {
39
- 0: { stream: 0 },
40
- 1: { stream: 1 },
41
- 2: { stream: 2 },
42
- 3: { entry: { dir: {} } },
43
- };
23
+ let _fileData = { dir: {} };
44
24
 
45
25
  const timeZero = {
46
26
  seconds: BigInt(0),
47
27
  nanoseconds: 0
48
28
  };
49
29
 
50
- function getDescriptor (fd) {
51
- const descriptor = descriptorTable[fd];
52
- if (!descriptor) throw 'bad-descriptor';
53
- return descriptor;
54
- }
55
-
56
- function getChildEntry (fd, subpath, openFlags) {
57
- if (subpath === '.' && _rootPreopen && _rootPreopen[0] === fd) {
30
+ function getChildEntry (parentEntry, subpath, openFlags) {
31
+ if (subpath === '.' && _rootPreopen && descriptorGetEntry(_rootPreopen[0]) === parentEntry) {
58
32
  subpath = _cwd;
59
- if (subpath.startsWith('/'))
33
+ if (subpath.startsWith('/') && subpath !== '/')
60
34
  subpath = subpath.slice(1);
61
35
  }
62
- let entry = getDescriptor(fd)?.entry;
36
+ let entry = parentEntry;
63
37
  let segmentIdx;
64
38
  do {
65
39
  if (!entry || !entry.dir) throw 'not-directory';
@@ -76,13 +50,6 @@ function getChildEntry (fd, subpath, openFlags) {
76
50
  return entry;
77
51
  }
78
52
 
79
- function createChildDescriptor (fd, subpath, openFlags) {
80
- const entry = getChildEntry(fd, subpath, openFlags);
81
- const childFd = descriptorCnt++;
82
- descriptorTable[childFd] = { entry };
83
- return childFd;
84
- }
85
-
86
53
  function getSource (fileEntry) {
87
54
  if (typeof fileEntry.source === 'string') {
88
55
  fileEntry.source = new TextEncoder().encode(fileEntry.source);
@@ -90,12 +57,12 @@ function getSource (fileEntry) {
90
57
  return fileEntry.source;
91
58
  }
92
59
 
93
- class DirStream {
60
+ class DirectoryEntryStream {
94
61
  constructor (entries) {
95
62
  this.idx = 0;
96
63
  this.entries = entries;
97
64
  }
98
- next () {
65
+ readDirectoryEntry () {
99
66
  if (this.idx === this.entries.length)
100
67
  return null;
101
68
  const [name, entry] = this.entries[this.idx];
@@ -107,117 +74,120 @@ class DirStream {
107
74
  }
108
75
  }
109
76
 
110
- export const preopens = {
111
- getDirectories () {
112
- return _preopens;
77
+ class Descriptor {
78
+ #stream;
79
+ #entry;
80
+ #mtime = 0;
81
+
82
+ _getEntry (descriptor) {
83
+ return descriptor.#entry;
113
84
  }
114
- }
115
85
 
116
- export const types = {
117
- readViaStream(fd, offset) {
118
- const descriptor = getDescriptor(fd);
119
- const source = getSource(descriptor.entry);
120
- return createStream({
121
- i: Number(offset),
122
- source,
123
- read (len) {
124
- const bytes = this.source.slice(this.i, this.i + Number(len));
125
- this.i += bytes.byteLength;
126
- return [bytes, this.i === this.source.byteLength ? 'ended' : 'open'];
86
+ constructor (entry, isStream) {
87
+ if (isStream)
88
+ this.#stream = entry;
89
+ else
90
+ this.#entry = entry;
91
+ }
92
+
93
+ readViaStream(_offset) {
94
+ const source = getSource(this.#entry);
95
+ let offset = Number(_offset);
96
+ return new InputStream({
97
+ blockingRead (len) {
98
+ if (offset === source.byteLength)
99
+ throw { tag: 'closed' };
100
+ const bytes = source.slice(offset, offset + Number(len));
101
+ offset += bytes.byteLength;
102
+ return bytes;
127
103
  }
128
104
  });
129
- },
105
+ }
130
106
 
131
- writeViaStream(fd, offset) {
132
- const descriptor = getDescriptor(fd);
133
- return createStream({
134
- i: Number(offset),
135
- entry: descriptor.entry,
107
+ writeViaStream(_offset) {
108
+ const entry = this.#entry;
109
+ let offset = Number(_offset);
110
+ return new OutputStream({
136
111
  write (buf) {
137
- const newSource = new Uint8Array(buf.byteLength + this.entry.source.byteLength);
138
- newSource.set(this.entry.source, 0);
139
- newSource.set(buf, this.i);
140
- this.i += buf.byteLength;
141
- this.entry.source = newSource;
112
+ const newSource = new Uint8Array(buf.byteLength + entry.source.byteLength);
113
+ newSource.set(entry.source, 0);
114
+ newSource.set(buf, offset);
115
+ offset += buf.byteLength;
116
+ entry.source = newSource;
142
117
  return buf.byteLength;
143
118
  }
144
119
  });
145
- },
146
-
147
- appendViaStream(fd) {
148
- console.log(`[filesystem] APPEND STREAM ${fd}`);
149
- },
150
-
151
- advise(fd, offset, length, advice) {
152
- console.log(`[filesystem] ADVISE`, fd, offset, length, advice);
153
- },
154
-
155
- syncData(fd) {
156
- console.log(`[filesystem] SYNC DATA ${fd}`);
157
- },
158
-
159
- getFlags(fd) {
160
- console.log(`[filesystem] FLAGS FOR ${fd}`);
161
- },
162
-
163
- getType(fd) {
164
- if (fd < 3) return 'fifo';
165
- const descriptor = getDescriptor(fd);
166
- if (descriptor.stream) return 'fifo';
167
- if (descriptor.entry.dir) return 'directory';
168
- if (descriptor.entry.source) return 'regular-file';
120
+ }
121
+
122
+ appendViaStream() {
123
+ console.log(`[filesystem] APPEND STREAM`);
124
+ }
125
+
126
+ advise(descriptor, offset, length, advice) {
127
+ console.log(`[filesystem] ADVISE`, descriptor, offset, length, advice);
128
+ }
129
+
130
+ syncData() {
131
+ console.log(`[filesystem] SYNC DATA`);
132
+ }
133
+
134
+ getFlags() {
135
+ console.log(`[filesystem] FLAGS FOR`);
136
+ }
137
+
138
+ getType() {
139
+ if (this.#stream) return 'fifo';
140
+ if (this.#entry.dir) return 'directory';
141
+ if (this.#entry.source) return 'regular-file';
169
142
  return 'unknown';
170
- },
143
+ }
171
144
 
172
- setFlags(fd, flags) {
173
- console.log(`[filesystem] SET FLAGS ${fd} ${JSON.stringify(flags)}`);
174
- },
145
+ setFlags(flags) {
146
+ console.log(`[filesystem] SET FLAGS ${JSON.stringify(flags)}`);
147
+ }
175
148
 
176
- setSize(fd, size) {
177
- console.log(`[filesystem] SET SIZE`, fd, size);
178
- },
149
+ setSize(size) {
150
+ console.log(`[filesystem] SET SIZE`, size);
151
+ }
179
152
 
180
- setTimes(fd, dataAccessTimestamp, dataModificationTimestamp) {
181
- console.log(`[filesystem] SET TIMES`, fd, dataAccessTimestamp, dataModificationTimestamp);
182
- },
153
+ setTimes(dataAccessTimestamp, dataModificationTimestamp) {
154
+ console.log(`[filesystem] SET TIMES`, dataAccessTimestamp, dataModificationTimestamp);
155
+ }
183
156
 
184
- read(fd, length, offset) {
185
- const descriptor = getDescriptor(fd);
186
- const source = getSource(descriptor.entry);
157
+ read(length, offset) {
158
+ const source = getSource(this.#entry);
187
159
  return [source.slice(offset, offset + length), offset + length >= source.byteLength];
188
- },
160
+ }
189
161
 
190
- write(fd, buffer, offset) {
191
- const descriptor = getDescriptor(fd);
162
+ write(buffer, offset) {
192
163
  if (offset !== 0) throw 'invalid-seek';
193
- descriptor.entry.source = buffer;
164
+ this.#entry.source = buffer;
194
165
  return buffer.byteLength;
195
- },
166
+ }
196
167
 
197
- readDirectory(fd) {
198
- const descriptor = getDescriptor(fd);
199
- if (!descriptor?.entry?.dir) throw 'bad-descriptor';
200
- return createStream(new DirStream(Object.entries(descriptor.entry.dir).sort(([a], [b]) => a > b ? 1 : -1)));
201
- },
168
+ readDirectory() {
169
+ if (!this.#entry?.dir)
170
+ throw 'bad-descriptor';
171
+ return new DirectoryEntryStream(Object.entries(this.#entry.dir).sort(([a], [b]) => a > b ? 1 : -1));
172
+ }
202
173
 
203
- sync(fd) {
204
- console.log(`[filesystem] SYNC`, fd);
205
- },
174
+ sync() {
175
+ console.log(`[filesystem] SYNC`);
176
+ }
206
177
 
207
- createDirectoryAt(fd, path) {
208
- const entry = getChildEntry(fd, path, { create: true, directory: true });
178
+ createDirectoryAt(path) {
179
+ const entry = getChildEntry(this.#entry, path, { create: true, directory: true });
209
180
  if (entry.source) throw 'exist';
210
- },
181
+ }
211
182
 
212
- stat(fd) {
213
- const descriptor = getDescriptor(fd);
183
+ stat() {
214
184
  let type = 'unknown', size = BigInt(0);
215
- if (descriptor.entry.source) {
185
+ if (this.#entry.source) {
216
186
  type = 'directory';
217
187
  }
218
- else if (descriptor.entry.dir) {
188
+ else if (this.#entry.dir) {
219
189
  type = 'regular-file';
220
- const source = getSource(descriptor.entry);
190
+ const source = getSource(this.#entry);
221
191
  size = BigInt(source.byteLength);
222
192
  }
223
193
  return {
@@ -228,10 +198,10 @@ export const types = {
228
198
  dataModificationTimestamp: timeZero,
229
199
  statusChangeTimestamp: timeZero,
230
200
  }
231
- },
201
+ }
232
202
 
233
- statAt(fd, pathFlags, path) {
234
- const entry = getChildEntry(fd, path);
203
+ statAt(_pathFlags, path) {
204
+ const entry = getChildEntry(this.#entry, path);
235
205
  let type = 'unknown', size = BigInt(0);
236
206
  if (entry.source) {
237
207
  type = 'regular-file';
@@ -249,95 +219,95 @@ export const types = {
249
219
  dataModificationTimestamp: timeZero,
250
220
  statusChangeTimestamp: timeZero,
251
221
  };
252
- },
253
-
254
- setTimesAt(fd) {
255
- console.log(`[filesystem] SET TIMES AT`, fd);
256
- },
257
-
258
- linkAt(fd) {
259
- console.log(`[filesystem] LINK AT`, fd);
260
- },
261
-
262
- openAt(fd, _pathFlags, path, openFlags, _descriptorFlags, _modes) {
263
- return createChildDescriptor(fd, path, openFlags);
264
- },
222
+ }
265
223
 
266
- readlinkAt(fd) {
267
- console.log(`[filesystem] READLINK AT`, fd);
268
- },
224
+ setTimesAt() {
225
+ console.log(`[filesystem] SET TIMES AT`);
226
+ }
269
227
 
270
- removeDirectoryAt(fd) {
271
- console.log(`[filesystem] REMOVE DIR AT`, fd);
272
- },
228
+ linkAt() {
229
+ console.log(`[filesystem] LINK AT`);
230
+ }
273
231
 
274
- renameAt(fd) {
275
- console.log(`[filesystem] RENAME AT`, fd);
276
- },
232
+ openAt(_pathFlags, path, openFlags, _descriptorFlags, _modes) {
233
+ const childEntry = getChildEntry(this.#entry, path, openFlags);
234
+ return new Descriptor(childEntry);
235
+ }
277
236
 
278
- symlinkAt(fd) {
279
- console.log(`[filesystem] SYMLINK AT`, fd);
280
- },
237
+ readlinkAt() {
238
+ console.log(`[filesystem] READLINK AT`);
239
+ }
281
240
 
282
- unlinkFileAt(fd) {
283
- console.log(`[filesystem] UNLINK FILE AT`, fd);
284
- },
241
+ removeDirectoryAt() {
242
+ console.log(`[filesystem] REMOVE DIR AT`);
243
+ }
285
244
 
286
- changeFilePermissionsAt(fd) {
287
- console.log(`[filesystem] CHANGE FILE PERMISSIONS AT`, fd);
288
- },
245
+ renameAt() {
246
+ console.log(`[filesystem] RENAME AT`);
247
+ }
289
248
 
290
- changeDirectoryPermissionsAt(fd) {
291
- console.log(`[filesystem] CHANGE DIR PERMISSIONS AT`, fd);
292
- },
249
+ symlinkAt() {
250
+ console.log(`[filesystem] SYMLINK AT`);
251
+ }
293
252
 
294
- lockShared(fd) {
295
- console.log(`[filesystem] LOCK SHARED`, fd);
296
- },
253
+ unlinkFileAt() {
254
+ console.log(`[filesystem] UNLINK FILE AT`);
255
+ }
297
256
 
298
- lockExclusive(fd) {
299
- console.log(`[filesystem] LOCK EXCLUSIVE`, fd);
300
- },
257
+ changeFilePermissionsAt() {
258
+ console.log(`[filesystem] CHANGE FILE PERMISSIONS AT`);
259
+ }
301
260
 
302
- tryLockShared(fd) {
303
- console.log(`[filesystem] TRY LOCK SHARED`, fd);
304
- },
261
+ changeDirectoryPermissionsAt() {
262
+ console.log(`[filesystem] CHANGE DIR PERMISSIONS AT`);
263
+ }
305
264
 
306
- tryLockExclusive(fd) {
307
- console.log(`[filesystem] TRY LOCK EXCLUSIVE`, fd);
308
- },
265
+ lockShared() {
266
+ console.log(`[filesystem] LOCK SHARED`);
267
+ }
309
268
 
310
- unlock(fd) {
311
- console.log(`[filesystem] UNLOCK`, fd);
312
- },
269
+ lockExclusive() {
270
+ console.log(`[filesystem] LOCK EXCLUSIVE`);
271
+ }
313
272
 
314
- dropDescriptor(fd) {
315
- if (fd < _preopens.length + 3)
316
- return;
317
- delete descriptorTable[fd];
318
- },
273
+ tryLockShared() {
274
+ console.log(`[filesystem] TRY LOCK SHARED`);
275
+ }
319
276
 
320
- readDirectoryEntry(sid) {
321
- return getStream(sid).next();
322
- },
277
+ tryLockExclusive() {
278
+ console.log(`[filesystem] TRY LOCK EXCLUSIVE`);
279
+ }
323
280
 
324
- dropDirectoryEntryStream(sid) {
325
- dropStream(sid);
326
- },
281
+ unlock() {
282
+ console.log(`[filesystem] UNLOCK`);
283
+ }
327
284
 
328
- metadataHash(fd) {
329
- const descriptor = getDescriptor(fd);
285
+ metadataHash() {
330
286
  let upper = BigInt(0);
331
- upper += BigInt(descriptor.mtime || 0);
287
+ upper += BigInt(this.#mtime);
332
288
  return { upper, lower: BigInt(0) };
333
- },
289
+ }
334
290
 
335
- metadataHashAt(fd, _pathFlags, _path) {
336
- const descriptor = getDescriptor(fd);
291
+ metadataHashAt(_pathFlags, _path) {
337
292
  let upper = BigInt(0);
338
- upper += BigInt(descriptor.mtime || 0);
293
+ upper += BigInt(this.#mtime);
339
294
  return { upper, lower: BigInt(0) };
340
295
  }
296
+ }
297
+ const descriptorGetEntry = Descriptor.prototype._getEntry;
298
+ delete Descriptor.prototype._getEntry;
299
+
300
+ let _preopens = [[new Descriptor(_fileData), '/']], _rootPreopen = _preopens[0];
301
+
302
+ export const preopens = {
303
+ getDirectories () {
304
+ return _preopens;
305
+ }
306
+ }
307
+
308
+ export const types = {
309
+ Descriptor,
310
+ DirectoryEntryStream
341
311
  };
342
312
 
343
- export { types as filesystemTypes }
313
+ export { types as filesystemTypes }
@@ -1,7 +1,7 @@
1
1
  import * as clocks from "./clocks.js";
2
2
  import * as filesystem from "./filesystem.js";
3
3
  import * as http from "./http.js";
4
- import * as io from "../common/io.js";
4
+ import * as io from "./io.js";
5
5
  import * as random from "./random.js";
6
6
  import * as sockets from "./sockets.js";
7
7
  import * as cli from "./cli.js";
@@ -15,5 +15,3 @@ export {
15
15
  sockets,
16
16
  cli,
17
17
  }
18
-
19
- export { WasiHttp } from "../http/wasi-http.js";
@@ -0,0 +1,183 @@
1
+ let id = 0;
2
+
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
+ */
46
+ constructor (handler) {
47
+ if (!handler)
48
+ console.trace('no handler');
49
+ this.id = ++id;
50
+ this.handler = handler;
51
+ }
52
+ read(len) {
53
+ if (this.handler.read)
54
+ return this.handler.read(len);
55
+ return this.handler.blockingRead.call(this, len);
56
+ }
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);
66
+ }
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;
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
173
+ }
174
+
175
+ function pollOne (_poll) {
176
+ // TODO
177
+ }
178
+
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;
@@ -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
 
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) {
@@ -308,5 +308,4 @@ export class WasiHttp {
308
308
  }
309
309
  }
310
310
 
311
- const http = new WasiHttp();
312
- export const { outgoingHandler, types } = http;
311
+ export const { outgoingHandler, types } = new WasiHttp();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytecodealliance/preview2-shim",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "WASI Preview2 shim for JS environments",
5
5
  "author": "Guy Bedford, Eduardo Rodrigues<16357187+eduardomourar@users.noreply.github.com>",
6
6
  "type": "module",