@velarscript/node 0.12.1 → 0.13.0
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/README.md +40 -11
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +98 -23
- package/dist/compiler.js.map +1 -1
- package/dist/http-runtime.d.ts.map +1 -1
- package/dist/http-runtime.js +19 -7
- package/dist/http-runtime.js.map +1 -1
- package/dist/node-host-runtime.d.ts.map +1 -1
- package/dist/node-host-runtime.js +14 -7
- package/dist/node-host-runtime.js.map +1 -1
- package/dist/node-host-worker-runtime.d.ts.map +1 -1
- package/dist/node-host-worker-runtime.js +59 -31
- package/dist/node-host-worker-runtime.js.map +1 -1
- package/dist/project-config.d.ts +1 -1
- package/dist/project-config.d.ts.map +1 -1
- package/dist/route-shape.d.ts +20 -0
- package/dist/route-shape.d.ts.map +1 -0
- package/dist/route-shape.js +36 -0
- package/dist/route-shape.js.map +1 -0
- package/dist/serve-runtime.d.ts.map +1 -1
- package/dist/serve-runtime.js +246 -42
- package/dist/serve-runtime.js.map +1 -1
- package/dist/server-analyzer.d.ts +51 -0
- package/dist/server-analyzer.d.ts.map +1 -1
- package/dist/server-analyzer.js +347 -15
- package/dist/server-analyzer.js.map +1 -1
- package/dist/websocket-runtime.d.ts.map +1 -1
- package/dist/websocket-runtime.js +73 -17
- package/dist/websocket-runtime.js.map +1 -1
- package/package.json +2 -2
|
@@ -13,10 +13,19 @@ const __velarWebSocketServers = new WeakMap();
|
|
|
13
13
|
const __velarWsPromise = globalThis.Promise;
|
|
14
14
|
const __velarWsPromiseAll = __velarWsPromise.all.bind(__velarWsPromise);
|
|
15
15
|
const __velarWsReflectApply = globalThis.Reflect.apply;
|
|
16
|
+
const __velarWsReflectConstruct = globalThis.Reflect.construct;
|
|
16
17
|
const __velarWsFinalizationRegistry = globalThis.FinalizationRegistry;
|
|
17
18
|
const __velarWsFinalizerRegister = __velarWsFinalizationRegistry.prototype.register;
|
|
18
19
|
const __velarWsSetTimeout = globalThis.setTimeout;
|
|
19
20
|
const __velarWsClearTimeout = globalThis.clearTimeout;
|
|
21
|
+
const __velarWsArrayIsArray = globalThis.Array.isArray;
|
|
22
|
+
const __velarWsObjectGetOwnPropertyDescriptor = globalThis.Object.getOwnPropertyDescriptor;
|
|
23
|
+
const __velarWsObjectFreeze = globalThis.Object.freeze;
|
|
24
|
+
const __velarWsSet = globalThis.Set;
|
|
25
|
+
const __velarWsSetHas = __velarWsSet.prototype.has;
|
|
26
|
+
const __velarWsSetAdd = __velarWsSet.prototype.add;
|
|
27
|
+
const __velarWsURL = globalThis.URL;
|
|
28
|
+
const __velarWsURLOriginGet = __velarWsObjectGetOwnPropertyDescriptor(__velarWsURL.prototype, "origin").get;
|
|
20
29
|
const __velarServeGzip = __velarServePromisify(__velarServeGzipNode);
|
|
21
30
|
const __velarServeBrotli = __velarServePromisify(__velarServeBrotliNode);
|
|
22
31
|
const __velarWsAggregateByteLimit = 128 * 1024 * 1024;
|
|
@@ -33,7 +42,49 @@ let __velarWsActiveHttpSockets = 0;
|
|
|
33
42
|
const __velarServeNativeOperations = Object.freeze({readFile: __velarServeReadFile, createReadStream: __velarServeCreateReadStream, realpath: __velarServeRealpath, stat: __velarServeStat, extname: __velarServeExtname, isAbsolute: __velarServeIsAbsolute, relative: __velarServeRelative, resolve: __velarServeResolve, compress: (encoding, value) => encoding === "br" ? __velarServeBrotli(value) : __velarServeGzip(value)});
|
|
34
43
|
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; }
|
|
35
44
|
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; }
|
|
36
|
-
function
|
|
45
|
+
function __velarWsOption(options, name) { const descriptor = __velarWsObjectGetOwnPropertyDescriptor(options, name); if (descriptor === undefined) return undefined; if (!("value" in descriptor)) throw new TypeError("WebSocket option '" + name + "' must be a data value"); return descriptor.value; }
|
|
46
|
+
function __velarWsOptions(options = {}) { if (options === null || typeof options !== "object" || __velarWsArrayIsArray(options)) throw new TypeError("WebSocket options must be a record"); return { maxMessageBytes: __velarWsInteger(__velarWsOption(options, "maxMessageBytes"), 16 * 1024 * 1024, 1, 64 * 1024 * 1024, "maxMessageBytes"), maxQueuedMessages: __velarWsInteger(__velarWsOption(options, "maxQueuedMessages"), 256, 1, 10000, "maxQueuedMessages"), maxQueuedBytes: __velarWsInteger(__velarWsOption(options, "maxQueuedBytes"), 16 * 1024 * 1024, 1, 64 * 1024 * 1024, "maxQueuedBytes"), maxPendingSendBytes: __velarWsInteger(__velarWsOption(options, "maxPendingSendBytes"), 16 * 1024 * 1024, 1, 64 * 1024 * 1024, "maxPendingSendBytes") }; }
|
|
47
|
+
function __velarWsOrigins(value) {
|
|
48
|
+
if (value === undefined || value === null) value = [];
|
|
49
|
+
if (!__velarWsArrayIsArray(value)) throw new TypeError("WebSocket origins must be a List<string>");
|
|
50
|
+
if (value.length > 256) throw new RangeError("WebSocket origins cannot contain more than 256 entries");
|
|
51
|
+
const exact = new __velarWsSet(); let exactCount = 0; let wildcard = false;
|
|
52
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
53
|
+
const descriptor = __velarWsObjectGetOwnPropertyDescriptor(value, index);
|
|
54
|
+
if (!descriptor?.enumerable || !("value" in descriptor) || typeof descriptor.value !== "string") throw new TypeError("WebSocket origins must contain enumerable text data values");
|
|
55
|
+
const origin = descriptor.value;
|
|
56
|
+
if (origin === "*") { if (wildcard || exactCount !== 0 || value.length !== 1) throw new TypeError("WebSocket origin '*' must be the only whitelist entry"); wildcard = true; continue; }
|
|
57
|
+
if (origin.length === 0 || origin.length > 2048 || !/^https?:\/\//iu.test(origin)) throw new TypeError("WebSocket origins must be exact HTTP or HTTPS origins");
|
|
58
|
+
let normalized;
|
|
59
|
+
try { normalized = __velarWsReflectApply(__velarWsURLOriginGet, __velarWsReflectConstruct(__velarWsURL, [origin]), []); }
|
|
60
|
+
catch { throw new TypeError("WebSocket origins must be exact HTTP or HTTPS origins"); }
|
|
61
|
+
if (normalized !== origin) throw new TypeError("WebSocket origins must not contain paths, credentials, queries, fragments, or non-canonical text");
|
|
62
|
+
if (__velarWsReflectApply(__velarWsSetHas, exact, [origin])) throw new TypeError("WebSocket origins cannot contain duplicates");
|
|
63
|
+
__velarWsReflectApply(__velarWsSetAdd, exact, [origin]); exactCount += 1;
|
|
64
|
+
}
|
|
65
|
+
return __velarWsObjectFreeze({exact, wildcard});
|
|
66
|
+
}
|
|
67
|
+
// The Origin an accepted connection was upgraded from, canonicalized through the
|
|
68
|
+
// same URL origin getter the whitelist is canonicalized with, or null when the
|
|
69
|
+
// client sent none, sent the opaque null a sandboxed document sends, or sent
|
|
70
|
+
// something that is not exactly one HTTP or HTTPS origin. The scheme test is
|
|
71
|
+
// case-insensitive because a scheme is case-insensitive and the origin getter
|
|
72
|
+
// canonicalizes it anyway; a canonical origin that still contains a comma came
|
|
73
|
+
// from a header carrying more than one, which is not an origin at all. This is
|
|
74
|
+
// the only upgrade header Vel code can read: nothing else about the request
|
|
75
|
+
// crosses into the connection.
|
|
76
|
+
function __velarWsRequestOrigin(request) {
|
|
77
|
+
const headers = request === null || typeof request !== "object" ? undefined : request.headers;
|
|
78
|
+
if (headers === null || typeof headers !== "object") return null;
|
|
79
|
+
const descriptor = __velarWsObjectGetOwnPropertyDescriptor(headers, "origin");
|
|
80
|
+
if (descriptor === undefined || !("value" in descriptor)) return null;
|
|
81
|
+
const header = descriptor.value;
|
|
82
|
+
if (typeof header !== "string" || header.length === 0 || header.length > 2048 || !/^https?:\/\//iu.test(header)) return null;
|
|
83
|
+
let origin;
|
|
84
|
+
try { origin = __velarWsReflectApply(__velarWsURLOriginGet, __velarWsReflectConstruct(__velarWsURL, [header]), []); }
|
|
85
|
+
catch { return null; }
|
|
86
|
+
return typeof origin !== "string" || origin.length === 0 || /,/u.test(origin) ? null : origin;
|
|
87
|
+
}
|
|
37
88
|
function __velarWsBytes(value) { if (value instanceof Uint8Array) { const output = new Uint8Array(value.byteLength); output.set(value); return output; } return null; }
|
|
38
89
|
function __velarWsMessageBytes(value) { if (typeof value === "string") return Buffer.byteLength(value, "utf8"); if (value instanceof Uint8Array) return value.byteLength; throw new TypeError("WebSocket.send requires text or Bytes"); }
|
|
39
90
|
function __velarWsReserve(size) { if (!Number.isSafeInteger(size) || size < 0 || __velarWsAggregateBytes + size > __velarWsAggregateByteLimit) return false; __velarWsAggregateBytes += size; return true; }
|
|
@@ -64,9 +115,9 @@ function __velarWsReceive(state, socket, data, binary) {
|
|
|
64
115
|
try { if (source) { message = new Uint8Array(size); message.set(source); } state.queue.push(message); state.queuedBytes += size; }
|
|
65
116
|
catch (error) { __velarWsReleaseQueue(size); throw error; }
|
|
66
117
|
}
|
|
67
|
-
function __velarWsWrap(socket, options) {
|
|
118
|
+
function __velarWsWrap(socket, options, origin = null) {
|
|
68
119
|
__velarWsActiveConnections += 1;
|
|
69
|
-
const value = Object.create(__velarWsConnectionPrototype); const state = { socket, options, queue: [], queuedBytes: 0, queueReleased: false, waiter: null, pendingSendBytes: 0, pendingSends: new Set(), finished: false, active: true, closePromise: null }; __velarWebSocketConnections.set(value, state);
|
|
120
|
+
const value = Object.create(__velarWsConnectionPrototype); value.origin = origin; const state = { socket, options, queue: [], queuedBytes: 0, queueReleased: false, waiter: null, pendingSendBytes: 0, pendingSends: new Set(), finished: false, active: true, closePromise: null }; __velarWebSocketConnections.set(value, state);
|
|
70
121
|
__velarWsReflectApply(__velarWsFinalizerRegister, __velarWsFinalizer, [value, state]);
|
|
71
122
|
socket.binaryType = "arraybuffer";
|
|
72
123
|
socket.on("message", (data, binary) => { try { __velarWsReceive(state, socket, data, binary); } catch { __velarWsRejectReceive(state, socket, 1011, "Message handling failed"); } });
|
|
@@ -138,27 +189,32 @@ const __velarWsServerPrototype = Object.freeze({
|
|
|
138
189
|
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; } });
|
|
139
190
|
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; } });
|
|
140
191
|
export const WebSocketConnection = __velarWsConnectionType; export const WebSocketServer = __velarWsServerType;
|
|
141
|
-
export function connect(url, options = {}) { if (typeof url !== "string" || !/^wss?:\/\//u.test(url)) return __velarWsPromise.reject(new TypeError("WebSocket URL must start with ws:// or wss://")); const limits = __velarWsOptions(options); const timeout = __velarWsDuration(options
|
|
192
|
+
export function connect(url, options = {}) { if (typeof url !== "string" || !/^wss?:\/\//u.test(url)) return __velarWsPromise.reject(new TypeError("WebSocket URL must start with ws:// or wss://")); const limits = __velarWsOptions(options); const timeout = __velarWsDuration(__velarWsOption(options, "timeout"), 10000); return new __velarWsPromise((resolve, reject) => { const socket = new __VelarWebSocket(url, { maxPayload: limits.maxMessageBytes }); let settled = false; const finish = (action) => { if (settled) return false; settled = true; __velarWsClearTimeout(timer); socket.off("open", opened); socket.off("error", failed); action(); return true; }; const absorbTerminalError = () => socket.once("error", () => {}); const opened = () => { if (__velarWsActiveConnections >= __velarWsActiveConnectionLimit) { if (finish(() => reject(new WebSocketBackpressureError("WebSocket connection limit exceeded")))) { absorbTerminalError(); socket.close(1013, "Client connection limit reached"); } return; } finish(() => resolve(__velarWsWrap(socket, limits))); }; const failed = error => finish(() => reject(new WebSocketProtocolError(error instanceof Error ? error.message : "WebSocket connection failed"))); const timer = __velarWsSetTimeout(() => { if (finish(() => reject(new WebSocketTimeoutError("WebSocket connection timed out")))) { absorbTerminalError(); socket.terminate(); } }, timeout); socket.once("open", opened); socket.once("error", failed); }); }
|
|
142
193
|
export async function listen(options) {
|
|
143
|
-
if (options === null || typeof options !== "object" ||
|
|
194
|
+
if (options === null || typeof options !== "object" || __velarWsArrayIsArray(options)) throw new TypeError("listen requires an options record");
|
|
144
195
|
const limits = __velarWsOptions(options);
|
|
145
|
-
const port = __velarWsInteger(options
|
|
146
|
-
const maxConnections = __velarWsInteger(options
|
|
147
|
-
const maxPendingConnections = __velarWsInteger(options
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
const
|
|
196
|
+
const port = __velarWsInteger(__velarWsOption(options, "port"), 0, 0, 65535, "port");
|
|
197
|
+
const maxConnections = __velarWsInteger(__velarWsOption(options, "maxConnections"), 1024, 1, 4096, "maxConnections");
|
|
198
|
+
const maxPendingConnections = __velarWsInteger(__velarWsOption(options, "maxPendingConnections"), 128, 1, 4096, "maxPendingConnections");
|
|
199
|
+
const maxBodyBytes = __velarWsInteger(__velarWsOption(options, "maxBodyBytes"), 16 * 1024 * 1024, 1, 16 * 1024 * 1024, "maxBodyBytes");
|
|
200
|
+
const host = __velarWsOption(options, "host");
|
|
201
|
+
const path = __velarWsOption(options, "path");
|
|
202
|
+
const http = __velarWsOption(options, "http");
|
|
203
|
+
const origins = __velarWsOrigins(__velarWsOption(options, "origins"));
|
|
204
|
+
if (host !== undefined && typeof host !== "string") throw new TypeError("WebSocket host must be text");
|
|
205
|
+
if (path !== undefined && (typeof path !== "string" || !path.startsWith("/"))) throw new TypeError("WebSocket path must start with '/'");
|
|
206
|
+
const application = __velarServeApp.is(http) ? await __velarServeApp.__velarCompilerBridge.nativeApp(http, maxBodyBytes) : null;
|
|
207
|
+
const handler = application ? application.handle : http;
|
|
152
208
|
if (handler !== undefined && typeof handler !== "function") { if (application) await application.close(); throw new TypeError("WebSocket http must be a ServeApp or velar/serve handler"); }
|
|
153
209
|
try {
|
|
154
210
|
return await new __velarWsPromise((resolve, reject) => {
|
|
155
211
|
const httpServer = __velarCreateHttpServer({maxHeaderSize: 64 * 1024, headersTimeout: 10000, requestTimeout: 60000, keepAliveTimeout: 5000, connectionsCheckingInterval: 1000}, (request, response) => {
|
|
156
|
-
if (handler) void __velarServeRuntime.__velarHandleNative(handler, request, response, __velarServeNativeOperations);
|
|
212
|
+
if (handler) void __velarServeRuntime.__velarHandleNative(handler, request, response, __velarServeNativeOperations, maxBodyBytes);
|
|
157
213
|
else { response.statusCode = 404; response.setHeader("content-type", "text/plain; charset=utf-8"); response.end(request.method === "HEAD" ? undefined : "Not found"); }
|
|
158
214
|
});
|
|
159
215
|
httpServer.maxConnections = 2048;
|
|
160
216
|
httpServer.maxRequestsPerSocket = 1000;
|
|
161
|
-
const server = new __VelarWebSocketServer({server: httpServer, path
|
|
217
|
+
const server = new __VelarWebSocketServer({server: httpServer, path, maxPayload: limits.maxMessageBytes, perMessageDeflate: false, clientTracking: true, verifyClient(info, complete) { const origin = info.origin; const allowed = origin === undefined || origins.wildcard || __velarWsReflectApply(__velarWsSetHas, origins.exact, [origin]); if (allowed) complete(true); else complete(false, 403, "Origin not allowed"); }});
|
|
162
218
|
const value = Object.create(__velarWsServerPrototype);
|
|
163
219
|
const state = {server, httpServer, application, queue: [], pendingConnections: new Set(), waiter: null, stopped: false, stopPromise: null, connections: new Set(), sockets: new Set()};
|
|
164
220
|
__velarWebSocketServers.set(value, state);
|
|
@@ -167,10 +223,10 @@ export async function listen(options) {
|
|
|
167
223
|
state.sockets.add(socket); __velarWsActiveHttpSockets += 1; socket.setNoDelay(true); socket.setKeepAlive(true, 5000);
|
|
168
224
|
socket.once("close", () => { if (!state.sockets.delete(socket)) return; __velarWsActiveHttpSockets -= 1; if (__velarWsActiveHttpSockets < 0) __velarWsActiveHttpSockets = 0; });
|
|
169
225
|
});
|
|
170
|
-
server.on("connection", socket => {
|
|
226
|
+
server.on("connection", (socket, request) => {
|
|
171
227
|
const willQueue = !state.waiter;
|
|
172
228
|
if (state.stopped || state.connections.size >= maxConnections || __velarWsActiveConnections >= __velarWsActiveConnectionLimit || (willQueue && (state.pendingConnections.size >= maxPendingConnections || __velarWsPendingConnections >= __velarWsPendingConnectionLimit))) { socket.close(1013, "Server busy"); return; }
|
|
173
|
-
const connection = __velarWsWrap(socket, limits); state.connections.add(connection);
|
|
229
|
+
const connection = __velarWsWrap(socket, limits, __velarWsRequestOrigin(request)); state.connections.add(connection);
|
|
174
230
|
socket.once("close", () => { state.connections.delete(connection); if (state.pendingConnections.delete(connection)) { __velarWsPendingConnections -= 1; if (__velarWsPendingConnections < 0) __velarWsPendingConnections = 0; __velarWsCompactPendingConnections(state); } });
|
|
175
231
|
if (state.waiter) { const waiter = state.waiter; state.waiter = null; waiter.resolve(connection); }
|
|
176
232
|
else { state.queue.push(connection); state.pendingConnections.add(connection); __velarWsPendingConnections += 1; }
|
|
@@ -185,7 +241,7 @@ export async function listen(options) {
|
|
|
185
241
|
};
|
|
186
242
|
server.on("error", fail);
|
|
187
243
|
httpServer.on("error", fail);
|
|
188
|
-
httpServer.listen({port, host:
|
|
244
|
+
httpServer.listen({port, host: host ?? "127.0.0.1"}, () => {
|
|
189
245
|
if (settled) return;
|
|
190
246
|
const address = httpServer.address();
|
|
191
247
|
if (!address || typeof address === "string") { fail(new Error("WebSocket server could not determine its bound port")); return; }
|
|
@@ -1 +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
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8PrD,CAAC,SAAS,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velarscript/node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "The official VelarScript Node.js module contract and runtime.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@velarscript/compiler": "0.
|
|
36
|
+
"@velarscript/compiler": "0.13.0",
|
|
37
37
|
"ws": "^8.21.1"
|
|
38
38
|
},
|
|
39
39
|
"exports": {
|