@jimhoyd/urlcode 0.5.6 → 0.5.9
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/.claude/skills/urlcode-authoring/SKILL.md +15 -0
- package/.claude/skills/urlcode-operations/SKILL.md +4 -0
- package/README.md +24 -14
- package/dist/BUILD-MANIFEST.json +51 -50
- package/dist/agent-context.js +9 -0
- package/dist/agents-guide.js +5 -2
- package/dist/assets.js +2 -2
- package/dist/authoring.js +3 -58
- package/dist/aws.js +21 -4
- package/dist/body-schema.js +2 -3
- package/dist/capability-query.js +3 -3
- package/dist/catalog.js +1 -1
- package/dist/cli.js +42 -15
- package/dist/cloudflare.js +26 -1
- package/dist/config.js +1 -1
- package/dist/context.js +6 -6
- package/dist/egress.js +1 -1
- package/dist/examples.js +2 -2
- package/dist/explain.js +4 -6
- package/dist/extension-artifacts.js +8 -8
- package/dist/extension-transport.js +6 -4
- package/dist/function-worker.js +5 -2
- package/dist/functions.js +1 -1
- package/dist/guest-api.js +22 -15
- package/dist/http-policy.js +1 -3
- package/dist/http-response.js +61 -10
- package/dist/init-with.js +8 -8
- package/dist/interchange.js +3 -2
- package/dist/logging.js +4 -4
- package/dist/match.js +1 -1
- package/dist/mcp-authoring.js +1 -2
- package/dist/mcp.js +6 -3
- package/dist/object-guards.js +17 -0
- package/dist/pattern-guard.js +33 -3
- package/dist/policies/cache.js +16 -4
- package/dist/policies/compression.js +1 -1
- package/dist/policies/throttle.js +1 -1
- package/dist/policy.js +3 -3
- package/dist/prerender.js +1 -2
- package/dist/project-dependencies.js +8 -8
- package/dist/proxy.js +1 -1
- package/dist/readiness.js +16 -17
- package/dist/review.js +2 -2
- package/dist/route-diff.js +4 -4
- package/dist/router.js +1 -1
- package/dist/runtime.js +4 -1
- package/dist/scaffold.js +1 -1
- package/dist/schema-query.js +1 -1
- package/dist/server.js +49 -7
- package/dist/signals.js +2 -2
- package/dist/site.js +2 -2
- package/dist/trusted-functions.js +1 -1
- package/dist/types/assets.d.ts +3 -2
- package/dist/types/authoring.d.ts +3 -6
- package/dist/types/body-schema.d.ts +2 -1
- package/dist/types/catalog.d.ts +2 -1
- package/dist/types/config.d.ts +0 -8
- package/dist/types/context.d.ts +3 -1
- package/dist/types/egress.d.ts +1 -1
- package/dist/types/explain.d.ts +4 -4
- package/dist/types/extension-artifacts.d.ts +4 -13
- package/dist/types/extension-transport.d.ts +6 -4
- package/dist/types/functions.d.ts +1 -1
- package/dist/types/http-response.d.ts +7 -3
- package/dist/types/init-with.d.ts +3 -9
- package/dist/types/logging.d.ts +4 -3
- package/dist/types/match.d.ts +2 -1
- package/dist/types/object-guards.d.ts +7 -0
- package/dist/types/pattern-guard.d.ts +0 -1
- package/dist/types/policies/cache.d.ts +4 -3
- package/dist/types/policies/compression.d.ts +1 -1
- package/dist/types/policies/throttle.d.ts +1 -1
- package/dist/types/policy.d.ts +4 -3
- package/dist/types/project-dependencies.d.ts +1 -40
- package/dist/types/proxy.d.ts +2 -1
- package/dist/types/readiness.d.ts +12 -16
- package/dist/types/review.d.ts +0 -2
- package/dist/types/route-diff.d.ts +4 -3
- package/dist/types/router.d.ts +1 -1
- package/dist/types/server.d.ts +22 -0
- package/dist/types/signals.d.ts +3 -2
- package/dist/types/site.d.ts +1 -2
- package/dist/types/trusted-functions.d.ts +1 -1
- package/dist/types/types.d.ts +17 -11
- package/dist/types/vercel.d.ts +1 -0
- package/dist/types/verify-deployment.d.ts +1 -1
- package/dist/types.js +18 -11
- package/dist/vercel.js +12 -3
- package/dist/verify-deployment.js +29 -11
- package/docs/AI-AUTHORING.md +9 -0
- package/docs/FUNCTION-SECURITY.md +4 -0
- package/docs/README.md +2 -2
- package/docs/TOOLING.md +35 -1
- package/examples/assets/Makefile +1 -1
- package/examples/compliance/rules.mjs +2 -2
- package/llms-full.txt +44 -13
- package/llms.txt +9 -2
- package/package.json +8 -3
- package/skills/urlcode/SKILL.md +6 -6
- package/starters/default/.github/workflows/urlcode.yml +2 -1
- package/starters/default/AGENTS.md +3 -3
- package/starters/default/Makefile +1 -1
- package/starters/default/README.md +13 -11
- package/starters/default/tests/requests.json +0 -50
- package/starters/default/urlcode.yaml +1 -4
- package/dist/scripts/operational-drills.js +0 -33
- package/starters/default/functions/hello.mjs +0 -3
- package/starters/default/middleware/headers.mjs +0 -6
- package/starters/default/routes/functions.yaml +0 -20
- package/starters/default/routes/marketing/links.yaml +0 -7
- package/starters/page/README.md +0 -14
- package/starters/page/public/index.html +0 -12
- package/starters/page/tests/requests.json +0 -17
- package/starters/page/urlcode.yaml +0 -6
package/dist/readiness.js
CHANGED
|
@@ -6,14 +6,15 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { readFile, lstat } from 'node:fs/promises';
|
|
7
7
|
import { safeFile } from './config.js';
|
|
8
8
|
import { assert } from './errors.js';
|
|
9
|
+
import { isRecord } from './object-guards.js';
|
|
9
10
|
import { parseTarget, matchRoute, contextFor, redirectLocation } from './router.js';
|
|
10
11
|
import { runCompliance } from './compliance.js';
|
|
11
|
-
|
|
12
|
+
import { handlerNames as handlers } from './types.js';
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
|
|
17
18
|
/** One configured route as the inventory reports it: a PlanInventoryEntry with the handler kind named. */
|
|
18
19
|
|
|
19
20
|
|
|
@@ -31,26 +32,26 @@ import { runCompliance } from './compliance.js';
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
/** Where a captured value comes from: a dotted path into a JSON response body, or one response header. */
|
|
34
|
-
|
|
35
|
+
|
|
35
36
|
/** A step that closes and restarts the runtime on the same project and data directory. */
|
|
36
|
-
|
|
37
|
+
|
|
37
38
|
/** An ordered fixture: requests that share captured values, optionally with restarts between them. */
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
|
|
40
41
|
export const isStepsFixture = (fixture ) => 'steps' in fixture;
|
|
41
|
-
|
|
42
|
+
const isRestartable = (app ) => typeof (app ).restart === 'function';
|
|
42
43
|
const isRestart = (step ) => 'restart' in step;
|
|
43
44
|
|
|
44
45
|
/** `captured` holds values from a step's `capture`; callers use it for substitution only and never print it. */
|
|
45
|
-
|
|
46
|
+
|
|
46
47
|
|
|
47
48
|
/** A started server as the audit and benchmark see it. structural: the real type is startServer's result in src/server.ts. */
|
|
48
49
|
|
|
49
50
|
/** An app that can also close and restart itself on the same project and data directory (fixture `restart` steps). */
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
|
|
54
|
+
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
|
|
@@ -71,17 +72,15 @@ const isRestart = (step ) => 'res
|
|
|
71
72
|
|
|
72
73
|
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
|
|
76
|
+
|
|
76
77
|
|
|
77
78
|
|
|
78
79
|
|
|
79
80
|
|
|
80
81
|
|
|
81
|
-
const handlers = ['extension','proxy','conditional','redirect','function','page','static','download','respond'] ;
|
|
82
82
|
/** Narrows a compiled route to one that redirects, so redirectLocation can read its spec. */
|
|
83
83
|
export const hasRedirect = (route ) => Boolean(route.redirect);
|
|
84
|
-
const isRecord = (value ) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
85
84
|
// Probes identify themselves so an agents policy that denies an empty
|
|
86
85
|
// User-Agent does not fail every generated case; fixtures may override it.
|
|
87
86
|
export const probeAgent = 'Mozilla/5.0 (compatible; RouteProbe/0.1)';
|
|
@@ -193,12 +192,12 @@ export async function readFixtures(root , optional = false)
|
|
|
193
192
|
return cases ; // trust boundary: fixture JSON, validated field by field above
|
|
194
193
|
}
|
|
195
194
|
/** Single-request fixtures only: the benchmark replays these and cannot run ordered steps. */
|
|
196
|
-
|
|
195
|
+
async function readCases(root , optional = false) {
|
|
197
196
|
return (await readFixtures(root,optional)).filter((fixture) => !isStepsFixture(fixture));
|
|
198
197
|
}
|
|
199
198
|
/** One request a fixture run sent: `test` is the request as sent (captured values filled in), `original` as written. Print `original`, never `test`. */
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
|
|
200
|
+
|
|
202
201
|
|
|
203
202
|
|
|
204
203
|
|
package/dist/review.js
CHANGED
|
@@ -24,8 +24,8 @@ import {effectivePolicies} from './policies.js';
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const reviewModuleByteLimit = MODULE_BYTE_LIMIT;
|
|
28
|
+
const reviewExcerptLimit = 240;
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
function locate(source , at ) {
|
package/dist/route-diff.js
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
// was added, removed or changed. Pure and generic: it knows nothing about
|
|
3
3
|
// pull requests; the GitHub action renders its Markdown into a PR comment.
|
|
4
4
|
import { ConfigError } from './errors.js';
|
|
5
|
+
import { isRecord } from './object-guards.js';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
/** One route as the diff sees it: the inventory entry plus its policy description from the `policies` map. */
|
|
8
|
-
|
|
9
|
+
|
|
9
10
|
/** The parts of a `urlcode routes` report the diff reads. */
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
|
|
13
14
|
|
|
14
15
|
const states = ['active','disabled','expired'];
|
|
15
|
-
const isRecord = (value ) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
16
16
|
const isStrings = (value ) => Array.isArray(value) && value.every(item => typeof item === 'string');
|
|
17
17
|
function entry(value , index ) {
|
|
18
18
|
const fail = (what ) => { throw new ConfigError(`Route report inventory[${index}] ${what}`); };
|
package/dist/router.js
CHANGED
|
@@ -19,7 +19,7 @@ import { parameterName } from './match.js';
|
|
|
19
19
|
export { parseTarget, matchRoute, contextFor, resolveValue, redirectLocation } from './match.js';
|
|
20
20
|
|
|
21
21
|
/** The grants compileRoutes consults: a validated OperatorPolicy (policy.ts) or nothing, which denies every binding. */
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
|
package/dist/runtime.js
CHANGED
|
@@ -278,7 +278,10 @@ export async function createRuntime(project , rawOptions
|
|
|
278
278
|
let native ;
|
|
279
279
|
if(route.extension){native=extensionResponse(await extensionRegistry.entries.get(route.extension) .instance.handle(extensionRequest),assetContext(method,parsed.path,route.pattern));}
|
|
280
280
|
else if(route.compiledProxy){
|
|
281
|
-
|
|
281
|
+
// Same credential-free projection a guest function receives: an
|
|
282
|
+
// extension-declared credential header in requestHeaders must not
|
|
283
|
+
// reach the upstream any more than it reaches a function's code.
|
|
284
|
+
try {const result=await executeProxy(proxyClient,route.compiledProxy,{method,url:origin+target,params:path,headers:Object.fromEntries(guestHeaders),...(body?{body}:{})});native={status:result.status,headers:Object.entries(result.headers),body:result.body};}
|
|
282
285
|
catch(error){throw new HttpError(error instanceof EgressError&&error.code==='timeout'?504:error instanceof EgressError&&['busy','closed','aborted'].includes(error.code)?503:502,'Proxy upstream unavailable');}
|
|
283
286
|
}
|
|
284
287
|
else if (route.conditionalRoutes) {
|
package/dist/scaffold.js
CHANGED
|
@@ -2,6 +2,7 @@ import {realpath,lstat,readFile,mkdir,open} from 'node:fs/promises';
|
|
|
2
2
|
import {join,extname} from 'node:path';
|
|
3
3
|
import {parseYaml,validateDocument,MAX_CONFIG_BYTES} from './config.js';
|
|
4
4
|
import {assert} from './errors.js';
|
|
5
|
+
import {isCode} from './object-guards.js';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
|
|
@@ -11,7 +12,6 @@ import {assert} from './errors.js';
|
|
|
11
12
|
|
|
12
13
|
const html='<!doctype html>\n<html lang="en"><meta charset="utf-8"><title>TODO</title><body><h1>TODO: replace this placeholder</h1></body></html>\n';
|
|
13
14
|
const sensitive=/^(?:node_modules|package(?:-lock)?\.json|.*\.(?:pem|key|p12|pfx|env))$/i;
|
|
14
|
-
const isCode=(error ,code ) =>error instanceof Error && 'code' in error && error.code===code;
|
|
15
15
|
function parts(path ) {
|
|
16
16
|
assert(typeof path==='string' && path.length<=1024,'Invalid scaffold path');
|
|
17
17
|
const values=path.split('/');
|
package/dist/schema-query.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {readFileSync} from 'node:fs';
|
|
2
2
|
import {fileURLToPath} from 'node:url';
|
|
3
3
|
import {ConfigError} from './errors.js';
|
|
4
|
+
import {isRecord as object} from './object-guards.js';
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
|
|
@@ -10,7 +11,6 @@ function schema() {
|
|
|
10
11
|
cached??=JSON.parse(readFileSync(fileURLToPath(new URL('../schemas/urlcode.schema.json',import.meta.url)),'utf8')) ;
|
|
11
12
|
return cached;
|
|
12
13
|
}
|
|
13
|
-
const object=(value ) =>value!==null&&typeof value==='object'&&!Array.isArray(value);
|
|
14
14
|
const properties=(node ) =>object(node.properties)?node.properties:{};
|
|
15
15
|
function definition(root ,ref ) {
|
|
16
16
|
const defs=object(root.$defs)?root.$defs:{};
|
package/dist/server.js
CHANGED
|
@@ -20,6 +20,28 @@ import { compileTrustedProxies, resolveClient } from './client-address.js';
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
23
45
|
|
|
24
46
|
|
|
25
47
|
|
|
@@ -105,7 +127,10 @@ async function startServerCore({ project = '.', host = '127.0.0.1', port = 3000,
|
|
|
105
127
|
local = false, log = createJsonLogger(),
|
|
106
128
|
maxBodyBytes = 1048576, maxInFlightRequests = 64, maxInFlightHealthRequests = 16,
|
|
107
129
|
requestLog = 'minimal', trustRequestId = false, origin, trustedProxies = [],
|
|
108
|
-
observers = [], metrics = false, metricsIntervalMs = 0,
|
|
130
|
+
observers = [], metrics = false, metricsIntervalMs = 0, healthDetails = metrics,
|
|
131
|
+
readinessDrainMs = 0, closeTimeoutMs = 10000,
|
|
132
|
+
headersTimeoutMs = 10000, requestTimeoutMs = 15000, keepAliveTimeoutMs = 5000,
|
|
133
|
+
...runtimeOptions } = {}) {
|
|
109
134
|
// Which peers may set X-Forwarded-For. Empty means the socket peer is the
|
|
110
135
|
// client for every policy; a forwarded header from anyone else is ignored.
|
|
111
136
|
const proxies = compileTrustedProxies(trustedProxies);
|
|
@@ -116,6 +141,12 @@ async function startServerCore({ project = '.', host = '127.0.0.1', port = 3000,
|
|
|
116
141
|
assert(typeof trustRequestId === 'boolean', 'Request ID trust must be a boolean');
|
|
117
142
|
assert(typeof metrics === 'boolean', 'Metrics exposition must be a boolean');
|
|
118
143
|
assert(metricsIntervalMs === 0 || (Number.isInteger(metricsIntervalMs) && metricsIntervalMs >= 1000 && metricsIntervalMs <= 3600000), 'Metrics interval must be 0 or 1000–3600000 ms');
|
|
144
|
+
assert(typeof healthDetails === 'boolean', 'Health details exposition must be a boolean');
|
|
145
|
+
assert(Number.isInteger(readinessDrainMs) && readinessDrainMs >= 0 && readinessDrainMs <= 300000, 'Readiness drain delay must be 0–300000 ms');
|
|
146
|
+
assert(Number.isInteger(closeTimeoutMs) && closeTimeoutMs >= 0 && closeTimeoutMs <= 300000, 'Close timeout must be 0–300000 ms');
|
|
147
|
+
assert(Number.isInteger(headersTimeoutMs) && headersTimeoutMs >= 1000 && headersTimeoutMs <= 300000, 'Headers timeout must be 1000–300000 ms');
|
|
148
|
+
assert(Number.isInteger(requestTimeoutMs) && requestTimeoutMs >= 1000 && requestTimeoutMs <= 300000, 'Request timeout must be 1000–300000 ms');
|
|
149
|
+
assert(Number.isInteger(keepAliveTimeoutMs) && keepAliveTimeoutMs >= 0 && keepAliveTimeoutMs <= 300000, 'Keep-alive timeout must be 0–300000 ms');
|
|
119
150
|
if (origin) {
|
|
120
151
|
let u ;
|
|
121
152
|
try { u = new URL(origin); } catch { assert(false, 'Invalid public origin'); }
|
|
@@ -127,9 +158,11 @@ async function startServerCore({ project = '.', host = '127.0.0.1', port = 3000,
|
|
|
127
158
|
const counters = sink.metrics;
|
|
128
159
|
const emit = (event , context ) => { try { sink(event, context); } catch { /* Logging cannot fail requests. */ } };
|
|
129
160
|
let current = await createRuntime(project, { local, log: emit, origin, ...runtimeOptions });
|
|
130
|
-
|
|
161
|
+
// `draining` flips /_urlcode/ready unhealthy ahead of `shuttingDown`, which stops
|
|
162
|
+
// serving entirely; the gap between them is the pre-close readiness delay.
|
|
163
|
+
let shuttingDown = false, draining = false, reloading = false, watching = false, interval , lastFingerprint , inFlight = 0, healthInFlight = 0;
|
|
131
164
|
const retired = new Set ();
|
|
132
|
-
const server = http.createServer({ maxHeaderSize: 16384, headersTimeout:
|
|
165
|
+
const server = http.createServer({ maxHeaderSize: 16384, headersTimeout: headersTimeoutMs, requestTimeout: requestTimeoutMs, keepAliveTimeout: keepAliveTimeoutMs }, async (req, res) => {
|
|
133
166
|
const started = performance.now();
|
|
134
167
|
const url = req.url ?? '', method = req.method ?? 'GET';
|
|
135
168
|
// Upstream correlation is opt-in: an untrusted client must not choose the ID
|
|
@@ -162,8 +195,13 @@ async function startServerCore({ project = '.', host = '127.0.0.1', port = 3000,
|
|
|
162
195
|
if (!['GET','HEAD'].includes(method)) result = { status: 405, headers: [['allow','GET, HEAD']], body: Buffer.alloc(0) };
|
|
163
196
|
else if (url === '/_urlcode/metrics') result = { status: 200, headers: [['content-type','text/plain; version=0.0.4; charset=utf-8'],['cache-control','no-store']], body: Buffer.from(renderPrometheus(snapshot())) };
|
|
164
197
|
else {
|
|
165
|
-
|
|
166
|
-
|
|
198
|
+
// Liveness (/_urlcode/health) stays healthy while draining, so a supervisor does
|
|
199
|
+
// not restart a process that is deliberately finishing in-flight work; readiness
|
|
200
|
+
// (/_urlcode/ready) reflects the drain so a load balancer stops routing here.
|
|
201
|
+
const ready = url === '/_urlcode/health' ? true : (!draining && current.healthy);
|
|
202
|
+
const body = { status: ready ? 'ok' : 'degraded' };
|
|
203
|
+
if (healthDetails) { body.version = current.version; body.routes = current.count; }
|
|
204
|
+
result = { status: ready ? 200 : 503, headers: [['content-type','application/json']], body: Buffer.from(JSON.stringify(body)) };
|
|
167
205
|
}
|
|
168
206
|
req.resume();
|
|
169
207
|
} else {
|
|
@@ -202,7 +240,7 @@ async function startServerCore({ project = '.', host = '127.0.0.1', port = 3000,
|
|
|
202
240
|
const snapshot = () => { const { healthy, slots } = current.workers; const out = counters.snapshot(); out.functionWorkers.healthySlots = healthy; out.functionWorkers.slots = slots; return out; };
|
|
203
241
|
let metricsTimer ;
|
|
204
242
|
if (metricsIntervalMs) { metricsTimer = setInterval(() => sink.publish(snapshot()), metricsIntervalMs); metricsTimer.unref(); }
|
|
205
|
-
server.setTimeout(
|
|
243
|
+
server.setTimeout(requestTimeoutMs, socket => socket.destroy());
|
|
206
244
|
server.maxRequestsPerSocket = 1000;
|
|
207
245
|
server.maxConnections = 1024;
|
|
208
246
|
server.on('clientError', (error, socket) => {
|
|
@@ -252,8 +290,12 @@ async function startServerCore({ project = '.', host = '127.0.0.1', port = 3000,
|
|
|
252
290
|
// the operator's --origin, never a forwarded header.
|
|
253
291
|
origin: publicOrigin(),
|
|
254
292
|
async close() {
|
|
293
|
+
if (!draining) {
|
|
294
|
+
draining = true;
|
|
295
|
+
if (readinessDrainMs > 0) await new Promise (resolve => setTimeout(resolve, readinessDrainMs));
|
|
296
|
+
}
|
|
255
297
|
shuttingDown = true; clearInterval(interval); clearInterval(metricsTimer);
|
|
256
|
-
const deadline = setTimeout(() => server.closeAllConnections(),
|
|
298
|
+
const deadline = setTimeout(() => server.closeAllConnections(), closeTimeoutMs);
|
|
257
299
|
deadline.unref();
|
|
258
300
|
await new Promise (resolve => server.close(() => resolve()));
|
|
259
301
|
clearTimeout(deadline);
|
package/dist/signals.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { egressUrl, safeEgressHeaders } from './egress.js';
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
|
|
6
6
|
export function validateSignal(definition ) {if(!definition||typeof definition!=='object'||Object.keys(definition).some(key=>!['url','headers'].includes(key))) throw new Error('Invalid signal');egressUrl(definition.url);safeEgressHeaders(definition.headers||{});}
|
|
7
7
|
/** No queue, retries, ordering promise, or durable delivery. Counters contain no URL/header data. */
|
|
8
8
|
export class SignalBroker {
|
package/dist/site.js
CHANGED
|
@@ -6,7 +6,7 @@ import { publishableAssetName } from './assets.js';
|
|
|
6
6
|
import { lists as bundled } from '../data/agents/index.js';
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
@@ -239,7 +239,7 @@ export async function applySite(loaded , options = {
|
|
|
239
239
|
// bounded and static: no templating, no request data. It is text/html; the
|
|
240
240
|
// artifact is JSON, which does the escaping, and a body that is not valid
|
|
241
241
|
// UTF-8 is refused rather than silently altered.
|
|
242
|
-
|
|
242
|
+
const notFoundInlineLimit = 65536;
|
|
243
243
|
export async function inlineNotFound(loaded ) {
|
|
244
244
|
const site = loaded.document.site, path = generatedPaths.notFound;
|
|
245
245
|
const route = loaded.routes[path];
|
package/dist/types/assets.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { AssetResult, CompiledRoute } from './types.ts';
|
|
2
2
|
/** The header surface assetResponse reads for conditional and range requests. */
|
|
3
|
-
|
|
3
|
+
interface AssetRequestHeaders {
|
|
4
4
|
has(name: string): boolean;
|
|
5
5
|
get(name: string): string | null | undefined;
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
interface AssetSnapshot {
|
|
8
8
|
watch: string[];
|
|
9
9
|
digest: string;
|
|
10
10
|
}
|
|
11
11
|
export declare const publishableAssetName: (name: string) => boolean;
|
|
12
12
|
export declare function compileAssets(root: string, routes: CompiledRoute[]): Promise<AssetSnapshot>;
|
|
13
13
|
export declare function assetResponse(route: CompiledRoute, path: string, method: string, request: AssetRequestHeaders): AssetResult;
|
|
14
|
+
export {};
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import type { DependencySet } from './project-dependencies.ts';
|
|
2
|
-
|
|
2
|
+
interface InitOptions {
|
|
3
3
|
/**
|
|
4
4
|
* When given, a `package.json` pinning exactly these versions is written beside `urlcode.yaml`. Route-only
|
|
5
5
|
* initialization stays the default: a project whose runtime is managed elsewhere gets no manifest at all.
|
|
6
6
|
*/
|
|
7
7
|
manifest?: DependencySet | undefined;
|
|
8
|
-
/** `default` (function, middleware, redirect) or `page`: urlcode.yaml, public/index.html, a README and fixtures only. */
|
|
9
|
-
template?: 'default' | 'page' | 'redirects' | undefined;
|
|
10
8
|
}
|
|
11
|
-
|
|
12
|
-
export declare function mergePackageJson(text: string, scripts: Record<string, string>, dependency: string, version: string): string;
|
|
13
|
-
export declare function initProject(destination: string, { manifest, template }?: InitOptions): Promise<string>;
|
|
9
|
+
export declare function initProject(destination: string, { manifest }?: InitOptions): Promise<string>;
|
|
14
10
|
export declare function addRedirect(project: string, destination: string, alias?: string | undefined): Promise<string>;
|
|
11
|
+
export {};
|
|
@@ -29,7 +29,7 @@ export declare function assertBodySchema(schema: unknown): asserts schema is Bod
|
|
|
29
29
|
* is the schema keyword that failed, and `expected` is the schema's own
|
|
30
30
|
* constraint. Nothing the client sent is ever placed in an issue.
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
interface BodySchemaIssue {
|
|
33
33
|
pointer: string;
|
|
34
34
|
keyword: string;
|
|
35
35
|
message: string;
|
|
@@ -56,3 +56,4 @@ export declare const bodySchemaLine: (issue: BodySchemaIssue) => string;
|
|
|
56
56
|
export declare function prefersJson(accept: string | null | undefined): boolean;
|
|
57
57
|
/** Renders the JSON answer: never more than `maxIssueBytes`, dropping trailing issues and saying so. */
|
|
58
58
|
export declare function bodySchemaJson(issues: BodySchemaIssue[]): string;
|
|
59
|
+
export {};
|
package/dist/types/catalog.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface CatalogMetadata {
|
|
|
36
36
|
behavior?: string[];
|
|
37
37
|
}
|
|
38
38
|
/** The fields the capability preflight determines; `npm run check` refuses hand-written values that differ. */
|
|
39
|
-
|
|
39
|
+
interface DerivedMetadata {
|
|
40
40
|
capabilities: CapabilityName[];
|
|
41
41
|
targets: Record<CapabilityTarget, TargetVerdict>;
|
|
42
42
|
routes: number;
|
|
@@ -59,3 +59,4 @@ export declare function searchTerms(text: string): string[];
|
|
|
59
59
|
* Ties keep catalog order, so the smallest entry wins when a caller sorts by size first.
|
|
60
60
|
*/
|
|
61
61
|
export declare function searchMetadata<T extends CatalogMetadata>(entries: readonly T[], text: string, extraText?: (entry: T) => string[]): SearchHit<T>[];
|
|
62
|
+
export {};
|
package/dist/types/config.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ErrorObject } from 'ajv';
|
|
2
1
|
import type { LoadedDocument, ProjectDocument, RouteConfig, SharedBlock } from './types.ts';
|
|
3
2
|
/** What config-worker.ts posts back: the loaded document, or the ConfigError message. */
|
|
4
3
|
export type ConfigWorkerResult = {
|
|
@@ -11,13 +10,6 @@ export interface ConfigWorkerData {
|
|
|
11
10
|
}
|
|
12
11
|
export declare const MAX_CONFIG_BYTES: number;
|
|
13
12
|
export declare function parseYaml(text: string): unknown;
|
|
14
|
-
/**
|
|
15
|
-
* One line for the first schema violation. Closed-key-set failures name the offending key and the keys the
|
|
16
|
-
* schema allows, and required failures name the missing key, because a bare keyword sends the reader hunting.
|
|
17
|
-
* Only key names, which come from the schema or the author's own mapping keys, are echoed, never values
|
|
18
|
-
* (values may hold secrets), and never more than MAX_NAMED_KEY characters of a key.
|
|
19
|
-
*/
|
|
20
|
-
export declare function describeSchemaError(e: ErrorObject): string;
|
|
21
13
|
export declare function validateDocument(data: unknown, inherited?: Record<string, SharedBlock>): ProjectDocument;
|
|
22
14
|
/** The input declaration a short-form function gets for each `{param}` it does not declare itself. */
|
|
23
15
|
export declare const SHORT_FORM_PATH_SCHEMA: {
|
package/dist/types/context.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface ProjectContext {
|
|
|
15
15
|
project: {
|
|
16
16
|
entry: string;
|
|
17
17
|
routes: number;
|
|
18
|
+
routeCountNote: string;
|
|
18
19
|
handlers: Record<string, number>;
|
|
19
20
|
extensions: string[];
|
|
20
21
|
policies: {
|
|
@@ -80,7 +81,7 @@ export interface TaskShape {
|
|
|
80
81
|
/** Established by running `urlcode validate` and `urlcode test` on each shape; test/context.test.ts compiles every `yaml` entry so this cannot drift from the runtime. */
|
|
81
82
|
export declare const redirectShapes: TaskShape[];
|
|
82
83
|
/** A complete, paste-ready project skeleton: every supported shape merged into one urlcode.yaml, plus the start script. */
|
|
83
|
-
|
|
84
|
+
interface TaskStarter {
|
|
84
85
|
file: string;
|
|
85
86
|
yaml: string;
|
|
86
87
|
/** Files the yaml references that must exist, with minimal content. */
|
|
@@ -120,3 +121,4 @@ export declare function buildTaskContext(project: string, task: string, options?
|
|
|
120
121
|
hostFile?: string | undefined;
|
|
121
122
|
projectFlag?: string | undefined;
|
|
122
123
|
}): Promise<TaskContext>;
|
|
124
|
+
export {};
|
package/dist/types/egress.d.ts
CHANGED
package/dist/types/explain.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import type { CapabilityName, CapabilityTarget } from './capabilities.ts';
|
|
2
2
|
import type { RuntimeExtension } from './extensions.ts';
|
|
3
|
-
import type { CompiledRoute, LoadedDocument, PolicyChain, PolicyInventory, RouteState } from './types.ts';
|
|
4
|
-
import type { HandlerName } from './readiness.ts';
|
|
3
|
+
import type { CompiledRoute, HandlerName, LoadedDocument, PolicyChain, PolicyInventory, RouteState } from './types.ts';
|
|
5
4
|
import type { ParameterSchema, ParameterLocation } from './match.ts';
|
|
6
5
|
import type { RequestBodyPolicy } from './http-policy.ts';
|
|
7
6
|
export interface ExplainedHandler {
|
|
8
7
|
kind: HandlerName | 'none';
|
|
9
8
|
[detail: string]: unknown;
|
|
10
9
|
}
|
|
11
|
-
|
|
10
|
+
interface ExplainedParameter {
|
|
12
11
|
name: string;
|
|
13
12
|
in: ParameterLocation;
|
|
14
13
|
required: boolean;
|
|
@@ -93,7 +92,7 @@ export interface RouteExplanation {
|
|
|
93
92
|
targets: Record<CapabilityTarget, TargetSupport>;
|
|
94
93
|
note: string;
|
|
95
94
|
}
|
|
96
|
-
|
|
95
|
+
interface ExplainOptions {
|
|
97
96
|
extensions?: RuntimeExtension[] | undefined;
|
|
98
97
|
projectSha256?: string | undefined;
|
|
99
98
|
now?: number | undefined;
|
|
@@ -101,3 +100,4 @@ export interface ExplainOptions {
|
|
|
101
100
|
/** Describe one compiled route. `chain` is the policy chain compiled for it, when the project declares policies. */
|
|
102
101
|
export declare function explainCompiledRoute(loaded: LoadedDocument, route: CompiledRoute, chain: PolicyChain | undefined, options?: ExplainOptions): RouteExplanation;
|
|
103
102
|
export declare function nearestRoutes(target: string, patterns: Iterable<string>, limit?: number): string[];
|
|
103
|
+
export {};
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { type ReleaseAsset } from './extension-transport.ts';
|
|
2
|
-
/** Offline, declarative extension bundles. These are deliberately not Node packages. */
|
|
3
|
-
export declare const ARTIFACT_REPOSITORY = "jimhoyd-com/urlcode";
|
|
4
2
|
export declare const ARTIFACT_WORKFLOW = "jimhoyd-com/urlcode/.github/workflows/extension-artifacts.yml";
|
|
5
|
-
|
|
3
|
+
interface ArtifactEntry {
|
|
6
4
|
name: string;
|
|
7
5
|
version: string;
|
|
8
6
|
asset: string;
|
|
@@ -19,13 +17,13 @@ export interface Catalog {
|
|
|
19
17
|
reason: string;
|
|
20
18
|
}[];
|
|
21
19
|
}
|
|
22
|
-
|
|
20
|
+
interface LockedArtifact extends ArtifactEntry {
|
|
23
21
|
catalog: {
|
|
24
22
|
tag: string;
|
|
25
23
|
commit: string;
|
|
26
24
|
};
|
|
27
25
|
}
|
|
28
|
-
|
|
26
|
+
interface ExtensionLock {
|
|
29
27
|
format: 1;
|
|
30
28
|
artifacts: LockedArtifact[];
|
|
31
29
|
}
|
|
@@ -35,7 +33,7 @@ export interface TarFile {
|
|
|
35
33
|
path: string;
|
|
36
34
|
bytes: Uint8Array;
|
|
37
35
|
}
|
|
38
|
-
|
|
36
|
+
interface ArchiveLimits {
|
|
39
37
|
archive: number;
|
|
40
38
|
expanded: number;
|
|
41
39
|
files: number;
|
|
@@ -45,7 +43,6 @@ export interface ArchiveLimits {
|
|
|
45
43
|
/** A minimal tar reader: only regular files are accepted, before any write occurs. */
|
|
46
44
|
export declare function readBoundedTgz(source: Uint8Array, limits: ArchiveLimits): TarFile[];
|
|
47
45
|
export declare function extractArtifact(bytes: Uint8Array, entry: ArtifactEntry, destination: string): Promise<void>;
|
|
48
|
-
export declare function readLock(project: string): Promise<ExtensionLock>;
|
|
49
46
|
export declare function writeLock(project: string, lock: ExtensionLock): Promise<void>;
|
|
50
47
|
export declare function cachePath(project: string, sha256: string): string;
|
|
51
48
|
export type { ReleaseAsset };
|
|
@@ -54,12 +51,6 @@ export interface ArtifactTransport {
|
|
|
54
51
|
download(url: string): Promise<Uint8Array>;
|
|
55
52
|
attest(path: string, release: string): Promise<void>;
|
|
56
53
|
}
|
|
57
|
-
/** The default transport accepts only GitHub Release asset URLs and verifies every downloaded subject. */
|
|
58
|
-
export declare const githubTransport: ArtifactTransport;
|
|
59
|
-
export declare function resolveCatalog(release: string, transport?: ArtifactTransport): Promise<{
|
|
60
|
-
catalog: Catalog;
|
|
61
|
-
assets: ReleaseAsset[];
|
|
62
|
-
}>;
|
|
63
54
|
export declare function installArtifact(project: string, release: string, artifactName: string, transport?: ArtifactTransport): Promise<ExtensionLock>;
|
|
64
55
|
export declare function inspectArtifacts(project: string): Promise<{
|
|
65
56
|
lock: ExtensionLock;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { isRecord } from './object-guards.ts';
|
|
2
|
+
import type { UnknownRecord } from './object-guards.ts';
|
|
1
3
|
/** Shared GitHub release/cache/lockfile plumbing for extension-artifacts.ts and extension-bundles.ts. No opinion on what content is allowed or executable; that trust boundary stays local to each caller (#441). */
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
+
export { isRecord };
|
|
5
|
+
export type { UnknownRecord };
|
|
4
6
|
export declare const digestHex: (bytes: Uint8Array) => string;
|
|
5
7
|
export declare function textField(value: unknown, what: string): string;
|
|
6
8
|
export declare function exactKeys(value: UnknownRecord, expected: readonly string[], what: string): void;
|
|
@@ -12,12 +14,12 @@ export interface ReleaseAsset {
|
|
|
12
14
|
name: string;
|
|
13
15
|
url: string;
|
|
14
16
|
}
|
|
15
|
-
|
|
17
|
+
interface GithubTransport {
|
|
16
18
|
release(tag: string): Promise<ReleaseAsset[]>;
|
|
17
19
|
download(url: string): Promise<Uint8Array>;
|
|
18
20
|
attest(path: string, release: string): Promise<void>;
|
|
19
21
|
}
|
|
20
|
-
|
|
22
|
+
interface GithubTransportConfig {
|
|
21
23
|
repository: string;
|
|
22
24
|
workflow: string;
|
|
23
25
|
tagPattern: RegExp;
|
|
@@ -30,7 +30,7 @@ export interface SandboxPoolOptions {
|
|
|
30
30
|
maxBytes?: number | undefined;
|
|
31
31
|
log?: LogFn | undefined;
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
type FunctionPoolOptions = SandboxPoolOptions;
|
|
34
34
|
export interface FunctionWorkerData {
|
|
35
35
|
sources: Record<string, string>;
|
|
36
36
|
dependencies: Record<string, string[]>;
|
|
@@ -7,18 +7,18 @@ export interface HandlerResult {
|
|
|
7
7
|
body?: ResponseBody;
|
|
8
8
|
contentLength?: number;
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
interface PreparedResponse {
|
|
11
11
|
status: number;
|
|
12
12
|
headers: HeaderPair[];
|
|
13
13
|
cookies: string[];
|
|
14
14
|
body: ResponseBody;
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
interface ErrorAnswer {
|
|
17
17
|
status: number;
|
|
18
18
|
headers: HeaderPair[];
|
|
19
19
|
body: string | undefined;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
interface ResponseOptions {
|
|
22
22
|
requestId: string;
|
|
23
23
|
method: string;
|
|
24
24
|
}
|
|
@@ -31,8 +31,11 @@ export interface ResponseWriter {
|
|
|
31
31
|
removeHeader(name: string): void;
|
|
32
32
|
end(body?: ResponseBody): unknown;
|
|
33
33
|
destroy(): unknown;
|
|
34
|
+
strictContentLength?: boolean;
|
|
34
35
|
}
|
|
35
36
|
export declare function prepareResponse(result: HandlerResult, { requestId, method }: ResponseOptions): PreparedResponse;
|
|
37
|
+
/** The UTF-8 bytes a body occupies on the wire; a string's `.length` counts UTF-16 units, not bytes. */
|
|
38
|
+
export declare function byteLength(body: ResponseBody): number;
|
|
36
39
|
export declare function writeResponse(res: ResponseWriter, result: HandlerResult, options: ResponseOptions): number;
|
|
37
40
|
export declare function errorResponse(error: unknown, { requestId, method, headers }: ResponseOptions & {
|
|
38
41
|
headers?: HeaderPair[];
|
|
@@ -40,3 +43,4 @@ export declare function errorResponse(error: unknown, { requestId, method, heade
|
|
|
40
43
|
export declare function writeError(res: ResponseWriter, error: unknown, options: ResponseOptions & {
|
|
41
44
|
headers?: HeaderPair[];
|
|
42
45
|
}): number;
|
|
46
|
+
export {};
|