@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.
- package/LICENSE +201 -0
- package/README.md +148 -0
- package/dist/compiler.d.ts +12 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +954 -0
- package/dist/compiler.js.map +1 -0
- package/dist/environment-runtime.d.ts +2 -0
- package/dist/environment-runtime.d.ts.map +1 -0
- package/dist/environment-runtime.js +53 -0
- package/dist/environment-runtime.js.map +1 -0
- package/dist/filesystem-runtime.d.ts +2 -0
- package/dist/filesystem-runtime.d.ts.map +1 -0
- package/dist/filesystem-runtime.js +329 -0
- package/dist/filesystem-runtime.js.map +1 -0
- package/dist/host-runtime.d.ts +2 -0
- package/dist/host-runtime.d.ts.map +1 -0
- package/dist/host-runtime.js +149 -0
- package/dist/host-runtime.js.map +1 -0
- package/dist/http-runtime.d.ts +2 -0
- package/dist/http-runtime.d.ts.map +1 -0
- package/dist/http-runtime.js +612 -0
- package/dist/http-runtime.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/node-host-runtime.d.ts +2 -0
- package/dist/node-host-runtime.d.ts.map +1 -0
- package/dist/node-host-runtime.js +314 -0
- package/dist/node-host-runtime.js.map +1 -0
- package/dist/node-host-worker-runtime.d.ts +2 -0
- package/dist/node-host-worker-runtime.d.ts.map +1 -0
- package/dist/node-host-worker-runtime.js +1144 -0
- package/dist/node-host-worker-runtime.js.map +1 -0
- package/dist/process-runtime.d.ts +2 -0
- package/dist/process-runtime.d.ts.map +1 -0
- package/dist/process-runtime.js +167 -0
- package/dist/process-runtime.js.map +1 -0
- package/dist/process-worker-runtime.d.ts +2 -0
- package/dist/process-worker-runtime.d.ts.map +1 -0
- package/dist/process-worker-runtime.js +473 -0
- package/dist/process-worker-runtime.js.map +1 -0
- package/dist/serve-runtime.d.ts +2 -0
- package/dist/serve-runtime.d.ts.map +1 -0
- package/dist/serve-runtime.js +463 -0
- package/dist/serve-runtime.js.map +1 -0
- package/dist/sqlite-runtime.d.ts +3 -0
- package/dist/sqlite-runtime.d.ts.map +1 -0
- package/dist/sqlite-runtime.js +92 -0
- package/dist/sqlite-runtime.js.map +1 -0
- package/dist/terminal-runtime.d.ts +2 -0
- package/dist/terminal-runtime.d.ts.map +1 -0
- package/dist/terminal-runtime.js +290 -0
- package/dist/terminal-runtime.js.map +1 -0
- package/dist/terminal-worker-runtime.d.ts +2 -0
- package/dist/terminal-worker-runtime.d.ts.map +1 -0
- package/dist/terminal-worker-runtime.js +311 -0
- package/dist/terminal-worker-runtime.js.map +1 -0
- package/dist/websocket-runtime.d.ts +2 -0
- package/dist/websocket-runtime.d.ts.map +1 -0
- package/dist/websocket-runtime.js +46 -0
- package/dist/websocket-runtime.js.map +1 -0
- package/dist/worker-runtime.d.ts +2 -0
- package/dist/worker-runtime.d.ts.map +1 -0
- package/dist/worker-runtime.js +160 -0
- package/dist/worker-runtime.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const VELAR_NODE_WEBSOCKET_RUNTIME = String.raw `
|
|
2
|
+
import __VelarWebSocket, { WebSocketServer as __VelarWebSocketServer } from "ws";
|
|
3
|
+
import { createServer as __velarCreateHttpServer } from "node:http";
|
|
4
|
+
import { readFile as __velarServeReadFile, realpath as __velarServeRealpath, stat as __velarServeStat } from "node:fs/promises";
|
|
5
|
+
import { isAbsolute as __velarServeIsAbsolute, relative as __velarServeRelative, resolve as __velarServeResolve } from "node:path";
|
|
6
|
+
import { ServeRequest as __velarServeRuntime } from "velar/serve";
|
|
7
|
+
|
|
8
|
+
const __velarWebSocketConnections = new WeakMap();
|
|
9
|
+
const __velarWebSocketServers = new WeakMap();
|
|
10
|
+
const __velarServeNativeOperations = Object.freeze({readFile: __velarServeReadFile, realpath: __velarServeRealpath, stat: __velarServeStat, isAbsolute: __velarServeIsAbsolute, relative: __velarServeRelative, resolve: __velarServeResolve});
|
|
11
|
+
function __velarWsInteger(value, fallback, minimum, maximum, name) { if (value === undefined || value === null) return fallback; if (!Number.isSafeInteger(value) || value < minimum || value > maximum) throw new RangeError(name + " must be an integer from " + minimum + " through " + maximum); return value; }
|
|
12
|
+
function __velarWsDuration(value, fallback) { if (value === undefined || value === null) return fallback; if (typeof value !== "string") throw new TypeError("WebSocket timeout must be Duration"); const match = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))(ms|s)$/.exec(value); if (!match) throw new TypeError("WebSocket timeout must be Duration such as 5s"); const result = Number(match[1]) * (match[2] === "s" ? 1000 : 1); if (!Number.isFinite(result) || result < 0 || result > 2147483647) throw new RangeError("WebSocket timeout is outside the supported range"); return result; }
|
|
13
|
+
function __velarWsOptions(options = {}) { if (options === null || typeof options !== "object" || Array.isArray(options)) throw new TypeError("WebSocket options must be a record"); return { maxMessageBytes: __velarWsInteger(options.maxMessageBytes, 16 * 1024 * 1024, 1, 64 * 1024 * 1024, "maxMessageBytes"), maxQueuedMessages: __velarWsInteger(options.maxQueuedMessages, 256, 1, 10000, "maxQueuedMessages"), maxQueuedBytes: __velarWsInteger(options.maxQueuedBytes, 16 * 1024 * 1024, 1, 64 * 1024 * 1024, "maxQueuedBytes"), maxPendingSendBytes: __velarWsInteger(options.maxPendingSendBytes, 16 * 1024 * 1024, 1, 64 * 1024 * 1024, "maxPendingSendBytes") }; }
|
|
14
|
+
function __velarWsBytes(value) { if (value instanceof Uint8Array) { const output = new Uint8Array(value.byteLength); output.set(value); return output; } return null; }
|
|
15
|
+
function __velarWsMessageBytes(value) { if (typeof value === "string") return Buffer.byteLength(value, "utf8"); const bytes = __velarWsBytes(value); if (bytes) return bytes.byteLength; throw new TypeError("WebSocket.send requires text or Bytes"); }
|
|
16
|
+
export class WebSocketBackpressureError extends Error { constructor(message = "WebSocket backpressure limit exceeded") { super(message); this.name = "WebSocketBackpressureError"; } }
|
|
17
|
+
export class WebSocketClosedError extends Error { constructor(message = "WebSocket is closed") { super(message); this.name = "WebSocketClosedError"; } }
|
|
18
|
+
export class WebSocketProtocolError extends Error { constructor(message = "WebSocket protocol error") { super(message); this.name = "WebSocketProtocolError"; } }
|
|
19
|
+
export class WebSocketTimeoutError extends Error { constructor(message = "WebSocket timed out") { super(message); this.name = "WebSocketTimeoutError"; } }
|
|
20
|
+
function __velarWsFinish(state) { if (state.finished) return; state.finished = true; if (state.waiter) { const waiter = state.waiter; state.waiter = null; waiter.resolve(null); } }
|
|
21
|
+
function __velarWsAbort(state) { state.finished = true; state.queue.length = 0; state.queuedBytes = 0; if (state.waiter) { const waiter = state.waiter; state.waiter = null; waiter.resolve(null); } }
|
|
22
|
+
function __velarWsRejectReceive(state, socket, code, reason) { __velarWsAbort(state); socket.close(code, reason); }
|
|
23
|
+
function __velarWsWrap(socket, options) {
|
|
24
|
+
const value = Object.create(__velarWsConnectionPrototype); const state = { socket, options, queue: [], queuedBytes: 0, waiter: null, pendingSendBytes: 0, finished: false, closePromise: null }; __velarWebSocketConnections.set(value, state);
|
|
25
|
+
socket.binaryType = "arraybuffer";
|
|
26
|
+
socket.on("message", (data, binary) => { if (state.finished) return; let message; let size; if (binary) { const source = data instanceof Uint8Array ? data : new Uint8Array(data); size = source.byteLength; if (size > options.maxMessageBytes) { __velarWsRejectReceive(state, socket, 1009, "Message too large"); return; } message = new Uint8Array(size); message.set(source); } else { message = typeof data === "string" ? data : data.toString("utf8"); size = Buffer.byteLength(message, "utf8"); if (size > options.maxMessageBytes) { __velarWsRejectReceive(state, socket, 1009, "Message too large"); return; } } if (state.waiter) { const waiter = state.waiter; state.waiter = null; waiter.resolve(message); } else if (state.queue.length >= options.maxQueuedMessages || state.queuedBytes + size > options.maxQueuedBytes) __velarWsRejectReceive(state, socket, 1009, "Unread message queue full"); else { state.queue.push(message); state.queuedBytes += size; } });
|
|
27
|
+
socket.on("close", () => __velarWsFinish(state)); socket.on("error", error => { if (state.waiter) { const waiter = state.waiter; state.waiter = null; waiter.reject(new WebSocketProtocolError(error instanceof Error ? error.message : "WebSocket error")); } __velarWsAbort(state); });
|
|
28
|
+
return Object.freeze(value);
|
|
29
|
+
}
|
|
30
|
+
const __velarWsConnectionPrototype = Object.freeze({
|
|
31
|
+
state() { const state = __velarWebSocketConnections.get(this); if (!state) throw new TypeError("WebSocket state requires a connection"); return state.socket.readyState === __VelarWebSocket.CONNECTING ? "connecting" : state.socket.readyState === __VelarWebSocket.OPEN ? "open" : state.socket.readyState === __VelarWebSocket.CLOSING ? "closing" : "closed"; },
|
|
32
|
+
send(message) { const state = __velarWebSocketConnections.get(this); if (!state) throw new TypeError("WebSocket.send requires a connection"); if (state.socket.readyState !== __VelarWebSocket.OPEN) return Promise.reject(new WebSocketClosedError()); const size = __velarWsMessageBytes(message); if (size > state.options.maxMessageBytes) return Promise.reject(new RangeError("WebSocket message exceeds maxMessageBytes")); if (state.pendingSendBytes + size > state.options.maxPendingSendBytes) return Promise.reject(new WebSocketBackpressureError()); const bytes = typeof message === "string" ? null : __velarWsBytes(message); state.pendingSendBytes += size; return new Promise((resolve, reject) => state.socket.send(bytes ?? message, { binary: bytes !== null }, error => { state.pendingSendBytes -= size; if (error) reject(new WebSocketClosedError(error.message)); else resolve(null); })); },
|
|
33
|
+
next() { const state = __velarWebSocketConnections.get(this); if (!state) throw new TypeError("WebSocket.next requires a connection"); if (state.queue.length) { const message = state.queue.shift(); state.queuedBytes -= __velarWsMessageBytes(message); return Promise.resolve(message); } if (state.finished) return Promise.resolve(null); if (state.waiter) return Promise.reject(new WebSocketBackpressureError("Only one WebSocket.next call may wait at a time")); return new Promise((resolve, reject) => { state.waiter = { resolve, reject }; }); },
|
|
34
|
+
close(code = 1000, reason = "") { const state = __velarWebSocketConnections.get(this); if (!state) throw new TypeError("WebSocket.close requires a connection"); if (!Number.isSafeInteger(code) || code < 1000 || code > 4999) return Promise.reject(new RangeError("WebSocket close code must be from 1000 through 4999")); if (typeof reason !== "string" || Buffer.byteLength(reason, "utf8") > 123) return Promise.reject(new RangeError("WebSocket close reason cannot exceed 123 UTF-8 bytes")); if (state.finished) return Promise.resolve(null); if (!state.closePromise) { state.closePromise = new Promise(resolve => state.socket.once("close", () => resolve(null))); state.socket.close(code, reason); } return state.closePromise; },
|
|
35
|
+
});
|
|
36
|
+
const __velarWsServerPrototype = Object.freeze({
|
|
37
|
+
next() { const state = __velarWebSocketServers.get(this); if (!state) throw new TypeError("WebSocketServer.next requires a server"); if (state.queue.length) return Promise.resolve(state.queue.shift()); if (state.stopped) return Promise.resolve(null); if (state.waiter) return Promise.reject(new WebSocketBackpressureError("Only one WebSocketServer.next call may wait at a time")); return new Promise((resolve, reject) => { state.waiter = { resolve, reject }; }); },
|
|
38
|
+
async stop() { const state = __velarWebSocketServers.get(this); if (!state) throw new TypeError("WebSocketServer.stop requires a server"); if (state.stopped) return null; state.stopped = true; if (state.waiter) { state.waiter.resolve(null); state.waiter = null; } for (const connection of state.connections) await connection.close(1001, "Server stopping"); await new Promise((resolve, reject) => state.server.close(error => error ? reject(error) : resolve(null))); if (state.httpServer) await new Promise((resolve, reject) => state.httpServer.close(error => error ? reject(error) : resolve(null))); return null; },
|
|
39
|
+
});
|
|
40
|
+
const __velarWsConnectionType = Object.freeze({ is(value) { return __velarWebSocketConnections.has(value); }, parse(value) { if (!this.is(value)) throw new TypeError("Value does not match WebSocketConnection"); return value; } });
|
|
41
|
+
const __velarWsServerType = Object.freeze({ is(value) { return __velarWebSocketServers.has(value); }, parse(value) { if (!this.is(value)) throw new TypeError("Value does not match WebSocketServer"); return value; } });
|
|
42
|
+
export const WebSocketConnection = __velarWsConnectionType; export const WebSocketServer = __velarWsServerType;
|
|
43
|
+
export function connect(url, options = {}) { if (typeof url !== "string" || !/^wss?:\/\//u.test(url)) return Promise.reject(new TypeError("WebSocket URL must start with ws:// or wss://")); const limits = __velarWsOptions(options); const timeout = __velarWsDuration(options.timeout, 10000); return new Promise((resolve, reject) => { const socket = new __VelarWebSocket(url, { maxPayload: limits.maxMessageBytes }); const timer = setTimeout(() => { socket.terminate(); reject(new WebSocketTimeoutError("WebSocket connection timed out")); }, timeout); socket.once("open", () => { clearTimeout(timer); resolve(__velarWsWrap(socket, limits)); }); socket.once("error", error => { clearTimeout(timer); reject(new WebSocketProtocolError(error instanceof Error ? error.message : "WebSocket connection failed")); }); }); }
|
|
44
|
+
export function listen(options) { if (options === null || typeof options !== "object" || Array.isArray(options)) return Promise.reject(new TypeError("listen requires an options record")); const limits = __velarWsOptions(options); const port = __velarWsInteger(options.port, 0, 0, 65535, "port"); const maxConnections = __velarWsInteger(options.maxConnections, 1024, 1, 100000, "maxConnections"); const maxPendingConnections = __velarWsInteger(options.maxPendingConnections, 128, 1, 10000, "maxPendingConnections"); if (options.host !== undefined && typeof options.host !== "string") return Promise.reject(new TypeError("WebSocket host must be text")); if (options.path !== undefined && (typeof options.path !== "string" || !options.path.startsWith("/"))) return Promise.reject(new TypeError("WebSocket path must start with '/'")); if (options.http !== undefined && typeof options.http !== "function") return Promise.reject(new TypeError("WebSocket http must be a velar/serve handler")); return new Promise((resolve, reject) => { const httpServer = options.http ? __velarCreateHttpServer((request, response) => { void __velarServeRuntime.__velarHandleNative(options.http, request, response, __velarServeNativeOperations); }) : null; const server = new __VelarWebSocketServer(httpServer ? { server: httpServer, path: options.path, maxPayload: limits.maxMessageBytes, perMessageDeflate: false, clientTracking: true } : { port, host: options.host ?? "127.0.0.1", path: options.path, maxPayload: limits.maxMessageBytes, perMessageDeflate: false, clientTracking: true }); const value = Object.create(__velarWsServerPrototype); const state = { server, httpServer, queue: [], waiter: null, stopped: false, connections: new Set() }; __velarWebSocketServers.set(value, state); server.on("connection", socket => { if (state.connections.size >= maxConnections || (!state.waiter && state.queue.length >= maxPendingConnections)) { socket.close(1013, "Server busy"); return; } const connection = __velarWsWrap(socket, limits); state.connections.add(connection); socket.once("close", () => state.connections.delete(connection)); if (state.waiter) { const waiter = state.waiter; state.waiter = null; waiter.resolve(connection); } else state.queue.push(connection); }); server.once("error", reject); const ready = () => { const address = (httpServer ?? server).address(); Object.defineProperty(value, "port", { value: typeof address === "object" && address ? address.port : port, enumerable: true }); resolve(Object.freeze(value)); }; if (httpServer) { httpServer.once("error", reject); httpServer.listen({port, host: options.host ?? "127.0.0.1"}, ready); } else server.once("listening", ready); }); }
|
|
45
|
+
`.trimStart();
|
|
46
|
+
//# sourceMappingURL=websocket-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-runtime.js","sourceRoot":"","sources":["../src/websocket-runtime.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,4BAA4B,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CrD,CAAC,SAAS,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-runtime.d.ts","sourceRoot":"","sources":["../src/worker-runtime.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,yBAAyB,QA8JzB,CAAC"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export const VELAR_NODE_WORKER_RUNTIME = String.raw `
|
|
2
|
+
import { Worker as __VelarNodeWorker, isMainThread as __velarWorkerIsMainThread, parentPort as __velarWorkerParentPort } from "node:worker_threads";
|
|
3
|
+
import { workerEntries as __velarWorkerEntries } from "velar/worker-manifest";
|
|
4
|
+
import { Cancellation, CancellationError, TaskTimeoutError } from "velar/task";
|
|
5
|
+
|
|
6
|
+
const __velarWorkerStates = new WeakMap();
|
|
7
|
+
const __velarWorkerPoolStates = new WeakMap();
|
|
8
|
+
let __velarWorkerServerInstalled = false;
|
|
9
|
+
const __velarWorkerStructuredClone = globalThis.structuredClone;
|
|
10
|
+
function __velarWorkerInteger(value, minimum, maximum, name) {
|
|
11
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) throw new RangeError(name + " must be an integer from " + minimum + " through " + maximum);
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
function __velarWorkerRuntimeType(value, name) {
|
|
15
|
+
if ((typeof value !== "object" && typeof value !== "function") || value === null || typeof value.parse !== "function" || typeof value.is !== "function") throw new TypeError(name + " must be a runtime Type");
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function __velarWorkerDuration(value) {
|
|
19
|
+
if (value === null) return 0;
|
|
20
|
+
if (typeof value !== "string") throw new TypeError("Worker timeout must be Duration or null");
|
|
21
|
+
const match = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))(ms|s)$/.exec(value);
|
|
22
|
+
if (!match) throw new TypeError("Worker timeout must be Duration such as 200ms or 2s");
|
|
23
|
+
const milliseconds = Number(match[1]) * (match[2] === "s" ? 1000 : 1);
|
|
24
|
+
if (!Number.isFinite(milliseconds) || milliseconds < 0 || milliseconds > 2147483647) throw new RangeError("Worker timeout is outside the supported range");
|
|
25
|
+
return milliseconds;
|
|
26
|
+
}
|
|
27
|
+
function __velarWorkerTransfer(value) {
|
|
28
|
+
const transfers = [];
|
|
29
|
+
const buffers = new Set();
|
|
30
|
+
const objects = new Set();
|
|
31
|
+
const pending = [{ value, depth: 0 }];
|
|
32
|
+
let visited = 0;
|
|
33
|
+
let transferBytes = 0;
|
|
34
|
+
while (pending.length > 0 && visited < 10000 && transfers.length < 1024) {
|
|
35
|
+
const current = pending.pop();
|
|
36
|
+
const item = current.value;
|
|
37
|
+
visited += 1;
|
|
38
|
+
if (item === null || (typeof item !== "object" && typeof item !== "function")) continue;
|
|
39
|
+
if (objects.has(item)) continue;
|
|
40
|
+
objects.add(item);
|
|
41
|
+
if (ArrayBuffer.isView(item)) {
|
|
42
|
+
const supported = item instanceof Uint8Array || item instanceof Uint16Array || item instanceof Uint32Array || item instanceof Float32Array;
|
|
43
|
+
const buffer = item.buffer;
|
|
44
|
+
if (supported && buffer instanceof ArrayBuffer && item.byteOffset === 0 && item.byteLength === buffer.byteLength
|
|
45
|
+
&& buffer.byteLength > 0 && transferBytes + buffer.byteLength <= 64 * 1024 * 1024 && !buffers.has(buffer)) {
|
|
46
|
+
buffers.add(buffer);
|
|
47
|
+
transfers.push(buffer);
|
|
48
|
+
transferBytes += buffer.byteLength;
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (current.depth >= 32) continue;
|
|
53
|
+
const depth = current.depth + 1;
|
|
54
|
+
if (item instanceof Map) { for (const [key, nested] of item) { pending.push({ value: key, depth }, { value: nested, depth }); } continue; }
|
|
55
|
+
if (item instanceof Set) { for (const nested of item) pending.push({ value: nested, depth }); continue; }
|
|
56
|
+
for (const key of Object.keys(item)) pending.push({ value: item[key], depth });
|
|
57
|
+
}
|
|
58
|
+
return transfers;
|
|
59
|
+
}
|
|
60
|
+
function __velarWorkerOutbound(value) {
|
|
61
|
+
const sourceTransfers = __velarWorkerTransfer(value);
|
|
62
|
+
if (sourceTransfers.length === 0) return { value, transfers: sourceTransfers };
|
|
63
|
+
if (typeof __velarWorkerStructuredClone !== "function") throw new Error("The structured clone API is unavailable");
|
|
64
|
+
const snapshot = __velarWorkerStructuredClone(value);
|
|
65
|
+
return { value: snapshot, transfers: __velarWorkerTransfer(snapshot) };
|
|
66
|
+
}
|
|
67
|
+
function __velarWorkerFailure(error) {
|
|
68
|
+
return { name: typeof error?.name === "string" ? error.name : "Error", message: typeof error?.message === "string" ? error.message : String(error), stack: typeof error?.stack === "string" ? error.stack : "" };
|
|
69
|
+
}
|
|
70
|
+
export class WorkerBackpressureError extends Error { constructor(message = "Worker queue is full") { super(message); this.name = "WorkerBackpressureError"; } }
|
|
71
|
+
export class WorkerCallError extends Error { constructor(message = "Worker call failed") { super(message); this.name = "WorkerCallError"; } }
|
|
72
|
+
export class WorkerCrashedError extends Error { constructor(message = "Worker crashed") { super(message); this.name = "WorkerCrashedError"; } }
|
|
73
|
+
export class WorkerClosedError extends Error { constructor(message = "Worker is closed") { super(message); this.name = "WorkerClosedError"; } }
|
|
74
|
+
|
|
75
|
+
function __velarWorkerRejectAll(state, failure) {
|
|
76
|
+
if (state.closed) return;
|
|
77
|
+
state.closed = true;
|
|
78
|
+
for (const pending of state.pending.values()) { if (pending.timer) clearTimeout(pending.timer); pending.unsubscribe(); pending.reject(failure); }
|
|
79
|
+
state.pending.clear();
|
|
80
|
+
}
|
|
81
|
+
function __velarWorkerClient(name, RequestType, ResponseType, capacity) {
|
|
82
|
+
if (!__velarWorkerIsMainThread) throw new Error("worker() can only be called outside a worker entry");
|
|
83
|
+
if (typeof name !== "string" || !Object.hasOwn(__velarWorkerEntries, name)) throw new RangeError("Unknown worker entry '" + String(name) + "'");
|
|
84
|
+
RequestType = __velarWorkerRuntimeType(RequestType, "Worker request Type"); ResponseType = __velarWorkerRuntimeType(ResponseType, "Worker response Type");
|
|
85
|
+
capacity = __velarWorkerInteger(capacity, 1, 10000, "Worker capacity");
|
|
86
|
+
const entry = __velarWorkerEntries[name];
|
|
87
|
+
const instance = new __VelarNodeWorker(new URL("../../" + entry, import.meta.url), { type: "module" });
|
|
88
|
+
const value = Object.create(__velarWorkerPrototype);
|
|
89
|
+
const state = { instance, RequestType, ResponseType, capacity, nextId: 1, pending: new Map(), closed: false };
|
|
90
|
+
__velarWorkerStates.set(value, state);
|
|
91
|
+
instance.on("message", message => {
|
|
92
|
+
if (!message || !Number.isSafeInteger(message.id)) { __velarWorkerRejectAll(state, new WorkerCrashedError("Worker returned an invalid message")); instance.terminate(); return; }
|
|
93
|
+
const pending = state.pending.get(message.id); if (!pending) return;
|
|
94
|
+
state.pending.delete(message.id); if (pending.timer) clearTimeout(pending.timer); pending.unsubscribe();
|
|
95
|
+
if (pending.cancel === "timeout") { pending.reject(new TaskTimeoutError("Worker call timed out after " + pending.timeout)); return; }
|
|
96
|
+
if (pending.cancel === "cancel") { pending.reject(new CancellationError(pending.reason)); return; }
|
|
97
|
+
if (!message.ok) { const remote = message.error ?? {}; const failure = new WorkerCallError((remote.name ? remote.name + ": " : "") + (remote.message ?? "Worker call failed")); if (typeof remote.stack === "string" && remote.stack) failure.stack = failure.stack + "\nRemote worker:\n" + remote.stack; pending.reject(failure); return; }
|
|
98
|
+
try { pending.resolve(state.ResponseType.parse(message.value)); } catch (error) { pending.reject(error); }
|
|
99
|
+
});
|
|
100
|
+
instance.on("error", error => __velarWorkerRejectAll(state, new WorkerCrashedError(error instanceof Error ? error.message : "Worker crashed")));
|
|
101
|
+
instance.on("exit", code => { if (!state.closed) __velarWorkerRejectAll(state, new WorkerCrashedError("Worker exited unexpectedly with code " + code)); });
|
|
102
|
+
return Object.freeze(value);
|
|
103
|
+
}
|
|
104
|
+
const __velarWorkerPrototype = Object.freeze({
|
|
105
|
+
call(request, cancellation = null, timeout = null) {
|
|
106
|
+
const state = __velarWorkerStates.get(this); if (!state) throw new TypeError("Worker.call requires a Worker receiver");
|
|
107
|
+
if (state.closed) return Promise.reject(new WorkerClosedError());
|
|
108
|
+
if (state.pending.size >= state.capacity) return Promise.reject(new WorkerBackpressureError("Worker queue capacity " + state.capacity + " is full"));
|
|
109
|
+
if (cancellation !== null && !Cancellation.is(cancellation)) return Promise.reject(new TypeError("Worker cancellation must be Cancellation or null"));
|
|
110
|
+
let validated; try { validated = state.RequestType.parse(request); } catch (error) { return Promise.reject(error); }
|
|
111
|
+
let outbound; try { outbound = __velarWorkerOutbound(validated); } catch (error) { return Promise.reject(error); }
|
|
112
|
+
const milliseconds = __velarWorkerDuration(timeout); const id = state.nextId++;
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const pending = { resolve, reject, cancel: null, reason: "Worker call cancelled", timeout, timer: null, unsubscribe: () => null };
|
|
115
|
+
state.pending.set(id, pending);
|
|
116
|
+
state.instance.postMessage({ kind: "call", id, value: outbound.value }, outbound.transfers);
|
|
117
|
+
if (cancellation !== null) pending.unsubscribe = Cancellation.__velarOn(cancellation, reason => { if (!state.pending.has(id) || pending.cancel) return; pending.cancel = "cancel"; pending.reason = reason ?? pending.reason; state.instance.postMessage({ kind: "cancel", id, reason: pending.reason }); });
|
|
118
|
+
if (milliseconds > 0) pending.timer = setTimeout(() => { if (!state.pending.has(id) || pending.cancel) return; pending.cancel = "timeout"; state.instance.postMessage({ kind: "cancel", id, reason: "Worker call timed out" }); }, milliseconds);
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
async close() {
|
|
122
|
+
const state = __velarWorkerStates.get(this); if (!state) throw new TypeError("Worker.close requires a Worker receiver");
|
|
123
|
+
if (state.closed) return null;
|
|
124
|
+
__velarWorkerRejectAll(state, new WorkerClosedError());
|
|
125
|
+
await state.instance.terminate(); return null;
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
const __velarWorkerPoolPrototype = Object.freeze({
|
|
129
|
+
call(request, cancellation = null, timeout = null) {
|
|
130
|
+
const state = __velarWorkerPoolStates.get(this); if (!state) throw new TypeError("WorkerPool.call requires a WorkerPool receiver");
|
|
131
|
+
if (state.closed) return Promise.reject(new WorkerClosedError("Worker pool is closed"));
|
|
132
|
+
let selected = state.workers[0], pending = __velarWorkerStates.get(selected).pending.size;
|
|
133
|
+
for (let index = 1; index < state.workers.length; index += 1) { const candidate = __velarWorkerStates.get(state.workers[index]).pending.size; if (candidate < pending) { selected = state.workers[index]; pending = candidate; } }
|
|
134
|
+
if (state.workers.reduce((total, worker) => total + __velarWorkerStates.get(worker).pending.size, 0) >= state.capacity) return Promise.reject(new WorkerBackpressureError("Worker pool queue capacity " + state.capacity + " is full"));
|
|
135
|
+
return selected.call(request, cancellation, timeout);
|
|
136
|
+
},
|
|
137
|
+
async close() { const state = __velarWorkerPoolStates.get(this); if (!state) throw new TypeError("WorkerPool.close requires a WorkerPool receiver"); if (state.closed) return null; state.closed = true; await Promise.all(state.workers.map(worker => worker.close())); return null; },
|
|
138
|
+
});
|
|
139
|
+
const __velarWorkerType = Object.freeze({ is(value) { return __velarWorkerStates.has(value); }, parse(value) { if (!this.is(value)) throw new TypeError("Value does not match Worker"); return value; } });
|
|
140
|
+
const __velarWorkerPoolType = Object.freeze({ is(value) { return __velarWorkerPoolStates.has(value); }, parse(value) { if (!this.is(value)) throw new TypeError("Value does not match WorkerPool"); return value; } });
|
|
141
|
+
export const Worker = Object.freeze({ ...__velarWorkerType, of() { return __velarWorkerType; } });
|
|
142
|
+
export const WorkerPool = Object.freeze({ ...__velarWorkerPoolType, of() { return __velarWorkerPoolType; } });
|
|
143
|
+
export function worker(name, RequestType, ResponseType, capacity = 64) { return __velarWorkerClient(name, RequestType, ResponseType, capacity); }
|
|
144
|
+
export function workerPool(name, RequestType, ResponseType, size, capacity = 256) {
|
|
145
|
+
size = __velarWorkerInteger(size, 1, 32, "Worker pool size"); capacity = __velarWorkerInteger(capacity, size, 10000, "Worker pool capacity");
|
|
146
|
+
const workers = Array.from({ length: size }, () => __velarWorkerClient(name, RequestType, ResponseType, Math.ceil(capacity / size)));
|
|
147
|
+
const value = Object.create(__velarWorkerPoolPrototype); __velarWorkerPoolStates.set(value, { workers, capacity, closed: false }); return Object.freeze(value);
|
|
148
|
+
}
|
|
149
|
+
export function serveWorker(RequestType, ResponseType, handler, capacity = 64) {
|
|
150
|
+
if (__velarWorkerIsMainThread || !__velarWorkerParentPort) throw new Error("serveWorker() can only run in a declared worker entry");
|
|
151
|
+
if (__velarWorkerServerInstalled) throw new Error("A worker entry may call serveWorker() only once");
|
|
152
|
+
RequestType = __velarWorkerRuntimeType(RequestType, "Worker request Type"); ResponseType = __velarWorkerRuntimeType(ResponseType, "Worker response Type");
|
|
153
|
+
if (typeof handler !== "function") throw new TypeError("serveWorker handler must be an async function"); capacity = __velarWorkerInteger(capacity, 1, 10000, "Worker capacity");
|
|
154
|
+
__velarWorkerServerInstalled = true; const queue = []; const active = new Map(); let running = false;
|
|
155
|
+
const drain = async () => { if (running) return; running = true; while (queue.length) { const message = queue.shift(); const cancellation = Cancellation.__velarCreate(); active.set(message.id, cancellation); try { const request = RequestType.parse(message.value); const result = ResponseType.parse(await handler(request, cancellation)); const outbound = __velarWorkerOutbound(result); __velarWorkerParentPort.postMessage({ id: message.id, ok: true, value: outbound.value }, outbound.transfers); } catch (error) { __velarWorkerParentPort.postMessage({ id: message.id, ok: false, error: __velarWorkerFailure(error) }); } finally { active.delete(message.id); } } running = false; };
|
|
156
|
+
__velarWorkerParentPort.on("message", message => { if (!message || !Number.isSafeInteger(message.id)) return; if (message.kind === "cancel") { const cancellation = active.get(message.id); if (cancellation) Cancellation.__velarCancel(cancellation, typeof message.reason === "string" ? message.reason : "Worker call cancelled"); else { const index = queue.findIndex(item => item.id === message.id); if (index >= 0) { queue.splice(index, 1); __velarWorkerParentPort.postMessage({ id: message.id, ok: false, error: __velarWorkerFailure(new CancellationError(typeof message.reason === "string" ? message.reason : "Worker call cancelled")) }); } } return; } if (message.kind !== "call") return; if (queue.length + active.size >= capacity) { __velarWorkerParentPort.postMessage({ id: message.id, ok: false, error: __velarWorkerFailure(new WorkerBackpressureError()) }); return; } queue.push(message); drain(); });
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
`.trimStart();
|
|
160
|
+
//# sourceMappingURL=worker-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-runtime.js","sourceRoot":"","sources":["../src/worker-runtime.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8JlD,CAAC,SAAS,EAAE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@velarscript/node",
|
|
3
|
+
"version": "0.10.1",
|
|
4
|
+
"description": "The official VelarScript Node.js module contract and runtime.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"clean": "node ../../scripts/clean-package-dist.mjs",
|
|
13
|
+
"build": "npm run clean && tsc -p tsconfig.build.json",
|
|
14
|
+
"prepack": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=24"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/VelarOS-AI/VelarScript.git",
|
|
22
|
+
"directory": "packages/node"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@velarscript/compiler": "0.10.1",
|
|
29
|
+
"ws": "^8.21.1"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./compiler": {
|
|
37
|
+
"types": "./dist/compiler.d.ts",
|
|
38
|
+
"default": "./dist/compiler.js"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|