@velarscript/node 0.10.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.
Files changed (67) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +148 -0
  3. package/dist/compiler.d.ts +12 -0
  4. package/dist/compiler.d.ts.map +1 -0
  5. package/dist/compiler.js +954 -0
  6. package/dist/compiler.js.map +1 -0
  7. package/dist/environment-runtime.d.ts +2 -0
  8. package/dist/environment-runtime.d.ts.map +1 -0
  9. package/dist/environment-runtime.js +53 -0
  10. package/dist/environment-runtime.js.map +1 -0
  11. package/dist/filesystem-runtime.d.ts +2 -0
  12. package/dist/filesystem-runtime.d.ts.map +1 -0
  13. package/dist/filesystem-runtime.js +329 -0
  14. package/dist/filesystem-runtime.js.map +1 -0
  15. package/dist/host-runtime.d.ts +2 -0
  16. package/dist/host-runtime.d.ts.map +1 -0
  17. package/dist/host-runtime.js +149 -0
  18. package/dist/host-runtime.js.map +1 -0
  19. package/dist/http-runtime.d.ts +2 -0
  20. package/dist/http-runtime.d.ts.map +1 -0
  21. package/dist/http-runtime.js +612 -0
  22. package/dist/http-runtime.js.map +1 -0
  23. package/dist/index.d.ts +9 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +18 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/node-host-runtime.d.ts +2 -0
  28. package/dist/node-host-runtime.d.ts.map +1 -0
  29. package/dist/node-host-runtime.js +314 -0
  30. package/dist/node-host-runtime.js.map +1 -0
  31. package/dist/node-host-worker-runtime.d.ts +2 -0
  32. package/dist/node-host-worker-runtime.d.ts.map +1 -0
  33. package/dist/node-host-worker-runtime.js +1144 -0
  34. package/dist/node-host-worker-runtime.js.map +1 -0
  35. package/dist/process-runtime.d.ts +2 -0
  36. package/dist/process-runtime.d.ts.map +1 -0
  37. package/dist/process-runtime.js +167 -0
  38. package/dist/process-runtime.js.map +1 -0
  39. package/dist/process-worker-runtime.d.ts +2 -0
  40. package/dist/process-worker-runtime.d.ts.map +1 -0
  41. package/dist/process-worker-runtime.js +473 -0
  42. package/dist/process-worker-runtime.js.map +1 -0
  43. package/dist/serve-runtime.d.ts +2 -0
  44. package/dist/serve-runtime.d.ts.map +1 -0
  45. package/dist/serve-runtime.js +463 -0
  46. package/dist/serve-runtime.js.map +1 -0
  47. package/dist/sqlite-runtime.d.ts +3 -0
  48. package/dist/sqlite-runtime.d.ts.map +1 -0
  49. package/dist/sqlite-runtime.js +92 -0
  50. package/dist/sqlite-runtime.js.map +1 -0
  51. package/dist/terminal-runtime.d.ts +2 -0
  52. package/dist/terminal-runtime.d.ts.map +1 -0
  53. package/dist/terminal-runtime.js +290 -0
  54. package/dist/terminal-runtime.js.map +1 -0
  55. package/dist/terminal-worker-runtime.d.ts +2 -0
  56. package/dist/terminal-worker-runtime.d.ts.map +1 -0
  57. package/dist/terminal-worker-runtime.js +311 -0
  58. package/dist/terminal-worker-runtime.js.map +1 -0
  59. package/dist/websocket-runtime.d.ts +2 -0
  60. package/dist/websocket-runtime.d.ts.map +1 -0
  61. package/dist/websocket-runtime.js +46 -0
  62. package/dist/websocket-runtime.js.map +1 -0
  63. package/dist/worker-runtime.d.ts +2 -0
  64. package/dist/worker-runtime.d.ts.map +1 -0
  65. package/dist/worker-runtime.js +160 -0
  66. package/dist/worker-runtime.js.map +1 -0
  67. package/package.json +41 -0
@@ -0,0 +1,1144 @@
1
+ // Privileged Node transport shared by official Node standard modules. This
2
+ // worker loads only compiler-owned source and static Node built-ins; VelarScript
3
+ // application code and npm dependencies remain in the application Realm.
4
+ export const VELAR_NODE_HOST_WORKER_SOURCE = String.raw `
5
+ import { Buffer } from "node:buffer";
6
+ import { watch as watchNode } from "node:fs";
7
+ import { appendFile, copyFile, lstat, mkdir, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
8
+ import { createServer, request as createHttpRequest } from "node:http";
9
+ import { request as createHttpsRequest } from "node:https";
10
+ import { basename, dirname, extname, isAbsolute, relative, resolve } from "node:path";
11
+ import { URL as NodeURL } from "node:url";
12
+ import { workerData } from "node:worker_threads";
13
+
14
+ const port = workerData;
15
+ const maxFileBytes = 16 * 1024 * 1024;
16
+ const maxListItems = 100000;
17
+ const maxListCodeUnits = 2 * 1024 * 1024;
18
+ const maxPathCodeUnits = 4096;
19
+ const maxServeBodyBytes = 16 * 1024 * 1024;
20
+ const maxServeFileBytes = 64 * 1024 * 1024;
21
+ const maxServeStreamBytes = 64 * 1024 * 1024;
22
+ const maxServeStreamChunkBytes = 1024 * 1024;
23
+ const maxServeAggregateBytes = 128 * 1024 * 1024;
24
+ const maxHttpBodyBytes = 16 * 1024 * 1024;
25
+ const maxHttpResponseBytes = 64 * 1024 * 1024;
26
+ const maxHttpResponseChunks = 1000000;
27
+ const maxHttpRequests = 1024;
28
+ const maxServers = 128;
29
+ const maxRequests = 4096;
30
+ const maxFileWatchers = 128;
31
+ const maxWatchPaths = 4096;
32
+ const maxWatchTextUnits = 2 * 1024 * 1024;
33
+ const watchDebounceMilliseconds = 20;
34
+ const operations = new Set([
35
+ "fs.readFile", "fs.createFile", "fs.replaceFileIfMatches", "fs.writeFile", "fs.appendFile", "fs.exists", "fs.list", "fs.info",
36
+ "fs.canonical", "fs.makeDirectory", "fs.copyFile", "fs.move", "fs.removeFile", "fs.watchStart", "fs.watchNext", "fs.watchClose",
37
+ "http.request", "http.read", "http.readBytes", "http.cancel", "http.close",
38
+ "serve.start", "serve.stop", "serve.body", "serve.respond", "serve.respondFile",
39
+ "serve.streamStart", "serve.streamWrite", "serve.streamEnd", "serve.fail",
40
+ ]);
41
+ const servers = new Map();
42
+ const requests = new Map();
43
+ const httpRequests = new Map();
44
+ const fileMutationTails = new Map();
45
+ const fileWatchers = new Map();
46
+ let nextServerHandle = 1;
47
+ let nextRequestHandle = 1;
48
+ let nextTextReplacementIdentity = 1;
49
+ let nextFileWatcherHandle = 1;
50
+ let reservedServeBytes = 0;
51
+ const contentTypes = Object.freeze({
52
+ ".css": "text/css; charset=utf-8", ".gif": "image/gif", ".html": "text/html; charset=utf-8",
53
+ ".ico": "image/x-icon", ".jpeg": "image/jpeg", ".jpg": "image/jpeg", ".js": "text/javascript; charset=utf-8",
54
+ ".json": "application/json; charset=utf-8", ".map": "application/json; charset=utf-8", ".mjs": "text/javascript; charset=utf-8",
55
+ ".png": "image/png", ".svg": "image/svg+xml", ".txt": "text/plain; charset=utf-8", ".wasm": "application/wasm",
56
+ ".webp": "image/webp", ".woff": "font/woff", ".woff2": "font/woff2",
57
+ });
58
+
59
+ class StaticNotFound extends Error {}
60
+
61
+ function allocateHandle(values, next, maximum, name) {
62
+ let candidate = next;
63
+ for (let attempts = 0; attempts <= maximum; attempts += 1) {
64
+ if (!values.has(candidate)) return candidate;
65
+ candidate = candidate >= Number.MAX_SAFE_INTEGER ? 1 : candidate + 1;
66
+ }
67
+ throw new RangeError(name + " handle space is unavailable");
68
+ }
69
+
70
+ function advanceHandle(handle) {
71
+ return handle >= Number.MAX_SAFE_INTEGER ? 1 : handle + 1;
72
+ }
73
+
74
+ function reserveServeBytes(task, bytes) {
75
+ if (!Number.isSafeInteger(bytes) || bytes < 0 || reservedServeBytes + bytes > maxServeAggregateBytes) {
76
+ throw new RangeError("Node serve aggregate byte budget is exhausted");
77
+ }
78
+ reservedServeBytes += bytes;
79
+ task.reservedBytes += bytes;
80
+ }
81
+
82
+ function releaseServeBytes(task, bytes = task.reservedBytes) {
83
+ if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > task.reservedBytes || bytes > reservedServeBytes) {
84
+ throw new Error("Node serve byte ownership is invalid");
85
+ }
86
+ task.reservedBytes -= bytes;
87
+ reservedServeBytes -= bytes;
88
+ }
89
+
90
+ function reserveTransientServeBytes(bytes) {
91
+ if (!Number.isSafeInteger(bytes) || bytes < 0 || reservedServeBytes + bytes > maxServeAggregateBytes) {
92
+ throw new RangeError("Node serve aggregate byte budget is exhausted");
93
+ }
94
+ reservedServeBytes += bytes;
95
+ }
96
+
97
+ function releaseTransientServeBytes(bytes) {
98
+ if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > reservedServeBytes) {
99
+ throw new Error("Node serve transient byte ownership is invalid");
100
+ }
101
+ reservedServeBytes -= bytes;
102
+ }
103
+
104
+ function cleanupRequest(task) {
105
+ if (!task.transportDone || !task.completed && !task.abandoned || task.activeOperations !== 0 || !requests.delete(task.handle)) return;
106
+ releaseServeBytes(task);
107
+ }
108
+
109
+ function closeRequest(task) {
110
+ task.transportDone = true;
111
+ cleanupRequest(task);
112
+ }
113
+
114
+ async function withRequest(task, action) {
115
+ task.activeOperations += 1;
116
+ try { return await action(); }
117
+ finally {
118
+ task.activeOperations -= 1;
119
+ cleanupRequest(task);
120
+ }
121
+ }
122
+
123
+ function completeRequest(task) {
124
+ if (task.completed) throw new Error("Node serve request is already completed");
125
+ task.completed = true;
126
+ }
127
+
128
+ function boundedPath(value, operation) {
129
+ if (typeof value !== "string" || value.length === 0) throw new TypeError(operation + " requires a non-empty path string");
130
+ if (value.length > maxPathCodeUnits || value.includes("\0")) throw new RangeError(operation + " path is outside the supported bounds");
131
+ return value;
132
+ }
133
+
134
+ function byteLimit(value, operation) {
135
+ if (!Number.isSafeInteger(value) || value < 1 || value > maxFileBytes) {
136
+ throw new RangeError(operation + " maxBytes must be an integer from 1 through 16777216");
137
+ }
138
+ return value;
139
+ }
140
+
141
+ function boolean(value, operation) {
142
+ if (typeof value !== "boolean") throw new TypeError(operation + " replace must be bool");
143
+ return value;
144
+ }
145
+
146
+ function byteArray(value, operation) {
147
+ if (!(value instanceof Uint8Array)) throw new TypeError(operation + " requires UTF-8 bytes");
148
+ if (value.byteLength > maxFileBytes) throw new RangeError(operation + " cannot write more than 16 MiB");
149
+ return value;
150
+ }
151
+
152
+ function missing(error) {
153
+ return error && typeof error === "object" && error.code === "ENOENT";
154
+ }
155
+
156
+ async function regularFile(path, operation, maxBytes) {
157
+ const metadata = await stat(path);
158
+ if (!metadata.isFile()) throw new TypeError(operation + " requires a file path");
159
+ if (metadata.size > maxBytes) throw new RangeError(operation + " file exceeds maxBytes");
160
+ const data = await readFile(path);
161
+ if (data.byteLength > maxBytes) throw new RangeError(operation + " file exceeds maxBytes");
162
+ return data;
163
+ }
164
+
165
+ // D50 rule 89: a target that already exists is the same recovery whether the
166
+ // operating system reported EEXIST or this pre-check found it first, so both
167
+ // spellings carry the same evidence into the classification below.
168
+ class AlreadyExists extends Error {
169
+ constructor(operation, path) {
170
+ super(operation + " target already exists");
171
+ this.code = "EEXIST";
172
+ this.path = path;
173
+ }
174
+ }
175
+
176
+ async function absent(path, operation) {
177
+ try { await lstat(path); }
178
+ catch (error) { if (missing(error)) return; throw error; }
179
+ throw new AlreadyExists(operation, path);
180
+ }
181
+
182
+ function equalBytes(left, right) {
183
+ if (left.byteLength !== right.byteLength) return false;
184
+ for (let index = 0; index < left.byteLength; index += 1) {
185
+ if (left[index] !== right[index]) return false;
186
+ }
187
+ return true;
188
+ }
189
+
190
+ async function fileMutationIdentities(paths) {
191
+ const identities = new Set();
192
+ for (const path of paths) {
193
+ const lexical = resolve(path);
194
+ identities.add(lexical);
195
+ try { identities.add(await realpath(lexical)); }
196
+ catch (error) { if (!missing(error)) throw error; }
197
+ }
198
+ return [...identities].sort();
199
+ }
200
+
201
+ async function withFileMutations(paths, action) {
202
+ const identities = await fileMutationIdentities(paths);
203
+ const reservations = identities.map(identity => {
204
+ const previous = fileMutationTails.get(identity) ?? null;
205
+ let release;
206
+ const tail = new Promise(resolveTail => { release = resolveTail; });
207
+ fileMutationTails.set(identity, tail);
208
+ return {identity, previous, release, tail};
209
+ });
210
+ await Promise.all(reservations.map(reservation => reservation.previous));
211
+ try { return await action(); }
212
+ finally {
213
+ for (const reservation of reservations) {
214
+ reservation.release();
215
+ if (fileMutationTails.get(reservation.identity) === reservation.tail) fileMutationTails.delete(reservation.identity);
216
+ }
217
+ }
218
+ }
219
+
220
+ function closeFileWatcher(task) {
221
+ if (task.closed) return false;
222
+ task.closed = true;
223
+ if (task.timer !== null) {
224
+ clearTimeout(task.timer);
225
+ task.timer = null;
226
+ }
227
+ try { task.watcher.close(); } catch {}
228
+ fileWatchers.delete(task.handle);
229
+ if (task.pending !== null) {
230
+ const pending = task.pending;
231
+ task.pending = null;
232
+ pending.resolve(null);
233
+ }
234
+ return true;
235
+ }
236
+
237
+ function fileWatchBatch(task) {
238
+ if (task.rescan) {
239
+ task.rescan = false;
240
+ task.paths.clear();
241
+ task.units = 0;
242
+ return {paths: [], rescan: true};
243
+ }
244
+ const paths = [...task.paths].sort();
245
+ task.paths.clear();
246
+ task.units = 0;
247
+ return {paths, rescan: false};
248
+ }
249
+
250
+ function settleFileWatch(task) {
251
+ if (task.pending === null || task.timer !== null || task.failure !== null || !task.rescan && task.paths.size === 0) return;
252
+ task.timer = setTimeout(() => {
253
+ task.timer = null;
254
+ if (task.pending === null || task.closed) return;
255
+ const pending = task.pending;
256
+ task.pending = null;
257
+ pending.resolve(fileWatchBatch(task));
258
+ }, watchDebounceMilliseconds);
259
+ }
260
+
261
+ function rescanFileWatch(task) {
262
+ task.rescan = true;
263
+ task.paths.clear();
264
+ task.units = 0;
265
+ settleFileWatch(task);
266
+ }
267
+
268
+ function enqueueFileWatch(task, filename) {
269
+ if (task.closed || task.failure !== null || task.rescan) return;
270
+ let path;
271
+ if (!task.directory) path = task.root;
272
+ else {
273
+ if (typeof filename !== "string" || filename.length === 0 || filename.length > maxPathCodeUnits || filename.includes("\0") || isAbsolute(filename)) {
274
+ rescanFileWatch(task);
275
+ return;
276
+ }
277
+ path = resolve(task.root, filename);
278
+ const local = relative(task.root, path);
279
+ if (local === ".." || local.startsWith("../") || isAbsolute(local)) {
280
+ rescanFileWatch(task);
281
+ return;
282
+ }
283
+ }
284
+ if (!task.paths.has(path)) {
285
+ if (task.paths.size >= maxWatchPaths || task.units + path.length > maxWatchTextUnits) {
286
+ rescanFileWatch(task);
287
+ return;
288
+ }
289
+ task.paths.add(path);
290
+ task.units += path.length;
291
+ }
292
+ settleFileWatch(task);
293
+ }
294
+
295
+ function failFileWatch(task, error) {
296
+ if (task.closed || task.failure !== null) return;
297
+ task.failure = error instanceof Error ? error : new Error("Node file watcher failed");
298
+ if (task.timer !== null) {
299
+ clearTimeout(task.timer);
300
+ task.timer = null;
301
+ }
302
+ try { task.watcher.close(); } catch {}
303
+ if (task.pending !== null) {
304
+ const pending = task.pending;
305
+ task.pending = null;
306
+ fileWatchers.delete(task.handle);
307
+ task.closed = true;
308
+ pending.reject(task.failure);
309
+ }
310
+ }
311
+
312
+ async function startFileWatch(args) {
313
+ if (args.length !== 2 || typeof args[1] !== "boolean") throw new TypeError("fs.watchStart arguments are invalid");
314
+ if (fileWatchers.size >= maxFileWatchers) throw new RangeError("Node host cannot own more than 128 file watchers");
315
+ const root = await realpath(boundedPath(args[0], "watchFiles"));
316
+ const metadata = await stat(root);
317
+ const directory = metadata.isDirectory();
318
+ if (!directory && !metadata.isFile()) throw new TypeError("watchFiles requires a file or directory path");
319
+ if (args[1] && !directory) throw new TypeError("recursive watchFiles requires a directory path");
320
+ const handle = allocateHandle(fileWatchers, nextFileWatcherHandle, maxFileWatchers, "Node file watcher");
321
+ nextFileWatcherHandle = advanceHandle(handle);
322
+ const task = {handle, root, directory, watcher: null, paths: new Set(), units: 0, rescan: false, pending: null, timer: null, failure: null, closed: false};
323
+ task.watcher = watchNode(root, {recursive: args[1], encoding: "utf8", persistent: true}, (_event, filename) => enqueueFileWatch(task, filename));
324
+ task.watcher.once("error", error => failFileWatch(task, error));
325
+ fileWatchers.set(handle, task);
326
+ return handle;
327
+ }
328
+
329
+ function nextFileWatch(args) {
330
+ if (args.length !== 1) throw new TypeError("fs.watchNext arguments are invalid");
331
+ const handle = integer(args[0], 1, Number.MAX_SAFE_INTEGER, "Node file watcher handle");
332
+ const task = fileWatchers.get(handle);
333
+ if (!task) throw new Error("Node file watcher handle is unknown or already released");
334
+ if (task.pending !== null) throw new Error("FileWatcher.next already has an active pull");
335
+ if (task.failure !== null) {
336
+ fileWatchers.delete(handle);
337
+ task.closed = true;
338
+ throw task.failure;
339
+ }
340
+ if (task.rescan || task.paths.size > 0) return fileWatchBatch(task);
341
+ return new Promise((resolveNext, rejectNext) => {
342
+ task.pending = {resolve: resolveNext, reject: rejectNext};
343
+ });
344
+ }
345
+
346
+ function closeFileWatchHandle(args) {
347
+ if (args.length !== 1) throw new TypeError("fs.watchClose arguments are invalid");
348
+ const handle = integer(args[0], 1, Number.MAX_SAFE_INTEGER, "Node file watcher handle");
349
+ const task = fileWatchers.get(handle);
350
+ return task ? closeFileWatcher(task) : false;
351
+ }
352
+
353
+ async function commitTextReplacement(path, data, mode) {
354
+ let temporary = null;
355
+ for (let attempts = 0; attempts < 1024; attempts += 1) {
356
+ const identity = nextTextReplacementIdentity;
357
+ nextTextReplacementIdentity = nextTextReplacementIdentity >= Number.MAX_SAFE_INTEGER ? 1 : nextTextReplacementIdentity + 1;
358
+ const candidate = resolve(dirname(path), ".velar-replace-" + identity + ".tmp");
359
+ try {
360
+ await writeFile(candidate, data, {flag: "wx", mode});
361
+ temporary = candidate;
362
+ break;
363
+ } catch (error) {
364
+ if (!error || typeof error !== "object" || error.code !== "EEXIST") throw error;
365
+ }
366
+ }
367
+ if (temporary === null) throw new Error("replaceTextIfMatches could not allocate a temporary file");
368
+ try { await rename(temporary, path); }
369
+ finally { await rm(temporary, {force: true, recursive: false}); }
370
+ }
371
+
372
+ function integer(value, minimum, maximum, name) {
373
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
374
+ throw new RangeError(name + " must be an integer from " + minimum + " through " + maximum);
375
+ }
376
+ return value;
377
+ }
378
+
379
+ const httpMethodPattern = /^[!#$%&'*+.^_\x60|~0-9A-Z-]+$/u;
380
+ const httpHeaderNamePattern = /^[!#$%&'*+.^_|~0-9A-Za-z-]+$/u;
381
+ const httpLineBreakPattern = /[\r\n]/u;
382
+ const forbiddenHttpSecretHeaders = new Set([
383
+ "connection", "content-length", "cookie", "cookie2", "host", "proxy-authorization",
384
+ "te", "trailer", "transfer-encoding", "upgrade",
385
+ ]);
386
+
387
+ function httpMethod(value) {
388
+ if (typeof value !== "string") throw new TypeError("HTTP method must be text");
389
+ const method = value.toUpperCase();
390
+ if (method.length === 0 || method.length > 32 || !httpMethodPattern.test(method)
391
+ || method === "CONNECT" || method === "TRACE" || method === "TRACK") {
392
+ throw new TypeError("HTTP method is invalid or forbidden");
393
+ }
394
+ return method;
395
+ }
396
+
397
+ function httpUrl(value, base = undefined) {
398
+ if (typeof value !== "string" || value.length === 0 || value.length > 2 * 1024 * 1024) {
399
+ throw new TypeError("HTTP URL must be bounded text");
400
+ }
401
+ let url;
402
+ try { url = base === undefined ? new NodeURL(value) : new NodeURL(value, base); }
403
+ catch { throw new TypeError("HTTP URL must be absolute"); }
404
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new TypeError("HTTP URL must use http or https");
405
+ if (url.username || url.password) throw new TypeError("HTTP URL credentials are not allowed; use an Authorization header");
406
+ return url;
407
+ }
408
+
409
+ function httpHeaderRecord(value) {
410
+ if (!Array.isArray(value) || value.length > 100) throw new TypeError("HTTP headers must be bounded pairs");
411
+ const headers = Object.create(null);
412
+ let units = 0;
413
+ for (let index = 0; index < value.length; index += 1) {
414
+ const pair = value[index];
415
+ if (!Array.isArray(pair) || pair.length !== 2 || typeof pair[0] !== "string" || typeof pair[1] !== "string"
416
+ || !httpHeaderNamePattern.test(pair[0]) || httpLineBreakPattern.test(pair[1])) {
417
+ throw new TypeError("HTTP headers must use valid string names and single-line values");
418
+ }
419
+ units += pair[0].length + pair[1].length;
420
+ if (units > 65536) throw new RangeError("HTTP headers cannot exceed 64 KiB");
421
+ headers[pair[0].toLowerCase()] = pair[1];
422
+ }
423
+ return headers;
424
+ }
425
+
426
+ function applyHttpSecrets(value, headers) {
427
+ if (!Array.isArray(value) || value.length > 16) throw new TypeError("HTTP secretHeaders must be a List with at most 16 entries");
428
+ const names = new Set();
429
+ for (let index = 0; index < value.length; index += 1) {
430
+ const item = value[index];
431
+ if (!item || typeof item !== "object" || Array.isArray(item)
432
+ || Object.keys(item).length !== 2 || typeof item.name !== "string" || !httpHeaderNamePattern.test(item.name)
433
+ || typeof item.value !== "string" || httpLineBreakPattern.test(item.value) || Buffer.byteLength(item.value, "utf8") > 65536) {
434
+ throw new TypeError("HTTP secret header descriptor is invalid");
435
+ }
436
+ const lower = item.name.toLowerCase();
437
+ if (forbiddenHttpSecretHeaders.has(lower)) throw new TypeError("HTTP secret header name is transport-controlled");
438
+ if (Object.hasOwn(headers, lower) || names.has(lower)) throw new TypeError("HTTP secret header conflicts with another header");
439
+ headers[lower] = item.value;
440
+ names.add(lower);
441
+ }
442
+ const headerNames = Object.keys(headers);
443
+ if (headerNames.length > 100) throw new RangeError("HTTP headers cannot exceed 100 fields");
444
+ let units = 0;
445
+ for (let index = 0; index < headerNames.length; index += 1) {
446
+ const name = headerNames[index];
447
+ units += name.length + headers[name].length;
448
+ if (units > 65536) throw new RangeError("HTTP headers cannot exceed 64 KiB");
449
+ }
450
+ return names;
451
+ }
452
+
453
+ function httpResponseHeaders(response) {
454
+ if (!Array.isArray(response.rawHeaders) || response.rawHeaders.length % 2 !== 0) {
455
+ throw new TypeError("Node HTTP returned invalid response headers");
456
+ }
457
+ const headers = new Map();
458
+ for (let index = 0; index < response.rawHeaders.length; index += 2) {
459
+ const name = response.rawHeaders[index];
460
+ const value = response.rawHeaders[index + 1];
461
+ if (typeof name !== "string" || typeof value !== "string" || !httpHeaderNamePattern.test(name)) {
462
+ throw new TypeError("Node HTTP returned invalid response headers");
463
+ }
464
+ const lower = name.toLowerCase();
465
+ headers.set(lower, headers.has(lower) ? headers.get(lower) + ", " + value : value);
466
+ }
467
+ if (headers.size > 100) throw new RangeError("HTTP response headers cannot exceed 100 fields");
468
+ const output = [];
469
+ let units = 0;
470
+ for (const [name, value] of headers) {
471
+ units += name.length + value.length;
472
+ if (units > 65536) throw new RangeError("HTTP response headers cannot exceed 64 KiB");
473
+ output.push([name, value]);
474
+ }
475
+ return output;
476
+ }
477
+
478
+ class HttpTransportFailure extends Error {
479
+ constructor(phase) {
480
+ super(phase === "request" ? "HTTP request transport failed" : "HTTP response transport failed");
481
+ this.name = "HttpTransportError";
482
+ this.phase = phase;
483
+ }
484
+ }
485
+
486
+ function requestHttpHop(task, url, method, headers, body) {
487
+ return new Promise((resolveResponse, rejectResponse) => {
488
+ const createRequest = url.protocol === "https:" ? createHttpsRequest : createHttpRequest;
489
+ let settled = false;
490
+ const finish = action => { if (settled) return; settled = true; action(); };
491
+ const request = createRequest(url, {method, headers}, response => finish(() => resolveResponse(response)));
492
+ task.request = request;
493
+ request.once("error", () => finish(() => rejectResponse(new HttpTransportFailure("request"))));
494
+ if (task.cancelled) request.destroy(new Error("HTTP request cancelled"));
495
+ else if (body === null) request.end();
496
+ else request.end(body);
497
+ });
498
+ }
499
+
500
+ function releaseHttpRequest(task, reason = null) {
501
+ if (!httpRequests.delete(task.handle)) return false;
502
+ task.cancelled = reason !== null;
503
+ const error = reason === null ? null : new Error(reason === "timeout" ? "HTTP request timed out" : "HTTP request cancelled");
504
+ if (task.request && !task.request.destroyed && error) task.request.destroy(error);
505
+ if (task.response && !task.response.destroyed && !task.ended) task.response.destroy(error ?? undefined);
506
+ return true;
507
+ }
508
+
509
+ async function startHttpRequest(args) {
510
+ if (args.length !== 7) throw new TypeError("http.request arguments are invalid");
511
+ const handle = integer(args[0], 1, Number.MAX_SAFE_INTEGER, "Node HTTP request handle");
512
+ if (httpRequests.size >= maxHttpRequests) throw new RangeError("Node HTTP cannot have more than 1024 active requests");
513
+ if (httpRequests.has(handle)) throw new Error("Node HTTP request handle is already active");
514
+ let method = httpMethod(args[1]);
515
+ let url = httpUrl(args[2]);
516
+ let headers = httpHeaderRecord(args[3]);
517
+ const secretHeaderNames = applyHttpSecrets(args[4], headers);
518
+ let body = args[5];
519
+ if (body !== null && typeof body !== "string" && !(body instanceof Uint8Array)) throw new TypeError("HTTP body must be validated text or bytes");
520
+ if (body !== null && (typeof body === "string" ? Buffer.byteLength(body, "utf8") : body.byteLength) > maxHttpBodyBytes) throw new RangeError("HTTP body cannot exceed 16 MiB");
521
+ if ((method === "GET" || method === "HEAD") && body !== null) throw new TypeError(method + " requests cannot have a body");
522
+ const maxBytes = integer(args[6], 1, maxHttpResponseBytes, "HTTP maxBytes");
523
+ const task = {
524
+ handle, request: null, response: null, iterator: null, decoder: new TextDecoder("utf-8", {fatal: true}),
525
+ maxBytes, bytes: 0, chunks: 0, reading: false, ended: false, cancelled: false,
526
+ };
527
+ httpRequests.set(handle, task);
528
+ try {
529
+ for (let redirects = 0; ; redirects += 1) {
530
+ if (task.cancelled) throw new Error("HTTP request cancelled");
531
+ const response = await requestHttpHop(task, url, method, headers, body);
532
+ task.request = null;
533
+ if (task.cancelled) { response.destroy(); throw new Error("HTTP request cancelled"); }
534
+ const status = response.statusCode;
535
+ if (!Number.isInteger(status) || status < 100 || status > 599) {
536
+ response.destroy();
537
+ throw new TypeError("Node HTTP returned invalid response status");
538
+ }
539
+ const location = response.headers.location;
540
+ const redirected = status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
541
+ if (!redirected || typeof location !== "string") {
542
+ const statusText = response.statusMessage ?? "";
543
+ if (typeof statusText !== "string" || statusText.length > 65536) {
544
+ response.destroy();
545
+ throw new RangeError("HTTP response status text cannot exceed 64 KiB");
546
+ }
547
+ const responseHeaders = httpResponseHeaders(response);
548
+ const hasBody = method !== "HEAD" && status !== 101 && status !== 103 && status !== 204 && status !== 205 && status !== 304;
549
+ if (!hasBody) {
550
+ task.ended = true;
551
+ response.resume();
552
+ httpRequests.delete(handle);
553
+ } else {
554
+ task.response = response;
555
+ task.iterator = response[Symbol.asyncIterator]();
556
+ }
557
+ return {handle, ok: status >= 200 && status <= 299, status, statusText, url: url.href, headers: responseHeaders, body: hasBody};
558
+ }
559
+ if (redirects >= 20) {
560
+ response.destroy();
561
+ throw new Error("HTTP redirect limit of 20 was exceeded");
562
+ }
563
+ let nextUrl;
564
+ try { nextUrl = httpUrl(location, url.href); }
565
+ finally { response.destroy(); }
566
+ if (nextUrl.origin !== url.origin) {
567
+ headers = {...headers};
568
+ delete headers.authorization;
569
+ delete headers["proxy-authorization"];
570
+ delete headers.cookie;
571
+ delete headers.cookie2;
572
+ for (const name of secretHeaderNames) delete headers[name];
573
+ }
574
+ if (status === 303 && method !== "HEAD" || (status === 301 || status === 302) && method === "POST") {
575
+ method = "GET";
576
+ body = null;
577
+ headers = {...headers};
578
+ delete headers["content-encoding"];
579
+ delete headers["content-language"];
580
+ delete headers["content-location"];
581
+ delete headers["content-type"];
582
+ delete headers["content-length"];
583
+ }
584
+ url = nextUrl;
585
+ }
586
+ } catch (error) {
587
+ releaseHttpRequest(task, null);
588
+ throw error;
589
+ }
590
+ }
591
+
592
+ async function readHttpRequest(args, binary = false) {
593
+ if (args.length !== 1) throw new TypeError("http.read arguments are invalid");
594
+ const handle = integer(args[0], 1, Number.MAX_SAFE_INTEGER, "Node HTTP request handle");
595
+ const task = httpRequests.get(handle);
596
+ if (!task || task.ended || !task.iterator) throw new Error("Node HTTP request is unknown or already completed");
597
+ if (task.reading) throw new Error("Node HTTP allows only one active body read");
598
+ task.reading = true;
599
+ try {
600
+ let next;
601
+ try { next = await task.iterator.next(); }
602
+ catch { throw new HttpTransportFailure("response"); }
603
+ if (!next || typeof next !== "object" || typeof next.done !== "boolean") throw new TypeError("Node HTTP returned an invalid response stream result");
604
+ if (next.done) {
605
+ task.ended = true;
606
+ return binary ? {done: true, bytes: new Uint8Array()} : {done: true, text: task.decoder.decode()};
607
+ }
608
+ if (!(next.value instanceof Uint8Array)) throw new TypeError("Node HTTP returned a non-byte response chunk");
609
+ task.bytes += next.value.byteLength;
610
+ task.chunks += 1;
611
+ if (task.bytes > task.maxBytes) throw new RangeError("HTTP response exceeds maxBytes");
612
+ if (task.chunks > maxHttpResponseChunks) throw new RangeError("HTTP responses cannot exceed 1000000 chunks");
613
+ if (binary) { const bytes = new Uint8Array(next.value.byteLength); bytes.set(next.value); return {done: false, bytes}; }
614
+ return {done: false, text: task.decoder.decode(next.value, {stream: true})};
615
+ } finally {
616
+ task.reading = false;
617
+ }
618
+ }
619
+
620
+ function boundedHost(value) {
621
+ if (typeof value !== "string" || value.length === 0 || value.length > 255 || value.includes("\0")) {
622
+ throw new TypeError("serve host must be bounded text");
623
+ }
624
+ return value;
625
+ }
626
+
627
+ function requestHandle(value) {
628
+ const handle = integer(value, 1, Number.MAX_SAFE_INTEGER, "Node serve request handle");
629
+ const request = requests.get(handle);
630
+ if (!request || request.completed) throw new Error("Node serve request is unknown or already completed");
631
+ if (request.transportDone) {
632
+ request.abandoned = true;
633
+ cleanupRequest(request);
634
+ throw new Error("Node serve client connection is closed");
635
+ }
636
+ return request;
637
+ }
638
+
639
+ function headerPairs(value) {
640
+ if (!Array.isArray(value) || value.length > 1000) throw new TypeError("ServeResponse headers must be bounded pairs");
641
+ const output = [];
642
+ let units = 0;
643
+ for (const item of value) {
644
+ if (!Array.isArray(item) || item.length !== 2 || typeof item[0] !== "string" || typeof item[1] !== "string"
645
+ || !/^[!#$%&'*+.^_|~0-9A-Za-z-]+$/u.test(item[0]) || /[\0\r\n]/u.test(item[1])) {
646
+ throw new TypeError("ServeResponse headers must use valid HTTP names and single-line values");
647
+ }
648
+ const lower = item[0].toLowerCase();
649
+ if (lower === "content-length" || lower === "transfer-encoding" || lower === "connection") {
650
+ throw new TypeError("ServeResponse cannot set transport-owned header '" + item[0] + "'");
651
+ }
652
+ units += item[0].length + item[1].length;
653
+ if (units > 1024 * 1024) throw new RangeError("ServeResponse headers cannot exceed 1 MiB of text");
654
+ output.push(item);
655
+ }
656
+ return output;
657
+ }
658
+
659
+ function setHeaders(response, values) {
660
+ for (const [name, value] of headerPairs(values)) response.setHeader(name, value);
661
+ }
662
+
663
+ function requestPath(value) {
664
+ if (typeof value !== "string" || value.length === 0 || value.length > maxPathCodeUnits || value.includes("\0") || value.includes("\\")) {
665
+ throw new StaticNotFound();
666
+ }
667
+ const source = value.startsWith("/") ? value : "/" + value;
668
+ const segments = source.split("/").filter(Boolean);
669
+ if (segments.some(segment => segment === "." || segment === "..")) throw new StaticNotFound();
670
+ return segments.join("/");
671
+ }
672
+
673
+ function inside(root, target) {
674
+ const path = relative(root, target);
675
+ return path === "" || !path.startsWith("..") && !isAbsolute(path);
676
+ }
677
+
678
+ async function staticFile(task, rootValue, pathValue, fallbackValue) {
679
+ const root = await realpath(resolve(boundedPath(rootValue, "fileResponse")));
680
+ const relativePath = requestPath(pathValue);
681
+ const fallback = fallbackValue === null ? null : requestPath(fallbackValue);
682
+ const load = async path => {
683
+ const target = await realpath(resolve(root, path));
684
+ if (!inside(root, target)) throw new StaticNotFound();
685
+ const metadata = await stat(target);
686
+ if (!metadata.isFile() || metadata.size > maxServeFileBytes) throw new StaticNotFound();
687
+ reserveServeBytes(task, metadata.size);
688
+ let reserved = metadata.size;
689
+ try {
690
+ const data = await readFile(target);
691
+ if (data.byteLength > maxServeFileBytes) throw new StaticNotFound();
692
+ if (data.byteLength > reserved) reserveServeBytes(task, data.byteLength - reserved);
693
+ else if (data.byteLength < reserved) releaseServeBytes(task, reserved - data.byteLength);
694
+ reserved = data.byteLength;
695
+ return {data, contentType: contentTypes[extname(target).toLowerCase()] ?? "application/octet-stream"};
696
+ } catch (error) {
697
+ releaseServeBytes(task, reserved);
698
+ throw error;
699
+ }
700
+ };
701
+ try { return await load(relativePath); }
702
+ catch (error) {
703
+ if ((error instanceof StaticNotFound || missing(error) || error?.code === "EISDIR") && fallback !== null) return load(fallback);
704
+ throw error;
705
+ }
706
+ }
707
+
708
+ function opaqueFailure(task) {
709
+ if (task.response.headersSent) task.response.destroy();
710
+ else {
711
+ task.response.statusCode = 500;
712
+ task.response.setHeader("Content-Type", "text/plain; charset=utf-8");
713
+ task.response.end("Internal server error");
714
+ }
715
+ completeRequest(task);
716
+ }
717
+
718
+ function bodyOf(task) {
719
+ if (task.body !== null) return task.body;
720
+ task.body = (async () => {
721
+ const declaredText = task.request.headers["content-length"];
722
+ if (typeof declaredText === "string" && /^[0-9]+$/u.test(declaredText)) {
723
+ const declared = Number(declaredText);
724
+ if (!Number.isSafeInteger(declared) || declared > maxServeBodyBytes) {
725
+ task.request.resume();
726
+ throw new RangeError("Request body cannot exceed 16 MiB");
727
+ }
728
+ }
729
+ const chunks = [];
730
+ let total = 0;
731
+ try {
732
+ for await (const chunk of task.request) {
733
+ const data = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
734
+ if (total + data.byteLength > maxServeBodyBytes) {
735
+ task.request.resume();
736
+ throw new RangeError("Request body cannot exceed 16 MiB");
737
+ }
738
+ reserveServeBytes(task, data.byteLength);
739
+ total += data.byteLength;
740
+ chunks.push(data);
741
+ }
742
+ try { return {text: new TextDecoder("utf-8", {fatal: true}).decode(Buffer.concat(chunks, total)), bytes: total}; }
743
+ catch { throw new TypeError("Request body must be valid UTF-8 text"); }
744
+ } catch (error) {
745
+ releaseServeBytes(task, total);
746
+ throw error;
747
+ }
748
+ })();
749
+ return task.body;
750
+ }
751
+
752
+ function incomingRequest(server, request, response) {
753
+ if (requests.size >= maxRequests) { response.statusCode = 503; response.end("Service unavailable"); return; }
754
+ const target = request.url ?? "/";
755
+ if (typeof target !== "string" || target.length === 0 || target.length > 2 * 1024 * 1024 || target.includes("\0")) {
756
+ response.statusCode = 400; response.end("Bad request"); return;
757
+ }
758
+ let url;
759
+ let path;
760
+ try {
761
+ url = new URL(target, "http://velar.invalid");
762
+ path = decodeURIComponent(url.pathname);
763
+ } catch { response.statusCode = 400; response.end("Bad request"); return; }
764
+ if (path.length === 0 || path.length > maxPathCodeUnits || path.includes("\0")) {
765
+ response.statusCode = 414; response.end("Request target too long"); return;
766
+ }
767
+ const headers = [];
768
+ for (const [name, value] of Object.entries(request.headers)) {
769
+ if (typeof value === "string") headers.push([name, value]);
770
+ else if (Array.isArray(value)) headers.push([name, value.join(", ")]);
771
+ }
772
+ const query = [];
773
+ for (const [name, value] of url.searchParams) if (!query.some(item => item[0] === name)) query.push([name, value]);
774
+ const handle = allocateHandle(requests, nextRequestHandle, maxRequests, "Node serve request");
775
+ nextRequestHandle = advanceHandle(handle);
776
+ const task = {
777
+ handle, server: server.handle, request, response, body: null, streamBytes: 0, streaming: false,
778
+ reservedBytes: 0, completed: false, abandoned: false, transportDone: false, activeOperations: 0,
779
+ };
780
+ requests.set(handle, task);
781
+ response.once("finish", () => closeRequest(task));
782
+ response.once("close", () => closeRequest(task));
783
+ port.postMessage({kind: "event", event: "serve.request", value: {
784
+ token: server.token,
785
+ request: handle,
786
+ method: request.method ?? "GET",
787
+ path,
788
+ query,
789
+ headers,
790
+ }});
791
+ }
792
+
793
+ async function startServer(args) {
794
+ if (args.length !== 3 || servers.size >= maxServers) throw new RangeError("Node serve server limit reached");
795
+ const token = integer(args[0], 1, Number.MAX_SAFE_INTEGER, "Node serve token");
796
+ const portValue = integer(args[1], 0, 65535, "serve port");
797
+ const host = boundedHost(args[2]);
798
+ const handle = allocateHandle(servers, nextServerHandle, maxServers, "Node serve server");
799
+ nextServerHandle = advanceHandle(handle);
800
+ const task = {handle, token, server: null};
801
+ const server = createServer((request, response) => incomingRequest(task, request, response));
802
+ task.server = server;
803
+ await new Promise((resolveListen, rejectListen) => {
804
+ const failed = error => { server.off("listening", ready); rejectListen(error); };
805
+ const ready = () => { server.off("error", failed); resolveListen(); };
806
+ server.once("error", failed);
807
+ server.once("listening", ready);
808
+ server.listen({port: portValue, host});
809
+ });
810
+ const address = server.address();
811
+ if (!address || typeof address === "string") throw new Error("velar/serve could not determine the bound port");
812
+ servers.set(handle, task);
813
+ return {handle, port: address.port};
814
+ }
815
+
816
+ async function stopServer(args) {
817
+ if (args.length !== 1) throw new TypeError("serve.stop arguments are invalid");
818
+ const handle = integer(args[0], 1, Number.MAX_SAFE_INTEGER, "Node serve server handle");
819
+ const task = servers.get(handle);
820
+ if (!task) return null;
821
+ await new Promise((resolveStop, rejectStop) => {
822
+ task.server.close(error => error ? rejectStop(error) : resolveStop(null));
823
+ task.server.closeIdleConnections?.();
824
+ });
825
+ servers.delete(handle);
826
+ return null;
827
+ }
828
+
829
+ async function dispatch(operation, args) {
830
+ if (!operations.has(operation) || !Array.isArray(args)) throw new TypeError("Node host request is invalid");
831
+ if (operation === "http.request") return startHttpRequest(args);
832
+ if (operation === "http.read") return readHttpRequest(args);
833
+ if (operation === "http.readBytes") return readHttpRequest(args, true);
834
+ if (operation === "http.cancel") {
835
+ if (args.length !== 2 || args[1] !== "cancelled" && args[1] !== "timeout") throw new TypeError("http.cancel arguments are invalid");
836
+ const handle = integer(args[0], 1, Number.MAX_SAFE_INTEGER, "Node HTTP request handle");
837
+ const task = httpRequests.get(handle);
838
+ return task ? releaseHttpRequest(task, args[1]) : false;
839
+ }
840
+ if (operation === "http.close") {
841
+ if (args.length !== 1) throw new TypeError("http.close arguments are invalid");
842
+ const handle = integer(args[0], 1, Number.MAX_SAFE_INTEGER, "Node HTTP request handle");
843
+ const task = httpRequests.get(handle);
844
+ return task ? releaseHttpRequest(task, null) : false;
845
+ }
846
+ if (operation === "fs.readFile") {
847
+ if (args.length !== 3 || args[2] !== "readText" && args[2] !== "readBytes") throw new TypeError("fs.readFile arguments are invalid");
848
+ return regularFile(boundedPath(args[0], args[2]), args[2], byteLimit(args[1], args[2]));
849
+ }
850
+ if (operation === "fs.createFile") {
851
+ if (args.length !== 2 && args.length !== 3 || args.length === 3 && args[2] !== "createBytes") throw new TypeError("fs.createFile arguments are invalid");
852
+ const name = args.length === 3 ? "createBytes" : "createText";
853
+ const path = boundedPath(args[0], name);
854
+ const data = byteArray(args[1], name);
855
+ return withFileMutations([path], async () => {
856
+ try { await writeFile(path, data, {flag: "wx"}); }
857
+ catch (error) {
858
+ if (error && typeof error === "object" && error.code === "EEXIST") throw new AlreadyExists(name, path);
859
+ throw error;
860
+ }
861
+ return null;
862
+ });
863
+ }
864
+ if (operation === "fs.replaceFileIfMatches") {
865
+ if (args.length !== 3) throw new TypeError("fs.replaceFileIfMatches arguments are invalid");
866
+ const requestedPath = boundedPath(args[0], "replaceTextIfMatches");
867
+ const expected = byteArray(args[1], "replaceTextIfMatches expected text");
868
+ const replacement = byteArray(args[2], "replaceTextIfMatches replacement text");
869
+ return withFileMutations([requestedPath], async () => {
870
+ const path = await realpath(requestedPath);
871
+ const metadata = await stat(path);
872
+ if (!metadata.isFile()) throw new TypeError("replaceTextIfMatches requires a file path");
873
+ if (metadata.size > maxFileBytes) throw new RangeError("replaceTextIfMatches file exceeds 16 MiB");
874
+ const current = await readFile(path);
875
+ if (current.byteLength > maxFileBytes) throw new RangeError("replaceTextIfMatches file exceeds 16 MiB");
876
+ if (!equalBytes(current, expected)) return false;
877
+ await commitTextReplacement(path, replacement, metadata.mode);
878
+ return true;
879
+ });
880
+ }
881
+ if (operation === "fs.writeFile" || operation === "fs.appendFile") {
882
+ if (args.length !== 2 && args.length !== 3 || args.length === 3 && (operation !== "fs.writeFile" || args[2] !== "writeBytes")) throw new TypeError(operation + " arguments are invalid");
883
+ const name = args.length === 3 ? "writeBytes" : operation === "fs.writeFile" ? "writeText" : "appendText";
884
+ const path = boundedPath(args[0], name);
885
+ const data = byteArray(args[1], name);
886
+ return withFileMutations([path], async () => {
887
+ let metadata = null;
888
+ try { metadata = await stat(path); }
889
+ catch (error) { if (!missing(error)) throw error; }
890
+ if (metadata && !metadata.isFile()) throw new TypeError(name + " requires a file path");
891
+ if (operation === "fs.appendFile" && metadata && metadata.size > maxFileBytes - data.byteLength) {
892
+ throw new RangeError("appendText result cannot exceed 16 MiB");
893
+ }
894
+ if (operation === "fs.writeFile") await writeFile(path, data);
895
+ else await appendFile(path, data);
896
+ return null;
897
+ });
898
+ }
899
+ if (operation === "fs.exists") {
900
+ if (args.length !== 1) throw new TypeError("fs.exists arguments are invalid");
901
+ try { await stat(boundedPath(args[0], "exists")); return true; }
902
+ catch (error) { if (missing(error)) return false; throw error; }
903
+ }
904
+ if (operation === "fs.list") {
905
+ if (args.length !== 2) throw new TypeError("fs.list arguments are invalid");
906
+ const path = boundedPath(args[0], "list");
907
+ const maximum = args[1];
908
+ if (!Number.isSafeInteger(maximum) || maximum < 1 || maximum > maxListItems) {
909
+ throw new RangeError("list maxItems must be an integer from 1 through 100000");
910
+ }
911
+ const names = await readdir(path);
912
+ if (names.length > maximum) throw new RangeError("list result exceeds maxItems");
913
+ let units = 0;
914
+ for (const name of names) {
915
+ if (typeof name !== "string") throw new TypeError("list host result must contain text names");
916
+ units += name.length;
917
+ if (units > maxListCodeUnits) throw new RangeError("list result cannot exceed 2 MiB of text");
918
+ }
919
+ names.sort();
920
+ return names;
921
+ }
922
+ if (operation === "fs.info") {
923
+ if (args.length !== 1) throw new TypeError("fs.info arguments are invalid");
924
+ const path = boundedPath(args[0], "info");
925
+ let metadata;
926
+ try { metadata = await lstat(path); }
927
+ catch (error) { if (missing(error)) return null; throw error; }
928
+ const kind = metadata.isFile() ? "file" : metadata.isDirectory() ? "directory" : metadata.isSymbolicLink() ? "symlink" : "other";
929
+ return {name: basename(path), kind, size: metadata.size, modifiedAt: metadata.mtimeMs};
930
+ }
931
+ if (operation === "fs.canonical") {
932
+ if (args.length !== 1) throw new TypeError("fs.canonical arguments are invalid");
933
+ return realpath(boundedPath(args[0], "canonical"));
934
+ }
935
+ if (operation === "fs.makeDirectory") {
936
+ if (args.length !== 1) throw new TypeError("fs.makeDirectory arguments are invalid");
937
+ await mkdir(boundedPath(args[0], "makeDirectory"), {recursive: true});
938
+ return null;
939
+ }
940
+ if (operation === "fs.copyFile") {
941
+ if (args.length !== 3) throw new TypeError("fs.copyFile arguments are invalid");
942
+ const source = boundedPath(args[0], "copyFile");
943
+ const target = boundedPath(args[1], "copyFile");
944
+ const replace = boolean(args[2], "copyFile");
945
+ if (!(await stat(source)).isFile()) throw new TypeError("copyFile requires a regular file source");
946
+ return withFileMutations([target], async () => {
947
+ if (!replace) await absent(target, "copyFile");
948
+ await copyFile(source, target);
949
+ return null;
950
+ });
951
+ }
952
+ if (operation === "fs.move") {
953
+ if (args.length !== 3) throw new TypeError("fs.move arguments are invalid");
954
+ const source = boundedPath(args[0], "move");
955
+ const target = boundedPath(args[1], "move");
956
+ const replace = boolean(args[2], "move");
957
+ return withFileMutations([source, target], async () => {
958
+ if (!replace) await absent(target, "move");
959
+ else await rm(target, {force: true, recursive: false});
960
+ await rename(source, target);
961
+ return null;
962
+ });
963
+ }
964
+ if (operation === "fs.removeFile") {
965
+ if (args.length !== 1) throw new TypeError("fs.removeFile arguments are invalid");
966
+ const path = boundedPath(args[0], "removeFile");
967
+ return withFileMutations([path], async () => {
968
+ if ((await lstat(path)).isDirectory()) throw new TypeError("removeFile refuses directories");
969
+ await rm(path, {force: false, recursive: false});
970
+ return null;
971
+ });
972
+ }
973
+ if (operation === "fs.watchStart") return startFileWatch(args);
974
+ if (operation === "fs.watchNext") return nextFileWatch(args);
975
+ if (operation === "fs.watchClose") return closeFileWatchHandle(args);
976
+ if (operation === "serve.start") return startServer(args);
977
+ if (operation === "serve.stop") return stopServer(args);
978
+ if (operation === "serve.body") {
979
+ if (args.length !== 2) throw new TypeError("serve.body arguments are invalid");
980
+ const task = requestHandle(args[0]);
981
+ return withRequest(task, async () => {
982
+ const maximum = integer(args[1], 1, maxServeBodyBytes, "Request body maxBytes");
983
+ const body = await bodyOf(task);
984
+ return body.bytes > maximum ? {text: null, bytes: body.bytes, tooLarge: true} : {text: body.text, bytes: body.bytes, tooLarge: false};
985
+ });
986
+ }
987
+ if (operation === "serve.respond") {
988
+ if (args.length !== 6) throw new TypeError("serve.respond arguments are invalid");
989
+ const task = requestHandle(args[0]);
990
+ return withRequest(task, async () => {
991
+ const status = integer(args[1], 100, 599, "ServeResponse.status");
992
+ const headers = headerPairs(args[2]);
993
+ const kind = args[3];
994
+ const body = args[4];
995
+ const contentType = args[5];
996
+ if (kind !== "json" && kind !== "text" || typeof body !== "string" || Buffer.byteLength(body, "utf8") > maxServeBodyBytes) {
997
+ throw new TypeError("ServeResponse body is invalid");
998
+ }
999
+ if (contentType !== null && (typeof contentType !== "string" || contentType.length === 0 || contentType.length > 1024 || /[\0\r\n]/u.test(contentType))) {
1000
+ throw new TypeError("ServeResponse.contentType must be bounded single-line text");
1001
+ }
1002
+ reserveServeBytes(task, Buffer.byteLength(body, "utf8"));
1003
+ task.response.statusCode = status;
1004
+ setHeaders(task.response, headers);
1005
+ if (!task.response.hasHeader("Content-Type")) {
1006
+ task.response.setHeader("Content-Type", kind === "json" ? "application/json; charset=utf-8" : contentType ?? "text/plain; charset=utf-8");
1007
+ }
1008
+ task.response.end(body);
1009
+ completeRequest(task);
1010
+ return null;
1011
+ });
1012
+ }
1013
+ if (operation === "serve.respondFile") {
1014
+ if (args.length !== 4) throw new TypeError("serve.respondFile arguments are invalid");
1015
+ const task = requestHandle(args[0]);
1016
+ return withRequest(task, async () => {
1017
+ try {
1018
+ const file = await staticFile(task, args[1], args[2], args[3]);
1019
+ task.response.statusCode = 200;
1020
+ task.response.setHeader("Content-Type", file.contentType);
1021
+ task.response.setHeader("Content-Length", file.data.byteLength);
1022
+ task.response.end(file.data);
1023
+ } catch (error) {
1024
+ if (!(error instanceof StaticNotFound) && !missing(error) && error?.code !== "EISDIR") throw error;
1025
+ task.response.statusCode = 404;
1026
+ task.response.setHeader("Content-Type", "text/plain; charset=utf-8");
1027
+ task.response.end("Not found");
1028
+ }
1029
+ completeRequest(task);
1030
+ return null;
1031
+ });
1032
+ }
1033
+ if (operation === "serve.streamStart") {
1034
+ if (args.length !== 3) throw new TypeError("serve.streamStart arguments are invalid");
1035
+ const task = requestHandle(args[0]);
1036
+ return withRequest(task, async () => {
1037
+ if (task.streaming) throw new Error("ServeResponse stream has already started");
1038
+ task.response.statusCode = integer(args[1], 100, 599, "ServeResponse.status");
1039
+ setHeaders(task.response, args[2]);
1040
+ task.streaming = true;
1041
+ return null;
1042
+ });
1043
+ }
1044
+ if (operation === "serve.streamWrite") {
1045
+ if (args.length !== 2) throw new TypeError("serve.streamWrite arguments are invalid");
1046
+ const task = requestHandle(args[0]);
1047
+ return withRequest(task, async () => {
1048
+ const chunk = args[1];
1049
+ if (!task.streaming || typeof chunk !== "string") throw new TypeError("ServeResponse.stream chunks must be strings");
1050
+ const bytes = Buffer.byteLength(chunk, "utf8");
1051
+ if (bytes > maxServeStreamChunkBytes || task.streamBytes + bytes > maxServeStreamBytes) {
1052
+ throw new RangeError("ServeResponse.stream exceeded its bounded output");
1053
+ }
1054
+ if (task.response.destroyed || task.response.writableEnded) throw new Error("ServeResponse.stream client connection is closed");
1055
+ reserveTransientServeBytes(bytes);
1056
+ task.streamBytes += bytes;
1057
+ try {
1058
+ await new Promise((resolveWrite, rejectWrite) => {
1059
+ let settled = false;
1060
+ const cleanup = () => {
1061
+ task.response.off("error", failed);
1062
+ task.response.off("close", closed);
1063
+ };
1064
+ const finish = action => { if (settled) return; settled = true; cleanup(); action(); };
1065
+ const disconnected = () => new Error("ServeResponse.stream client connection is closed");
1066
+ const flushed = error => error ? finish(() => rejectWrite(disconnected())) : finish(resolveWrite);
1067
+ const failed = () => finish(() => rejectWrite(disconnected()));
1068
+ const closed = () => finish(() => rejectWrite(disconnected()));
1069
+ task.response.once("error", failed);
1070
+ task.response.once("close", closed);
1071
+ task.response.write(chunk, flushed);
1072
+ if (task.response.destroyed || task.response.writableEnded) closed();
1073
+ });
1074
+ } finally {
1075
+ releaseTransientServeBytes(bytes);
1076
+ }
1077
+ return null;
1078
+ });
1079
+ }
1080
+ if (operation === "serve.streamEnd") {
1081
+ if (args.length !== 1) throw new TypeError("serve.streamEnd arguments are invalid");
1082
+ const task = requestHandle(args[0]);
1083
+ return withRequest(task, async () => {
1084
+ if (!task.streaming) throw new Error("ServeResponse stream has not started");
1085
+ task.response.end();
1086
+ completeRequest(task);
1087
+ return null;
1088
+ });
1089
+ }
1090
+ if (operation === "serve.fail") {
1091
+ if (args.length !== 1) throw new TypeError("serve.fail arguments are invalid");
1092
+ const task = requestHandle(args[0]);
1093
+ return withRequest(task, async () => { opaqueFailure(task); return null; });
1094
+ }
1095
+ throw new TypeError("Unknown Node host operation");
1096
+ }
1097
+
1098
+ // D50 rule 89: the operating system's errno vocabulary is not an API — it is
1099
+ // evidence. These are the failures whose recovery differs (create the file,
1100
+ // request access, take the other branch on kind, choose another name, choose
1101
+ // another port); everything else stays an ordinary Error, because a caller
1102
+ // writes the same recovery for all of it: none.
1103
+ const namedFailures = new Map([
1104
+ ["ENOENT", "FileNotFoundError"],
1105
+ ["EACCES", "PermissionError"],
1106
+ ["EPERM", "PermissionError"],
1107
+ ["ENOTDIR", "NotADirectoryError"],
1108
+ ["EEXIST", "FileExistsError"],
1109
+ ["EADDRINUSE", "AddressInUseError"],
1110
+ ]);
1111
+
1112
+ function errorRecord(error) {
1113
+ if (error instanceof HttpTransportFailure) return {name: "HttpTransportError", message: error.message, phase: error.phase};
1114
+ const message = error instanceof Error && typeof error.message === "string" && error.message.length > 0
1115
+ ? error.message.slice(0, 65536)
1116
+ : "Node host operation failed";
1117
+ const failure = error && typeof error === "object" && typeof error.code === "string" ? namedFailures.get(error.code) : undefined;
1118
+ if (failure === "AddressInUseError") return {name: failure, message};
1119
+ if (failure) return {name: failure, message, path: typeof error.path === "string" ? error.path.slice(0, 65536) : ""};
1120
+ const name = error instanceof RangeError ? "RangeError" : error instanceof TypeError ? "TypeError" : "Error";
1121
+ return {name, message};
1122
+ }
1123
+
1124
+ port.on("message", value => {
1125
+ const id = value && typeof value === "object" && Number.isSafeInteger(value.id) ? value.id : 0;
1126
+ Promise.resolve().then(() => {
1127
+ if (!value || typeof value !== "object" || Array.isArray(value)
1128
+ || !Number.isSafeInteger(value.id) || value.id < 1
1129
+ || typeof value.operation !== "string" || !Array.isArray(value.args)) {
1130
+ throw new TypeError("Node host request is invalid");
1131
+ }
1132
+ return dispatch(value.operation, value.args);
1133
+ }).then(
1134
+ result => port.postMessage({kind: "response", id, ok: true, value: result, error: null}),
1135
+ error => port.postMessage({kind: "response", id, ok: false, value: null, error: errorRecord(error)}),
1136
+ );
1137
+ });
1138
+ port.on("close", () => {
1139
+ for (const task of fileWatchers.values()) closeFileWatcher(task);
1140
+ });
1141
+ port.start();
1142
+ port.postMessage({kind: "ready"});
1143
+ `.trimStart();
1144
+ //# sourceMappingURL=node-host-worker-runtime.js.map