@jimhoyd/urlcode 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/CONTRIBUTING.md +80 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +20 -0
  4. package/README.md +342 -0
  5. package/ROADMAP.md +248 -0
  6. package/SECURITY.md +40 -0
  7. package/data/agents/LICENSES/ai-robots-txt.txt +24 -0
  8. package/data/agents/LICENSES/crawler-user-agents.txt +23 -0
  9. package/data/agents/ai-crawlers.json +1240 -0
  10. package/data/agents/crawlers.json +10515 -0
  11. package/data/agents/index.d.ts +3 -0
  12. package/data/agents/index.js +2453 -0
  13. package/data/agents/monitoring.json +1520 -0
  14. package/data/agents/seo.json +3865 -0
  15. package/dist/BUILD-MANIFEST.json +60 -0
  16. package/dist/adapters.js +59 -0
  17. package/dist/agent-lists.js +59 -0
  18. package/dist/assets.js +129 -0
  19. package/dist/authoring.js +68 -0
  20. package/dist/aws.js +110 -0
  21. package/dist/build-cloudflare.js +164 -0
  22. package/dist/cli.js +185 -0
  23. package/dist/client-address.js +85 -0
  24. package/dist/cloudflare.js +165 -0
  25. package/dist/compliance-rules/baseline.js +98 -0
  26. package/dist/compliance-rules/privacy.js +38 -0
  27. package/dist/compliance-rules/shared.js +62 -0
  28. package/dist/compliance-rules/strict.js +61 -0
  29. package/dist/compliance.js +219 -0
  30. package/dist/config-worker.js +9 -0
  31. package/dist/config.js +172 -0
  32. package/dist/errors.js +8 -0
  33. package/dist/function-sources.js +54 -0
  34. package/dist/function-worker.js +105 -0
  35. package/dist/functions.js +174 -0
  36. package/dist/guest-api.js +114 -0
  37. package/dist/header-validation.js +18 -0
  38. package/dist/http-policy.js +74 -0
  39. package/dist/http-response.js +84 -0
  40. package/dist/index.js +11 -0
  41. package/dist/link-api.js +136 -0
  42. package/dist/link-cli.js +141 -0
  43. package/dist/link-events.js +76 -0
  44. package/dist/link-records.js +31 -0
  45. package/dist/link-store-worker.js +150 -0
  46. package/dist/link-store.js +250 -0
  47. package/dist/logging.js +22 -0
  48. package/dist/management-policy.js +41 -0
  49. package/dist/match.js +124 -0
  50. package/dist/observability.js +242 -0
  51. package/dist/plugins.js +74 -0
  52. package/dist/policies/agents.js +248 -0
  53. package/dist/policies/cache.js +297 -0
  54. package/dist/policies/compression.js +187 -0
  55. package/dist/policies/security.js +0 -0
  56. package/dist/policies/throttle.js +131 -0
  57. package/dist/policies.js +142 -0
  58. package/dist/policy.js +55 -0
  59. package/dist/prerender.js +165 -0
  60. package/dist/project-tests.js +23 -0
  61. package/dist/readiness.js +216 -0
  62. package/dist/router.js +166 -0
  63. package/dist/runtime.js +244 -0
  64. package/dist/scaffold.js +0 -0
  65. package/dist/scripts/operational-drills.js +75 -0
  66. package/dist/server.js +270 -0
  67. package/dist/site.js +0 -0
  68. package/dist/sqlite-version.js +6 -0
  69. package/dist/types/adapters.d.ts +12 -0
  70. package/dist/types/agent-lists.d.ts +5 -0
  71. package/dist/types/assets.d.ts +13 -0
  72. package/dist/types/authoring.d.ts +2 -0
  73. package/dist/types/aws.d.ts +35 -0
  74. package/dist/types/build-cloudflare.d.ts +14 -0
  75. package/dist/types/cli.d.ts +2 -0
  76. package/dist/types/client-address.d.ts +9 -0
  77. package/dist/types/cloudflare.d.ts +75 -0
  78. package/dist/types/compliance-rules/baseline.d.ts +12 -0
  79. package/dist/types/compliance-rules/privacy.d.ts +6 -0
  80. package/dist/types/compliance-rules/shared.d.ts +24 -0
  81. package/dist/types/compliance-rules/strict.d.ts +9 -0
  82. package/dist/types/compliance.d.ts +119 -0
  83. package/dist/types/config-worker.d.ts +1 -0
  84. package/dist/types/config.d.ts +21 -0
  85. package/dist/types/errors.d.ts +7 -0
  86. package/dist/types/function-sources.d.ts +21 -0
  87. package/dist/types/function-worker.d.ts +1 -0
  88. package/dist/types/functions.d.ts +92 -0
  89. package/dist/types/guest-api.d.ts +16 -0
  90. package/dist/types/header-validation.d.ts +2 -0
  91. package/dist/types/http-policy.d.ts +39 -0
  92. package/dist/types/http-response.d.ts +43 -0
  93. package/dist/types/index.d.ts +11 -0
  94. package/dist/types/link-api.d.ts +30 -0
  95. package/dist/types/link-cli.d.ts +36 -0
  96. package/dist/types/link-events.d.ts +27 -0
  97. package/dist/types/link-records.d.ts +11 -0
  98. package/dist/types/link-store-worker.d.ts +1 -0
  99. package/dist/types/link-store.d.ts +130 -0
  100. package/dist/types/logging.d.ts +9 -0
  101. package/dist/types/management-policy.d.ts +9 -0
  102. package/dist/types/match.d.ts +81 -0
  103. package/dist/types/observability.d.ts +104 -0
  104. package/dist/types/plugins.d.ts +27 -0
  105. package/dist/types/policies/agents.d.ts +86 -0
  106. package/dist/types/policies/cache.d.ts +82 -0
  107. package/dist/types/policies/compression.d.ts +41 -0
  108. package/dist/types/policies/security.d.ts +37 -0
  109. package/dist/types/policies/throttle.d.ts +55 -0
  110. package/dist/types/policies.d.ts +36 -0
  111. package/dist/types/policy.d.ts +21 -0
  112. package/dist/types/prerender.d.ts +31 -0
  113. package/dist/types/project-tests.d.ts +13 -0
  114. package/dist/types/readiness.d.ts +121 -0
  115. package/dist/types/router.d.ts +11 -0
  116. package/dist/types/runtime.d.ts +91 -0
  117. package/dist/types/scaffold.d.ts +17 -0
  118. package/dist/types/server.d.ts +37 -0
  119. package/dist/types/site.d.ts +12 -0
  120. package/dist/types/sqlite-version.d.ts +1 -0
  121. package/dist/types/types.d.ts +332 -0
  122. package/dist/types/vercel.d.ts +12 -0
  123. package/dist/types.js +177 -0
  124. package/dist/vercel.js +65 -0
  125. package/docs/AI-AUTHORING.md +113 -0
  126. package/docs/ASSETS.md +106 -0
  127. package/docs/AWS.md +77 -0
  128. package/docs/BEST-PRACTICES.md +267 -0
  129. package/docs/CAPACITY.md +208 -0
  130. package/docs/CLOUDFLARE.md +110 -0
  131. package/docs/COMPLIANCE.md +242 -0
  132. package/docs/DYNAMIC-LINKS.md +561 -0
  133. package/docs/FUNCTION-SECURITY.md +113 -0
  134. package/docs/HTTP.md +129 -0
  135. package/docs/INSTALL.md +90 -0
  136. package/docs/LOAD-TESTING.md +91 -0
  137. package/docs/LOCAL-DEVELOPMENT.md +99 -0
  138. package/docs/MANAGEMENT-SECURITY.md +82 -0
  139. package/docs/MIDDLEWARE.md +83 -0
  140. package/docs/MONITORING.md +132 -0
  141. package/docs/OBSERVABILITY.md +229 -0
  142. package/docs/OPERATIONAL-PROOF.md +42 -0
  143. package/docs/OPERATIONS.md +207 -0
  144. package/docs/ORGANIZATION.md +135 -0
  145. package/docs/PERFORMANCE.md +72 -0
  146. package/docs/PLUGINS.md +234 -0
  147. package/docs/POLICIES.md +489 -0
  148. package/docs/PRERENDER.md +193 -0
  149. package/docs/PROJECT-DIRECTION.md +51 -0
  150. package/docs/READINESS.md +125 -0
  151. package/docs/README.md +58 -0
  152. package/docs/RELEASE-READINESS.md +95 -0
  153. package/docs/RELEASE-SECURITY.md +72 -0
  154. package/docs/RESILIENCE.md +160 -0
  155. package/docs/ROUTING.md +95 -0
  156. package/docs/SANDBOX-REVIEW.md +59 -0
  157. package/docs/SCAFFOLDING.md +72 -0
  158. package/docs/SECURITY-AUDIT.md +124 -0
  159. package/docs/SITE.md +150 -0
  160. package/docs/SPECIFICATION.md +232 -0
  161. package/docs/SPIKE-EXTENSIONS.md +475 -0
  162. package/docs/STANDARDS.md +303 -0
  163. package/docs/STARTERS.md +51 -0
  164. package/docs/TUNNELS.md +75 -0
  165. package/docs/TYPESCRIPT.md +102 -0
  166. package/docs/VERCEL.md +107 -0
  167. package/docs/YAML-GUIDE.md +521 -0
  168. package/docs/YAML-REFERENCE.md +328 -0
  169. package/docs/policies/agents.md +182 -0
  170. package/docs/policies/cache.md +139 -0
  171. package/docs/policies/compression.md +168 -0
  172. package/docs/policies/security.md +160 -0
  173. package/docs/policies/throttle.md +103 -0
  174. package/examples/assets/.env.example +1 -0
  175. package/examples/assets/.gitattributes +2 -0
  176. package/examples/assets/Makefile +30 -0
  177. package/examples/assets/README.md +18 -0
  178. package/examples/assets/functions/hello.mjs +3 -0
  179. package/examples/assets/gitignore.template +7 -0
  180. package/examples/assets/public/about.html +2 -0
  181. package/examples/assets/public/assets/example.txt +1 -0
  182. package/examples/assets/public/guide.txt +1 -0
  183. package/examples/assets/tests/requests.json +44 -0
  184. package/examples/assets/urlcode.yaml +27 -0
  185. package/examples/aws/README.md +13 -0
  186. package/examples/aws/handler.mjs +5 -0
  187. package/examples/aws/public/index.html +3 -0
  188. package/examples/aws/public/notes.txt +1 -0
  189. package/examples/aws/template.yaml +33 -0
  190. package/examples/aws/tests/requests.json +9 -0
  191. package/examples/aws/urlcode.yaml +19 -0
  192. package/examples/cloudflare/README.md +18 -0
  193. package/examples/cloudflare/package.json +14 -0
  194. package/examples/cloudflare/tests/requests.json +9 -0
  195. package/examples/cloudflare/urlcode.yaml +24 -0
  196. package/examples/cloudflare/wrangler.toml +5 -0
  197. package/examples/compliance/README.md +20 -0
  198. package/examples/compliance/rules.mjs +65 -0
  199. package/examples/cookbook/README.md +21 -0
  200. package/examples/cookbook/functions/choice.mjs +4 -0
  201. package/examples/cookbook/functions/echo.mjs +3 -0
  202. package/examples/cookbook/functions/hello.mjs +3 -0
  203. package/examples/cookbook/functions/text.mjs +3 -0
  204. package/examples/cookbook/middleware/headers.mjs +6 -0
  205. package/examples/cookbook/public/about.html +1 -0
  206. package/examples/cookbook/public/assets/index.html +1 -0
  207. package/examples/cookbook/public/assets/site.css +1 -0
  208. package/examples/cookbook/public/favicon.svg +1 -0
  209. package/examples/cookbook/public/guide.txt +1 -0
  210. package/examples/cookbook/public/llms.txt +6 -0
  211. package/examples/cookbook/routes/code.yaml +55 -0
  212. package/examples/cookbook/routes/files.yaml +17 -0
  213. package/examples/cookbook/routes/policies.yaml +28 -0
  214. package/examples/cookbook/routes/redirects.yaml +38 -0
  215. package/examples/cookbook/routes/responses.yaml +26 -0
  216. package/examples/cookbook/tests/requests.json +254 -0
  217. package/examples/cookbook/urlcode.yaml +22 -0
  218. package/examples/live-links/README.md +11 -0
  219. package/examples/live-links/tests/requests.json +6 -0
  220. package/examples/live-links/urlcode.yaml +16 -0
  221. package/examples/monitoring/blackbox-jobs.yaml +37 -0
  222. package/examples/monitoring/prometheus-rules.yaml +88 -0
  223. package/examples/monitoring/prometheus-scrape.yaml +20 -0
  224. package/examples/monitoring/vector.toml +65 -0
  225. package/examples/prerender/README.md +48 -0
  226. package/examples/prerender/functions/page.mjs +6 -0
  227. package/examples/prerender/middleware/template.mjs +29 -0
  228. package/examples/prerender/prerender.d.mts +4 -0
  229. package/examples/prerender/prerender.mjs +63 -0
  230. package/examples/prerender/tests/requests.json +39 -0
  231. package/examples/prerender/urlcode.yaml +50 -0
  232. package/examples/tunnel/dev-with-ngrok.sh +61 -0
  233. package/examples/vercel/README.md +13 -0
  234. package/examples/vercel/api/index.js +5 -0
  235. package/examples/vercel/package.json +8 -0
  236. package/examples/vercel/public/index.html +3 -0
  237. package/examples/vercel/public/notes.txt +1 -0
  238. package/examples/vercel/tests/requests.json +48 -0
  239. package/examples/vercel/urlcode.yaml +19 -0
  240. package/examples/vercel/vercel.json +10 -0
  241. package/llms.txt +46 -0
  242. package/package.json +123 -0
  243. package/schemas/urlcode.schema.json +1230 -0
  244. package/starters/default/.gitattributes +1 -0
  245. package/starters/default/Makefile +30 -0
  246. package/starters/default/README.md +34 -0
  247. package/starters/default/functions/hello.mjs +3 -0
  248. package/starters/default/gitignore.template +8 -0
  249. package/starters/default/middleware/headers.mjs +6 -0
  250. package/starters/default/routes/functions.yaml +20 -0
  251. package/starters/default/routes/marketing/links.yaml +7 -0
  252. package/starters/default/starter.json +5 -0
  253. package/starters/default/tests/requests.json +56 -0
  254. package/starters/default/urlcode.yaml +8 -0
@@ -0,0 +1,267 @@
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
+ Review [sandbox constraints](FUNCTION-SECURITY.md) before choosing dependencies.
170
+
171
+ ## Middleware should have one clear responsibility
172
+
173
+ `middleware/response-headers.mjs`:
174
+
175
+ ```js
176
+ export default async function responseHeaders(request, context, next) {
177
+ const response = await next();
178
+ response.headers.set('x-app', 'my-links');
179
+ return response;
180
+ }
181
+ ```
182
+
183
+ Use middleware for reusable behavior around a handler, not to conceal the entire
184
+ application flow. Prefer YAML headers for fixed route-specific headers; this
185
+ example demonstrates a shared wrapper, but native YAML avoids sandbox overhead
186
+ when no custom code is needed. Keep middleware order explicit in each route.
187
+
188
+ Always return a Response. Call `await next()` once when continuing, or return
189
+ an early Response when intentionally stopping. Do not launch unawaited work or
190
+ assume background tasks will survive. A body read consumes the request body;
191
+ if downstream code needs parsed data, agree on a documented `context.state` field.
192
+ Use specific field names to avoid collisions among middleware.
193
+
194
+ Catch only errors you can handle meaningfully. Do not turn every failure into a
195
+ 200 response or include secrets in errors. Keep native body/status/header
196
+ preservation rules visible in code review. All middleware share the route's
197
+ bindings and execution budget; splitting modules does not create privilege
198
+ separation. See [middleware semantics](MIDDLEWARE.md).
199
+
200
+ ## Organize tests around observable behavior
201
+
202
+ Keep runnable HTTP assertions in `tests/requests.json`, currently the single file
203
+ read by the CLI. Group adjacent cases by route: ordinary success, HEAD, boundary
204
+ inputs, invalid input, wrong method and relevant early responses. Add meaningful
205
+ body/header checks instead of relying only on status codes.
206
+
207
+ For the two-route feature layout above:
208
+
209
+ ```json
210
+ [
211
+ {"path":"/welcome/Ada","status":200,"expectBody":"{\"message\":\"Hello, Ada!\"}","expectHeaders":{"x-app":"my-links"}},
212
+ {"path":"/welcome/Ada","method":"HEAD","status":200,"expectBody":"","expectHeaders":{"x-app":"my-links"}},
213
+ {"path":"/welcome/Ada","method":"POST","status":405,"expectHeaders":{"allow":"GET, HEAD"}},
214
+ {"path":"/guide","status":302,"expectHeaders":{"location":"https://example.com/guide"}},
215
+ {"path":"/guide","method":"HEAD","status":302,"expectBody":"","expectHeaders":{"location":"https://example.com/guide"}}
216
+ ]
217
+ ```
218
+
219
+ Additional ordinary JavaScript unit tests for pure helpers are your project's
220
+ choice. Unit tests alone do not verify sandbox compatibility: always exercise
221
+ HTTP behavior through URLCode too. Keep large fixture generation explicit and
222
+ deterministic if you add your own tooling; nested test directories and JSON
223
+ fragments are not automatically discovered or merged by URLCode.
224
+
225
+ Assert observable contracts, not incidental timings or internal variable names.
226
+ Avoid checking generated request IDs, current Date values or performance numbers
227
+ as fixed functional outputs. Test cache/range semantics with controlled assets.
228
+ Update expected route counts deliberately when adding or removing a route.
229
+
230
+ ## Keep configuration, code, assets and operations separate
231
+
232
+ - Git owns behavior and reviewed code. Keep the runtime as a pinned dependency;
233
+ upgrading it should not regenerate or overwrite application files.
234
+ - Publish only intentionally public files in `public/`. File filters cannot
235
+ recognize every secret. A harmless filename is not proof of public content.
236
+ - Put local secrets in ignored `.env.local`; production values come from the
237
+ operator. Keep operator grants outside the application checkout. Never use
238
+ YAML anchors, shell expansion or generated credentials for convenience.
239
+ - Deployment limits, TLS, DNS, DDoS filters and worker tuning belong to operations,
240
+ not invented route fields. Document them separately from portable behavior.
241
+ - Use [stored links](DYNAMIC-LINKS.md) for live short-code records instead of a
242
+ Git/YAML edit per record. General session/application storage remains future work.
243
+
244
+ ## Refactor without changing the public contract
245
+
246
+ Move one feature at a time. Update explicit includes and project-root source/asset
247
+ paths, then check relative JavaScript imports. Keep public route paths, methods,
248
+ headers and bodies stable unless the change is intentional. Avoid mixing URL
249
+ renames, dependency upgrades and folder rearrangement in one review.
250
+
251
+ Run validate, HTTP tests and the expected-count audit before and after moving
252
+ files. Re-review operator grants: code/config changes invalidate the digest even
253
+ when intended behavior is unchanged. A successful dev reload is not a production
254
+ deployment. Record activation/rollback steps in the project README.
255
+
256
+ ## A useful project README
257
+
258
+ Document how to install the pinned runtime, start locally, run tests/audit, and
259
+ activate a release. Include a small folder map, the owner of each major feature,
260
+ public route behavior and required external binding names (never values). State
261
+ supported Node/runtime versions and link to matching URLCode docs. Explain any
262
+ middleware ordering or surprising defaults that a new maintainer might miss.
263
+
264
+ For AI-generated changes, require the assistant to follow existing conventions,
265
+ keep diffs focused, add response assertions and report checks actually run. Reject
266
+ invented YAML keys, hidden side effects and unnecessary abstractions. The
267
+ [AI authoring guide](AI-AUTHORING.md) provides a reusable prompt and capability list.
@@ -0,0 +1,208 @@
1
+ # Capacity, concurrency and system limits
2
+
3
+ These are 0.2.0 implementation limits and planning models, not a throughput
4
+ SLA. Route count, connections, in-flight requests and sandbox concurrency are
5
+ four different quantities. Always measure the actual application on deployment
6
+ hardware with the intended proxy, TLS, logging and limits enabled.
7
+
8
+ ## What happens for each request
9
+
10
+ A single Node process accepts HTTP, parses/validates inputs, matches a compiled
11
+ route and builds the response. Exact routes use a Map lookup (expected O(1)
12
+ lookup after path parsing). Parameter candidates are grouped by segment count
13
+ and scanned in specificity order; matching is O(P × L) in the worst case for P
14
+ candidates and L segments. Static mount prefixes are scanned longest first.
15
+
16
+ Plain redirects, declared responses, stored-link lookups and assets do not enter
17
+ the sandbox. Stored links use a separate bounded database pools. A
18
+ function or any attached middleware occupies one shared worker slot for its
19
+ whole chain. Workers are shared by all programmable routes in that snapshot;
20
+ there is no per-route fairness or reserved capacity. Awaiting guest timers still
21
+ occupies the slot. A fresh guest and module initialization are part of each call.
22
+
23
+ Node's main event loop remains a shared bottleneck for HTTP parsing, logging and
24
+ native responses. Sandboxing contains application code authority and bounds
25
+ individual execution; it does not make all host resources immune to exhaustion.
26
+
27
+ ## Enforced limits and defaults
28
+
29
+ | Resource | Current behavior | Scope / configuration |
30
+ |---|---|---|
31
+ | Routes | 100,000 combined | Per project snapshot; schema/loader cap |
32
+ | Parameter routes | 1,000 | Per snapshot; not 1,000 concurrent requests |
33
+ | Included files / YAML size | 256 / 32 MiB per file / 64 MiB aggregate | Parser worker: 256 MiB old heap, 10 s deadline, two concurrent loads per isolate |
34
+ | Route path | 2,048 characters, 32 segments | Configured path; no regex or greedy parameters |
35
+ | Request target / headers | 8,192 characters / 16 KiB headers | Target is checked as a JS string; HTTP header limit is bytes |
36
+ | HTTP connections | 1,024 | Per server; includes keep-alive sockets, not worker slots or users |
37
+ | In-flight application requests | 64 default, no queue; excess gets 503 | From body receipt through response finish/disconnect; health probes exempt |
38
+ | Socket inactivity | 15 s | Destroys inactive sockets, including stalled response writers; not an absolute response deadline |
39
+ | Requests per socket | 1,000 | Connection recycling; not a requests-per-second limit |
40
+ | Header / request receipt / keep-alive timeouts | 10 s / 15 s / 5 s | These are not an overall end-to-end response deadline |
41
+ | Request body | 1 MiB default | Buffered; route maxBytes can tighten to 0–1 MiB |
42
+ | Sandbox concurrency | 2 workers, no queue | Shared per snapshot; full pool returns 503 |
43
+ | Execution deadline | 5 s default | Entire middleware + handler invocation; timeout returns 504 |
44
+ | Guest heap / stack | 32 MiB / 512 KiB | Fresh per invocation; not a bound on total process RSS |
45
+ | Outer worker old-generation V8 budget | 128 MiB | Separate from WASM/host/native allocations |
46
+ | Function response | 1 MiB default, 16 KiB / 256 header pairs | Buffered text/JSON; YAML headers also bounded |
47
+ | Middleware | 16 entries per route | One shared slot/deadline, not 16 independent workers |
48
+ | Function sources | 128 modules, 1 MiB/module, 4 MiB total | Project snapshot, including middleware dependencies |
49
+ | Worker startup | 5 s deadline | Failure rejects activation; no untrusted host fallback |
50
+ | Worker replacement | Up to 3 exits/minute per slot trigger replacement | Further churn leaves the slot unavailable until reload/restart |
51
+ | Assets | 16 MiB/file, 64 MiB unique contents | Buffered immutable snapshots; 10,000 static entries, depth 20 |
52
+ | Logger buffering | Drop at 1 MiB stdout buffering | Reports logs_dropped when output recovers |
53
+
54
+ The 1,024-connection cap is not a global memory bound, fairness policy or DDoS
55
+ protection. At the default admission/body limits, accepted uploads can buffer up to
56
+ 64 MiB of payload before copies and other allocations. Slow readers can hold
57
+ sockets/response memory until completion/disconnect or the 15-second inactivity
58
+ timeout. A peer that continues making progress can stay connected longer. Use
59
+ proxy admission limits, timeouts and OS/container limits.
60
+
61
+ The CLI and the embedding JS API accept `--workers`/`workers` (1–32),
62
+ `--function-timeout-ms`/`timeoutMs` (10–60,000), `--max-response-bytes`/`maxBytes`
63
+ (response limit, 1–16 MiB), `--max-body-bytes`/`maxBodyBytes` (request limit, 1–16 MiB),
64
+ `--max-in-flight`/`maxInFlightRequests` (1–1,024; default 64) and
65
+ `--max-in-flight-health`/`maxInFlightHealthRequests` (1–1,024; default 16). Measure the effect with
66
+ [load testing](LOAD-TESTING.md) rather than guessing; `shedResponses` names the
67
+ limit that bound. These are
68
+ operator choices on `startServer`, not supported YAML fields or CLI flags.
69
+ Route body policy still cannot exceed 1 MiB. More workers consume memory and CPU;
70
+ increasing a timeout also increases how long an attacker can occupy capacity.
71
+ The CLI uses defaults. Keep settings identical across replicas unless testing a
72
+ controlled rollout. See [operations](OPERATIONS.md).
73
+
74
+ ## A useful theoretical model
75
+
76
+ Let W be worker slots, S the measured mean slot occupancy in seconds (including
77
+ sandbox startup and cleanup effects), and lambda the offered programmable
78
+ requests per second. An idealized worker ceiling is:
79
+
80
+ ```text
81
+ worker-limited throughput <= W / S
82
+ mean offered worker load A = lambda * S
83
+ ```
84
+
85
+ This ignores CPU contention, event-loop work, garbage collection, worker failures
86
+ and network overhead. It is an upper bound under simplified assumptions, not a
87
+ recommended arrival rate. With the default W=2:
88
+
89
+ | Mean slot time S | Idealized ceiling W/S |
90
+ |---|---:|
91
+ | 5 ms | 400 requests/s |
92
+ | 50 ms | 40 requests/s |
93
+ | 500 ms | 4 requests/s |
94
+ | 5 s | 0.4 requests/s (at the timeout boundary; not useful successful capacity) |
95
+
96
+ No queue means requests are rejected when both slots are occupied, even if the
97
+ average arrival rate is below the ceiling. Under a simplified independent
98
+ Poisson-arrival loss model, Erlang B gives blocking probability:
99
+
100
+ ```text
101
+ B(W,A) = (A^W / W!) / sum(k=0..W, A^k / k!)
102
+ ```
103
+
104
+ For W=2 and S=50 ms, an offered 20 requests/s gives A=1 and B=20%. That is a
105
+ model illustration, not a measured URLCode result. Bursts, correlated traffic and
106
+ CPU-dependent service times can differ substantially. Measure rejection rate as
107
+ well as latency; fast 503 responses must not count as successful throughput.
108
+ An upstream bounded queue may smooth bursts but adds latency and memory; it is
109
+ not included in this runtime. Unbounded queues just move the failure.
110
+
111
+ Little's law, L=lambda*R, describes average in-flight work for a stable system
112
+ using admitted/completed throughput and mean residence time. It does not turn
113
+ 1,024 sockets into 1,024 execution slots or predict tail latency. CPU and bandwidth
114
+ put separate ceilings on throughput. For CPU-bound work, adding workers beyond
115
+ available cores cannot produce linear scaling. Bandwidth must also carry asset
116
+ bytes, response headers, TLS and protocol overhead.
117
+
118
+ ## Native routes and mixed traffic
119
+
120
+ For native-only traffic, the Node event loop, network, buffers and logging dominate;
121
+ the W/S sandbox model does not apply. Function saturation does not itself consume
122
+ native route worker slots. However all routes share the server/event loop and
123
+ host resources, so a flood can still degrade ordinary redirects and health checks.
124
+ A single expensive function can starve other functions. For stronger isolation,
125
+ use separate processes/containers and proxy routing; there is no per-route pool
126
+ configuration in YAML today.
127
+
128
+ Horizontal replicas can add capacity if balanced well and supplied identical
129
+ runtime/application revisions and bindings. Scaling is not perfectly linear,
130
+ and capacity falls during failures/rollouts. Rate limits must account for all
131
+ replicas. In-memory counters in middleware reset per request and cannot implement
132
+ a shared rate limiter or durable application state.
133
+
134
+ Optional [policies](POLICIES.md) keep their state per runtime instance, and
135
+ their memory bounds are per instance too: the `throttle` counter table is one
136
+ LRU table per runtime capped by the largest declared `maxKeys` (default
137
+ 100,000 keys), and the `cache` policy's origin cache is bounded by its
138
+ `maxEntries` and `maxBytes` per route and by 64 MiB of bodies across the
139
+ whole runtime; the `compression` policy holds up to 64 MiB of precompressed
140
+ asset variants per runtime, the same figure as the asset snapshot itself, so
141
+ a fully policied instance can hold three such budgets. Neither is shared between replicas or
142
+ serverless instances, so a client budget across N replicas is up to N times
143
+ the declared quota and a cached response is computed once per replica. Both
144
+ tables are dropped on a snapshot reload. Sharing state across instances is a
145
+ [plugin](PLUGINS.md) concern.
146
+
147
+ ## Memory, startup and reload
148
+
149
+ A practical memory budget includes the Node baseline, parsed YAML/compiled route
150
+ objects, asset snapshots, source copies in workers, WASM heaps, active request
151
+ and response buffers, sockets, logs and transient garbage collection allocations.
152
+ These are not all covered by worker heap limits. Production needs measured peak
153
+ RSS with an OS/container ceiling, plus headroom.
154
+
155
+ Reload constructs a complete new snapshot while the old one serves/drains. Old
156
+ and new assets and worker pools can overlap; repeated reloads with in-flight calls
157
+ can retain multiple generations. Host route compilation and snapshot transfer can delay the shared event
158
+ loop even though the HTTP listener is not restarted. Do not equate atomic swap
159
+ with zero latency impact or incremental route updates. Prefer candidate replicas
160
+ and traffic switching for production. `serve` does not watch configuration.
161
+
162
+ Before parser-worker limits were introduced, recorded 100k-route startup RSS was about 621 MiB on one development machine,
163
+ above the illustrative 512 MiB container example. Route limits are acceptance
164
+ caps, not a promise that the maximum fits your deployment. See [measurements](PERFORMANCE.md).
165
+ That short benchmark uses 5,000 measured requests and does not exercise all routes
166
+ in the larger datasets; client/server share a process, logs are off, and no TLS
167
+ or production proxy is involved. No NGINX performance ratio has been measured.
168
+
169
+ ## Establish a deployment budget
170
+
171
+ 1. Pin runtime, app, dependency locks and image; record CPU, RAM, Node, proxy/TLS
172
+ settings, workers, timeouts and logging. Select representative input/body sizes.
173
+ 2. Measure native redirects, parameter hits/misses, functions, middleware and
174
+ assets separately, then use the expected mixed workload and hot-route skew.
175
+ 3. Use a separate load generator for deployment tests. Increase offered rate and
176
+ concurrency gradually; record successful throughput, all status counts,
177
+ p50/p95/p99, CPU, peak RSS, sockets, restarts and network bytes.
178
+ 4. Include bursts, slow clients, saturation, invalid inputs and one failed replica.
179
+ Sustain tests long enough to observe memory/GC behavior and stable plateaus.
180
+ 5. Choose admission limits below the measured failure knee with explicit spare
181
+ capacity for a replica loss. Verify the service recovers after load stops.
182
+ 6. Record the accepted load, error and latency budgets and repeat after changes.
183
+
184
+ The built-in local benchmark is a quick correctness-aware signal, not the above
185
+ production exercise. The readiness endpoint can stay 200 while all worker slots
186
+ are busy. Use error/latency signals too. No universal safe RPS can be derived
187
+ from the route count or these defaults alone. See [resilience](RESILIENCE.md).
188
+
189
+ ## Optional stored-link capacity
190
+
191
+ Each SQLite store defaults to two read-only worker connections; writable stores
192
+ add one writer. Independent read/write admission caps default to 32 each across
193
+ their pool. Readers are configurable from 1–8, and caps from 1–32. Operations have
194
+ a 5-second deadline and one-second SQLite lock wait. Public serving has no writer. The
195
+ initial cap is 100,000 stored records across collections; this is separate from
196
+ the YAML route count. No lookup cache is used, so visibility does not depend on
197
+ cache invalidation. Store failures/overload return 503 and failed workers need
198
+ reload/restart; healthy readers can continue while readiness is degraded. Management has a separate listener with 64 connections, 8 KiB
199
+ headers and 16 KiB JSON bodies. Rate limiting remains an ingress responsibility.
200
+ Do not extrapolate in-memory redirect benchmark numbers to database lookups;
201
+ measure disk, writes, contention and restoration on the target host.
202
+
203
+ Configuration parsing/schema validation now run in a terminated-on-deadline worker;
204
+ route compilation still runs cooperatively on the host (10 seconds, yields every
205
+ 64 routes). Source, AST, structured-clone output, compiled routes, assets, module
206
+ snapshots and overlapping runtimes all consume memory. Worker V8 limits do not cap
207
+ external buffers or aggregate process RSS. Enforce container/process limits and
208
+ operator-controlled activation; see [review scope](SANDBOX-REVIEW.md).
@@ -0,0 +1,110 @@
1
+ # Deploying to Cloudflare Workers
2
+
3
+ Cloudflare Workers is not a Node host. There is no `worker_threads`, no
4
+ filesystem, and code generation at runtime is forbidden. So this target does not
5
+ adapt the runtime the way [Vercel](VERCEL.md) and [AWS](AWS.md) do — it
6
+ **compiles the project ahead of time** and ships a Worker that reads the result.
7
+
8
+ ```sh
9
+ urlcode build --target cloudflare --project . --out dist
10
+ npx wrangler deploy
11
+ ```
12
+
13
+ A working project is in [`examples/cloudflare/`](../examples/cloudflare/).
14
+
15
+ ## Declarative routes only
16
+
17
+ This target serves **redirects** and **declared responses** (`respond:`), with
18
+ path, query and header parameters, defaults, validation, response headers,
19
+ `enabled` and `expires`. Everything else is refused **at build time**, with the
20
+ route pattern and the reason named:
21
+
22
+ | Handler | Why it is refused |
23
+ | --- | --- |
24
+ | `function`, `middleware` | need worker threads and the QuickJS WASM engine |
25
+ | `link` | needs a durable writable store the platform does not provide here |
26
+ | `page`, `static`, `download` | need a platform static-asset binding, not an inline copy |
27
+ | `env`, `secrets` | would have to be baked into the artifact |
28
+
29
+ Generated [site conventions](SITE.md) follow the same table: `robots`,
30
+ `sitemap` and `securityTxt` are `respond` routes and compile into the artifact
31
+ (pass `--origin` to `build` for the absolute URLs they contain); `favicon` and
32
+ `llms` are `page` routes and are refused.
33
+
34
+ A build artifact is a file that gets copied, cached and committed by mistake, so
35
+ it never carries a secret. That is why `env` and `secrets` are refused even when
36
+ the value is a literal in the YAML.
37
+
38
+ Refusing at build time rather than at runtime is the point: a project that
39
+ cannot be served fails `urlcode build`, so it never reaches a deployment.
40
+
41
+ ## What the build emits
42
+
43
+ `--out` receives three generated files. None of them are edited by hand, and
44
+ `dist/` belongs in `.gitignore`:
45
+
46
+ - `artifact.js` — the compiled routes. **This is an internal build output, not a
47
+ published contract.** Its `format` may change in any release, and the runtime
48
+ refuses a format it does not recognise rather than guessing. Always rebuild
49
+ with the same version of URLCode that the Worker imports.
50
+ - `validators.js` — the parameter schemas, precompiled by Ajv into standalone ES
51
+ modules. The platform forbids runtime code generation, so a validator cannot
52
+ be compiled on the Worker; it has to be compiled by the build.
53
+ - `index.js` — the Worker entry, which is three lines over
54
+ `createFetchHandler` from `@jimhoyd/urlcode/cloudflare`. That import resolves to the
55
+ package's built `dist/cloudflare.js` (and its declarations, for a TypeScript
56
+ Worker); the artifact never depends on the TypeScript sources or on type
57
+ stripping.
58
+
59
+ Ajv's standalone output hardcodes a CommonJS `require` for its runtime helpers
60
+ even in ESM mode, which an ES module cannot evaluate. The build inlines each
61
+ helper from the installed Ajv — the real function, not a copy that could drift —
62
+ and fails if it meets a `require` it does not recognise, rather than emitting a
63
+ Worker that cannot start.
64
+
65
+ `wrangler.toml` needs no `nodejs_compat` flag. The runtime and the generated
66
+ validators use Web standards only.
67
+
68
+ ## Portability, and where it stops
69
+
70
+ The Worker shares its route matching, request policy and response policy with
71
+ the self-hosted server: `src/match.ts`, `src/http-policy.ts` and
72
+ `src/http-response.ts` are the same modules, with no Node imports. Two checks
73
+ keep it that way: an ESLint rule forbids `node:` imports in the modules that
74
+ ship to the Worker, and `scripts/check.ts` (part of `npm run verify`) walks the
75
+ import closure of `src/cloudflare.ts` and fails on any `node:` specifier that
76
+ is not an `import type`. `test/cloudflare.test.ts` builds a project, runs the same project on the
77
+ self-hosted server, and asserts both return the same status, body and headers
78
+ (everything but the per-request identifier) — including the example in this
79
+ repository, replayed through the compiled Worker.
80
+
81
+ [Policies](POLICIES.md) follow the same rule: `agents` and `security` are
82
+ compiled into the artifact with project list files embedded as entries,
83
+ `compression` is delegated to the edge, and `throttle` and `cache` are refused
84
+ at build time with the route named. The artifact also carries the project-level
85
+ `security` policy, so the Worker's own 404 and thrown-error responses get the
86
+ same security headers the self-hosted server gives them.
87
+
88
+ Two differences are real and deliberate:
89
+
90
+ - **Duplicate request headers.** The platform joins repeated headers into one
91
+ value before the Worker runs, so per-header counts do not exist. The
92
+ self-hosted server rejects a duplicated scalar header parameter with 400. Here
93
+ that check cannot fire: the parameter sees the joined value (`a, b`) and is
94
+ validated against its schema like any other. A constrained schema still
95
+ rejects it; an unconstrained `type: string` accepts it where the self-hosted
96
+ server would not. Constrain header parameters you care about. The duplicate
97
+ `Content-Type` check on a declared request body is unavailable for the same
98
+ reason; a joined value fails the media-type check instead.
99
+ - **The request target.** The self-hosted server inspects the request line
100
+ verbatim. The Worker only ever sees a parsed `Request`, so the target is
101
+ reconstructed from `URL`, and a malformed target the self-hosted server would
102
+ refuse may have been normalised or rejected by the platform before this code
103
+ runs. Path traversal, control characters, over-long targets and ambiguous `%`
104
+ sequences that do survive are still refused by the shared `parseTarget`.
105
+
106
+ **This has never been deployed to Cloudflare.** Everything above is verified
107
+ against the runtime's own test suite and a local build, not against the
108
+ platform. A first real deployment is the next thing that would change that, and
109
+ until it happens, treat compatibility with a specific `compatibility_date` and
110
+ with Wrangler's bundler as unproven.