@jimhoyd/urlcode 0.4.2 → 0.4.6

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 (214) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +57 -14
  2. package/.claude/skills/urlcode-operations/SKILL.md +4 -0
  3. package/README.md +19 -15
  4. package/SECURITY.md +5 -3
  5. package/dist/BUILD-MANIFEST.json +30 -27
  6. package/dist/agent-context.js +82 -0
  7. package/dist/agents-guide.js +42 -42
  8. package/dist/authoring.js +12 -2
  9. package/dist/body-schema.js +159 -0
  10. package/dist/build-cloudflare.js +2 -0
  11. package/dist/capabilities.js +1 -1
  12. package/dist/cli.js +28 -12
  13. package/dist/config.js +74 -6
  14. package/dist/context.js +4 -6
  15. package/dist/errors.js +3 -1
  16. package/dist/examples.js +1 -1
  17. package/dist/extensions.js +60 -2
  18. package/dist/http-policy.js +19 -4
  19. package/dist/http-response.js +2 -2
  20. package/dist/init-with.js +71 -9
  21. package/dist/mcp.js +15 -2
  22. package/dist/pattern-guard.js +32 -0
  23. package/dist/policies/security.js +0 -0
  24. package/dist/project-tests.js +35 -11
  25. package/dist/readiness.js +184 -32
  26. package/dist/recipes.js +1 -1
  27. package/dist/router.js +17 -0
  28. package/dist/runtime.js +26 -3
  29. package/dist/scaffold.js +0 -0
  30. package/dist/server.js +26 -3
  31. package/dist/site.js +0 -0
  32. package/dist/tooling.js +2 -1
  33. package/dist/types/agent-context.d.ts +44 -0
  34. package/dist/types/authoring.d.ts +3 -1
  35. package/dist/types/body-schema.d.ts +58 -0
  36. package/dist/types/config.d.ts +10 -2
  37. package/dist/types/context.d.ts +1 -1
  38. package/dist/types/errors.d.ts +9 -1
  39. package/dist/types/examples.d.ts +1 -1
  40. package/dist/types/extensions.d.ts +47 -2
  41. package/dist/types/http-policy.d.ts +3 -0
  42. package/dist/types/init-with.d.ts +10 -1
  43. package/dist/types/pattern-guard.d.ts +10 -0
  44. package/dist/types/project-tests.d.ts +9 -0
  45. package/dist/types/readiness.d.ts +68 -0
  46. package/dist/types/recipes.d.ts +1 -1
  47. package/dist/types/runtime.d.ts +4 -0
  48. package/dist/types/server.d.ts +9 -1
  49. package/dist/types/tooling.d.ts +1 -0
  50. package/dist/types/types.d.ts +13 -0
  51. package/dist/types.js +10 -2
  52. package/dist/typescript-authoring.js +5 -3
  53. package/dist/verify-deployment.js +16 -10
  54. package/examples/body-validation/README.md +16 -0
  55. package/examples/body-validation/example.yaml +17 -0
  56. package/examples/body-validation/tests/requests.json +15 -0
  57. package/examples/body-validation/urlcode.yaml +34 -0
  58. package/examples/coverage-waiver/README.md +8 -0
  59. package/examples/coverage-waiver/example.yaml +16 -0
  60. package/examples/coverage-waiver/functions/notes.mjs +2 -0
  61. package/examples/coverage-waiver/tests/requests.json +3 -0
  62. package/examples/coverage-waiver/urlcode.yaml +10 -0
  63. package/examples/data-dir/README.md +39 -0
  64. package/examples/data-dir/data/welcome.txt +1 -0
  65. package/examples/data-dir/example.yaml +22 -0
  66. package/examples/data-dir/functions/note.mjs +18 -0
  67. package/examples/data-dir/tests/requests.json +6 -0
  68. package/examples/data-dir/urlcode.yaml +6 -0
  69. package/examples/lifecycle/README.md +15 -0
  70. package/examples/lifecycle/example.yaml +19 -0
  71. package/examples/lifecycle/functions/notes.mjs +29 -0
  72. package/examples/lifecycle/tests/requests.json +12 -0
  73. package/examples/lifecycle/urlcode.yaml +29 -0
  74. package/examples/not-found/README.md +10 -0
  75. package/examples/not-found/example.yaml +17 -0
  76. package/examples/not-found/public/404.html +3 -0
  77. package/examples/not-found/public/index.html +3 -0
  78. package/examples/not-found/tests/requests.json +7 -0
  79. package/examples/not-found/urlcode.yaml +7 -0
  80. package/examples/shared-blocks/README.md +11 -0
  81. package/examples/shared-blocks/example.yaml +17 -0
  82. package/examples/shared-blocks/tests/requests.json +7 -0
  83. package/examples/shared-blocks/urlcode.yaml +37 -0
  84. package/llms-full.txt +364 -49
  85. package/llms.txt +43 -5
  86. package/package.json +15 -22
  87. package/recipes/static-page/README.md +9 -0
  88. package/recipes/static-page/public/index.html +11 -0
  89. package/recipes/static-page/recipe.yaml +21 -0
  90. package/recipes/static-page/tests/requests.json +22 -0
  91. package/recipes/static-page/urlcode.yaml +7 -0
  92. package/recipes/static-plus-api/README.md +6 -0
  93. package/recipes/static-plus-api/urlcode.yaml +4 -0
  94. package/recipes/store-crud/README.md +53 -0
  95. package/recipes/store-crud/recipe.yaml +31 -0
  96. package/recipes/store-crud/tests/requests.json +18 -0
  97. package/recipes/store-crud/urlcode.yaml +18 -0
  98. package/schemas/urlcode.schema.json +125 -60
  99. package/skills/urlcode/SKILL.md +47 -27
  100. package/starters/default/AGENTS.md +43 -43
  101. package/starters/page/README.md +14 -0
  102. package/starters/page/public/index.html +12 -0
  103. package/starters/page/tests/requests.json +17 -0
  104. package/starters/page/urlcode.yaml +6 -0
  105. package/.claude-plugin/marketplace.json +0 -18
  106. package/CONTRIBUTING.md +0 -148
  107. package/ROADMAP.md +0 -70
  108. package/docs/AI-AUTHORING.md +0 -339
  109. package/docs/ASSETS.md +0 -107
  110. package/docs/AUTH-BACKUP.md +0 -32
  111. package/docs/AWS.md +0 -86
  112. package/docs/BEST-PRACTICES.md +0 -276
  113. package/docs/BULK.md +0 -79
  114. package/docs/CAPABILITIES.md +0 -192
  115. package/docs/CAPACITY.md +0 -305
  116. package/docs/CI-FOLLOWUP-2026-09-19.md +0 -97
  117. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +0 -322
  118. package/docs/CI.md +0 -147
  119. package/docs/CLOUDFLARE.md +0 -109
  120. package/docs/CODEBASE-AUDIT-2026-09-20.md +0 -284
  121. package/docs/COMPLIANCE.md +0 -239
  122. package/docs/COMPOSING-A-SITE.md +0 -287
  123. package/docs/CONDITIONS.md +0 -74
  124. package/docs/CONTAINER-PROMOTION.md +0 -74
  125. package/docs/DEPLOYMENT-CHECKS.md +0 -108
  126. package/docs/DEVELOPMENT-PIPELINE.md +0 -304
  127. package/docs/EGRESS.md +0 -125
  128. package/docs/EXTENSIONS.md +0 -403
  129. package/docs/FRAMEWORK.md +0 -217
  130. package/docs/FUNCTION-SECURITY.md +0 -251
  131. package/docs/HTTP.md +0 -129
  132. package/docs/INSTALL.md +0 -128
  133. package/docs/INTERCHANGE.md +0 -134
  134. package/docs/LOAD-TESTING.md +0 -91
  135. package/docs/LOCAL-DEVELOPMENT.md +0 -102
  136. package/docs/MIDDLEWARE-EXAMPLES.md +0 -75
  137. package/docs/MIDDLEWARE.md +0 -102
  138. package/docs/MONITORING.md +0 -115
  139. package/docs/OBSERVABILITY.md +0 -222
  140. package/docs/OPEN-DECISIONS.md +0 -149
  141. package/docs/OPERATIONAL-PROOF.md +0 -41
  142. package/docs/OPERATIONS.md +0 -201
  143. package/docs/ORGANIZATION.md +0 -135
  144. package/docs/PERFORMANCE.md +0 -72
  145. package/docs/PLUGINS.md +0 -271
  146. package/docs/POLICIES.md +0 -211
  147. package/docs/PRERENDER.md +0 -245
  148. package/docs/PROJECT-DIRECTION.md +0 -118
  149. package/docs/PROVIDER-VERIFICATION.md +0 -84
  150. package/docs/READINESS.md +0 -153
  151. package/docs/README.md +0 -93
  152. package/docs/RECIPES.md +0 -99
  153. package/docs/RELEASE-0.4.0-alpha.3.md +0 -50
  154. package/docs/RELEASE-0.4.1.md +0 -73
  155. package/docs/RELEASE-0.4.2.md +0 -30
  156. package/docs/RELEASE-READINESS.md +0 -146
  157. package/docs/RELEASE-SECURITY.md +0 -100
  158. package/docs/RESILIENCE.md +0 -161
  159. package/docs/ROUTING.md +0 -92
  160. package/docs/SANDBOX-REVIEW.md +0 -72
  161. package/docs/SCAFFOLDING.md +0 -70
  162. package/docs/SECURITY-AUDIT.md +0 -164
  163. package/docs/SITE.md +0 -150
  164. package/docs/SPECIFICATION.md +0 -359
  165. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +0 -288
  166. package/docs/SPIKE-BUSINESS-SUITE.md +0 -1029
  167. package/docs/SPIKE-CORE-LAYERING.md +0 -368
  168. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +0 -207
  169. package/docs/STANDARDS.md +0 -311
  170. package/docs/STARTERS.md +0 -83
  171. package/docs/STATIC.md +0 -105
  172. package/docs/TOOLING.md +0 -298
  173. package/docs/TUNNELS.md +0 -72
  174. package/docs/TYPESCRIPT-AUTHORING.md +0 -87
  175. package/docs/TYPESCRIPT.md +0 -123
  176. package/docs/VERCEL.md +0 -114
  177. package/docs/VERSION-ALIGNMENT.md +0 -88
  178. package/docs/YAML-GUIDE.md +0 -57
  179. package/docs/YAML-REFERENCE.md +0 -449
  180. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +0 -68
  181. package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +0 -102
  182. package/docs/archive/2026-09-19/NEXT-PHASE-PLAN.md +0 -108
  183. package/docs/archive/2026-09-19/NEXT-STEPS.md +0 -646
  184. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +0 -277
  185. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +0 -186
  186. package/docs/archive/2026-09-19/ROADMAP.md +0 -387
  187. package/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md +0 -430
  188. package/docs/archive/2026-09-19/SPIKE-EXTENSIONS.md +0 -492
  189. package/docs/archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md +0 -365
  190. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +0 -778
  191. package/docs/archive/2026-09-19/USABILITY-REVIEW.md +0 -139
  192. package/docs/archive/2026-09-20/OPEN-DECISIONS-COMPLETED.md +0 -116
  193. package/docs/archive/README.md +0 -29
  194. package/docs/policies/agents.md +0 -182
  195. package/docs/policies/cache.md +0 -152
  196. package/docs/policies/compression.md +0 -169
  197. package/docs/policies/contract.md +0 -52
  198. package/docs/policies/hardened.md +0 -56
  199. package/docs/policies/interoperability.md +0 -169
  200. package/docs/policies/operations.md +0 -45
  201. package/docs/policies/security.md +0 -161
  202. package/docs/policies/throttle.md +0 -103
  203. package/docs/yaml/assets.md +0 -36
  204. package/docs/yaml/conditions.md +0 -20
  205. package/docs/yaml/functions.md +0 -168
  206. package/docs/yaml/middleware.md +0 -31
  207. package/docs/yaml/organization.md +0 -74
  208. package/docs/yaml/policies.md +0 -37
  209. package/docs/yaml/redirects.md +0 -64
  210. package/docs/yaml/responses.md +0 -57
  211. package/docs/yaml/site.md +0 -24
  212. package/packaging/claude-plugin/.claude-plugin/plugin.json +0 -19
  213. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +0 -130
  214. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +0 -108
@@ -1,91 +0,0 @@
1
- # Load testing a deployment
2
-
3
- `urlcode benchmark` sends a project's own workload at a running runtime and
4
- reports throughput, tail latency and how many requests were refused. It answers
5
- one question — *does this deployment meet its budget under this load* — and
6
- nothing else. It is not a capacity model, not a soak harness and not a
7
- substitute for the drills in [release readiness](RELEASE-READINESS.md).
8
-
9
- ## Measure the deployment, not a fresh local snapshot
10
-
11
- By default the command starts a private runtime and loads that. Useful for a
12
- regression budget in CI; useless for judging a deployment, because it bypasses
13
- your TLS termination, proxy, network and host.
14
-
15
- `--target` sends the same workload at something already running:
16
-
17
- ```sh
18
- urlcode benchmark --project . --target https://links.example \
19
- --requests 5000 --concurrency 8 --warmup 100 --max-p95-ms 50
20
- ```
21
-
22
- The project is still required: it supplies the routes and fixtures that make up
23
- the workload. Load goes to the target; the local runtime is only consulted for
24
- the plan. `rssMiB` is `null` in this mode, because the memory of the machine
25
- running the benchmark says nothing about the machine under test.
26
-
27
- Point `--target` only at systems you operate. A load generator aimed at someone
28
- else's host is an attack, whatever the intent.
29
-
30
- ## Warm up before measuring
31
-
32
- `--warmup N` sends and discards N requests first. A cold asset snapshot, an
33
- empty connection pool and a just-started function worker are not what a latency
34
- budget is about. Warm-up traffic reaches the deployment and is excluded from
35
- every statistic; `warmupRequests` records how many.
36
-
37
- ## Reading the report
38
-
39
- | Field | Meaning |
40
- |---|---|
41
- | `pass` | Every request completed, none failed, and `p95Ms` met `--max-p95-ms` if given. |
42
- | `completed` / `complete` | How many requests ran. Short of `requested` means the `--seconds` budget ended the run first — the numbers describe a shorter run than you asked for. |
43
- | `failed` | Responses that did not match the expectation for that case. |
44
- | `transportErrors` | Connections that never produced a response: refused, reset or timed out. Distinct from a deployment deliberately refusing work. |
45
- | `shedResponses` | 503 and 504 responses — admission, function-pool capacity or a deadline. Not errors so much as the runtime protecting itself. |
46
- | `p50Ms` / `p95Ms` / `p99Ms` | Latency percentiles over completed requests. |
47
- | `statuses` | Full status histogram, so a "pass" that is secretly all redirects is visible. |
48
- | `rssMiB` | Local mode only. |
49
-
50
- ## A worked example
51
-
52
- Against the default starter, which has one function route:
53
-
54
- ```
55
- $ urlcode serve --project starters/default --port 3456
56
- $ urlcode benchmark --project starters/default --target http://127.0.0.1:3456 \
57
- --requests 60 --warmup 10 --concurrency 4
58
- pass: false failed: 6 shedResponses: 6 p95Ms: 6.7
59
- ```
60
-
61
- Six of sixty requests were shed. Not a bug: `serve` runs **2 function workers**
62
- by default, so a concurrency of 4 exceeds the pool and the runtime returns 503
63
- rather than queueing without bound. Raising the pool:
64
-
65
- ```
66
- $ urlcode serve --project starters/default --port 3457 --workers 8
67
- $ urlcode benchmark ... --concurrency 4
68
- pass: true failed: 0 shedResponses: 0 statuses: {"200":24,"302":36}
69
- ```
70
-
71
- That is the loop this tool exists for: measure, read `shedResponses`, tune the
72
- [capacity controls](CAPACITY.md), measure again. More workers cost memory and
73
- CPU; the right number is the one your workload and host justify, not the
74
- largest one that makes a number go green.
75
-
76
- ## What a passing run does not prove
77
-
78
- - **GET and HEAD only.** Function routes with request bodies are not exercised.
79
- `workload` states this in every report.
80
- - **Redirects are not followed**, so a redirect's destination is never loaded.
81
- - **One client, one host, no slow peers.** Tail latency under adversarial
82
- clients, connection churn or packet loss is not measured.
83
- - **Not a soak.** `--seconds` caps at 300. Memory drift, file-descriptor leaks
84
- and log-volume growth need a long run watched through
85
- [monitoring](MONITORING.md).
86
- - **A number from one environment is not a claim about another.** Record the
87
- runtime and application revisions, host, and command with any figure you keep.
88
-
89
- `scripts/operational-drills.ts` covers the adjacent ground — mixed
90
- native/function load, an invalid reload and rollback — as a local proof,
91
- never a statement about production.
@@ -1,102 +0,0 @@
1
- # Local development
2
-
3
- Use Node.js 22.18+ and npm (CI targets Node 22, 24 and 26). The runtime is
4
- written in TypeScript and runs from source with no build step: `npm run dev`
5
- is `node src/cli.ts`, which Node runs through its own type stripping (that is
6
- why a contributor needs 22.18, while an installed package still runs on 22.13).
7
- `npm run typecheck` is the type gate and part of `npm run verify`.
8
- `npm run build` emits the JavaScript in `dist/` that the package and container
9
- ship, together with the declarations; `dist` is never committed. Make is an optional
10
- shortcut layer; npm and the CLI work on Windows, macOS and Linux. No global
11
- package install, hosting account, database or Docker is needed for the local loop.
12
-
13
- For the repository's prose-only checks, CI selection and release helper commands,
14
- see [the development pipeline](DEVELOPMENT-PIPELINE.md).
15
-
16
- ## Try the runtime
17
-
18
- From the runtime checkout, `make dev` installs locked dependencies if needed and
19
- starts the function/redirect starter at http://127.0.0.1:3000. Without Make, run `npm ci`
20
- once, then `npm run dev`. Dependency installation requires npm registry access;
21
- the examples themselves work locally.
22
-
23
- Try `/hello/Ada` (sandboxed function) and `/go` (redirect).
24
- For pages/files/downloads, run `make dev PROJECT=examples/assets` instead. Edit the files in
25
- `starters/default/` to experiment. `dev` watches configuration, source and assets;
26
- invalid edits leave the last valid snapshot running. Ctrl+C drains and stops it.
27
- Runtime source changes under `src/` require restarting the dev command; project
28
- reload is not a runtime-code watcher.
29
-
30
- ## Own an application
31
-
32
- Run `make init DEST=../my-links`, then
33
- `make dev PROJECT=../my-links`. The CLI equivalents from the runtime checkout:
34
-
35
- ```sh
36
- npm run init -- ../my-links
37
- npm run dev -- --project ../my-links
38
- npm run validate -- --project ../my-links
39
- npm run test:project -- --project ../my-links
40
- ```
41
-
42
- There is one starter, containing both examples. Initialization never overwrites
43
- an existing directory. Once created, edits belong to your app repository; upgrading
44
- the runtime does not regenerate them. Each starter has a Makefile for its own
45
- `dev`, `serve`, `validate`, `test` and `doctor` commands. It uses an installed
46
- `urlcode`, or an explicit runtime command:
47
-
48
- ```sh
49
- cd ../my-links
50
- make dev URLCODE='node /path/to/urlcode/src/cli.ts'
51
- # Without Make or a global install:
52
- node /path/to/urlcode/src/cli.ts dev
53
- ```
54
-
55
- ## Command reference (runtime checkout)
56
-
57
- | Make | npm | Purpose |
58
- |---|---|---|
59
- | `make setup` | `npm ci` | Install exact dependencies; replaces node_modules |
60
- | `make dev` | `npm run dev` | Watched function/redirect starter, local dotenv |
61
- | `make validate` | `npm run validate` | Validate the app and local bindings |
62
- | `make test-project` | `npm run test:project` | App HTTP assertions, redirects not followed |
63
- | `make test` | `npm test` | Runtime unit, HTTP and sandbox tests |
64
- | — | `npm run typecheck` | Strict TypeScript check of runtime, scripts, tests and benchmarks |
65
- | `make verify` | `npm run verify` | Lint, type check, syntax/JSON checks and runtime tests |
66
- | — | `npm run build` | Emit `dist/` (stripped JavaScript and declarations); never committed |
67
- | `make test-package` | `npm run test:package` | Actual archive install and starter tests; registry access |
68
- | `make serve` | `npm run serve` | Fixed snapshot, no watcher or dotenv |
69
- | `make doctor` | `npm run doctor` | Runtime/platform details |
70
-
71
- Run `make help` for shortcuts. `PROJECT` defaults to `starters/default`; `HOST`
72
- to `127.0.0.1`; `PORT` to `3000`. Quote paths containing spaces:
73
-
74
- ```sh
75
- make dev PROJECT="../my-links demo" PORT=3001
76
- npm run dev -- --project "../my-links demo" --port 3001
77
- ```
78
-
79
- Make automatically runs `npm ci` when its dependency marker is missing or older
80
- than package metadata. Use `make setup` after manually changing node_modules.
81
- Do not run setup concurrently with a running dev server or tests.
82
-
83
- ## Environment and troubleshooting
84
-
85
- `.env.local` belongs in the selected app directory and is ignored by Git; process
86
- environment values take precedence. No starter requires secret values. Do not
87
- copy placeholder credentials into a working secret store. External env/secret
88
- bindings still need an operator policy outside the app, pinned to its config/code.
89
- Inspect and set it up using the [security guide](FUNCTION-SECURITY.md); pass it
90
- through the CLI, for example `npm run dev -- --project ../my-links --policy /path/to/policy.json`.
91
- Local convenience never bypasses the function sandbox or grants permissions.
92
-
93
- - Port busy: change `PORT=3001` or pass `--port 3001` through npm.
94
- - Missing Make: use the npm commands; Make is not a runtime dependency.
95
- - Invalid edits: run validation for diagnostics; fix the project and the watcher retries.
96
- - Changed runtime source: stop and restart dev, then `npm run typecheck`. Changed app source: reload is automatic.
97
- - Missing dependency or wrong Node: check `node --version`, then `npm ci`.
98
- - Need access from another device: explicitly use `HOST=0.0.0.0` or `--host 0.0.0.0`;
99
- this exposes the development listener to your network. Loopback remains the default.
100
-
101
- Production deployment uses `serve` behind the HTTPS setup described in
102
- [operations](OPERATIONS.md). These shortcuts do not provision providers or select a license.
@@ -1,75 +0,0 @@
1
- # Middleware examples
2
-
3
- Fourteen reusable middleware modules covering fifteen patterns (`auth.mjs`
4
- exports both `bearer` and `basic`), each in
5
- [`examples/cookbook/middleware`](../examples/cookbook/middleware) with a route in
6
- [`routes/middleware.yaml`](../examples/cookbook/routes/middleware.yaml) and request
7
- fixtures in the cookbook tests. The same modules ship as the `middleware`
8
- [local recipe](RECIPES.md):
9
-
10
- ```sh
11
- urlcode recipes add middleware --out ./my-middleware
12
- urlcode test --project ./my-middleware
13
- ```
14
-
15
- They cover what Express, Koa, Fastify, Hono, Next.js and edge runtimes usually
16
- ship as middleware, rewritten for URLCode's [guest API](MIDDLEWARE.md): text/JSON
17
- `Request`/`Response`, `Headers`, `context.inputs/args/env/secrets/state`, timers
18
- and nothing else. There is no `crypto`, `URL`, `fetch`, storage or console, and
19
- `context.state` dies with the request. Patterns that need any of those are listed
20
- at the end so nobody wastes time porting them.
21
-
22
- | Pattern | Module | Framework equivalent | Demonstrates |
23
- | --- | --- | --- | --- |
24
- | Bearer token gate | `auth.mjs` `bearer` | Hono `bearerAuth`, `express-bearer-token` | Early 401, `www-authenticate`, constant-time compare against an `env` binding |
25
- | Basic authentication | `auth.mjs` `basic` | Hono `basicAuth`, `express-basic-auth` | Hand-written base64 (no `atob`), both checks always evaluated, `state.user` |
26
- | CORS | `cors.mjs` | Express `cors`, Hono `cors` | `OPTIONS` answered before the handler, origin allowlist, `vary: origin` |
27
- | Correlation id and timing | `request-id.mjs` | Hono `requestId`/`timing`, Express `response-time` | Validating a caller header, `server-timing`, coexisting with the runtime's own `x-request-id` |
28
- | Maintenance switch | `maintenance.mjs` | Next.js and Netlify Edge maintenance examples | 503 with `retry-after`, bypass header, flipping behavior from a binding |
29
- | Error boundary | `errors.mjs` | Koa `onerror`, Express error handlers | Catching a downstream throw, JSON 500 instead of a bare 502 |
30
- | JSON envelope | `envelope.mjs` | Response transformers | Reading a function body once, passing native bodies through untouched |
31
- | Content negotiation | `negotiate.mjs` | Express `res.format` | Parsing `accept` with q-values, 406, `vary: accept` |
32
- | Method override | `methods.mjs` `override` | Express `method-override` | Bounded tunneling through POST, 405 with `allow` |
33
- | ETag and 304 | `etag.mjs` | Express `etag`, Fastify `@fastify/etag` | FNV-1a weak tag, `if-none-match`, null-body 304 |
34
- | A/B bucket | `bucket.mjs` | Vercel and Cloudflare A/B examples | Cookie parsing, `set-cookie`, replacing a native redirect |
35
- | Locale redirect | `locale.mjs` | Next.js i18n middleware | `accept-language` ranking, allowlisted languages, `vary` |
36
- | Referer allowlist | `referer.mjs` | Hotlink protection rules | Gating a native download without reading it |
37
- | Body validation | `body.mjs` | `express-validator`, Fastify schemas | Single-use body, 422 error list, handoff through `state` |
38
- | Debug echo | `debug.mjs` | Request loggers | Inspecting inputs, args and redacted headers when the console is silent |
39
-
40
- ## Reading the modules
41
-
42
- Every module follows the shape in [middleware](MIDDLEWARE.md): read the request,
43
- optionally return early, otherwise `await next()` once and return a `Response`.
44
- Three habits recur and are worth copying:
45
-
46
- - **Configuration lives in bindings.** Tokens, allowlists and switches are read
47
- from `context.env`. The cookbook binds literal values so it runs without grants;
48
- a deployed project uses `{secret: name}` for credentials and an operator grant.
49
- - **Native bodies stay opaque.** `envelope`, `negotiate` and `etag` only rewrite a
50
- body when the downstream response is a function response with a readable
51
- content type. `bucket`, `locale` and `referer` wrap native redirects and
52
- downloads without touching their bytes; to change the destination they return
53
- a new `Response` instead.
54
- - **Chains compose through `state`.** `/fragile` runs `request-id` before
55
- `errors`, so the fallback JSON carries the correlation id. `/profile` parses
56
- the body once in middleware and the function reads `context.state.body`.
57
-
58
- ## Limits these examples respect
59
-
60
- - **No `crypto`.** The auth modules compare a shared token; they cannot verify
61
- HMAC signatures, JWTs or password hashes. Signed URLs would be a runtime
62
- feature, not a middleware example.
63
- - **No cross-request state.** Rate limiting, caching, sessions and CSRF tokens
64
- need storage the guest does not have. Throttling and cache headers exist as
65
- native [policies](POLICIES.md) instead.
66
- - **No logging target.** The guest console is a no-op; `debug.mjs` returns the
67
- information to the caller instead, and only when both a binding and a header
68
- ask for it. Remove that route before publishing a project.
69
- - **Runtime headers win.** The runtime stamps `x-request-id` on every response,
70
- which is why the tracing example uses `x-correlation-id`. YAML
71
- `response.headers` also override middleware headers.
72
-
73
- Each cookbook route has fixtures for its success path, its early responses, its
74
- validation failures and every declared method, which is what `urlcode audit`
75
- expects before it reports a middleware-wrapped route as covered.
@@ -1,102 +0,0 @@
1
- # Middleware
2
-
3
- Middleware is reusable JavaScript around any route handler. It is optional and
4
- route-local; plain redirects and assets retain their native fast path when no
5
- middleware is attached. Middleware runs trusted and unsandboxed by default,
6
- in-process with full Node access, the same as a `function` route; add
7
- `sandbox: true` on the route to run the whole chain isolated instead (see
8
- [trust model and sandbox opt-in](FUNCTION-SECURITY.md)).
9
-
10
- ```yaml
11
- version: "1"
12
- routes:
13
- /go:
14
- middleware:
15
- - source: middleware/headers.mjs
16
- export: default
17
- redirect:
18
- url: https://example.com
19
- ```
20
-
21
- ```js
22
- export default async function headers(request, context, next) {
23
- context.state.startedAt = Date.now();
24
- const response = await next();
25
- response.headers.set('x-example-middleware', 'active');
26
- return response;
27
- }
28
- ```
29
-
30
- Paths resolve from the project root. Up to 16 entries are allowed; each accepts
31
- `source` and optional `export` (default `default`). Reuse a module across routes.
32
- There are no global middleware settings or middleware-specific argument fields.
33
- This is URLCode's small portable API, not Express/Node middleware compatibility.
34
- Fourteen ready-to-copy patterns (auth, CORS, tracing, error boundary, ETag and
35
- more) are in [middleware examples](MIDDLEWARE-EXAMPLES.md).
36
-
37
- ## Order and responses
38
-
39
- For `[first, second]`, execution is first-before, second-before, handler,
40
- second-after, first-after. Call `await next()` to obtain the downstream response.
41
- Call it at most once, during the middleware invocation, with no arguments.
42
- Always return a `Response`. Return early to skip downstream middleware and the
43
- handler, for example `return new Response('Denied', {status: 403})`.
44
- Middleware may catch downstream JavaScript exceptions and return a fallback.
45
-
46
- Every middleware and the function share the same request and context. Validated
47
- `inputs`, function `args`, and route-scoped `env`/`secrets` are available along
48
- with a fresh `context.state` object for this chain. State never survives the
49
- request. Header edits are visible downstream; editing the request does not
50
- reroute it or change already validated inputs. Request bodies are single-use:
51
- reading one consumes it for downstream code. On a `sandbox: true` route there is
52
- no `clone()` or streaming API at all, so pass parsed data through
53
- `context.state`; a trusted route receives Node's own `Request`/`Response` and so
54
- does have `clone()`, but passing parsed data through `context.state` keeps the
55
- chain portable between the two modes.
56
-
57
- Function responses support the existing text/JSON guest API. To transform their
58
- body, read it and return a new `Response`. On a `sandbox: true` route, native
59
- redirect/respond/page/static/download bodies are opaque and cannot be read
60
- through `text()` or `json()`; a trusted chain receives them as an ordinary
61
- `Response` and can read them, so wrapping `respond: {text: hello}` and returning
62
- `HELLO` works there and fails in the guest.
63
- Returning the same native response preserves original bytes, including binary
64
- files, ranges and HEAD lengths. You may add headers, but cannot change its
65
- original status or existing native headers while preserving that body. To replace
66
- status, destination or content, return a new `Response` instead. Replacement
67
- responses follow the normal response size limits, and (on a `sandbox: true`
68
- route) the guest's text/JSON constraints. To wrap a shared
69
- template around file content, render it through a function at build time and
70
- publish the result: see [prerendering](PRERENDER.md).
71
-
72
- Route selection, enabled/expiry checks, methods and input/body validation run
73
- before middleware. Their errors do not pass through the chain. A missing file
74
- inside a selected static mount is a downstream 404 response. YAML
75
- `response.headers` apply last and override matching middleware headers. Runtime
76
- framing and asset metadata protections still apply.
77
-
78
- ## Trust, isolation and testing
79
-
80
- The whole chain and handler run as one unit, in one execution mode, chosen by
81
- the route's `sandbox` field — not a per-middleware-entry choice. By default
82
- (`sandbox` false/absent) that means trusted, in-process execution with full
83
- Node access and no fixed worker-pool ceiling. With `sandbox: true` it means
84
- one fresh QuickJS/WASM guest with one memory budget and one deadline: no
85
- Node, filesystem, shell, fetch or ambient environment, and modules limited to
86
- this route's declared dependency graph. Either way, all middleware on a route
87
- receive that route's approved bindings, so review the whole chain; source
88
- changes invalidate grants. See [trust model and sandbox opt-in](FUNCTION-SECURITY.md).
89
-
90
- Invalid responses and repeated `next()` calls fail with 502, and the deadline
91
- returns 504 either way. A `sandbox: true` chain also sheds load with 503 when
92
- the shared worker pool is exhausted, and cannot extend the deadline or catch
93
- the outer worker termination; forgotten downstream work is still drained
94
- within it. A trusted chain has no worker pool to exhaust (see
95
- [capacity](CAPACITY.md)), but its deadline is a race against the call's own
96
- promise rather than a forced kill — it cannot preempt code that blocks the
97
- event loop synchronously.
98
-
99
- Include explicit request fixtures for middleware-wrapped routes: test success,
100
- early responses, validation failures and every configured method. Audit will
101
- report missing coverage instead of assuming native handler behavior. Benchmark
102
- with middleware enabled to measure its actual overhead, sandboxed or trusted.
@@ -1,115 +0,0 @@
1
- # Monitoring a URLCode deployment
2
-
3
- URLCode emits one JSON object per line on stdout, answers two unauthenticated
4
- probes and, when an operator opts in, serves in-process counters in Prometheus
5
- format. Everything below is built from those sources, and the example
6
- configuration in [`examples/monitoring/`](../examples/monitoring/) is the
7
- runnable form of it. The contract behind all of it (the event catalogue, the
8
- observer interface for in-process sinks, the metrics snapshot and the
9
- exposition endpoint) is in [observability](OBSERVABILITY.md).
10
-
11
- What you can measure is shaped by a deliberate logging decision: records carry
12
- **no URL, query string, header, body, binding or user exception text**. You can
13
- alert on error rates and latency per configured route; you cannot get per-URL
14
- analytics from these logs, and adding it would mean logging user data. See
15
- [operations](OPERATIONS.md).
16
-
17
- ## Probes
18
-
19
- | Endpoint | Meaning | Alert when |
20
- |---|---|---|
21
- | `GET /_urlcode/health` | The process is alive and not shutting down. | It fails at all (`UrlcodeDown`). |
22
- | `GET /_urlcode/ready` | The active snapshot and every function worker are available. | It fails for longer than replacement takes (`UrlcodeNotReady`). |
23
-
24
- Both return `{status, version, routes}`. A third endpoint,
25
- `GET /_urlcode/metrics`, exists only with `startServer({ metrics: true })` and
26
- answers 404 otherwise; see [metrics](#metrics) below. Keep both: alerting only on health hides
27
- a process that is up and serving nothing, while alerting only on readiness pages
28
- for a brief, self-healing worker replacement. Probes have their own bounded
29
- admission budget (`--max-in-flight-health`), so they keep answering while the
30
- application sheds load — but they are unauthenticated and disclose the
31
- configuration digest and route count, so keep them internal.
32
-
33
- ## Log records
34
-
35
- | Event | Fields | Why it matters |
36
- |---|---|---|
37
- | `request` | `requestId`, `status`, `durationMs`; plus `method` and `route` with `--request-log detailed` | Error rate and latency per route. `route` is the configured pattern such as `/u/{id}`, never the requested path. |
38
- | `reload` | `status` (`ok`/`rejected`); `version` and `routes` on `ok` | A `rejected` reload means the last-good snapshot is still serving and a deploy did not take effect. |
39
- | `watch` | `status` | Development watcher failure; not used by `serve`. |
40
- | `function_worker` | `status` (`started`/`restarting`), `slot`; `attempt` and `delayMs` on `restarting` | Sustained `restarting` means a function is failing on real traffic. |
41
- | `logs_dropped` | `count` | The logger shed records because the collector fell behind. Every other signal is unreliable while this fires. |
42
- | `observer` | `status` (`failed`), `name` | An in-process observer threw; the request was unaffected. Written to the log only, never to observers. Sustained failures mean the observer's own sink is broken. |
43
- | `throttle`, `agents`, `cache` | `route`, `outcome`; `remaining` or `list` | Policy decisions; see [policies](POLICIES.md). `throttle` logs `allowed` only in report mode. |
44
- | `site` | `key`, `path`, `status` (`generated`/`shadowed`); or `severity` (`info`/`warning`) and `message` | Activation records for [site conventions](SITE.md). `shadowed` means a declared route took the path; an `info`/`warning` line reports an omitted `Sitemap:` line (no `--origin`), skipped list names or a far-future `security.txt` expiry. |
45
-
46
- Startup prints `listening` with the effective `origin`, which is what functions
47
- and absolute URLs see. Behind a proxy or tunnel this must be your public origin;
48
- forwarded headers are deliberately not trusted. See [tunnels](TUNNELS.md).
49
-
50
- ## Metrics
51
-
52
- `startServer({ metrics: true })` serves `GET /_urlcode/metrics` in Prometheus
53
- text format: requests by status class and by configured route, in-flight
54
- gauges, shed 503s, reloads, worker restarts and healthy slots, policy
55
- outcomes, dropped logs and observer errors, all prefixed
56
- `urlcode_`. The same numbers are available in process as `app.metrics()`. The
57
- endpoint shares the probes' admission budget and bind host and is off by
58
- default; it discloses route patterns and traffic shape, so keep it internal
59
- like the probes. [`examples/monitoring/prometheus-scrape.yaml`](../examples/monitoring/prometheus-scrape.yaml)
60
- scrapes it directly, without a log pipeline. Field names and label sets are
61
- fixed in [observability](OBSERVABILITY.md).
62
-
63
- If you would rather keep everything in one process, an observer passed as
64
- `startServer({ observers })` receives every log record and a periodic metrics
65
- snapshot; the same page shows an OpenTelemetry sketch.
66
-
67
- ## What to alert on
68
-
69
- The example rules in
70
- [`examples/monitoring/prometheus-rules.yaml`](../examples/monitoring/prometheus-rules.yaml)
71
- cover:
72
-
73
- - **`UrlcodeDown`** — liveness probe failing. Process-level; check the supervisor.
74
- - **`UrlcodeNotReady`** — readiness failing for more than a few minutes.
75
- - **`UrlcodeServerErrors`** — over 5% 5xx on a route. 502 is a function error,
76
- 503 is capacity, 504 is a deadline.
77
- - **`UrlcodeCapacityShedding`** — sustained 503. Raise `--max-in-flight` or
78
- `--workers`, or find what is occupying the pool.
79
- - **`UrlcodeWorkerRestartLoop`** — replacement backs off but never stops, so a
80
- persistent cause appears as a steady restart rate rather than a stopped pool.
81
- This is the signal that a function is exceeding its deadline on real traffic.
82
- - **`UrlcodeLogsDropped`** — the collector is behind, so the other rules are
83
- blind until it recovers.
84
-
85
- Pick service objectives for your own application; these thresholds are a
86
- starting point, not a recommendation for your workload.
87
-
88
- ## Wiring it up
89
-
90
- 1. Send the process's stdout to a collector. The runtime never writes log files
91
- and owns no rotation or retention; that belongs to the collector.
92
- 2. Get counters either by scraping `/_urlcode/metrics` (enable `metrics`
93
- and load [`examples/monitoring/prometheus-scrape.yaml`](../examples/monitoring/prometheus-scrape.yaml))
94
- or by deriving them from the JSON records — see
95
- [`examples/monitoring/vector.toml`](../examples/monitoring/vector.toml), which
96
- produces `urlcode_requests_total`, `urlcode_worker_restarts_total` and
97
- `urlcode_logs_dropped_total`. Fluent Bit, Promtail and Alloy work equally
98
- well; the field names are what matter. The endpoint labels requests by
99
- `status_class` where the log pipeline keeps the exact `status`; the example
100
- rules carry both forms.
101
- 3. Probe both endpoints with blackbox_exporter — see
102
- [`examples/monitoring/blackbox-jobs.yaml`](../examples/monitoring/blackbox-jobs.yaml).
103
- 4. Load the alert rules and set the thresholds to your objectives.
104
-
105
- Latency percentiles need a histogram; `durationMs` is per record, so have the
106
- log pipeline bucket it rather than averaging in the alert.
107
-
108
- ## What this does not give you
109
-
110
- Dashboards here describe one process. There is no built-in tracing, no
111
- per-URL analytics, no distributed aggregation, no metrics persistence across
112
- restarts and no automatic capacity management. The example configuration is a starting point that has not been
113
- run against a production workload; validate it in your own environment before
114
- relying on it, and run the drills in
115
- [release readiness](RELEASE-READINESS.md) before treating any of it as proof.