@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/serve-runtime.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ROUTE_SHAPE_FROM_SEGMENTS_SOURCE } from "./route-shape.js";
|
|
1
2
|
// Application-facing velar/serve contract. HTTP sockets, request streams,
|
|
2
3
|
// backpressure and static-file effects live in the shared isolated Node host;
|
|
3
4
|
// this Realm owns only Velar values, handlers and strict JSON/type boundaries.
|
|
@@ -6,7 +7,7 @@ import { __velarNodeHostInvoke, __velarNodeHostOn } from "velar/node-host-v1";
|
|
|
6
7
|
import { normalizeError as __velarServeNormalizeError } from "velar/compiler-runtime-errors-v1";
|
|
7
8
|
import { __velarValidateDenseList as __velarServeValidateDenseList } from "velar/compiler-runtime-collection-lowering-v1";
|
|
8
9
|
import { Bytes as __velarServeBytesType } from "velar/binary";
|
|
9
|
-
import { writeBytes as __velarServeWriteBytes } from "velar/fs";
|
|
10
|
+
import { canonical as __velarServeFsCanonical, info as __velarServeFsInfo, writeBytes as __velarServeWriteBytes } from "velar/fs";
|
|
10
11
|
import { Cancellation as __velarServeCancellation } from "velar/task";
|
|
11
12
|
|
|
12
13
|
const __velarServeArray = globalThis.Array;
|
|
@@ -186,9 +187,8 @@ function __velarServeIsSafeInteger(value) {
|
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
function __velarServeReserveOutbound(bytes) {
|
|
189
|
-
if (!__velarServeIsSafeInteger(bytes) || bytes < 0
|
|
190
|
-
|
|
191
|
-
}
|
|
190
|
+
if (!__velarServeIsSafeInteger(bytes) || bytes < 0) throw new __velarServeRangeError("ServeResponse outbound byte reservation must be a non-negative integer");
|
|
191
|
+
if (__velarServeOutboundBytes + bytes > __velarServeMaxOutboundBytes) throw new __velarServeOutboundBudgetError();
|
|
192
192
|
__velarServeOutboundBytes += bytes;
|
|
193
193
|
}
|
|
194
194
|
|
|
@@ -430,6 +430,19 @@ export class HttpError extends __velarServeError {
|
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
+
// Exhausting the aggregate outbound budget is a temporary load condition, not a
|
|
434
|
+
// server fault: 503 with retry-after is what a load balancer and a client both
|
|
435
|
+
// know how to act on, and 500 is what neither can. Most reserves happen while a
|
|
436
|
+
// response is already on its way out, past the HttpError catch in
|
|
437
|
+
// __velarServeHandleRequest, so both send paths recognize this one error by
|
|
438
|
+
// identity and answer it themselves. The shed answer reserves nothing: it is a
|
|
439
|
+
// fixed, tiny payload, and reserving for it would fail by construction.
|
|
440
|
+
class __velarServeOutboundBudgetError extends HttpError {
|
|
441
|
+
constructor() {
|
|
442
|
+
super(503, {error: "outbound_budget_exhausted"}, new __velarServeMap([["retry-after", "1"]]));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
433
446
|
function __velarServeIsFileResponse(value) {
|
|
434
447
|
if (!value || typeof value !== "object") return false;
|
|
435
448
|
const descriptor = __velarServeOwnDescriptor(value, __velarServeFileMarker);
|
|
@@ -600,6 +613,74 @@ function __velarServeIsUpload(value) {
|
|
|
600
613
|
|
|
601
614
|
export const Upload = __velarServeTypeObject(__velarServeIsUpload, "Upload values are created from multipart route inputs");
|
|
602
615
|
|
|
616
|
+
// Upload.save takes its containment root as a required argument and refuses any
|
|
617
|
+
// target that resolves outside it. The host operations bag __velarServeNativeFile
|
|
618
|
+
// uses for static files is not reachable from this Realm, so containment is
|
|
619
|
+
// composed from velar/fs instead of a new host operation: fail-closed textual
|
|
620
|
+
// rules on the caller's path, then a canonical check of the directory that will
|
|
621
|
+
// hold the file so a symbolic link cannot lead out of the root. That is the
|
|
622
|
+
// smaller of the two shapes and adds no trust boundary. This is complementary to
|
|
623
|
+
// the basename reduction in __velarServeUploadBasename, which bounds only what a
|
|
624
|
+
// client can put into Upload.filename.
|
|
625
|
+
function __velarServeUploadSegments(path) {
|
|
626
|
+
if (typeof path !== "string" || path.length === 0) throw new __velarServeTypeError("Upload.save path must be a non-empty path relative to its root");
|
|
627
|
+
if (path.length > __velarServeMaxPathCodeUnits || __velarServeCall(__velarServeStringIncludes, path, ["\0"])) {
|
|
628
|
+
throw new __velarServeRangeError("Upload.save path is outside the supported bounds");
|
|
629
|
+
}
|
|
630
|
+
// The root's separator is a host detail this Realm cannot consult, so both
|
|
631
|
+
// separators are refused, exactly as __velarServeNativeEscapes refuses both.
|
|
632
|
+
// Only '..' and an absolute path genuinely leave the root; a backslash, an
|
|
633
|
+
// empty segment and a '.' are refused because this Realm will not normalize a
|
|
634
|
+
// path on the caller's behalf, so each refusal says which of the two it is
|
|
635
|
+
// rather than naming an escape that did not happen.
|
|
636
|
+
if (__velarServeCall(__velarServeStringIncludes, path, ["\\"])) throw new __velarServeError("Upload.save path cannot contain a backslash: it is a path separator on some hosts");
|
|
637
|
+
if (__velarServeCall(__velarServeStringStartsWith, path, ["/"])) throw new __velarServeError("Upload.save path escapes its root: it is absolute");
|
|
638
|
+
const segments = __velarServeCall(__velarServeStringSplit, path, ["/"]);
|
|
639
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
640
|
+
const segment = segments[index];
|
|
641
|
+
if (segment === "..") throw new __velarServeError("Upload.save path escapes its root: it has a '..' segment");
|
|
642
|
+
if (segment === "") throw new __velarServeError("Upload.save path must be normalized: it has an empty segment");
|
|
643
|
+
if (segment === ".") throw new __velarServeError("Upload.save path must be normalized: it has a '.' segment");
|
|
644
|
+
}
|
|
645
|
+
return segments;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function __velarServeUploadContains(root, target) {
|
|
649
|
+
if (target === root) return true;
|
|
650
|
+
if (target.length <= root.length || __velarServeCall(__velarServeStringSlice, target, [0, root.length]) !== root) return false;
|
|
651
|
+
const boundary = __velarServeCall(__velarServeStringSlice, target, [root.length, root.length + 1]);
|
|
652
|
+
return boundary === "/" || boundary === "\\";
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async function __velarServeUploadTarget(path, root) {
|
|
656
|
+
const segments = __velarServeUploadSegments(path);
|
|
657
|
+
if (typeof root !== "string" || root.length === 0) throw new __velarServeTypeError("Upload.save root must be a non-empty directory path");
|
|
658
|
+
// Resolution failures arrive from the host as an errno naming an absolute path
|
|
659
|
+
// the caller never wrote, so both are answered in the caller's own terms: the
|
|
660
|
+
// root it named, or the relative directory it asked for.
|
|
661
|
+
let base;
|
|
662
|
+
try { base = await __velarServeFsCanonical(root); }
|
|
663
|
+
catch { throw new __velarServeError("Upload.save root does not resolve to an existing directory"); }
|
|
664
|
+
let directory = base;
|
|
665
|
+
let relative = "";
|
|
666
|
+
for (let index = 0; index + 1 < segments.length; index += 1) {
|
|
667
|
+
directory += "/" + segments[index];
|
|
668
|
+
relative += relative === "" ? segments[index] : "/" + segments[index];
|
|
669
|
+
}
|
|
670
|
+
if (directory !== base) {
|
|
671
|
+
try { directory = await __velarServeFsCanonical(directory); }
|
|
672
|
+
catch { throw new __velarServeError("Upload.save path names a directory that does not exist under the root: " + relative); }
|
|
673
|
+
if (!__velarServeUploadContains(base, directory)) throw new __velarServeError("Upload.save path escapes its root through a symbolic link");
|
|
674
|
+
}
|
|
675
|
+
const target = directory + "/" + segments[segments.length - 1];
|
|
676
|
+
// A write follows a symbolic link at the target itself, so the last segment is
|
|
677
|
+
// canonicalized by kind rather than by path: refusing the link is fail-closed
|
|
678
|
+
// and needs no second containment test.
|
|
679
|
+
const existing = await __velarServeFsInfo(target);
|
|
680
|
+
if (existing !== null && existing.kind === "symlink") throw new __velarServeError("Upload.save refuses to write through a symbolic link");
|
|
681
|
+
return target;
|
|
682
|
+
}
|
|
683
|
+
|
|
603
684
|
function __velarServeUploadValue(name, filename, contentType, data, states) {
|
|
604
685
|
if (!__velarServeBytesType.is(data)) throw new __velarServeTypeError("Upload data must be Bytes");
|
|
605
686
|
const state = {data};
|
|
@@ -620,7 +701,11 @@ function __velarServeUploadValue(name, filename, contentType, data, states) {
|
|
|
620
701
|
catch { throw new __velarServeTypeError("Upload is not valid UTF-8 text"); }
|
|
621
702
|
},
|
|
622
703
|
bytes: async () => __velarServeBytesType.parse(current()),
|
|
623
|
-
save: async
|
|
704
|
+
save: async (path, root) => {
|
|
705
|
+
const value = current();
|
|
706
|
+
await __velarServeWriteBytes(await __velarServeUploadTarget(path, root), value);
|
|
707
|
+
return null;
|
|
708
|
+
},
|
|
624
709
|
}]);
|
|
625
710
|
}
|
|
626
711
|
|
|
@@ -929,12 +1014,13 @@ function __velarServeRoutePath(path, name = "Route path") {
|
|
|
929
1014
|
return path;
|
|
930
1015
|
}
|
|
931
1016
|
|
|
1017
|
+
// D90 R19(c): the shape rule is written once, in route-shape.ts, and this
|
|
1018
|
+
// module interpolates that one definition. The shared core touches only
|
|
1019
|
+
// indexed access and .length, so it stays sound inside this hardened Realm;
|
|
1020
|
+
// the split it never performs itself happens here with the captured split.
|
|
1021
|
+
const __velarServeRouteShapeFromSegments = ${ROUTE_SHAPE_FROM_SEGMENTS_SOURCE};
|
|
932
1022
|
function __velarServeRouteShape(path) {
|
|
933
|
-
|
|
934
|
-
for (let index = 1; index < segments.length; index += 1) {
|
|
935
|
-
if (__velarServeCall(__velarServeStringStartsWith, segments[index], ["{"]) && __velarServeCall(__velarServeStringEndsWith, segments[index], ["}"])) segments[index] = "{}";
|
|
936
|
-
}
|
|
937
|
-
return __velarServeCall(__velarServeArrayJoin, segments, ["/"]);
|
|
1023
|
+
return __velarServeRouteShapeFromSegments(__velarServeCall(__velarServeStringSplit, path, ["/"]));
|
|
938
1024
|
}
|
|
939
1025
|
|
|
940
1026
|
function __velarCreateServeRoute(method, path, parameters, handler, metadata = {}) {
|
|
@@ -1158,23 +1244,33 @@ function __velarCreateServeApp(name, items) {
|
|
|
1158
1244
|
const routes = [];
|
|
1159
1245
|
const lifecycles = [];
|
|
1160
1246
|
let notFound = null;
|
|
1247
|
+
// D90 R19(b): assembly is the moment the final table exists, so the final
|
|
1248
|
+
// table is judged here — a conflict names both routes and both origins,
|
|
1249
|
+
// because the statically invisible half of a collision is exactly the one
|
|
1250
|
+
// the author cannot see in his own file.
|
|
1161
1251
|
const shapes = new __velarServeMap();
|
|
1162
|
-
const
|
|
1252
|
+
const describeRoute = entry => "'" + entry.route.method + " " + entry.route.path + "'"
|
|
1253
|
+
+ (entry.source === null ? " declared by this server" : " composed in from '" + entry.source + "'");
|
|
1254
|
+
const append = (route, source) => {
|
|
1163
1255
|
if (routes.length >= __velarServeMaxRoutes) throw new __velarServeRangeError("ServeApp cannot contain more than 4096 routes after composition");
|
|
1164
1256
|
const key = route.method + " " + __velarServeRouteShape(route.path);
|
|
1165
|
-
|
|
1166
|
-
|
|
1257
|
+
const previous = __velarServeCall(__velarServeMapGet, shapes, [key]);
|
|
1258
|
+
if (previous !== undefined) {
|
|
1259
|
+
throw new __velarServeTypeError("ServeApp '" + name + "' contains conflicting routes: " + describeRoute({route, source})
|
|
1260
|
+
+ " and " + describeRoute(previous) + " both answer '" + key + "' — narrow or remove one");
|
|
1261
|
+
}
|
|
1262
|
+
__velarServeCall(__velarServeMapSet, shapes, [key, {route, source}]);
|
|
1167
1263
|
routes[routes.length] = route;
|
|
1168
1264
|
};
|
|
1169
1265
|
for (let index = 0; index < items.length; index += 1) {
|
|
1170
1266
|
const item = items[index];
|
|
1171
|
-
if (__velarServeIsRoute(item)) append(item);
|
|
1267
|
+
if (__velarServeIsRoute(item)) append(item, null);
|
|
1172
1268
|
else if (__velarServeIsNotFound(item)) {
|
|
1173
1269
|
if (notFound !== null) throw new __velarServeTypeError("ServeApp contains more than one @notFound fallback");
|
|
1174
1270
|
notFound = item;
|
|
1175
1271
|
}
|
|
1176
1272
|
else if (__velarServeIsApp(item)) {
|
|
1177
|
-
for (let route = 0; route < item.routes.length; route += 1) append(item.routes[route]);
|
|
1273
|
+
for (let route = 0; route < item.routes.length; route += 1) append(item.routes[route], item.name);
|
|
1178
1274
|
for (let hook = 0; hook < item.lifecycles.length; hook += 1) {
|
|
1179
1275
|
if (lifecycles.length >= __velarServeMaxLifecycles) throw new __velarServeRangeError("ServeApp cannot contain more than 4096 lifecycle pairs after composition");
|
|
1180
1276
|
lifecycles[lifecycles.length] = item.lifecycles[hook];
|
|
@@ -1336,6 +1432,7 @@ function __velarServeCors(origins = ["*"], methods = ["GET", "POST", "PUT", "PAT
|
|
|
1336
1432
|
methods = __velarServeStringList(methods, "middleware.cors methods");
|
|
1337
1433
|
headers = __velarServeStringList(headers, "middleware.cors headers");
|
|
1338
1434
|
if (typeof credentials !== "boolean" || !__velarServeIsSafeInteger(maxAge) || maxAge < 0 || maxAge > 86400) throw new __velarServeTypeError("middleware.cors options are invalid");
|
|
1435
|
+
if (credentials && __velarServeCall(__velarServeArrayIncludes, origins, ["*"])) throw new __velarServeTypeError("middleware.cors cannot combine credentials with the '*' origin wildcard");
|
|
1339
1436
|
return async (request, next) => {
|
|
1340
1437
|
const origin = __velarServeCall(__velarServeMapHas, request.headers, ["origin"]) ? __velarServeCall(__velarServeMapGet, request.headers, ["origin"]) : null;
|
|
1341
1438
|
const wildcard = __velarServeCall(__velarServeArrayIncludes, origins, ["*"]);
|
|
@@ -1466,18 +1563,26 @@ function __velarServeTimeout(milliseconds) {
|
|
|
1466
1563
|
if (__velarServeActiveTimeouts >= __velarServeMaxActiveTimeouts) {
|
|
1467
1564
|
return {status: 503, json: {error: "server_busy"}, headers: new __velarServeMap([["retry-after", "1"]])};
|
|
1468
1565
|
}
|
|
1469
|
-
__velarServeActiveTimeouts += 1;
|
|
1470
1566
|
let timer = null;
|
|
1471
|
-
|
|
1472
|
-
let pending;
|
|
1473
|
-
try { pending = next(); }
|
|
1474
|
-
catch (error) { __velarServeActiveTimeouts -= 1; throw error; }
|
|
1567
|
+
const pending = next();
|
|
1475
1568
|
const expired = new __velarServePromise(resolve => { timer = __velarServeCall(__velarServeSetTimeout, globalThis, [() => resolve(__velarServeMissing), milliseconds]); });
|
|
1476
1569
|
try {
|
|
1477
1570
|
const result = await __velarServeCall(__velarServePromiseRace, __velarServePromise, [__velarServeCall(__velarServeObjectFreeze, __velarServeObject, [[pending, expired]])]);
|
|
1478
1571
|
if (result !== __velarServeMissing) return result;
|
|
1479
|
-
detached = true;
|
|
1480
1572
|
__velarServeCancellation.__velarCancel(request.cancellation, "Request timed out");
|
|
1573
|
+
// The published bound counts unfinished timed-out continuations, and the
|
|
1574
|
+
// background reservation at __velarServeRunBackground subtracts exactly
|
|
1575
|
+
// this many slots from the process total. Admission alone cannot hold
|
|
1576
|
+
// that count: a burst admitted while nothing was detached can expire
|
|
1577
|
+
// together. When the detached budget is full the request has already been
|
|
1578
|
+
// cancelled, so wait for it to unwind here instead of detaching work the
|
|
1579
|
+
// process no longer accounts for.
|
|
1580
|
+
if (__velarServeActiveTimeouts >= __velarServeMaxActiveTimeouts) {
|
|
1581
|
+
try { await pending; }
|
|
1582
|
+
catch (error) { __velarServeReportFailure(error); }
|
|
1583
|
+
return {status: 504, json: {error: "request_timeout"}};
|
|
1584
|
+
}
|
|
1585
|
+
__velarServeActiveTimeouts += 1;
|
|
1481
1586
|
__velarServeActiveBackgroundTasks += 1;
|
|
1482
1587
|
const settlement = (async () => {
|
|
1483
1588
|
try { await pending; }
|
|
@@ -1500,7 +1605,6 @@ function __velarServeTimeout(milliseconds) {
|
|
|
1500
1605
|
};
|
|
1501
1606
|
} finally {
|
|
1502
1607
|
if (timer !== null) __velarServeCall(__velarServeClearTimeout, globalThis, [timer]);
|
|
1503
|
-
if (!detached) __velarServeActiveTimeouts -= 1;
|
|
1504
1608
|
}
|
|
1505
1609
|
};
|
|
1506
1610
|
}
|
|
@@ -1604,15 +1708,19 @@ function __velarServeDecodeScalar(raw, parameter) {
|
|
|
1604
1708
|
function __velarServeCookieValue(request, name) {
|
|
1605
1709
|
if (!__velarServeCall(__velarServeMapHas, request.headers, ["cookie"])) return __velarServeMissing;
|
|
1606
1710
|
const pieces = __velarServeCall(__velarServeStringSplit, __velarServeCall(__velarServeMapGet, request.headers, ["cookie"]), [";"]);
|
|
1711
|
+
let encoded = null;
|
|
1712
|
+
let matches = 0;
|
|
1607
1713
|
for (let index = 0; index < pieces.length; index += 1) {
|
|
1608
1714
|
const piece = __velarServeCall(__velarServeStringTrim, pieces[index], []);
|
|
1609
1715
|
const separator = __velarServeCall(__velarServeStringIndexOf, piece, ["="]);
|
|
1610
1716
|
if (separator < 0 || __velarServeCall(__velarServeStringSlice, piece, [0, separator]) !== name) continue;
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1717
|
+
matches += 1;
|
|
1718
|
+
if (matches > 1) throw new HttpError(400, {error: "duplicate_cookie", parameter: name});
|
|
1719
|
+
encoded = __velarServeCall(__velarServeStringSlice, piece, [separator + 1]);
|
|
1614
1720
|
}
|
|
1615
|
-
return __velarServeMissing;
|
|
1721
|
+
if (matches === 0) return __velarServeMissing;
|
|
1722
|
+
try { return __velarServeCall(__velarServeDecodeURIComponent, undefined, [encoded]); }
|
|
1723
|
+
catch { throw new HttpError(400, {error: "invalid_cookie", parameter: name}); }
|
|
1616
1724
|
}
|
|
1617
1725
|
|
|
1618
1726
|
function __velarServeNamedInputRaw(descriptor, parameterName, request) {
|
|
@@ -1813,6 +1921,17 @@ function __velarServeMultipartHeaders(text) {
|
|
|
1813
1921
|
return output;
|
|
1814
1922
|
}
|
|
1815
1923
|
|
|
1924
|
+
function __velarServeUploadBasename(filename) {
|
|
1925
|
+
// A client may send a full path, including a Windows path with backslashes.
|
|
1926
|
+
// An Upload name is one file name, never a path an application can compose
|
|
1927
|
+
// into a directory it did not intend to write.
|
|
1928
|
+
const slashed = __velarServeCall(__velarServeStringSplit, filename, ["/"]);
|
|
1929
|
+
const separated = __velarServeCall(__velarServeStringSplit, slashed[slashed.length - 1], ["\\"]);
|
|
1930
|
+
const base = separated[separated.length - 1];
|
|
1931
|
+
if (base === "" || base === "." || base === ".." || __velarServeCall(__velarServeStringIncludes, base, ["\0"])) throw new HttpError(400, {error: "invalid_multipart"});
|
|
1932
|
+
return base;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1816
1935
|
function __velarServeMultipart(data, boundary) {
|
|
1817
1936
|
const opening = __velarServeBytePattern("--" + boundary);
|
|
1818
1937
|
const separator = __velarServeBytePattern("\r\n\r\n");
|
|
@@ -1844,8 +1963,9 @@ function __velarServeMultipart(data, boundary) {
|
|
|
1844
1963
|
__velarServeAddFormField(fields, name, __velarServeDecodeBytes(part));
|
|
1845
1964
|
} else {
|
|
1846
1965
|
if (filename.length > 1024 || __velarServeCall(__velarServeMapHas, files, [name])) throw new HttpError(400, {error: "invalid_multipart"});
|
|
1966
|
+
const base = __velarServeUploadBasename(filename);
|
|
1847
1967
|
const contentType = typeof headers["content-type"] === "string" ? headers["content-type"] : "application/octet-stream";
|
|
1848
|
-
__velarServeCall(__velarServeMapSet, files, [name, __velarServeUploadValue(name,
|
|
1968
|
+
__velarServeCall(__velarServeMapSet, files, [name, __velarServeUploadValue(name, base, contentType, part, uploadStates)]);
|
|
1849
1969
|
}
|
|
1850
1970
|
parts += 1;
|
|
1851
1971
|
if (parts > 128) throw new HttpError(413, {error: "too_many_form_parts"});
|
|
@@ -2024,14 +2144,22 @@ function __velarServeJsonContentType(headers) {
|
|
|
2024
2144
|
|| __velarServeCall(__velarServeStringStartsWith, value, ["application/"]) && __velarServeCall(__velarServeStringEndsWith, value, ["+json"]);
|
|
2025
2145
|
}
|
|
2026
2146
|
|
|
2147
|
+
function __velarServeIsResponseAttempt(value) {
|
|
2148
|
+
if (!value || typeof value !== "object" || __velarServeIsArray(value)) return false;
|
|
2149
|
+
if (!__velarServeOwnDescriptor(value, "status")) return false;
|
|
2150
|
+
return !!(__velarServeOwnDescriptor(value, "json") || __velarServeOwnDescriptor(value, "text") || __velarServeOwnDescriptor(value, "stream"));
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2027
2153
|
function __velarServeAutomaticResponse(value) {
|
|
2028
2154
|
if (__velarServeIsFileResponse(value)) return value;
|
|
2155
|
+
if (__velarServeIsResponseAttempt(value)) return __velarServeResponse(value);
|
|
2029
2156
|
try { return __velarServeResponse(value); }
|
|
2030
2157
|
catch { return __velarServeResponse({status: 200, json: value}); }
|
|
2031
2158
|
}
|
|
2032
2159
|
|
|
2033
2160
|
function __velarServeNotFoundResponse(value) {
|
|
2034
2161
|
if (__velarServeIsFileResponse(value)) return value;
|
|
2162
|
+
if (__velarServeIsResponseAttempt(value)) return __velarServeResponse(value);
|
|
2035
2163
|
try { return __velarServeResponse(value); }
|
|
2036
2164
|
catch { return __velarServeResponse({status: 404, json: value}); }
|
|
2037
2165
|
}
|
|
@@ -2081,6 +2209,7 @@ async function __velarServeHandleAppResponse(app, request, maxBodyBytes, context
|
|
|
2081
2209
|
return await __velarServeApplyMiddleware(selected.route, request, invokeRoute);
|
|
2082
2210
|
} catch (error) {
|
|
2083
2211
|
if (error instanceof HttpError) return {status: error.status, json: error.body, headers: error.headers};
|
|
2212
|
+
if (error instanceof RequestBodyTooLargeError) return {status: 413, json: {error: "request_too_large"}};
|
|
2084
2213
|
throw error;
|
|
2085
2214
|
}
|
|
2086
2215
|
}
|
|
@@ -2612,6 +2741,16 @@ function __velarServeRequest(value) {
|
|
|
2612
2741
|
}])};
|
|
2613
2742
|
}
|
|
2614
2743
|
|
|
2744
|
+
// The budget error can also surface after a response has started, where the host
|
|
2745
|
+
// refuses a second terminal response; that attempt fails and the request falls
|
|
2746
|
+
// through to the opaque failure exactly as any other late error does.
|
|
2747
|
+
async function __velarServeShedOutbound(handle) {
|
|
2748
|
+
try {
|
|
2749
|
+
await __velarNodeHostInvoke("serve.respond", [handle, 503, [["retry-after", "1"]], "json", '{"error":"outbound_budget_exhausted"}', null, null, []]);
|
|
2750
|
+
return true;
|
|
2751
|
+
} catch { return false; }
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2615
2754
|
async function __velarServeWriteResponse(handle, value) {
|
|
2616
2755
|
let cleanup = null;
|
|
2617
2756
|
let backgroundTasks = null;
|
|
@@ -2681,7 +2820,8 @@ function __velarServeNativeHeaders(request) {
|
|
|
2681
2820
|
return output;
|
|
2682
2821
|
}
|
|
2683
2822
|
|
|
2684
|
-
function __velarServeNativeRequest(request) {
|
|
2823
|
+
function __velarServeNativeRequest(request, maximum = __velarServeMaxBodyBytes) {
|
|
2824
|
+
maximum = __velarServeBodyLimit(maximum);
|
|
2685
2825
|
const method = request.method ?? "GET";
|
|
2686
2826
|
if (typeof method !== "string" || !__velarServeCall(__velarServeRegExpTest, __velarServeMethodPattern, [method])) throw new __velarServeTypeError("Native HTTP method is invalid");
|
|
2687
2827
|
const target = request.url ?? "/";
|
|
@@ -2698,7 +2838,7 @@ function __velarServeNativeRequest(request) {
|
|
|
2698
2838
|
try {
|
|
2699
2839
|
for await (const chunk of request) {
|
|
2700
2840
|
const data = chunk instanceof __velarServeUint8Array ? chunk : __velarServeCall(__velarServeTextEncode, __velarServeUtf8Encoder, [__velarServeString(chunk)]);
|
|
2701
|
-
if (total + data.byteLength >
|
|
2841
|
+
if (total + data.byteLength > maximum) { request.resume(); throw new RequestBodyTooLargeError(maximum); }
|
|
2702
2842
|
__velarServeReserveOutbound(data.byteLength);
|
|
2703
2843
|
total += data.byteLength;
|
|
2704
2844
|
reservedBodyBytes += data.byteLength;
|
|
@@ -2721,20 +2861,21 @@ function __velarServeNativeRequest(request) {
|
|
|
2721
2861
|
})();
|
|
2722
2862
|
return await bodyPromise;
|
|
2723
2863
|
};
|
|
2724
|
-
const bytes = async (maxBytes =
|
|
2864
|
+
const bytes = async (maxBytes = maximum) => {
|
|
2725
2865
|
if (!__velarServeIsSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > __velarServeMaxBodyBytes) throw new __velarServeRangeError("Request body maxBytes must be an integer from 1 through 16777216");
|
|
2726
2866
|
const data = await rawBody();
|
|
2727
|
-
|
|
2867
|
+
const effective = maxBytes > maximum ? maximum : maxBytes;
|
|
2868
|
+
if (data.byteLength > effective) throw new RequestBodyTooLargeError(effective);
|
|
2728
2869
|
return data;
|
|
2729
2870
|
};
|
|
2730
|
-
const body = async (maxBytes =
|
|
2871
|
+
const body = async (maxBytes = maximum) => {
|
|
2731
2872
|
const data = await bytes(maxBytes);
|
|
2732
2873
|
try { return __velarServeCall(__velarServeTextDecode, __velarServeUtf8Decoder, [data]); }
|
|
2733
2874
|
catch { throw new __velarServeTypeError("Request body must be valid UTF-8 text"); }
|
|
2734
2875
|
};
|
|
2735
|
-
const json = async (maxBytes =
|
|
2876
|
+
const json = async (maxBytes = maximum) => __velarJsonParse(await body(maxBytes), "ServeRequest JSON text");
|
|
2736
2877
|
return {
|
|
2737
|
-
request: __velarServeCall(__velarServeObjectFreeze, __velarServeObject, [{method, path, query: query.values, queryAll: query.all, headers: __velarServeNativeHeaders(request), cancellation, text: body, bytes, json, parse: async (Type, maxBytes =
|
|
2878
|
+
request: __velarServeCall(__velarServeObjectFreeze, __velarServeObject, [{method, path, query: query.values, queryAll: query.all, headers: __velarServeNativeHeaders(request), cancellation, text: body, bytes, json, parse: async (Type, maxBytes = maximum) => { Type = __velarRequireRuntimeType(Type, "ServeRequest.parse"); return Type.parse(await json(maxBytes)); }}]),
|
|
2738
2879
|
cancellation,
|
|
2739
2880
|
cleanup() { if (reservedBodyBytes > 0) { __velarServeReleaseOutbound(reservedBodyBytes); reservedBodyBytes = 0; } return null; },
|
|
2740
2881
|
};
|
|
@@ -2749,14 +2890,49 @@ function __velarServeNativeSetHeaders(response, headers, cookies = []) {
|
|
|
2749
2890
|
for (let index = 0; index < cookies.length; index += 1) allCookies[allCookies.length] = cookies[index];
|
|
2750
2891
|
if (allCookies.length > 0) response.setHeader("Set-Cookie", allCookies);
|
|
2751
2892
|
}
|
|
2893
|
+
// Every error branch of __velarServeHandleNative answers with a body of its own,
|
|
2894
|
+
// so it has to start from an empty header set: a content-length staged for the
|
|
2895
|
+
// response that failed makes the client wait for bytes that will never arrive,
|
|
2896
|
+
// and a Set-Cookie staged by a handler whose request was never served hands out
|
|
2897
|
+
// a session for nothing. The isolated-host transport gets this for free — it
|
|
2898
|
+
// sheds before the host ever sets a header — so this is the native transport
|
|
2899
|
+
// reaching the same state.
|
|
2900
|
+
function __velarServeNativeResetHeaders(response) {
|
|
2901
|
+
const names = response.getHeaderNames();
|
|
2902
|
+
for (let index = 0; index < names.length; index += 1) response.removeHeader(names[index]);
|
|
2903
|
+
}
|
|
2904
|
+
class __velarServeNativeNotFound extends __velarServeError {}
|
|
2905
|
+
function __velarServeNativeMissing(error) {
|
|
2906
|
+
const code = error?.code;
|
|
2907
|
+
return code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR";
|
|
2908
|
+
}
|
|
2909
|
+
// A relative path carries .. only as a whole segment, so a bare two-dot prefix
|
|
2910
|
+
// test also refuses an ordinary top-level file whose own name begins with two
|
|
2911
|
+
// dots — the same over-strict form the host worker's containment check carried.
|
|
2912
|
+
// The operations bag has no separator to consult and may come from a bridge
|
|
2913
|
+
// embedding, so both separators are refused: fail closed on Windows rather than
|
|
2914
|
+
// trust a default.
|
|
2915
|
+
function __velarServeNativeEscapes(path, operations) {
|
|
2916
|
+
return path === ".." || path.startsWith("../") || path.startsWith("..\\") || operations.isAbsolute(path);
|
|
2917
|
+
}
|
|
2752
2918
|
async function __velarServeNativeFile(value, operations) {
|
|
2753
|
-
|
|
2919
|
+
let root;
|
|
2920
|
+
// A static root that does not exist is the same miss as a file that does not
|
|
2921
|
+
// exist: reporting it as a failure would answer 500 and write the absolute
|
|
2922
|
+
// deployment path to stderr, which the host transport never does.
|
|
2923
|
+
try { root = await operations.realpath(operations.resolve(value.root)); }
|
|
2924
|
+
catch (error) { if (__velarServeNativeMissing(error)) throw new __velarServeNativeNotFound("fileResponse root does not name a directory"); throw error; }
|
|
2754
2925
|
const load = async path => {
|
|
2755
|
-
|
|
2926
|
+
let target;
|
|
2927
|
+
try { target = await operations.realpath(operations.resolve(root, path.startsWith("/") ? "." + path : path)); }
|
|
2928
|
+
catch (error) { if (__velarServeNativeMissing(error)) throw new __velarServeNativeNotFound("fileResponse path does not name a file"); throw error; }
|
|
2756
2929
|
const relative = operations.relative(root, target);
|
|
2757
|
-
if (relative
|
|
2758
|
-
|
|
2759
|
-
|
|
2930
|
+
if (__velarServeNativeEscapes(relative, operations)) throw new __velarServeNativeNotFound("fileResponse path escapes its root");
|
|
2931
|
+
let info;
|
|
2932
|
+
try { info = await operations.stat(target); }
|
|
2933
|
+
catch (error) { if (__velarServeNativeMissing(error)) throw new __velarServeNativeNotFound("fileResponse path does not name a file"); throw error; }
|
|
2934
|
+
if (!info.isFile()) throw new __velarServeNativeNotFound("fileResponse path does not name a file");
|
|
2935
|
+
if (info.size > 64 * 1024 * 1024) throw new __velarServeRangeError("fileResponse file exceeds 64 MiB");
|
|
2760
2936
|
return {target, info};
|
|
2761
2937
|
};
|
|
2762
2938
|
try { return await load(value.path); } catch (error) { if (value.fallback === null) throw error; return load(value.fallback); }
|
|
@@ -2826,13 +3002,13 @@ async function __velarServeNativeBody(response, value, checked, suppressBody, op
|
|
|
2826
3002
|
if (!response.hasHeader("vary")) response.setHeader("vary", "Accept-Encoding");
|
|
2827
3003
|
return __velarServeWithOutbound(compressed.byteLength, () => __velarServeNativeEnd(response, compressed));
|
|
2828
3004
|
}
|
|
2829
|
-
async function __velarServeHandleNative(handler, request, response, operations) {
|
|
3005
|
+
async function __velarServeHandleNative(handler, request, response, operations, maxBodyBytes = __velarServeMaxBodyBytes) {
|
|
2830
3006
|
let cleanup = null;
|
|
2831
3007
|
let backgroundTasks = null;
|
|
2832
3008
|
let incoming = null;
|
|
2833
3009
|
let disconnected = null;
|
|
2834
3010
|
try {
|
|
2835
|
-
incoming = __velarServeNativeRequest(request);
|
|
3011
|
+
incoming = __velarServeNativeRequest(request, maxBodyBytes);
|
|
2836
3012
|
disconnected = () => { if (!response.writableFinished) __velarServeCancellation.__velarCancel(incoming.cancellation, "client_disconnect"); };
|
|
2837
3013
|
request.once("aborted", disconnected);
|
|
2838
3014
|
response.once("close", disconnected);
|
|
@@ -2847,7 +3023,34 @@ async function __velarServeHandleNative(handler, request, response, operations)
|
|
|
2847
3023
|
let writing = false;
|
|
2848
3024
|
const write = async chunk => { if (writing) throw new __velarServeError("ServeResponse allows only one active stream write"); writing = true; try { if (typeof chunk !== "string" || __velarUtf8ByteLength(chunk) > 1024 * 1024) throw new __velarServeTypeError("ServeResponse.stream chunks must be text of at most 1 MiB"); if (!suppressBody) await __velarServeWithOutbound(__velarUtf8ByteLength(chunk), () => __velarServeNativeWrite(response, chunk)); return null; } finally { writing = false; } };
|
|
2849
3025
|
const result = await checked.stream(write); if (result !== null) throw new __velarServeTypeError("ServeResponse.stream producer must resolve to null"); if (writing) throw new __velarServeError("ServeResponse stream producer returned before its write completed"); await __velarServeNativeEnd(response); return null;
|
|
2850
|
-
} catch (error) {
|
|
3026
|
+
} catch (error) {
|
|
3027
|
+
if (error instanceof RequestBodyTooLargeError && !response.headersSent) {
|
|
3028
|
+
__velarServeNativeResetHeaders(response);
|
|
3029
|
+
response.statusCode = 413;
|
|
3030
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
3031
|
+
response.end(request.method === "HEAD" ? undefined : '{"error":"request_too_large"}');
|
|
3032
|
+
return null;
|
|
3033
|
+
}
|
|
3034
|
+
if (error instanceof __velarServeNativeNotFound && !response.headersSent) {
|
|
3035
|
+
__velarServeNativeResetHeaders(response);
|
|
3036
|
+
response.statusCode = 404;
|
|
3037
|
+
response.setHeader("content-type", "text/plain; charset=utf-8");
|
|
3038
|
+
response.end(request.method === "HEAD" ? undefined : "Not found");
|
|
3039
|
+
return null;
|
|
3040
|
+
}
|
|
3041
|
+
if (error instanceof __velarServeOutboundBudgetError && !response.headersSent) {
|
|
3042
|
+
__velarServeNativeResetHeaders(response);
|
|
3043
|
+
response.statusCode = 503;
|
|
3044
|
+
response.setHeader("retry-after", "1");
|
|
3045
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
3046
|
+
response.end(request.method === "HEAD" ? undefined : '{"error":"outbound_budget_exhausted"}');
|
|
3047
|
+
return null;
|
|
3048
|
+
}
|
|
3049
|
+
__velarServeReportFailure(error);
|
|
3050
|
+
if (!response.headersSent) { __velarServeNativeResetHeaders(response); response.statusCode = 500; response.setHeader("content-type", "text/plain; charset=utf-8"); response.end("Internal server error"); }
|
|
3051
|
+
else response.destroy();
|
|
3052
|
+
return null;
|
|
3053
|
+
}
|
|
2851
3054
|
finally {
|
|
2852
3055
|
if (disconnected !== null) { request.off("aborted", disconnected); response.off("close", disconnected); }
|
|
2853
3056
|
await __velarServeRunBackground(backgroundTasks);
|
|
@@ -3129,6 +3332,7 @@ async function __velarServeDispatch(event) {
|
|
|
3129
3332
|
if (typeof handler !== "function") { __velarServeReportFailure(new __velarServeError("Node host requested an unknown server token")); await __velarNodeHostInvoke("serve.fail", [value.handle]); return; }
|
|
3130
3333
|
try { await __velarServeWriteResponse(value.handle, await handler(value.request)); }
|
|
3131
3334
|
catch (error) {
|
|
3335
|
+
if (error instanceof __velarServeOutboundBudgetError && await __velarServeShedOutbound(value.handle)) return;
|
|
3132
3336
|
__velarServeReportFailure(error);
|
|
3133
3337
|
try { await __velarNodeHostInvoke("serve.fail", [value.handle]); }
|
|
3134
3338
|
catch {}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve-runtime.js","sourceRoot":"","sources":["../src/serve-runtime.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"serve-runtime.js","sourceRoot":"","sources":["../src/serve-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gCAAgC,EAAE,MAAM,kBAAkB,CAAC;AAEpE,0EAA0E;AAC1E,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6CAw/BL,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA63E5E,CAAC,SAAS,EAAE,CAAC"}
|
|
@@ -18,6 +18,16 @@ export declare function parseRouteResultHint(value: string | undefined): {
|
|
|
18
18
|
export declare class VelarNodeAnalyzer extends Analyzer {
|
|
19
19
|
private readonly contextualRouteParameters;
|
|
20
20
|
private readonly routeInputs;
|
|
21
|
+
/** Servers declared by the module under analysis, the only spread targets this analyzer can resolve. */
|
|
22
|
+
private readonly moduleServers;
|
|
23
|
+
/** Module-level `const alias = name` and `let alias = name` bindings, so a spread of an alias resolves to the server it names. */
|
|
24
|
+
private readonly moduleServerAliases;
|
|
25
|
+
/** Local names imported from velar/serve that name a path-preserving combinator, so a spread of a call resolves through it. */
|
|
26
|
+
private readonly moduleServeCombinators;
|
|
27
|
+
/** One answer per `let` alias name to "was this binding ever reassigned?", because the predicate walks the whole program. */
|
|
28
|
+
private readonly stableAliases;
|
|
29
|
+
/** The program under analysis, held for the alias-stability walk. */
|
|
30
|
+
private moduleProgram;
|
|
21
31
|
private readonly nodeModulePath;
|
|
22
32
|
constructor(context?: AnalysisContext, extensions?: readonly CompilerAnalysisExtension[]);
|
|
23
33
|
analyze(program: Program): readonly import("@velarscript/compiler").Diagnostic[];
|
|
@@ -27,6 +37,47 @@ export declare class VelarNodeAnalyzer extends Analyzer {
|
|
|
27
37
|
readonly kind: string;
|
|
28
38
|
}, parameter: Parameter): ValueType | null;
|
|
29
39
|
private analyzeServer;
|
|
40
|
+
/**
|
|
41
|
+
* Enters one route into this server's shape map and compares it against every route already
|
|
42
|
+
* entered, whether that route was written here or composed in by a spread. Composition is why the
|
|
43
|
+
* entries carry an origin: a conflicting route the author cannot see in his own file has to name
|
|
44
|
+
* the server it came from.
|
|
45
|
+
*/
|
|
46
|
+
private recordRoute;
|
|
47
|
+
/**
|
|
48
|
+
* The routes and fallback a spread composes into the server that writes it, or null when the
|
|
49
|
+
* spread is not statically resolvable. This analyzer sees one module, so a spread contributes
|
|
50
|
+
* only when its value reaches a server declared in this module: a plain identifier, an alias of
|
|
51
|
+
* one, or a velar/serve combinator call around one — `use`, `bodyLimit`, `docs` and `lifecycle`
|
|
52
|
+
* carry paths through unchanged, and `prefix` translates them by its literal path. An imported
|
|
53
|
+
* server, a computed prefix path, or any other expression is let through unchecked, because a
|
|
54
|
+
* false conflict here would block a correct program; D90 R19's runtime referee judges the final
|
|
55
|
+
* table at assembly instead. Composition is followed transitively; the visited set bounds a
|
|
56
|
+
* cycle and starts holding the composing server, so a cycle never folds a server's own routes
|
|
57
|
+
* back into itself and reports each as conflicting with itself.
|
|
58
|
+
*/
|
|
59
|
+
private composedItems;
|
|
60
|
+
/**
|
|
61
|
+
* The server declaration a spread value names, or null when it is anything else. A
|
|
62
|
+
* `const other = base` alias chain of this module's own servers resolves too, because the alias
|
|
63
|
+
* holds exactly that ServeApp — and so does a `let` alias the whole module never reassigns,
|
|
64
|
+
* because an unwritten `let` holds its initializer exactly as a `const` does. A reassigned or
|
|
65
|
+
* ambiguous `let`, a member path, a conditional, an import, or a parameter contributes nothing.
|
|
66
|
+
* A call resolves through the path-preserving velar/serve combinators when the callee still
|
|
67
|
+
* reaches its velar/serve import: `prefix` with a literal path translates what its app argument
|
|
68
|
+
* declares, and `use`/`bodyLimit`/`docs`/`lifecycle` pass it through untouched. A computed
|
|
69
|
+
* prefix path contributes nothing — the assembly-time referee owns it. An alias's initializer
|
|
70
|
+
* re-enters this resolver whole, so `const scoped = prefix("/api", routes)` resolves exactly as
|
|
71
|
+
* the spelled-out spread does; the followed set bounds an alias cycle.
|
|
72
|
+
*/
|
|
73
|
+
private resolveComposedServer;
|
|
74
|
+
/** Whether a `let` alias has never been reassigned, asked once per name and program. */
|
|
75
|
+
private aliasBindingIsStable;
|
|
76
|
+
/**
|
|
77
|
+
* Whether a name still reaches the declaration this module recorded for it. An import, a
|
|
78
|
+
* shadowing binding, or a parameter of the same name reaches a different binding.
|
|
79
|
+
*/
|
|
80
|
+
private resolvesTo;
|
|
30
81
|
private analyzeNotFound;
|
|
31
82
|
private analyzeRoute;
|
|
32
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-analyzer.d.ts","sourceRoot":"","sources":["../src/server-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EASR,KAAK,eAAe,EACpB,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,
|
|
1
|
+
{"version":3,"file":"server-analyzer.d.ts","sourceRoot":"","sources":["../src/server-analyzer.ts"],"names":[],"mappings":"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"}
|