@jimhoyd/urlcode 0.4.1 → 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 (212) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +60 -7
  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 +31 -28
  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 +29 -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 +138 -3
  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/policy.js +16 -0
  25. package/dist/project-tests.js +35 -11
  26. package/dist/readiness.js +188 -32
  27. package/dist/recipes.js +1 -1
  28. package/dist/router.js +17 -0
  29. package/dist/runtime.js +26 -3
  30. package/dist/scaffold.js +0 -0
  31. package/dist/server.js +26 -3
  32. package/dist/site.js +0 -0
  33. package/dist/tooling.js +3 -1
  34. package/dist/types/agent-context.d.ts +44 -0
  35. package/dist/types/authoring.d.ts +3 -1
  36. package/dist/types/body-schema.d.ts +58 -0
  37. package/dist/types/config.d.ts +10 -2
  38. package/dist/types/context.d.ts +1 -1
  39. package/dist/types/errors.d.ts +9 -1
  40. package/dist/types/examples.d.ts +1 -1
  41. package/dist/types/extensions.d.ts +105 -2
  42. package/dist/types/http-policy.d.ts +3 -0
  43. package/dist/types/init-with.d.ts +10 -1
  44. package/dist/types/pattern-guard.d.ts +10 -0
  45. package/dist/types/project-tests.d.ts +9 -0
  46. package/dist/types/readiness.d.ts +71 -0
  47. package/dist/types/recipes.d.ts +1 -1
  48. package/dist/types/runtime.d.ts +4 -0
  49. package/dist/types/server.d.ts +9 -1
  50. package/dist/types/tooling.d.ts +2 -0
  51. package/dist/types/types.d.ts +13 -0
  52. package/dist/types.js +10 -2
  53. package/dist/typescript-authoring.js +5 -3
  54. package/dist/verify-deployment.js +16 -10
  55. package/examples/body-validation/README.md +16 -0
  56. package/examples/body-validation/example.yaml +17 -0
  57. package/examples/body-validation/tests/requests.json +15 -0
  58. package/examples/body-validation/urlcode.yaml +34 -0
  59. package/examples/coverage-waiver/README.md +8 -0
  60. package/examples/coverage-waiver/example.yaml +16 -0
  61. package/examples/coverage-waiver/functions/notes.mjs +2 -0
  62. package/examples/coverage-waiver/tests/requests.json +3 -0
  63. package/examples/coverage-waiver/urlcode.yaml +10 -0
  64. package/examples/data-dir/README.md +39 -0
  65. package/examples/data-dir/data/welcome.txt +1 -0
  66. package/examples/data-dir/example.yaml +22 -0
  67. package/examples/data-dir/functions/note.mjs +18 -0
  68. package/examples/data-dir/tests/requests.json +6 -0
  69. package/examples/data-dir/urlcode.yaml +6 -0
  70. package/examples/lifecycle/README.md +15 -0
  71. package/examples/lifecycle/example.yaml +19 -0
  72. package/examples/lifecycle/functions/notes.mjs +29 -0
  73. package/examples/lifecycle/tests/requests.json +12 -0
  74. package/examples/lifecycle/urlcode.yaml +29 -0
  75. package/examples/not-found/README.md +10 -0
  76. package/examples/not-found/example.yaml +17 -0
  77. package/examples/not-found/public/404.html +3 -0
  78. package/examples/not-found/public/index.html +3 -0
  79. package/examples/not-found/tests/requests.json +7 -0
  80. package/examples/not-found/urlcode.yaml +7 -0
  81. package/examples/shared-blocks/README.md +11 -0
  82. package/examples/shared-blocks/example.yaml +17 -0
  83. package/examples/shared-blocks/tests/requests.json +7 -0
  84. package/examples/shared-blocks/urlcode.yaml +37 -0
  85. package/llms-full.txt +436 -149
  86. package/llms.txt +44 -6
  87. package/package.json +15 -22
  88. package/recipes/static-page/README.md +9 -0
  89. package/recipes/static-page/public/index.html +11 -0
  90. package/recipes/static-page/recipe.yaml +21 -0
  91. package/recipes/static-page/tests/requests.json +22 -0
  92. package/recipes/static-page/urlcode.yaml +7 -0
  93. package/recipes/static-plus-api/README.md +6 -0
  94. package/recipes/static-plus-api/urlcode.yaml +4 -0
  95. package/recipes/store-crud/README.md +53 -0
  96. package/recipes/store-crud/recipe.yaml +31 -0
  97. package/recipes/store-crud/tests/requests.json +18 -0
  98. package/recipes/store-crud/urlcode.yaml +18 -0
  99. package/schemas/urlcode.schema.json +125 -60
  100. package/skills/urlcode/SKILL.md +53 -26
  101. package/starters/default/AGENTS.md +43 -43
  102. package/starters/page/README.md +14 -0
  103. package/starters/page/public/index.html +12 -0
  104. package/starters/page/tests/requests.json +17 -0
  105. package/starters/page/urlcode.yaml +6 -0
  106. package/.claude-plugin/marketplace.json +0 -18
  107. package/CONTRIBUTING.md +0 -112
  108. package/ROADMAP.md +0 -66
  109. package/docs/AI-AUTHORING.md +0 -338
  110. package/docs/ASSETS.md +0 -107
  111. package/docs/AUTH-BACKUP.md +0 -32
  112. package/docs/AWS.md +0 -86
  113. package/docs/BEST-PRACTICES.md +0 -276
  114. package/docs/BULK.md +0 -79
  115. package/docs/CAPABILITIES.md +0 -192
  116. package/docs/CAPACITY.md +0 -305
  117. package/docs/CI-FOLLOWUP-2026-09-19.md +0 -97
  118. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +0 -322
  119. package/docs/CI.md +0 -147
  120. package/docs/CLOUDFLARE.md +0 -109
  121. package/docs/CODEBASE-AUDIT-2026-09-20.md +0 -284
  122. package/docs/COMPLIANCE.md +0 -239
  123. package/docs/COMPOSING-A-SITE.md +0 -278
  124. package/docs/CONDITIONS.md +0 -74
  125. package/docs/DEPLOYMENT-CHECKS.md +0 -108
  126. package/docs/DEVELOPMENT-PIPELINE.md +0 -270
  127. package/docs/EGRESS.md +0 -125
  128. package/docs/EXTENSIONS.md +0 -438
  129. package/docs/FRAMEWORK.md +0 -217
  130. package/docs/FUNCTION-SECURITY.md +0 -254
  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 -224
  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 -150
  151. package/docs/README.md +0 -87
  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-READINESS.md +0 -117
  156. package/docs/RELEASE-SECURITY.md +0 -96
  157. package/docs/RESILIENCE.md +0 -161
  158. package/docs/ROUTING.md +0 -92
  159. package/docs/SANDBOX-REVIEW.md +0 -72
  160. package/docs/SCAFFOLDING.md +0 -70
  161. package/docs/SECURITY-AUDIT.md +0 -164
  162. package/docs/SITE.md +0 -150
  163. package/docs/SPECIFICATION.md +0 -359
  164. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +0 -288
  165. package/docs/SPIKE-BUSINESS-SUITE.md +0 -1029
  166. package/docs/SPIKE-CORE-LAYERING.md +0 -368
  167. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +0 -211
  168. package/docs/STANDARDS.md +0 -311
  169. package/docs/STARTERS.md +0 -83
  170. package/docs/STATIC.md +0 -105
  171. package/docs/TOOLING.md +0 -298
  172. package/docs/TUNNELS.md +0 -72
  173. package/docs/TYPESCRIPT-AUTHORING.md +0 -87
  174. package/docs/TYPESCRIPT.md +0 -123
  175. package/docs/VERCEL.md +0 -114
  176. package/docs/VERSION-ALIGNMENT.md +0 -80
  177. package/docs/YAML-GUIDE.md +0 -57
  178. package/docs/YAML-REFERENCE.md +0 -449
  179. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +0 -68
  180. package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +0 -102
  181. package/docs/archive/2026-09-19/NEXT-PHASE-PLAN.md +0 -108
  182. package/docs/archive/2026-09-19/NEXT-STEPS.md +0 -646
  183. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +0 -277
  184. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +0 -186
  185. package/docs/archive/2026-09-19/ROADMAP.md +0 -387
  186. package/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md +0 -430
  187. package/docs/archive/2026-09-19/SPIKE-EXTENSIONS.md +0 -492
  188. package/docs/archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md +0 -365
  189. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +0 -778
  190. package/docs/archive/2026-09-19/USABILITY-REVIEW.md +0 -139
  191. package/docs/archive/README.md +0 -28
  192. package/docs/policies/agents.md +0 -182
  193. package/docs/policies/cache.md +0 -152
  194. package/docs/policies/compression.md +0 -169
  195. package/docs/policies/contract.md +0 -52
  196. package/docs/policies/hardened.md +0 -56
  197. package/docs/policies/interoperability.md +0 -169
  198. package/docs/policies/operations.md +0 -45
  199. package/docs/policies/security.md +0 -161
  200. package/docs/policies/throttle.md +0 -103
  201. package/docs/yaml/assets.md +0 -36
  202. package/docs/yaml/conditions.md +0 -20
  203. package/docs/yaml/functions.md +0 -168
  204. package/docs/yaml/middleware.md +0 -31
  205. package/docs/yaml/organization.md +0 -74
  206. package/docs/yaml/policies.md +0 -37
  207. package/docs/yaml/redirects.md +0 -64
  208. package/docs/yaml/responses.md +0 -57
  209. package/docs/yaml/site.md +0 -24
  210. package/packaging/claude-plugin/.claude-plugin/plugin.json +0 -19
  211. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +0 -120
  212. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +0 -108
package/docs/AWS.md DELETED
@@ -1,86 +0,0 @@
1
- # Deploying to AWS Lambda
2
-
3
- The AWS adapter runs a URLCode project as a Lambda function behind a **Function
4
- URL** or an **API Gateway HTTP API**. The same `urlcode.yaml` that runs locally
5
- or in a container serves the deployment.
6
-
7
- **This adapter serves native handlers only** — redirects, validated responses,
8
- pages, static assets and downloads — for the same reasons as
9
- [the Vercel adapter](VERCEL.md): `function` and `middleware` routes need the
10
- self-hosted Node lifecycle, and a `sandbox: true` route would pay worker and
11
- WASM startup on every cold start. Both are refused at activation with the route
12
- named, never per request, trusted or sandboxed alike.
13
-
14
- That refusal is a settled position, not a gap awaiting an adapter: per-route
15
- Lambda compilation was considered and declined
16
- ([the decision](OPEN-DECISIONS.md#accepted-one-node-deployment-per-project),
17
- [the analysis behind it](archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md)). A project that uses
18
- `function` or `middleware` deploys instead as one trusted Node process — a
19
- container or a VM running the project as it runs locally — which supports every
20
- route type today. That process can run on AWS: ECS, EC2 and App Runner all
21
- serve it. The decision is about the execution model, not about avoiding AWS.
22
-
23
- A working project is in [`examples/aws/`](../examples/aws/).
24
-
25
- ## Set it up
26
-
27
- ```js
28
- // handler.mjs
29
- import { createLambdaHandler } from '@jimhoyd/urlcode/aws';
30
-
31
- export const handler = createLambdaHandler({ project: process.env.LAMBDA_TASK_ROOT });
32
- ```
33
-
34
- The deployment package must contain every file the project reads: the entry
35
- YAML, any `includes`, and every page, download and static directory. They are
36
- read at activation, so a missing one fails the deployment rather than one route.
37
- Node 22.13 or newer.
38
-
39
- ## Payload format 2.0 only
40
-
41
- Function URLs and API Gateway **HTTP APIs** send payload format 2.0, which
42
- carries `rawPath` and `rawQueryString` — the request as it arrived.
43
-
44
- A **REST API** sends format 1.0, which supplies the path and query already
45
- decoded. The original bytes cannot be recovered from them, and this runtime
46
- rejects ambiguous encoding deliberately: rebuilding a target from decoded parts
47
- would either re-encode differently than the client sent or quietly accept what
48
- the runtime refuses. Rather than guess, the adapter refuses a 1.0 event and says
49
- why. Front the function with an HTTP API, or run the container image with
50
- `urlcode serve`.
51
-
52
- ## Bindings and origin
53
-
54
- `URLCODE_POLICY` carries the same grant document the operator policy file holds,
55
- validated identically including the `projectSha256` pin — see
56
- [the Vercel guide](VERCEL.md#bindings), which describes the same mechanism.
57
- Store it in the function's environment, or fetch it from Secrets Manager and set
58
- it before the handler is created.
59
-
60
- `URLCODE_PUBLIC_HOST` sets the public hostname for generated URLs. Lambda has no
61
- platform variable naming your domain, so unlike Vercel there is nothing to infer
62
- from: set it when a custom domain or an API Gateway stage prefix is in play.
63
- Forwarded headers stay untrusted.
64
-
65
- ## Limits worth knowing before you deploy
66
-
67
- | Limit | Consequence |
68
- |---|---|
69
- | Lambda response payload is 6 MB | A larger asset cannot be returned. The runtime allows 16 MB per asset, so a project valid self-hosted can exceed what Lambda can send. Keep large files in object storage and redirect to them. |
70
- | Response bodies are base64-encoded | Guessing whether a body is text is how binary assets get corrupted, so every response is encoded. Base64 inflates by about a third, against that 6 MB ceiling. |
71
- | Each execution environment activates independently | Parsing, asset snapshotting and route compilation happen per cold start, and the [capacity limits](CAPACITY.md) apply per instance: a 64 MiB snapshot is 64 MiB in every concurrent one. |
72
- | No reload | A deployment serves the revision it was packaged from. Ship a change by deploying. |
73
- | No `/_urlcode/health` or `/_urlcode/ready` | Those describe a long-lived process. Use Lambda's own metrics; the [monitoring recipes](MONITORING.md) that parse stdout records need adapting for CloudWatch, though the record fields are the same. |
74
-
75
- ## Verification status
76
-
77
- The adapter is tested against the self-hosted runtime for identical status, body
78
- and headers across redirects, parameters, responses, pages, static files and
79
- misses; for format 1.0 and malformed events being refused; for base64 request
80
- bodies and route body policy; for cookies arriving and leaving through the
81
- format 2.0 array; and for the policy pin.
82
-
83
- **It has not been deployed to AWS.** The tests drive the real handler with real
84
- payload format 2.0 events, but no invocation on Lambda has happened. Treat
85
- package contents, cold-start latency, API Gateway's own header handling and the
86
- 6 MB ceiling as unverified until you deploy the example and see it work.
@@ -1,276 +0,0 @@
1
- # Organization and readability
2
-
3
- These are conventions for URLCode applications, not extra schema requirements.
4
- Choose the smallest structure that makes a route easy to find, understand and
5
- test. Preserve an existing project's conventions unless there is a clear reason
6
- to change them. See [file composition](ORGANIZATION.md) for enforced rules and
7
- [the YAML guide](YAML-GUIDE.md) for supported features.
8
-
9
- ## Start small, split by responsibility
10
-
11
- The default starter's two routes do not need a framework or many abstraction
12
- layers. A small project can keep all definitions in `urlcode.yaml`:
13
-
14
- ```text
15
- my-links/
16
- urlcode.yaml
17
- functions/
18
- welcome.mjs
19
- middleware/
20
- response-headers.mjs
21
- public/
22
- guide.txt
23
- tests/
24
- requests.json
25
- README.md
26
- ```
27
-
28
- Create only the folders you use. When navigation or ownership becomes difficult,
29
- group by feature, audience or campaign, rather than creating one YAML file per
30
- short link. There is no universal file-size threshold; split when a reviewer can
31
- no longer follow the changes comfortably. Thousands of redirects usually belong
32
- in a few meaningful collections, not thousands of include entries.
33
-
34
- For a growing application, colocating feature-specific code is often clearer:
35
-
36
- ```text
37
- my-links/
38
- urlcode.yaml
39
- features/
40
- welcome/
41
- routes.yaml
42
- welcome.mjs
43
- greeting.mjs
44
- campaigns/
45
- routes.yaml
46
- middleware/
47
- response-headers.mjs
48
- public/
49
- guide.txt
50
- tests/
51
- requests.json
52
- README.md
53
- ```
54
-
55
- The root remains a short explicit table of contents:
56
-
57
- ```yaml
58
- version: "1"
59
- includes:
60
- - features/welcome/routes.yaml
61
- - features/campaigns/routes.yaml
62
- routes: {}
63
- ```
64
-
65
- Each included file has its own `version: "1"` and `routes`. Do not add recursive
66
- includes, glob patterns or inherited defaults. Folder names never change public
67
- URLs. Keep the two styles available: folders by file type for small apps, or by
68
- feature when it improves ownership. Neither requires forking the runtime.
69
-
70
- ## Make a route readable from top to bottom
71
-
72
- Use two spaces, no tabs, and one consistent key order. A useful order is:
73
- `description`, lifecycle (`enabled`/`expires`), `methods`, `parameters`,
74
- `request`, bindings (`env`/`secrets`), `middleware`, the handler, then `response`.
75
- This is visual organization, not an execution-order declaration.
76
-
77
- Use block style for nested behavior. Short scalar lists such as `[GET, HEAD]`
78
- and small leaf schemas such as `{type: string}` can stay inline. Omit ordinary
79
- defaults unless making them explicit clarifies a decision. Quote timestamps and
80
- numeric-looking header values. Avoid unrelated reformatting when changing a route.
81
-
82
- Example `features/welcome/routes.yaml`:
83
-
84
- ```yaml
85
- version: "1"
86
- routes:
87
- /welcome/{name}:
88
- description: A personalized welcome message with a consistent response header
89
- parameters:
90
- - name: name
91
- in: path
92
- required: true
93
- schema:
94
- type: string
95
- minLength: 1
96
- maxLength: 80
97
- middleware:
98
- - source: middleware/response-headers.mjs
99
- function:
100
- source: features/welcome/welcome.mjs
101
- args:
102
- name: {from: path, name: name}
103
- response:
104
- headers:
105
- Cache-Control: no-store
106
- ```
107
-
108
- Example `features/campaigns/routes.yaml`:
109
-
110
- ```yaml
111
- version: "1"
112
- routes:
113
- /guide:
114
- # Keep this temporary so a campaign destination can change later.
115
- redirect:
116
- url: https://example.com/guide
117
- ```
118
-
119
- Use comments for the reason behind a choice, migration notes or a business rule.
120
- Do not narrate obvious syntax or include secrets, private customer details or
121
- stale deployment instructions. `description` is useful authoring metadata; do
122
- not invent `owner`, `tags` or other unsupported YAML fields. Record ownership in
123
- the README or repository tooling instead.
124
-
125
- Order routes consistently within each collection, such as alphabetically or by
126
- business flow. Do not rely on file order for precedence: exact routes win before
127
- parameter routes, then static mounts. Avoid ambiguous overlaps; see [routing](ROUTING.md).
128
-
129
- ## Keep handlers short and name their job
130
-
131
- Name files for behavior (`welcome.mjs`, `select-destination.mjs`) rather than
132
- `utils2.mjs` or `handler-final.mjs`. Name exports clearly even when using a default
133
- export. Use descriptive variables and early returns for error cases. Separate
134
- business transformations from Request/Response handling when that improves clarity.
135
- Do not extract a one-line helper merely to create more files.
136
-
137
- `features/welcome/welcome.mjs`:
138
-
139
- ```js
140
- import {formatGreeting} from './greeting.mjs';
141
-
142
- export default function welcome(request, {args}) {
143
- return Response.json({message: formatGreeting(args.name)});
144
- }
145
- ```
146
-
147
- `features/welcome/greeting.mjs`:
148
-
149
- ```js
150
- export function formatGreeting(name) {
151
- return `Hello, ${name}!`;
152
- }
153
- ```
154
-
155
- YAML `source` paths are project-root relative. JavaScript imports are relative to
156
- the importing module. Explicit `.mjs` filenames make that distinction clear.
157
- Only relative project JavaScript imports are supported; do not introduce npm,
158
- Node, remote imports or a transpilation requirement accidentally. There is a
159
- project-wide module/source budget; excessive fragmentation has a real limit.
160
-
161
- Use validated `args` and `inputs` instead of parsing query strings again. Return
162
- JSON through `Response.json`; escape user data explicitly when producing HTML.
163
- For dynamic redirects, map a validated enum to known destinations rather than
164
- accepting any user-controlled URL. Keep modules free of top-level work other than
165
- simple definitions: initialization runs during validation and fresh invocations.
166
-
167
- Prefer pure helpers with explicit inputs and outputs. Module globals are not a
168
- cache, database, session store or rate limiter: guest state resets per request
169
- regardless of trust mode. If a route declares `sandbox: true`, review
170
- [sandbox constraints](FUNCTION-SECURITY.md) before choosing dependencies —
171
- trusted (default) routes have ordinary Node module access instead.
172
-
173
- ## Middleware should have one clear responsibility
174
-
175
- `middleware/response-headers.mjs`:
176
-
177
- ```js
178
- export default async function responseHeaders(request, context, next) {
179
- const response = await next();
180
- response.headers.set('x-app', 'my-links');
181
- return response;
182
- }
183
- ```
184
-
185
- Use middleware for reusable behavior around a handler, not to conceal the entire
186
- application flow. Prefer YAML headers for fixed route-specific headers; this
187
- example demonstrates a shared wrapper, but native YAML avoids any
188
- function/middleware invocation overhead when no custom code is needed —
189
- including the extra cost of `sandbox: true` where that is declared. Keep
190
- middleware order explicit in each route.
191
-
192
- Always return a Response. Call `await next()` once when continuing, or return
193
- an early Response when intentionally stopping. Do not launch unawaited work or
194
- assume background tasks will survive. A body read consumes the request body;
195
- if downstream code needs parsed data, agree on a documented `context.state` field.
196
- Use specific field names to avoid collisions among middleware.
197
-
198
- Catch only errors you can handle meaningfully. Do not turn every failure into a
199
- 200 response or include secrets in errors. Keep native body/status/header
200
- preservation rules visible in code review. All middleware share the route's
201
- bindings and execution budget; splitting modules does not create privilege
202
- separation. See [middleware semantics](MIDDLEWARE.md).
203
-
204
- ## Organize tests around observable behavior
205
-
206
- Keep runnable HTTP assertions in `tests/requests.json`, currently the single file
207
- read by the CLI. Group adjacent cases by route: ordinary success, HEAD, boundary
208
- inputs, invalid input, wrong method and relevant early responses. Add meaningful
209
- body/header checks instead of relying only on status codes.
210
-
211
- For the two-route feature layout above:
212
-
213
- ```json
214
- [
215
- {"path":"/welcome/Ada","status":200,"expectBody":"{\"message\":\"Hello, Ada!\"}","expectHeaders":{"x-app":"my-links"}},
216
- {"path":"/welcome/Ada","method":"HEAD","status":200,"expectBody":"","expectHeaders":{"x-app":"my-links"}},
217
- {"path":"/welcome/Ada","method":"POST","status":405,"expectHeaders":{"allow":"GET, HEAD"}},
218
- {"path":"/guide","status":302,"expectHeaders":{"location":"https://example.com/guide"}},
219
- {"path":"/guide","method":"HEAD","status":302,"expectBody":"","expectHeaders":{"location":"https://example.com/guide"}}
220
- ]
221
- ```
222
-
223
- Additional ordinary JavaScript unit tests for pure helpers are your project's
224
- choice. Unit tests alone do not verify runtime compatibility — including the
225
- guest API restrictions of a route declaring `sandbox: true` — or HTTP framing:
226
- always exercise HTTP behavior through URLCode too. Keep large fixture generation explicit and
227
- deterministic if you add your own tooling; nested test directories and JSON
228
- fragments are not automatically discovered or merged by URLCode.
229
-
230
- Assert observable contracts, not incidental timings or internal variable names.
231
- Avoid checking generated request IDs, current Date values or performance numbers
232
- as fixed functional outputs. Test cache/range semantics with controlled assets.
233
- Update expected route counts deliberately when adding or removing a route.
234
-
235
- ## Keep configuration, code, assets and operations separate
236
-
237
- - Git owns behavior and reviewed code. Keep the runtime as a pinned dependency;
238
- upgrading it should not regenerate or overwrite application files.
239
- - Publish only intentionally public files in `public/`. File filters cannot
240
- recognize every secret. A harmless filename is not proof of public content.
241
- - Put local secrets in ignored `.env.local`; production values come from the
242
- operator. Keep operator grants outside the application checkout. Never use
243
- YAML anchors, shell expansion or generated credentials for convenience.
244
- - Deployment limits, TLS, DNS, DDoS filters and worker tuning belong to operations,
245
- not invented route fields. Document them separately from portable behavior.
246
- - Live short-code records need durable storage core does not have. The
247
- `urlcode-dynamic-link` extension provided it and has been retired; its
248
- published `0.1.0-alpha.1` pins core `0.4.0-alpha.1` exactly and so cannot be
249
- installed beside `0.4.0-alpha.2`. Treat stored short links as unsupported
250
- until that work lands somewhere else. General session/application storage
251
- remains future work.
252
-
253
- ## Refactor without changing the public contract
254
-
255
- Move one feature at a time. Update explicit includes and project-root source/asset
256
- paths, then check relative JavaScript imports. Keep public route paths, methods,
257
- headers and bodies stable unless the change is intentional. Avoid mixing URL
258
- renames, dependency upgrades and folder rearrangement in one review.
259
-
260
- Run validate, HTTP tests and the expected-count audit before and after moving
261
- files. Re-review operator grants: code/config changes invalidate the digest even
262
- when intended behavior is unchanged. A successful dev reload is not a production
263
- deployment. Record activation/rollback steps in the project README.
264
-
265
- ## A useful project README
266
-
267
- Document how to install the pinned runtime, start locally, run tests/audit, and
268
- activate a release. Include a small folder map, the owner of each major feature,
269
- public route behavior and required external binding names (never values). State
270
- supported Node/runtime versions and link to matching URLCode docs. Explain any
271
- middleware ordering or surprising defaults that a new maintainer might miss.
272
-
273
- For AI-generated changes, require the assistant to follow existing conventions,
274
- keep diffs focused, add response assertions and report checks actually run. Reject
275
- invented YAML keys, hidden side effects and unnecessary abstractions. The
276
- [AI authoring guide](AI-AUTHORING.md) provides a reusable prompt and capability list.
package/docs/BULK.md DELETED
@@ -1,79 +0,0 @@
1
- # Bulk redirect projects
2
-
3
- Bulk import converts CSV, JSON or YAML redirect rows into an ordinary Git-owned
4
- URLCode project. It delegates semantics and row diagnostics to the strict
5
- [redirect interchange](INTERCHANGE.md) converter. Only literal paths and
6
- absolute HTTP(S) redirect destinations are accepted; unsupported behavior and
7
- duplicate paths fail rather than being discarded or overwritten.
8
-
9
- ```sh
10
- urlcode bulk-import csv redirects.csv --out ./imported --dry-run
11
- urlcode bulk-import csv redirects.csv --out ./imported
12
- urlcode validate --local --project ./imported
13
- ```
14
-
15
- CSV requires the header `path,url,status`; status can be empty for 302. JSON and
16
- YAML are arrays of `{path, url, status?}` rows. Input is limited to 32 MiB and
17
- 100,000 routes. This command is for data import, not arbitrary provider config.
18
- Use explicit provider interchange commands when migrating provider files.
19
-
20
- The SDK provides
21
- `importBulkProject(text, format, output, {dryRun, source})`. `format` is `csv`,
22
- `json` or `yaml`. The report includes `ok`, `routeCount`, source/row diagnostics,
23
- a SHA-256 fingerprint of the exact input text, and a bounded output-file plan.
24
- Invalid input returns `ok: false` without writing files. Filesystem/output
25
- refusals throw. `source` is a caller-supplied provenance label, not a file to read.
26
-
27
- Routes are sorted by literal path and divided into include files with at most
28
- 1,000 routes each. At 100,000 routes this produces 100 include files. The entry
29
- `urlcode.yaml` holds the include list and empty `routes`. `provenance.json`
30
- records the source label, input fingerprint, counts and each shard's first/last
31
- path. It does not embed the original input or claim exact source line mappings
32
- for successful output rows; retain the original file in version control if that
33
- traceability is needed. Invalid row diagnostics preserve their source positions.
34
-
35
- Dry-run performs conversion and output planning without writing the requested
36
- destination. Import requires a new output directory under an existing parent;
37
- there is no implicit merge or overwrite mode. As with recipes, dependencies are
38
- written first and a rename publishes the completed entry YAML last. Failed
39
- writes clean up the directory created by that invocation. Source data is never
40
- interpreted as shell code. The caller must control the output parent while
41
- publishing.
42
-
43
- ## Local scale evidence
44
-
45
- Run each dataset in a fresh process, sequentially, without concurrent builds:
46
-
47
- ```sh
48
- node benchmarks/bulk.ts 1000
49
- node benchmarks/bulk.ts 10000
50
- node benchmarks/bulk.ts 100000
51
- ```
52
-
53
- Observed on 2026-09-17 against the final integrated next-phase working tree,
54
- with the merged TypeScript 6.0.3 dependency lock, Node v26.8.2, macOS arm64,
55
- Apple M4 Pro and 48 GiB system memory. These measurements include the final
56
- capability, revision-pin and egress activation paths. Synthetic routes redirect `/rN` to `https://example.com/items/N`.
57
- Conversion includes input validation, semantic compilation and sharded output
58
- publication; activation uses the normal `createRuntime` loader and compiler.
59
- Lookup measures 5,000 `Runtime.handle` requests at concurrency one after 100
60
- warm-up requests, validating each status and Location. It excludes socket/TLS
61
- transport. Memory is sampled process RSS/heap after phases, not peak memory or
62
- an isolated worker measurement.
63
-
64
- | Routes | Includes | Conversion ms | Activation ms | RSS after activation MiB | Heap MiB | Lookup p95 ms |
65
- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
66
- | 1,000 | 1 | 56 | 139 | 180 | 27 | 0.002292 |
67
- | 10,000 | 10 | 272 | 373 | 288 | 46 | 0.002916 |
68
- | 100,000 | 100 | 5,530 | 8,838 | 537 | 214 | 0.003791 |
69
-
70
- All three sequential fresh-process datasets activated and returned the expected
71
- redirects. These are single observations, not a repeated-run statistical study;
72
- background host load can change startup and memory measurements. Unlike the
73
- previous single-document 100,000-route routing benchmark, the sharded project
74
- fits the existing configuration worker's 256 MiB heap and 10-second deadline.
75
- No loader, route-count, compiler deadline or runtime safety limit was increased.
76
- This local result does not establish cross-platform capacity, provider
77
- performance, peak-memory bounds, concurrency/soak behavior or production SLOs.
78
- The benchmark reports failure phase and error if a limit is reached on another
79
- machine; it does not retry with relaxed limits.
@@ -1,192 +0,0 @@
1
- # Capabilities and normalized route representation
2
-
3
- URLCode is a portable runtime for programmable URL behavior: **URL behavior as
4
- code**. YAML describes behavior; operators supply infrastructure and authority.
5
-
6
- ## Inspect target support
7
-
8
- ```sh
9
- urlcode capabilities
10
- urlcode capabilities --target self-hosted
11
- urlcode capabilities --target cloudflare --json
12
- urlcode capabilities --target aws
13
- urlcode capabilities --target vercel
14
- urlcode capabilities --target static
15
- ```
16
-
17
- This command needs no project or credentials. `node` is an alias for
18
- `self-hosted`, matching the existing embedding API. Unknown target names fail.
19
- JSON has `format: 1`, target deployment evidence and capability rows.
20
- `doctor` also reports `capabilityTargets`; its `providers` list remains empty
21
- because no provider deployment has been verified. Canonical
22
- names follow the schema (`respond`, `extension`, `policies.security`), not
23
- marketing synonyms. `proxy` and `signals` are self-hosted capabilities requiring
24
- external revision-pinned origin grants. `conditions` (`match`) and `conditional`
25
- (disjoint cases) are supported by self-hosted/AWS/Vercel and refused by
26
- Cloudflare (no artifact lowering yet) and by `static` (no server to match a
27
- request against). `extension`/`policies.extensions` report per-extension
28
- support from the registered extension's own declared `targets` when a
29
- `--host-file` is supplied; without one they report `conditional`/`unknown`
30
- rather than a blanket answer. See [egress](EGRESS.md) and
31
- [conditions](CONDITIONS.md).
32
-
33
- | Support | Meaning |
34
- | --- | --- |
35
- | native | Implemented by the local runtime or Node adapter |
36
- | compiled | Implemented by the Cloudflare or static-hosting compiler and its runtime/build output |
37
- | conditional | Depends on configuration; inspect the actual project |
38
- | delegated | Existing policy contract relies on provider behavior |
39
- | refused | No implementation that this target can activate |
40
- | unknown | No support evidence; fail closed during project analysis |
41
-
42
- `native` and `compiled` describe local implementation tests. AWS, Vercel,
43
- Cloudflare and static deployment evidence remains **unverified**. This is not a
44
- blanket exact-portability promise. Cloudflare coalesces duplicate headers and
45
- receives a normalized Request target; AWS accepts payload v2 only; `static` has
46
- no server at all, so it refuses every capability that needs one (parameters,
47
- request bodies, response headers, bindings, every `policies.*`) in addition to
48
- `function`/`middleware`. See [Cloudflare](CLOUDFLARE.md), [AWS](AWS.md),
49
- [Vercel](VERCEL.md) and [static hosting](STATIC.md) for transport and fidelity
50
- limits. Compression is explicitly delegated, not verified equivalent to
51
- operator-selected settings. Route throttle counters and caches remain per
52
- instance. No supported entry bypasses semantic validation, required operator
53
- grants or deployment prerequisites.
54
-
55
- ## One capability or one schema fragment
56
-
57
- ```sh
58
- urlcode capabilities redirect
59
- urlcode capabilities policies.throttle --json
60
- urlcode schema route
61
- urlcode schema policies.cache --json
62
- urlcode schema site.sitemap --yaml
63
- ```
64
-
65
- `urlcode capabilities <name>` prints one catalog entry: its kind (handler,
66
- policy, routing, request, binding, egress, middleware or project), a summary,
67
- the resolved schema fragment(s), constraints, the operator grants the capability
68
- needs at activation, support per target, the targets that refuse it, and the
69
- bundled recipes and cookbook routes that use it. Names are the catalog names
70
- (`redirect`, `bindings`, `policies.cache`); an unknown name fails with exit 1
71
- and lists the valid names. `--target` applies to the full catalog only.
72
-
73
- `urlcode schema <path>` prints only that fragment of
74
- `schemas/urlcode.schema.json` with local `$ref`s resolved inline. Paths are
75
- top-level document keys (`routes`, `policies`, `site`, `extensions`), `route`,
76
- or a route property (`redirect`, `middleware`, `match`, `env`), optionally
77
- followed by nested property names (`policies.cache`, `request.body`,
78
- `site.sitemap`). Resolution is bounded and cycle-safe; where a nested object is
79
- its own path (`route` inside `routes`, `policies` inside `route`) it is
80
- summarized with a `$comment` naming that path so every fragment stays under
81
- 16 KiB. Fragments describe shape only: they carry no defaults, validation
82
- result or operator authority, and the full schema remains the contract.
83
-
84
- Both commands read bundled package data and need no project, credentials or
85
- network. The SDK exposes them as `getCapability(name)` and
86
- `getSchemaFragment(path)`; the MCP server as `get_capability` and `get_schema`
87
- (see [tooling](TOOLING.md)). Grant descriptions name the operator flag or policy
88
- involved, never binding values.
89
-
90
- ## Programmatic analysis
91
-
92
- The main package exports `getCapabilities`, `routeCapabilities`,
93
- `analyzeProjectCapabilities`, `analyzeCompiledCapabilities`,
94
- `assertTargetCompatibility`, `normalizeCapabilityTarget` and their report types.
95
-
96
- ```js
97
- import { loadDocument, analyzeProjectCapabilities,
98
- assertTargetCompatibility } from '@jimhoyd/urlcode';
99
-
100
- const loaded = await loadDocument('./project');
101
- const report = analyzeProjectCapabilities(loaded, 'cloudflare');
102
- console.log(report.issues); // path, capability, support, reason; never binding values
103
- assertTargetCompatibility(report);
104
- ```
105
-
106
- This low-level example examines declared routes. Runtime activation and builds
107
- first expand `site` conventions using the operator origin, then analyze all
108
- routes including generated ones. A report is a compatibility preflight, **not**
109
- a substitute for compilation/validation. Disabled and expired routes are still
110
- checked. `analyzeProjectCapabilities`/`analyzeCompiledCapabilities` take an
111
- optional resolved extension registration set (the same shape `--host-file`
112
- loads); pass it to get per-extension `refused`/`native` from that extension's
113
- own `targets` instead of the generic `conditional`/`unknown` answer.
114
- `compatible` means there are no refused, unknown or unresolved conditional
115
- requirements. Explicit delegation and transport limitations still apply.
116
-
117
- Requirements include effective inherited/profile policies after route overrides
118
- and `false` removals. Policy modules' existing `targets(config)` functions remain
119
- the source of policy decisions. The catalog says serverless throttle is
120
- conditional; a project report resolves `partition: route` to native and the
121
- client partitions to refused. Reports contain paths and capability facts, not
122
- sources, destinations, binding names/values, code, validator closures or assets.
123
-
124
- Build/activation refusals aggregate all incompatible requirements and name each
125
- route, capability, target and reason before any artifact files are written.
126
- Unsupported bindings on Cloudflare fail before credentials are resolved.
127
-
128
- ## Existing IR, formalized
129
-
130
- The implementation already has a useful internal representation:
131
-
132
- ```text
133
- strict YAML + schema validation + includes
134
- → site expansion
135
- → shared capability preflight (declarations, no credentials)
136
- → semantic route compilation
137
- → CompiledRouteTable / CompiledRoute
138
- → capability analysis / target lowering
139
- → host assets + policy chains + trusted or isolated function dispatch, or Worker artifact
140
- ```
141
-
142
- `CompiledRoute` in `src/types.ts` extends shared `MatchableRoute` with validated
143
- parameters, normalized HTTP replies/headers, resolved bindings and source
144
- references. `CompiledRouteTable` indexes literal paths, parameter buckets and
145
- static mounts. `router.ts` owns precedence, collision checks, default methods,
146
- input/reference validation and normalization. `match.ts` supplies portable
147
- request-time matching to both runtime and Worker. No second parser or competing
148
- route IR is introduced.
149
-
150
- `routeCapabilities` is a value-free projection used for both declaration
151
- preflight and compiled analysis. Preflight deliberately precedes full semantic
152
- validation to report unsupported features before reading missing assets or
153
- resolving secrets; it cannot authorize or validate a project. Normal compilation
154
- and policy validation still run. AWS/Vercel consume the shared runtime IR;
155
- Cloudflare additionally analyzes the compiled table before serializing its
156
- allowlisted artifact fields and standalone validators.
157
-
158
- The compiled table is internal, mutable during activation and **not serializable
159
- as an interchange contract**: bindings contain resolved secrets, validators are
160
- functions, assets contain bytes and policy chains own host state. The existing
161
- Cloudflare artifact is a separate versioned lowering, not a replacement IR.
162
- [Interchange](INTERCHANGE.md) projects a validated literal-redirect subset and
163
- explicitly reports unsupported semantics; it never dumps compiled routes.
164
- The next-phase schema extends this same IR with normalized condition cases,
165
- proxy headers and signal definitions. Resolved egress headers are private runtime
166
- state and must never be serialized. Capability analysis itself adds no authority
167
- and does not run in the request path.
168
-
169
- See the [repository review and incremental plan](archive/2026-09-19/NEXT-PHASE-PLAN.md) for the
170
- implementation status. Provider deployment tests, independent security review and
171
- operational soak/recovery proof are separate work.
172
-
173
- ## Local performance check
174
-
175
- 2026-09-17, Node 26.8.2, macOS arm64, Apple M4 Pro, 48 GiB RAM.
176
- `npm run benchmark -- <count>` runs 5,000 loopback requests at concurrency 16.
177
- Single runs against baseline `1a00294` and this change, not a statistical study:
178
-
179
- | Routes | Startup ms before / after | RSS MiB before / after | Heap MiB before / after | Requests/s before / after | p95 ms before / after |
180
- | --- | --- | --- | --- | --- | --- |
181
- | 1,000 | 135 / 138 | 183 / 182 | 29 / 34 | 24,645 / 24,701 | 1.18 / 1.23 |
182
- | 10,000 | 312 / 303 | 211 / 207 | 55 / 63 | 6,762 / 6,829 | 4.08 / 4.09 |
183
-
184
- Both baseline and updated 100,000-route runs failed with `Configuration worker
185
- resource limit or failure` before route compilation. The worker's existing
186
- resource bounds are unchanged; the configured 100k route ceiling is not evidence
187
- that every 100k YAML document fits those bounds. Bulk-scale remediation and
188
- repeatable memory profiling were subsequent work at Phase A. The new
189
- [bulk sharding benchmark](BULK.md) successfully activates 100,000 routes within
190
- the unchanged worker limits. Capability analysis adds
191
- linear activation work and temporary report allocations; no request-time checks
192
- were added. These measurements are not provider, soak or capacity certification.