@jimhoyd/urlcode 0.5.6 → 0.5.8

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.
Files changed (113) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +15 -0
  2. package/.claude/skills/urlcode-operations/SKILL.md +4 -0
  3. package/README.md +24 -14
  4. package/dist/BUILD-MANIFEST.json +50 -49
  5. package/dist/agents-guide.js +5 -2
  6. package/dist/assets.js +2 -2
  7. package/dist/authoring.js +3 -58
  8. package/dist/aws.js +21 -4
  9. package/dist/body-schema.js +2 -3
  10. package/dist/capability-query.js +3 -3
  11. package/dist/catalog.js +1 -1
  12. package/dist/cli.js +42 -15
  13. package/dist/cloudflare.js +26 -1
  14. package/dist/config.js +1 -1
  15. package/dist/context.js +6 -6
  16. package/dist/egress.js +1 -1
  17. package/dist/examples.js +2 -2
  18. package/dist/explain.js +4 -6
  19. package/dist/extension-artifacts.js +8 -8
  20. package/dist/extension-transport.js +6 -4
  21. package/dist/function-worker.js +5 -2
  22. package/dist/functions.js +1 -1
  23. package/dist/guest-api.js +22 -15
  24. package/dist/http-policy.js +1 -3
  25. package/dist/http-response.js +61 -10
  26. package/dist/init-with.js +8 -8
  27. package/dist/interchange.js +3 -2
  28. package/dist/logging.js +4 -4
  29. package/dist/match.js +1 -1
  30. package/dist/mcp-authoring.js +1 -2
  31. package/dist/mcp.js +2 -2
  32. package/dist/object-guards.js +17 -0
  33. package/dist/pattern-guard.js +33 -3
  34. package/dist/policies/cache.js +16 -4
  35. package/dist/policies/compression.js +1 -1
  36. package/dist/policies/throttle.js +1 -1
  37. package/dist/policy.js +3 -3
  38. package/dist/prerender.js +1 -2
  39. package/dist/project-dependencies.js +8 -8
  40. package/dist/proxy.js +1 -1
  41. package/dist/readiness.js +16 -17
  42. package/dist/review.js +2 -2
  43. package/dist/route-diff.js +4 -4
  44. package/dist/router.js +1 -1
  45. package/dist/runtime.js +4 -1
  46. package/dist/scaffold.js +1 -1
  47. package/dist/schema-query.js +1 -1
  48. package/dist/server.js +49 -7
  49. package/dist/signals.js +2 -2
  50. package/dist/site.js +2 -2
  51. package/dist/trusted-functions.js +1 -1
  52. package/dist/types/assets.d.ts +3 -2
  53. package/dist/types/authoring.d.ts +3 -6
  54. package/dist/types/body-schema.d.ts +2 -1
  55. package/dist/types/catalog.d.ts +2 -1
  56. package/dist/types/config.d.ts +0 -8
  57. package/dist/types/context.d.ts +3 -1
  58. package/dist/types/egress.d.ts +1 -1
  59. package/dist/types/explain.d.ts +4 -4
  60. package/dist/types/extension-artifacts.d.ts +4 -13
  61. package/dist/types/extension-transport.d.ts +6 -4
  62. package/dist/types/functions.d.ts +1 -1
  63. package/dist/types/http-response.d.ts +7 -3
  64. package/dist/types/init-with.d.ts +3 -9
  65. package/dist/types/logging.d.ts +4 -3
  66. package/dist/types/match.d.ts +2 -1
  67. package/dist/types/object-guards.d.ts +7 -0
  68. package/dist/types/pattern-guard.d.ts +0 -1
  69. package/dist/types/policies/cache.d.ts +4 -3
  70. package/dist/types/policies/compression.d.ts +1 -1
  71. package/dist/types/policies/throttle.d.ts +1 -1
  72. package/dist/types/policy.d.ts +4 -3
  73. package/dist/types/project-dependencies.d.ts +1 -40
  74. package/dist/types/proxy.d.ts +2 -1
  75. package/dist/types/readiness.d.ts +12 -16
  76. package/dist/types/review.d.ts +0 -2
  77. package/dist/types/route-diff.d.ts +4 -3
  78. package/dist/types/router.d.ts +1 -1
  79. package/dist/types/server.d.ts +22 -0
  80. package/dist/types/signals.d.ts +3 -2
  81. package/dist/types/site.d.ts +1 -2
  82. package/dist/types/trusted-functions.d.ts +1 -1
  83. package/dist/types/types.d.ts +17 -11
  84. package/dist/types/vercel.d.ts +1 -0
  85. package/dist/types/verify-deployment.d.ts +1 -1
  86. package/dist/types.js +18 -11
  87. package/dist/vercel.js +12 -3
  88. package/dist/verify-deployment.js +29 -11
  89. package/docs/AI-AUTHORING.md +9 -0
  90. package/docs/FUNCTION-SECURITY.md +4 -0
  91. package/docs/README.md +2 -2
  92. package/docs/TOOLING.md +27 -1
  93. package/examples/assets/Makefile +1 -1
  94. package/examples/compliance/rules.mjs +2 -2
  95. package/llms-full.txt +44 -13
  96. package/llms.txt +9 -2
  97. package/package.json +4 -3
  98. package/skills/urlcode/SKILL.md +6 -6
  99. package/starters/default/.github/workflows/urlcode.yml +2 -1
  100. package/starters/default/AGENTS.md +3 -3
  101. package/starters/default/Makefile +1 -1
  102. package/starters/default/README.md +13 -11
  103. package/starters/default/tests/requests.json +0 -50
  104. package/starters/default/urlcode.yaml +1 -4
  105. package/dist/scripts/operational-drills.js +0 -33
  106. package/starters/default/functions/hello.mjs +0 -3
  107. package/starters/default/middleware/headers.mjs +0 -6
  108. package/starters/default/routes/functions.yaml +0 -20
  109. package/starters/default/routes/marketing/links.yaml +0 -7
  110. package/starters/page/README.md +0 -14
  111. package/starters/page/public/index.html +0 -12
  112. package/starters/page/tests/requests.json +0 -17
  113. 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
- export const isRestartable = (app ) => typeof (app ).restart === 'function';
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
- export async function readCases(root , optional = false) {
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
- export const reviewModuleByteLimit = MODULE_BYTE_LIMIT;
28
- export const reviewExcerptLimit = 240;
27
+ const reviewModuleByteLimit = MODULE_BYTE_LIMIT;
28
+ const reviewExcerptLimit = 240;
29
29
 
30
30
 
31
31
  function locate(source , at ) {
@@ -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
- try {const result=await executeProxy(proxyClient,route.compiledProxy,{method,url:origin+target,params:path,headers:Object.fromEntries(headers),...(body?{body}:{})});native={status:result.status,headers:Object.entries(result.headers),body:result.body};}
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('/');
@@ -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, ...runtimeOptions } = {}) {
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
- let shuttingDown = false, reloading = false, watching = false, interval , lastFingerprint , inFlight = 0, healthInFlight = 0;
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: 10000, requestTimeout: 15000, keepAliveTimeout: 5000 }, async (req, res) => {
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
- const ready = url === '/_urlcode/health' || current.healthy;
166
- result = { status: ready ? 200 : 503, headers: [['content-type','application/json']], body: Buffer.from(JSON.stringify({ status: ready ? 'ok' : 'degraded', version: current.version, routes: current.count })) };
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(15000, socket => socket.destroy());
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(), 10000);
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
- export const notFoundInlineLimit = 65536;
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];
@@ -34,7 +34,7 @@ import { routeFunctions } from './function-sources.js';
34
34
 
35
35
 
36
36
 
37
-
37
+
38
38
 
39
39
 
40
40
 
@@ -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
- export interface AssetRequestHeaders {
3
+ interface AssetRequestHeaders {
4
4
  has(name: string): boolean;
5
5
  get(name: string): string | null | undefined;
6
6
  }
7
- export interface AssetSnapshot {
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
- export interface InitOptions {
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
- /** Adds what the starter needs to an existing package.json and changes nothing else; a conflicting script is refused, never overwritten. */
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
- export interface BodySchemaIssue {
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 {};
@@ -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
- export interface DerivedMetadata {
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 {};
@@ -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: {
@@ -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
- export interface TaskStarter {
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 {};
@@ -12,7 +12,7 @@ export interface EgressResponse {
12
12
  headers: Record<string, string>;
13
13
  body: Buffer;
14
14
  }
15
- export interface EgressOptions {
15
+ interface EgressOptions {
16
16
  grantOrigins: string[];
17
17
  timeoutMs?: number;
18
18
  maxRequestBytes?: number;
@@ -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
- export interface ExplainedParameter {
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
- export interface ExplainOptions {
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
- export interface ArtifactEntry {
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
- export interface LockedArtifact extends ArtifactEntry {
20
+ interface LockedArtifact extends ArtifactEntry {
23
21
  catalog: {
24
22
  tag: string;
25
23
  commit: string;
26
24
  };
27
25
  }
28
- export interface ExtensionLock {
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
- export interface ArchiveLimits {
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 type UnknownRecord = Record<string, unknown>;
3
- export declare const isRecord: (v: unknown) => v is UnknownRecord;
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
- export interface GithubTransport {
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
- export interface GithubTransportConfig {
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
- export type FunctionPoolOptions = SandboxPoolOptions;
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
- export interface PreparedResponse {
10
+ interface PreparedResponse {
11
11
  status: number;
12
12
  headers: HeaderPair[];
13
13
  cookies: string[];
14
14
  body: ResponseBody;
15
15
  }
16
- export interface ErrorAnswer {
16
+ interface ErrorAnswer {
17
17
  status: number;
18
18
  headers: HeaderPair[];
19
19
  body: string | undefined;
20
20
  }
21
- export interface ResponseOptions {
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 {};