@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
package/dist/http-runtime.js
CHANGED
|
@@ -144,6 +144,12 @@ function urlOf(value) {
|
|
|
144
144
|
if (urlUsername(url) || urlPassword(url)) throw new NativeTypeError("HTTP URL credentials are not allowed; use an Authorization header");
|
|
145
145
|
return urlHref(url);
|
|
146
146
|
}
|
|
147
|
+
// The transport owns message framing and routing, so an application header map
|
|
148
|
+
// may never restate them: a caller-supplied content-length, transfer-encoding
|
|
149
|
+
// or host lands on the wire beside the host's own framing and is a
|
|
150
|
+
// request-smuggling primitive. Cookie and proxy credentials are ordinary
|
|
151
|
+
// application headers here; they are forbidden only as secretHeader names.
|
|
152
|
+
const transportOwnedHttpHeaders = setOf(["connection", "content-length", "expect", "host", "keep-alive", "proxy-connection", "te", "trailer", "transfer-encoding", "upgrade"]);
|
|
147
153
|
function headersOf(value) {
|
|
148
154
|
if (value == null) { requireHttpHost(); return new NativeMap(); }
|
|
149
155
|
let size;
|
|
@@ -156,6 +162,9 @@ function headersOf(value) {
|
|
|
156
162
|
if (typeof name !== "string" || typeof item !== "string" || !patternMatches(headerNamePattern, name) || patternMatches(lineBreakPattern, item)) {
|
|
157
163
|
throw new NativeTypeError("HTTP headers must use valid string names and single-line values");
|
|
158
164
|
}
|
|
165
|
+
if (call(nativeSetHas, transportOwnedHttpHeaders, [stringLower(name)])) {
|
|
166
|
+
throw new NativeTypeError("HTTP header '" + name + "' is transport-controlled");
|
|
167
|
+
}
|
|
159
168
|
units += name.length + item.length;
|
|
160
169
|
if (units > 65536) throw new NativeRangeError("HTTP headers cannot exceed 64 KiB");
|
|
161
170
|
nativeReflectApply(nativeMapSet, output, [name, item]);
|
|
@@ -390,7 +399,7 @@ export class HttpTransportError extends NativeError {
|
|
|
390
399
|
this.phase = phase;
|
|
391
400
|
}
|
|
392
401
|
}
|
|
393
|
-
export class
|
|
402
|
+
export class HttpResponseError extends NativeError {
|
|
394
403
|
constructor(message, status, url, body = null) {
|
|
395
404
|
if (typeof message !== "string") throw new NativeTypeError("HTTP error message must be text");
|
|
396
405
|
if (message.length > 65536) throw new NativeRangeError("HTTP error messages cannot exceed 64 KiB");
|
|
@@ -398,7 +407,7 @@ export class HttpError extends NativeError {
|
|
|
398
407
|
if (typeof url !== "string") throw new NativeTypeError("HTTP error URL must be text");
|
|
399
408
|
if (url.length > 2 * 1024 * 1024) throw new NativeRangeError("HTTP error URLs cannot exceed 2 MiB");
|
|
400
409
|
super(message);
|
|
401
|
-
this.name = "
|
|
410
|
+
this.name = "HttpResponseError";
|
|
402
411
|
this.status = status;
|
|
403
412
|
this.url = url;
|
|
404
413
|
this.body = body;
|
|
@@ -409,7 +418,6 @@ class HttpResponse {
|
|
|
409
418
|
constructor(response, request) {
|
|
410
419
|
this.request = request;
|
|
411
420
|
this.body = response.body;
|
|
412
|
-
this.ok = response.ok;
|
|
413
421
|
this.status = response.status;
|
|
414
422
|
this.statusText = response.statusText;
|
|
415
423
|
this.url = response.url;
|
|
@@ -519,7 +527,7 @@ class HttpResponse {
|
|
|
519
527
|
const text = await this.text();
|
|
520
528
|
return parseJsonText(text);
|
|
521
529
|
}
|
|
522
|
-
async parse(Type) { Type = runtimeHttpType(Type); return Type
|
|
530
|
+
async parse(Type) { Type = runtimeHttpType(Type); return __velarJsonParseTyped(Type, await this.text(), "HTTP JSON text"); }
|
|
523
531
|
}
|
|
524
532
|
|
|
525
533
|
class Request {
|
|
@@ -579,12 +587,16 @@ class Request {
|
|
|
579
587
|
const response = hostResponse(wire, this.handle);
|
|
580
588
|
if (this.abortError) throw this.abortError;
|
|
581
589
|
const wrapped = new HttpResponse(response, this);
|
|
582
|
-
|
|
590
|
+
// D90 R20: the 2xx question is asked here and nowhere else. The
|
|
591
|
+
// transport snapshot still carries ok; the response an author holds
|
|
592
|
+
// does not, because by the time it is returned the answer is always
|
|
593
|
+
// yes.
|
|
594
|
+
if (!response.ok) {
|
|
583
595
|
const text = await wrapped.text();
|
|
584
596
|
let body = text;
|
|
585
597
|
try { body = text ? parseJsonText(text) : null; } catch {}
|
|
586
598
|
const errorUrl = wrapped.url || this.url;
|
|
587
|
-
throw new
|
|
599
|
+
throw new HttpResponseError("HTTP " + wrapped.status + " for " + errorUrl, wrapped.status, errorUrl, body);
|
|
588
600
|
}
|
|
589
601
|
return wrapped;
|
|
590
602
|
} catch (error) {
|
|
@@ -599,7 +611,7 @@ class Request {
|
|
|
599
611
|
async bytes() { return (await this.response()).bytes(); }
|
|
600
612
|
async json() { return (await this.response()).json(); }
|
|
601
613
|
async streamText(consumer) { return (await this.response()).streamText(consumer); }
|
|
602
|
-
async parse(Type) { Type = runtimeHttpType(Type); return
|
|
614
|
+
async parse(Type) { Type = runtimeHttpType(Type); return (await this.response()).parse(Type); }
|
|
603
615
|
cancel() { this.abort("cancelled"); return null; }
|
|
604
616
|
}
|
|
605
617
|
|
package/dist/http-runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-runtime.js","sourceRoot":"","sources":["../src/http-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAE7H,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;EAI/C,yBAAyB;EACzB,2BAA2B;EAC3B,kBAAkB
|
|
1
|
+
{"version":3,"file":"http-runtime.js","sourceRoot":"","sources":["../src/http-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAE7H,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;EAI/C,yBAAyB;EACzB,2BAA2B;EAC3B,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAumBnB,CAAC,SAAS,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-host-runtime.d.ts","sourceRoot":"","sources":["../src/node-host-runtime.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,
|
|
1
|
+
{"version":3,"file":"node-host-runtime.d.ts","sourceRoot":"","sources":["../src/node-host-runtime.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,QA4UvB,CAAC"}
|
|
@@ -96,16 +96,22 @@ export class __velarNodeHostHttpTransportError extends __velarNodeHostError {
|
|
|
96
96
|
const __velarNodeHostPathErrorClasses = __velarNodeHostObjectCreate(null);
|
|
97
97
|
${VELAR_HOST_ERROR_PATH_NAMES.map((name) => `__velarNodeHostPathErrorClasses[${JSON.stringify(name)}] = __Velar${name};`).join("\n")}
|
|
98
98
|
|
|
99
|
+
// This function must never throw. Its caller runs inside the port handler,
|
|
100
|
+
// whose catch latches the permanent host failure, closes the port and
|
|
101
|
+
// terminates the worker, so one error record the proxy failed to enumerate
|
|
102
|
+
// would brick velar/fs, velar/http and velar/serve for the whole process. An
|
|
103
|
+
// unrecognised record rejects only its own request instead.
|
|
99
104
|
function __velarNodeHostErrorOf(value, operation) {
|
|
100
|
-
value = __velarNodeHostRecord(value, "Node host error");
|
|
105
|
+
try { value = __velarNodeHostRecord(value, "Node host error"); }
|
|
106
|
+
catch { return new __velarNodeHostTypeError("Node host returned an invalid error"); }
|
|
101
107
|
if (typeof value.message !== "string" || value.message.length === 0 || value.message.length > 65536) {
|
|
102
|
-
|
|
108
|
+
return new __velarNodeHostTypeError("Node host returned an invalid error");
|
|
103
109
|
}
|
|
104
110
|
if (value.name === "HttpTransportError") {
|
|
105
|
-
if ((operation !== "http.request" && operation !== "http.read")
|
|
111
|
+
if ((operation !== "http.request" && operation !== "http.read" && operation !== "http.readBytes")
|
|
106
112
|
|| (value.phase !== "request" && value.phase !== "response")
|
|
107
113
|
|| (operation === "http.request" ? value.phase !== "request" : value.phase !== "response")) {
|
|
108
|
-
|
|
114
|
+
return new __velarNodeHostTypeError("Node host returned an invalid HTTP transport error");
|
|
109
115
|
}
|
|
110
116
|
return new __velarNodeHostHttpTransportError(value.message, value.phase);
|
|
111
117
|
}
|
|
@@ -115,7 +121,7 @@ function __velarNodeHostErrorOf(value, operation) {
|
|
|
115
121
|
const pathed = __velarNodeHostPathErrorClasses[value.name];
|
|
116
122
|
if (pathed) {
|
|
117
123
|
if (typeof value.path !== "string" || value.path.length > 65536) {
|
|
118
|
-
|
|
124
|
+
return new __velarNodeHostTypeError("Node host returned an invalid error path");
|
|
119
125
|
}
|
|
120
126
|
return new pathed(value.message, value.path.length > 0 ? value.path : null);
|
|
121
127
|
}
|
|
@@ -123,7 +129,7 @@ function __velarNodeHostErrorOf(value, operation) {
|
|
|
123
129
|
if (value.name === "RangeError") return new __velarNodeHostRangeError(value.message);
|
|
124
130
|
if (value.name === "TypeError") return new __velarNodeHostTypeError(value.message);
|
|
125
131
|
if (value.name === "Error") return new __velarNodeHostError(value.message);
|
|
126
|
-
|
|
132
|
+
return new __velarNodeHostTypeError("Node host returned an invalid error");
|
|
127
133
|
}
|
|
128
134
|
|
|
129
135
|
function __velarNodeHostUpdateReference() {
|
|
@@ -300,7 +306,8 @@ export function __velarNodeHostInvoke(operation, args) {
|
|
|
300
306
|
if (__velarNodeHostFailure) return new __velarNodeHostPromise((_resolve, reject) => reject(__velarNodeHostFailure));
|
|
301
307
|
const id = __velarNodeHostRequestId();
|
|
302
308
|
return new __velarNodeHostPromise((resolve, reject) => {
|
|
303
|
-
const handle = operation === "http.request" || operation === "http.read" || operation === "http.
|
|
309
|
+
const handle = operation === "http.request" || operation === "http.read" || operation === "http.readBytes"
|
|
310
|
+
|| operation === "http.cancel" || operation === "http.close"
|
|
304
311
|
|| operation === "fs.watchNext" || operation === "fs.watchClose"
|
|
305
312
|
? args[0]
|
|
306
313
|
: null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-host-runtime.js","sourceRoot":"","sources":["../src/node-host-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gCAAgC,EAAE,sBAAsB,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAExI,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC,GAAG,CAAA;WACtC,sBAAsB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,cAAc,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,gCAAgC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4FhJ,2BAA2B,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,mCAAmC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,cAAc,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC
|
|
1
|
+
{"version":3,"file":"node-host-runtime.js","sourceRoot":"","sources":["../src/node-host-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gCAAgC,EAAE,sBAAsB,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAExI,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC,GAAG,CAAA;WACtC,sBAAsB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,cAAc,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,gCAAgC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4FhJ,2BAA2B,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,mCAAmC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,cAAc,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+OnI,CAAC,SAAS,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-host-worker-runtime.d.ts","sourceRoot":"","sources":["../src/node-host-worker-runtime.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,6BAA6B,
|
|
1
|
+
{"version":3,"file":"node-host-worker-runtime.d.ts","sourceRoot":"","sources":["../src/node-host-worker-runtime.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,6BAA6B,QAiiD7B,CAAC"}
|
|
@@ -7,7 +7,7 @@ import { createReadStream, watch as watchNode } from "node:fs";
|
|
|
7
7
|
import { appendFile, copyFile, lstat, mkdir, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
8
8
|
import { createServer, request as createHttpRequest } from "node:http";
|
|
9
9
|
import { request as createHttpsRequest } from "node:https";
|
|
10
|
-
import { basename, dirname, extname, isAbsolute, relative, resolve } from "node:path";
|
|
10
|
+
import { basename, dirname, extname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
11
11
|
import { URL as NodeURL } from "node:url";
|
|
12
12
|
import { brotliCompress as brotliCompressNode, gzip as gzipNode } from "node:zlib";
|
|
13
13
|
import { promisify } from "node:util";
|
|
@@ -87,10 +87,18 @@ function advanceHandle(handle) {
|
|
|
87
87
|
return handle >= Number.MAX_SAFE_INTEGER ? 1 : handle + 1;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// Exhausting the aggregate budget is a temporary load condition, not a server
|
|
91
|
+
// fault: admission already answers it with 503 at rejectIncomingRequest, and a
|
|
92
|
+
// response that cannot be reserved now gets the same answer instead of the
|
|
93
|
+
// opaque 500 that every other late failure gets. The identity is a class rather
|
|
94
|
+
// than the message so the send path can tell it apart from a genuine fault.
|
|
95
|
+
class ServeBudgetError extends RangeError {
|
|
96
|
+
constructor() { super("Node serve aggregate byte budget is exhausted"); }
|
|
97
|
+
}
|
|
98
|
+
|
|
90
99
|
function reserveServeBytes(task, bytes) {
|
|
91
|
-
if (!Number.isSafeInteger(bytes) || bytes < 0
|
|
92
|
-
|
|
93
|
-
}
|
|
100
|
+
if (!Number.isSafeInteger(bytes) || bytes < 0) throw new RangeError("Node serve byte reservation must be a non-negative integer");
|
|
101
|
+
if (reservedServeBytes + bytes > maxServeAggregateBytes) throw new ServeBudgetError();
|
|
94
102
|
reservedServeBytes += bytes;
|
|
95
103
|
task.reservedBytes += bytes;
|
|
96
104
|
}
|
|
@@ -104,9 +112,8 @@ function releaseServeBytes(task, bytes = task.reservedBytes) {
|
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
function reserveTransientServeBytes(bytes) {
|
|
107
|
-
if (!Number.isSafeInteger(bytes) || bytes < 0
|
|
108
|
-
|
|
109
|
-
}
|
|
115
|
+
if (!Number.isSafeInteger(bytes) || bytes < 0) throw new RangeError("Node serve byte reservation must be a non-negative integer");
|
|
116
|
+
if (reservedServeBytes + bytes > maxServeAggregateBytes) throw new ServeBudgetError();
|
|
110
117
|
reservedServeBytes += bytes;
|
|
111
118
|
}
|
|
112
119
|
|
|
@@ -425,6 +432,15 @@ const forbiddenHttpSecretHeaders = new Set([
|
|
|
425
432
|
"connection", "content-length", "cookie", "cookie2", "host", "proxy-authorization",
|
|
426
433
|
"te", "trailer", "transfer-encoding", "upgrade",
|
|
427
434
|
]);
|
|
435
|
+
// Framing and routing belong to the transport, not to the caller. An
|
|
436
|
+
// application that could set these beside Node's own framing could put two
|
|
437
|
+
// disagreeing lengths, a second encoding, or a forged authority on the wire.
|
|
438
|
+
// Credential names stay legal here: they are the caller's to send, and remain
|
|
439
|
+
// forbidden only for the secret-header path above.
|
|
440
|
+
const transportOwnedHttpHeaders = new Set([
|
|
441
|
+
"connection", "content-length", "expect", "host", "keep-alive", "proxy-connection",
|
|
442
|
+
"te", "trailer", "transfer-encoding", "upgrade",
|
|
443
|
+
]);
|
|
428
444
|
|
|
429
445
|
function httpMethod(value) {
|
|
430
446
|
if (typeof value !== "string") throw new TypeError("HTTP method must be text");
|
|
@@ -458,6 +474,7 @@ function httpHeaderRecord(value) {
|
|
|
458
474
|
|| !httpHeaderNamePattern.test(pair[0]) || httpLineBreakPattern.test(pair[1])) {
|
|
459
475
|
throw new TypeError("HTTP headers must use valid string names and single-line values");
|
|
460
476
|
}
|
|
477
|
+
if (transportOwnedHttpHeaders.has(pair[0].toLowerCase())) throw new TypeError("HTTP header '" + pair[0] + "' is transport-controlled");
|
|
461
478
|
units += pair[0].length + pair[1].length;
|
|
462
479
|
if (units > 65536) throw new RangeError("HTTP headers cannot exceed 64 KiB");
|
|
463
480
|
headers[pair[0].toLowerCase()] = pair[1];
|
|
@@ -732,8 +749,12 @@ function requestPath(value) {
|
|
|
732
749
|
}
|
|
733
750
|
|
|
734
751
|
function inside(root, target) {
|
|
752
|
+
// relative() emits ".." only as a whole segment, so the escape test compares
|
|
753
|
+
// whole segments too: a prefix test also rejects an ordinary top-level file
|
|
754
|
+
// whose own name begins with two dots. The separator is the platform's,
|
|
755
|
+
// because relative() writes an escape with a backslash on Windows.
|
|
735
756
|
const path = relative(root, target);
|
|
736
|
-
return path === "" || !path.startsWith("..") && !isAbsolute(path);
|
|
757
|
+
return path === "" || path !== ".." && !path.startsWith(".." + sep) && !isAbsolute(path);
|
|
737
758
|
}
|
|
738
759
|
|
|
739
760
|
async function staticFile(rootValue, pathValue, fallbackValue) {
|
|
@@ -864,6 +885,15 @@ function responseHasNoBody(task, status) {
|
|
|
864
885
|
return task.request.method === "HEAD" || status >= 100 && status < 200 || status === 204 || status === 304;
|
|
865
886
|
}
|
|
866
887
|
|
|
888
|
+
async function shedServeResponse(task) {
|
|
889
|
+
task.response.statusCode = 503;
|
|
890
|
+
task.response.setHeader("Retry-After", "1");
|
|
891
|
+
task.response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
892
|
+
await endServeResponse(task, responseHasNoBody(task, 503) ? undefined : '{"error":"outbound_budget_exhausted"}');
|
|
893
|
+
completeRequest(task);
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
|
|
867
897
|
async function endServeResponse(task, value) {
|
|
868
898
|
await new Promise((resolveEnd, rejectEnd) => {
|
|
869
899
|
let settled = false;
|
|
@@ -893,27 +923,19 @@ function rawBodyOf(task, maximum) {
|
|
|
893
923
|
return {data: null, bytes: maximum, tooLarge: true};
|
|
894
924
|
}
|
|
895
925
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
if (offset + data.byteLength > declared) { task.request.resume(); releaseServeBytes(task, declared); return {data: null, bytes: maximum, tooLarge: true}; }
|
|
904
|
-
data.copy(output, offset);
|
|
905
|
-
offset += data.byteLength;
|
|
906
|
-
}
|
|
907
|
-
if (offset !== declared) throw new TypeError("Request body length does not match Content-Length");
|
|
908
|
-
return {data: output, bytes: declared, tooLarge: false};
|
|
909
|
-
} catch (error) { if (task.reservedBytes >= declared) releaseServeBytes(task, declared); throw error; }
|
|
910
|
-
}
|
|
926
|
+
// A declared Content-Length is a client claim, not a delivered body, so it
|
|
927
|
+
// buys no allocation and no budget reservation up front: a header-only
|
|
928
|
+
// socket that declares 16 MiB and sends nothing would otherwise spend the
|
|
929
|
+
// process-global aggregate budget for the whole request timeout. The
|
|
930
|
+
// declaration is only the effective ceiling; the bytes that actually arrive
|
|
931
|
+
// are charged as they arrive.
|
|
932
|
+
const limit = declared === null ? maximum : declared;
|
|
911
933
|
const chunks = [];
|
|
912
934
|
let total = 0;
|
|
913
935
|
try {
|
|
914
936
|
for await (const chunk of task.request) {
|
|
915
937
|
const data = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
916
|
-
if (total + data.byteLength >
|
|
938
|
+
if (total + data.byteLength > limit) {
|
|
917
939
|
task.request.resume();
|
|
918
940
|
releaseServeBytes(task, total);
|
|
919
941
|
return {data: null, bytes: maximum, tooLarge: true};
|
|
@@ -922,6 +944,7 @@ function rawBodyOf(task, maximum) {
|
|
|
922
944
|
total += data.byteLength;
|
|
923
945
|
chunks.push(data);
|
|
924
946
|
}
|
|
947
|
+
if (declared !== null && total !== declared) throw new TypeError("Request body length does not match Content-Length");
|
|
925
948
|
reserveTransientServeBytes(total);
|
|
926
949
|
try { return {data: Buffer.concat(chunks, total), bytes: total, tooLarge: false}; }
|
|
927
950
|
finally { releaseTransientServeBytes(total); }
|
|
@@ -1354,14 +1377,19 @@ async function dispatch(operation, args) {
|
|
|
1354
1377
|
if (compression !== null && compression !== "gzip" && compression !== "br") throw new TypeError("ServeResponse compression is invalid");
|
|
1355
1378
|
const suppressBody = responseHasNoBody(task, status);
|
|
1356
1379
|
let output = body;
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1380
|
+
try {
|
|
1381
|
+
if (!suppressBody && compression !== null) {
|
|
1382
|
+
const inputBytes = Buffer.byteLength(body, "utf8");
|
|
1383
|
+
reserveTransientServeBytes(inputBytes * 2);
|
|
1384
|
+
try { output = await (compression === "br" ? brotliCompress(body) : gzip(body)); }
|
|
1385
|
+
finally { releaseTransientServeBytes(inputBytes * 2); }
|
|
1386
|
+
if (output.byteLength > maxServeBodyBytes) throw new RangeError("Compressed ServeResponse exceeds 16 MiB");
|
|
1387
|
+
}
|
|
1388
|
+
if (!suppressBody) reserveServeBytes(task, typeof output === "string" ? Buffer.byteLength(output, "utf8") : output.byteLength);
|
|
1389
|
+
} catch (error) {
|
|
1390
|
+
if (!(error instanceof ServeBudgetError)) throw error;
|
|
1391
|
+
return await shedServeResponse(task);
|
|
1363
1392
|
}
|
|
1364
|
-
if (!suppressBody) reserveServeBytes(task, typeof output === "string" ? Buffer.byteLength(output, "utf8") : output.byteLength);
|
|
1365
1393
|
task.response.statusCode = status;
|
|
1366
1394
|
setHeaders(task.response, headers, cookies);
|
|
1367
1395
|
if (!task.response.hasHeader("Content-Type") && (task.request.method === "HEAD" || !suppressBody)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-host-worker-runtime.js","sourceRoot":"","sources":["../src/node-host-worker-runtime.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,iFAAiF;AACjF,yEAAyE;AACzE,MAAM,CAAC,MAAM,6BAA6B,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"node-host-worker-runtime.js","sourceRoot":"","sources":["../src/node-host-worker-runtime.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,iFAAiF;AACjF,yEAAyE;AACzE,MAAM,CAAC,MAAM,6BAA6B,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiiDtD,CAAC,SAAS,EAAE,CAAC"}
|
package/dist/project-config.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-config.d.ts","sourceRoot":"","sources":["../src/project-config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,
|
|
1
|
+
{"version":3,"file":"project-config.d.ts","sourceRoot":"","sources":["../src/project-config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,2FAA2F;IAC3F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;KAC9B,CAAC;CACH;AAED,eAAO,MAAM,qBAAqB;;;iBAGnB,OAAO,gBAAgB,MAAM,GAAG,eAAe;EAG5D,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Folds a route path's pre-split segments into its shape: every `{name:type}`
|
|
3
|
+
* capture collapses to `{}` and every literal segment stays itself, so two
|
|
4
|
+
* paths share a shape exactly when a request cannot tell them apart by
|
|
5
|
+
* position. The body uses only indexed access, `.length` and primitive string
|
|
6
|
+
* concatenation — no method lookups — because the serve runtime embeds this
|
|
7
|
+
* exact source inside its hardened primordial Realm, where prototypes are
|
|
8
|
+
* assumed hostile. Callers split on "/" themselves with whatever split they
|
|
9
|
+
* trust.
|
|
10
|
+
*/
|
|
11
|
+
export declare function routeShapeFromSegments(segments: readonly string[]): string;
|
|
12
|
+
/**
|
|
13
|
+
* The same definition as JavaScript source, for the serve runtime template.
|
|
14
|
+
* Deriving it from the compiled function keeps the rule written once: editing
|
|
15
|
+
* `routeShapeFromSegments` edits both referees.
|
|
16
|
+
*/
|
|
17
|
+
export declare const ROUTE_SHAPE_FROM_SEGMENTS_SOURCE: string;
|
|
18
|
+
/** The shape of a full route path, for callers outside the hardened Realm. */
|
|
19
|
+
export declare function routeShape(path: string): string;
|
|
20
|
+
//# sourceMappingURL=route-shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-shape.d.ts","sourceRoot":"","sources":["../src/route-shape.ts"],"names":[],"mappings":"AAOA;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAQ1E;AAED;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,EAAE,MAA0C,CAAC;AAE1F,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// D90 R19(c): the shape of a route — its method-independent collision key — is
|
|
2
|
+
// one concept, so it has exactly one definition. The static analyzer imports
|
|
3
|
+
// the function below; the velar/serve runtime interpolates its source into the
|
|
4
|
+
// emitted module. Two referees, one rule: a shape the compiler and the
|
|
5
|
+
// assembly check disagree about is the defect class this file exists to
|
|
6
|
+
// prevent.
|
|
7
|
+
/**
|
|
8
|
+
* Folds a route path's pre-split segments into its shape: every `{name:type}`
|
|
9
|
+
* capture collapses to `{}` and every literal segment stays itself, so two
|
|
10
|
+
* paths share a shape exactly when a request cannot tell them apart by
|
|
11
|
+
* position. The body uses only indexed access, `.length` and primitive string
|
|
12
|
+
* concatenation — no method lookups — because the serve runtime embeds this
|
|
13
|
+
* exact source inside its hardened primordial Realm, where prototypes are
|
|
14
|
+
* assumed hostile. Callers split on "/" themselves with whatever split they
|
|
15
|
+
* trust.
|
|
16
|
+
*/
|
|
17
|
+
export function routeShapeFromSegments(segments) {
|
|
18
|
+
let shape = "";
|
|
19
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
20
|
+
const segment = segments[index];
|
|
21
|
+
const capture = segment !== undefined && segment[0] === "{" && segment[segment.length - 1] === "}";
|
|
22
|
+
shape += (index === 0 ? "" : "/") + (capture ? "{}" : segment);
|
|
23
|
+
}
|
|
24
|
+
return shape;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The same definition as JavaScript source, for the serve runtime template.
|
|
28
|
+
* Deriving it from the compiled function keeps the rule written once: editing
|
|
29
|
+
* `routeShapeFromSegments` edits both referees.
|
|
30
|
+
*/
|
|
31
|
+
export const ROUTE_SHAPE_FROM_SEGMENTS_SOURCE = routeShapeFromSegments.toString();
|
|
32
|
+
/** The shape of a full route path, for callers outside the hardened Realm. */
|
|
33
|
+
export function routeShape(path) {
|
|
34
|
+
return routeShapeFromSegments(path.split("/"));
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=route-shape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-shape.js","sourceRoot":"","sources":["../src/route-shape.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AAC7E,+EAA+E;AAC/E,uEAAuE;AACvE,wEAAwE;AACxE,WAAW;AAEX;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAA2B;IAChE,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACxD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,OAAO,GAAG,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC;QACnG,KAAK,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAW,sBAAsB,CAAC,QAAQ,EAAE,CAAC;AAE1F,8EAA8E;AAC9E,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve-runtime.d.ts","sourceRoot":"","sources":["../src/serve-runtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"serve-runtime.d.ts","sourceRoot":"","sources":["../src/serve-runtime.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,wBAAwB,QAq3GxB,CAAC"}
|