@jimhoyd/urlcode 0.3.0 → 0.4.0-alpha.1

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 (326) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +106 -0
  2. package/.claude/skills/urlcode-operations/SKILL.md +114 -0
  3. package/.claude-plugin/marketplace.json +18 -0
  4. package/CONTRIBUTING.md +30 -2
  5. package/README.md +157 -230
  6. package/ROADMAP.md +85 -2
  7. package/SECURITY.md +8 -1
  8. package/dist/BUILD-MANIFEST.json +62 -32
  9. package/dist/adapters.js +4 -23
  10. package/dist/agent-lists.js +1 -1
  11. package/dist/agents-guide.js +113 -0
  12. package/dist/authoring-files.js +60 -0
  13. package/dist/authoring.js +11 -1
  14. package/dist/aws.js +4 -3
  15. package/dist/build-cloudflare.js +11 -24
  16. package/dist/bulk.js +37 -0
  17. package/dist/capabilities.js +208 -0
  18. package/dist/capability-query.js +72 -0
  19. package/dist/catalog.js +105 -0
  20. package/dist/cli.js +154 -15
  21. package/dist/client-address.js +1 -1
  22. package/dist/compliance-rules/baseline.js +8 -8
  23. package/dist/compliance-rules/privacy.js +3 -3
  24. package/dist/compliance-rules/strict.js +5 -5
  25. package/dist/conditions.js +88 -0
  26. package/dist/config.js +60 -4
  27. package/dist/context.js +156 -0
  28. package/dist/ecosystem-cli.js +88 -0
  29. package/dist/egress.js +98 -0
  30. package/dist/examples.js +92 -0
  31. package/dist/explain-cli.js +64 -0
  32. package/dist/explain.js +132 -0
  33. package/dist/extensions.js +172 -0
  34. package/dist/function-sources.js +15 -3
  35. package/dist/index.js +37 -0
  36. package/dist/init-with.js +165 -0
  37. package/dist/interchange-cli.js +42 -0
  38. package/dist/interchange.js +189 -0
  39. package/dist/link-cli.js +1 -1
  40. package/dist/management-policy.js +0 -1
  41. package/dist/manifest.js +111 -0
  42. package/dist/match.js +2 -2
  43. package/dist/mcp-authoring.js +147 -0
  44. package/dist/mcp.js +97 -0
  45. package/dist/observability.js +6 -0
  46. package/dist/operator-host.js +29 -0
  47. package/dist/plugins.js +12 -0
  48. package/dist/policies/agents.js +2 -2
  49. package/dist/policies/compression.js +2 -1
  50. package/dist/policies/security.js +0 -0
  51. package/dist/policies.js +1 -1
  52. package/dist/policy.js +29 -7
  53. package/dist/prerender.js +100 -41
  54. package/dist/project-tests.js +3 -3
  55. package/dist/provider-verification.js +92 -0
  56. package/dist/proxy.js +44 -0
  57. package/dist/readiness.js +5 -5
  58. package/dist/recipes.js +41 -0
  59. package/dist/route-diff.js +106 -0
  60. package/dist/router.js +42 -2
  61. package/dist/runtime.js +95 -14
  62. package/dist/schema-query.js +62 -0
  63. package/dist/signals.js +24 -0
  64. package/dist/site.js +0 -0
  65. package/dist/tooling.js +96 -0
  66. package/dist/types/adapters.d.ts +7 -4
  67. package/dist/types/agent-lists.d.ts +0 -1
  68. package/dist/types/agents-guide.d.ts +17 -0
  69. package/dist/types/authoring-files.d.ts +10 -0
  70. package/dist/types/aws.d.ts +3 -1
  71. package/dist/types/build-cloudflare.d.ts +1 -0
  72. package/dist/types/bulk.d.ts +27 -0
  73. package/dist/types/capabilities.d.ts +55 -0
  74. package/dist/types/capability-query.d.ts +24 -0
  75. package/dist/types/catalog.d.ts +65 -0
  76. package/dist/types/client-address.d.ts +0 -1
  77. package/dist/types/compliance-rules/baseline.d.ts +1 -9
  78. package/dist/types/compliance-rules/privacy.d.ts +1 -4
  79. package/dist/types/compliance-rules/strict.d.ts +0 -5
  80. package/dist/types/conditions.d.ts +19 -0
  81. package/dist/types/config.d.ts +20 -2
  82. package/dist/types/context.d.ts +65 -0
  83. package/dist/types/ecosystem-cli.d.ts +17 -0
  84. package/dist/types/egress.d.ts +46 -0
  85. package/dist/types/examples.d.ts +50 -0
  86. package/dist/types/explain-cli.d.ts +11 -0
  87. package/dist/types/explain.d.ts +95 -0
  88. package/dist/types/extensions.d.ts +122 -0
  89. package/dist/types/function-sources.d.ts +5 -0
  90. package/dist/types/index.d.ts +33 -0
  91. package/dist/types/init-with.d.ts +30 -0
  92. package/dist/types/interchange-cli.d.ts +16 -0
  93. package/dist/types/interchange.d.ts +42 -0
  94. package/dist/types/link-cli.d.ts +2 -1
  95. package/dist/types/management-policy.d.ts +0 -1
  96. package/dist/types/manifest.d.ts +81 -0
  97. package/dist/types/match.d.ts +1 -0
  98. package/dist/types/mcp-authoring.d.ts +92 -0
  99. package/dist/types/mcp.d.ts +12 -0
  100. package/dist/types/observability.d.ts +2 -0
  101. package/dist/types/operator-host.d.ts +8 -0
  102. package/dist/types/plugins.d.ts +2 -0
  103. package/dist/types/policies/agents.d.ts +0 -2
  104. package/dist/types/policies/compression.d.ts +2 -0
  105. package/dist/types/policies/security.d.ts +0 -1
  106. package/dist/types/policy.d.ts +15 -4
  107. package/dist/types/project-tests.d.ts +3 -1
  108. package/dist/types/provider-verification.d.ts +53 -0
  109. package/dist/types/proxy.d.ts +21 -0
  110. package/dist/types/readiness.d.ts +1 -1
  111. package/dist/types/recipes.d.ts +30 -0
  112. package/dist/types/route-diff.d.ts +27 -0
  113. package/dist/types/runtime.d.ts +11 -0
  114. package/dist/types/schema-query.d.ts +12 -0
  115. package/dist/types/signals.d.ts +25 -0
  116. package/dist/types/site.d.ts +0 -1
  117. package/dist/types/tooling.d.ts +115 -0
  118. package/dist/types/types.d.ts +57 -0
  119. package/dist/types/typescript-authoring.d.ts +12 -0
  120. package/dist/types/vercel.d.ts +3 -1
  121. package/dist/types/verify-deployment.d.ts +47 -0
  122. package/dist/types.js +21 -2
  123. package/dist/typescript-authoring.js +104 -0
  124. package/dist/vercel.js +4 -3
  125. package/dist/verify-deployment.js +270 -0
  126. package/docs/AI-AUTHORING.md +130 -8
  127. package/docs/BULK.md +79 -0
  128. package/docs/CAPABILITIES.md +179 -0
  129. package/docs/CAPACITY.md +1 -1
  130. package/docs/CI.md +142 -0
  131. package/docs/CONDITIONS.md +74 -0
  132. package/docs/DEPLOYMENT-CHECKS.md +108 -0
  133. package/docs/DYNAMIC-LINKS.md +18 -518
  134. package/docs/EGRESS.md +125 -0
  135. package/docs/EXTENSIONS.md +226 -0
  136. package/docs/FRAMEWORK.md +182 -0
  137. package/docs/INSTALL.md +45 -7
  138. package/docs/INTERCHANGE.md +134 -0
  139. package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
  140. package/docs/MIDDLEWARE.md +2 -0
  141. package/docs/NEXT-PHASE-PLAN.md +90 -0
  142. package/docs/NEXT-STEPS.md +415 -0
  143. package/docs/OBSERVABILITY.md +4 -2
  144. package/docs/OPERATIONAL-PROOF.md +4 -1
  145. package/docs/OPERATIONS.md +6 -3
  146. package/docs/PLUGINS.md +37 -0
  147. package/docs/POLICIES.md +12 -309
  148. package/docs/PRERENDER.md +40 -0
  149. package/docs/PROJECT-DIRECTION.md +42 -0
  150. package/docs/PROVIDER-VERIFICATION.md +84 -0
  151. package/docs/READINESS.md +21 -1
  152. package/docs/README.md +82 -31
  153. package/docs/RECIPES.md +99 -0
  154. package/docs/RELEASE-READINESS.md +11 -9
  155. package/docs/RELEASE-SECURITY.md +27 -4
  156. package/docs/SECURITY-AUDIT.md +1 -1
  157. package/docs/SPECIFICATION.md +95 -8
  158. package/docs/SPIKE-BUSINESS-SUITE.md +1013 -0
  159. package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
  160. package/docs/SPIKE-EXTENSIONS.md +1 -0
  161. package/docs/SPIKE-LAMBDA-COMPILE.md +199 -0
  162. package/docs/STANDARDS.md +150 -142
  163. package/docs/STARTERS.md +21 -1
  164. package/docs/TOOLING.md +291 -0
  165. package/docs/TYPESCRIPT-AUTHORING.md +67 -0
  166. package/docs/TYPESCRIPT.md +1 -1
  167. package/docs/USABILITY-REVIEW.md +123 -0
  168. package/docs/YAML-GUIDE.md +18 -479
  169. package/docs/YAML-REFERENCE.md +127 -16
  170. package/docs/links/cli.md +110 -0
  171. package/docs/links/limits.md +175 -0
  172. package/docs/links/management-api.md +80 -0
  173. package/docs/links/pools.md +75 -0
  174. package/docs/links/setup.md +135 -0
  175. package/docs/policies/agents.md +1 -1
  176. package/docs/policies/contract.md +52 -0
  177. package/docs/policies/hardened.md +56 -0
  178. package/docs/policies/interoperability.md +169 -0
  179. package/docs/policies/operations.md +45 -0
  180. package/docs/yaml/assets.md +36 -0
  181. package/docs/yaml/conditions.md +20 -0
  182. package/docs/yaml/functions.md +160 -0
  183. package/docs/yaml/links.md +30 -0
  184. package/docs/yaml/middleware.md +29 -0
  185. package/docs/yaml/organization.md +74 -0
  186. package/docs/yaml/policies.md +37 -0
  187. package/docs/yaml/redirects.md +64 -0
  188. package/docs/yaml/responses.md +57 -0
  189. package/docs/yaml/site.md +24 -0
  190. package/examples/assets/example.yaml +17 -0
  191. package/examples/aws/example.yaml +20 -0
  192. package/examples/cloudflare/example.yaml +19 -0
  193. package/examples/compliance/example.yaml +11 -0
  194. package/examples/conditions/README.md +12 -0
  195. package/examples/conditions/example.yaml +19 -0
  196. package/examples/conditions/tests/requests.json +13 -0
  197. package/examples/conditions/urlcode.yaml +24 -0
  198. package/examples/cookbook/README.md +8 -4
  199. package/examples/cookbook/example.yaml +17 -0
  200. package/examples/cookbook/functions/catalog.mjs +3 -0
  201. package/examples/cookbook/functions/fail.mjs +4 -0
  202. package/examples/cookbook/functions/items.mjs +3 -0
  203. package/examples/cookbook/functions/profile.mjs +3 -0
  204. package/examples/cookbook/functions/resource.mjs +3 -0
  205. package/examples/cookbook/functions/status.mjs +3 -0
  206. package/examples/cookbook/middleware/auth.mjs +48 -0
  207. package/examples/cookbook/middleware/body.mjs +15 -0
  208. package/examples/cookbook/middleware/bucket.mjs +19 -0
  209. package/examples/cookbook/middleware/cors.mjs +21 -0
  210. package/examples/cookbook/middleware/debug.mjs +13 -0
  211. package/examples/cookbook/middleware/envelope.mjs +11 -0
  212. package/examples/cookbook/middleware/errors.mjs +11 -0
  213. package/examples/cookbook/middleware/etag.mjs +18 -0
  214. package/examples/cookbook/middleware/locale.mjs +16 -0
  215. package/examples/cookbook/middleware/maintenance.mjs +10 -0
  216. package/examples/cookbook/middleware/methods.mjs +15 -0
  217. package/examples/cookbook/middleware/negotiate.mjs +20 -0
  218. package/examples/cookbook/middleware/referer.mjs +12 -0
  219. package/examples/cookbook/middleware/request-id.mjs +16 -0
  220. package/examples/cookbook/route-index.json +676 -0
  221. package/examples/cookbook/routes/middleware.yaml +126 -0
  222. package/examples/cookbook/tests/requests.json +526 -0
  223. package/examples/cookbook/urlcode.yaml +1 -0
  224. package/examples/egress/README.md +22 -0
  225. package/examples/egress/example.yaml +19 -0
  226. package/examples/egress/urlcode.yaml +19 -0
  227. package/examples/extensions/README.md +7 -0
  228. package/examples/extensions/example.yaml +21 -0
  229. package/examples/extensions/urlcode.yaml +25 -0
  230. package/examples/live-links/example.yaml +21 -0
  231. package/examples/monitoring/example.yaml +8 -0
  232. package/examples/prerender/example.yaml +16 -0
  233. package/examples/provider-conformance/README.md +12 -0
  234. package/examples/provider-conformance/example.yaml +14 -0
  235. package/examples/provider-conformance/urlcode.yaml +34 -0
  236. package/examples/tunnel/example.yaml +8 -0
  237. package/examples/vercel/example.yaml +19 -0
  238. package/llms-full.txt +2709 -0
  239. package/llms.txt +48 -19
  240. package/package.json +29 -7
  241. package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
  242. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +106 -0
  243. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +114 -0
  244. package/recipes/authenticated-json-api/README.md +51 -0
  245. package/recipes/authenticated-json-api/functions/profile.mjs +5 -0
  246. package/recipes/authenticated-json-api/recipe.yaml +34 -0
  247. package/recipes/authenticated-json-api/tests/requests.json +39 -0
  248. package/recipes/authenticated-json-api/urlcode.yaml +12 -0
  249. package/recipes/contact-form/README.md +25 -0
  250. package/recipes/contact-form/functions/contact.mjs +17 -0
  251. package/recipes/contact-form/recipe.yaml +33 -0
  252. package/recipes/contact-form/tests/requests.json +47 -0
  253. package/recipes/contact-form/urlcode.yaml +18 -0
  254. package/recipes/cors-api/README.md +16 -0
  255. package/recipes/cors-api/functions/items.mjs +3 -0
  256. package/recipes/cors-api/middleware/cors.mjs +21 -0
  257. package/recipes/cors-api/recipe.yaml +26 -0
  258. package/recipes/cors-api/tests/requests.json +65 -0
  259. package/recipes/cors-api/urlcode.yaml +12 -0
  260. package/recipes/health-page/README.md +13 -0
  261. package/recipes/health-page/recipe.yaml +23 -0
  262. package/recipes/health-page/tests/requests.json +36 -0
  263. package/recipes/health-page/urlcode.yaml +19 -0
  264. package/recipes/json-api/README.md +6 -0
  265. package/recipes/json-api/functions/echo.mjs +3 -0
  266. package/recipes/json-api/recipe.yaml +25 -0
  267. package/recipes/json-api/tests/requests.json +34 -0
  268. package/recipes/json-api/urlcode.yaml +12 -0
  269. package/recipes/middleware/README.md +34 -0
  270. package/recipes/middleware/functions/catalog.mjs +3 -0
  271. package/recipes/middleware/functions/fail.mjs +4 -0
  272. package/recipes/middleware/functions/items.mjs +3 -0
  273. package/recipes/middleware/functions/profile.mjs +3 -0
  274. package/recipes/middleware/functions/resource.mjs +3 -0
  275. package/recipes/middleware/functions/status.mjs +3 -0
  276. package/recipes/middleware/middleware/auth.mjs +48 -0
  277. package/recipes/middleware/middleware/body.mjs +15 -0
  278. package/recipes/middleware/middleware/bucket.mjs +19 -0
  279. package/recipes/middleware/middleware/cors.mjs +21 -0
  280. package/recipes/middleware/middleware/debug.mjs +13 -0
  281. package/recipes/middleware/middleware/envelope.mjs +11 -0
  282. package/recipes/middleware/middleware/errors.mjs +11 -0
  283. package/recipes/middleware/middleware/etag.mjs +18 -0
  284. package/recipes/middleware/middleware/locale.mjs +16 -0
  285. package/recipes/middleware/middleware/maintenance.mjs +10 -0
  286. package/recipes/middleware/middleware/methods.mjs +15 -0
  287. package/recipes/middleware/middleware/negotiate.mjs +20 -0
  288. package/recipes/middleware/middleware/referer.mjs +12 -0
  289. package/recipes/middleware/middleware/request-id.mjs +16 -0
  290. package/recipes/middleware/public/guide.txt +1 -0
  291. package/recipes/middleware/recipe.yaml +50 -0
  292. package/recipes/middleware/tests/requests.json +528 -0
  293. package/recipes/middleware/urlcode.yaml +127 -0
  294. package/recipes/protected-download/README.md +22 -0
  295. package/recipes/protected-download/files/report.txt +1 -0
  296. package/recipes/protected-download/recipe.yaml +31 -0
  297. package/recipes/protected-download/tests/requests.json +32 -0
  298. package/recipes/protected-download/urlcode.yaml +15 -0
  299. package/recipes/redirect/README.md +7 -0
  300. package/recipes/redirect/recipe.yaml +25 -0
  301. package/recipes/redirect/tests/requests.json +19 -0
  302. package/recipes/redirect/urlcode.yaml +9 -0
  303. package/recipes/static-plus-api/README.md +15 -0
  304. package/recipes/static-plus-api/functions/info.mjs +3 -0
  305. package/recipes/static-plus-api/public/assets/index.html +3 -0
  306. package/recipes/static-plus-api/public/assets/site.css +1 -0
  307. package/recipes/static-plus-api/public/index.html +8 -0
  308. package/recipes/static-plus-api/recipe.yaml +29 -0
  309. package/recipes/static-plus-api/tests/requests.json +56 -0
  310. package/recipes/static-plus-api/urlcode.yaml +23 -0
  311. package/recipes/typescript/README.md +7 -0
  312. package/recipes/typescript/functions/hello.ts +5 -0
  313. package/recipes/typescript/recipe.yaml +23 -0
  314. package/recipes/typescript/tests/requests.json +18 -0
  315. package/recipes/typescript/urlcode.yaml +5 -0
  316. package/recipes/webhook-receiver/README.md +16 -0
  317. package/recipes/webhook-receiver/functions/receive.mjs +16 -0
  318. package/recipes/webhook-receiver/recipe.yaml +26 -0
  319. package/recipes/webhook-receiver/tests/requests.json +59 -0
  320. package/recipes/webhook-receiver/urlcode.yaml +16 -0
  321. package/schemas/recipe.schema.json +138 -0
  322. package/schemas/urlcode.schema.json +656 -80
  323. package/skills/urlcode/SKILL.md +98 -0
  324. package/starters/default/.github/workflows/urlcode.yml +23 -0
  325. package/starters/default/.mcp.json +12 -0
  326. package/starters/default/AGENTS.md +79 -0
@@ -0,0 +1,127 @@
1
+ version: "1"
2
+ # Reusable middleware patterns, one route per module. Bindings are literal values
3
+ # here; production projects use {secret: name} with an operator grant instead.
4
+ # Rename or delete the routes you do not need; the modules are independent.
5
+ routes:
6
+ /api/private:
7
+ description: Bearer token gate with a constant-time comparison
8
+ env:
9
+ API_TOKEN: {value: cookbook-token}
10
+ middleware:
11
+ - source: middleware/auth.mjs
12
+ export: bearer
13
+ respond: {json: {private: true}}
14
+ /admin/panel:
15
+ description: HTTP Basic authentication decoded inside the guest
16
+ env:
17
+ ADMIN_USER: {value: admin}
18
+ ADMIN_PASSWORD: {value: cookbook-password}
19
+ middleware:
20
+ - source: middleware/auth.mjs
21
+ export: basic
22
+ respond: {text: Admin panel}
23
+ /cors/data:
24
+ description: CORS allowlist with preflight handled before the handler
25
+ methods: [GET, HEAD, OPTIONS]
26
+ env:
27
+ ALLOWED_ORIGINS: {value: https://app.example.com https://admin.example.com}
28
+ middleware:
29
+ - source: middleware/cors.mjs
30
+ respond: {json: {shared: true}}
31
+ /traced:
32
+ description: Caller correlation id and Server-Timing on every response
33
+ middleware:
34
+ - source: middleware/request-id.mjs
35
+ respond: {text: Traced}
36
+ /maintenance:
37
+ description: Kill switch returning 503 unless a bypass header is presented
38
+ env:
39
+ MAINTENANCE: {value: "true"}
40
+ MAINTENANCE_BYPASS: {value: let-me-in}
41
+ middleware:
42
+ - source: middleware/maintenance.mjs
43
+ respond: {text: Still here}
44
+ /fragile:
45
+ description: Error boundary turning a thrown exception into a JSON 500
46
+ parameters:
47
+ - name: fail
48
+ in: query
49
+ schema: {type: boolean, default: false}
50
+ middleware:
51
+ - source: middleware/request-id.mjs
52
+ - source: middleware/errors.mjs
53
+ function:
54
+ source: functions/fail.mjs
55
+ args:
56
+ fail: {from: query, name: fail}
57
+ /api/items:
58
+ description: JSON envelope around a function response
59
+ middleware:
60
+ - source: middleware/envelope.mjs
61
+ function: {source: functions/items.mjs}
62
+ /negotiated:
63
+ description: Accept-driven JSON or plain-text representation, 406 otherwise
64
+ middleware:
65
+ - source: middleware/negotiate.mjs
66
+ function: {source: functions/status.mjs}
67
+ /resource:
68
+ description: Method override tunneling PUT, PATCH or DELETE through POST
69
+ methods: [GET, HEAD, POST, PUT, PATCH, DELETE]
70
+ middleware:
71
+ - source: middleware/methods.mjs
72
+ export: override
73
+ function: {source: functions/resource.mjs}
74
+ /versioned:
75
+ description: Weak ETag and 304 for function output
76
+ middleware:
77
+ - source: middleware/etag.mjs
78
+ function: {source: functions/catalog.mjs}
79
+ /experiment:
80
+ description: Sticky A/B bucket cookie choosing between two destinations
81
+ env:
82
+ VARIANT_URL: {value: https://example.com/landing-b}
83
+ middleware:
84
+ - source: middleware/bucket.mjs
85
+ redirect: {url: https://example.com/landing}
86
+ /welcome:
87
+ description: Accept-Language redirect to a localized destination
88
+ env:
89
+ LOCALES: {value: en de fr}
90
+ SITE: {value: https://example.com}
91
+ middleware:
92
+ - source: middleware/locale.mjs
93
+ redirect: {url: https://example.com/en/welcome}
94
+ /downloads/report:
95
+ description: Referer allowlist in front of a native download
96
+ env:
97
+ ALLOWED_REFERERS: {value: example.com www.example.com}
98
+ middleware:
99
+ - source: middleware/referer.mjs
100
+ download:
101
+ file: public/guide.txt
102
+ filename: report.txt
103
+ contentType: text/plain
104
+ cacheControl: no-store
105
+ /profile:
106
+ description: Body validated once in middleware, parsed object passed through state
107
+ methods: [POST]
108
+ request:
109
+ body:
110
+ required: true
111
+ maxBytes: 4096
112
+ contentTypes: [application/json]
113
+ format: json
114
+ middleware:
115
+ - source: middleware/body.mjs
116
+ function: {source: functions/profile.mjs}
117
+ /inspect:
118
+ description: Authoring aid echoing inputs, args and redacted headers on demand
119
+ parameters:
120
+ - name: topic
121
+ in: query
122
+ schema: {type: string, default: routing}
123
+ env:
124
+ DEBUG: {value: "true"}
125
+ middleware:
126
+ - source: middleware/debug.mjs
127
+ respond: {text: Nothing to see}
@@ -0,0 +1,22 @@
1
+ # Protected download
2
+
3
+ `/downloads/report` serves `files/report.txt` as an attachment, but only after
4
+ the operator-installed `auth` extension authorizes the request (`auth: true`,
5
+ the short form of `policies.extensions.auth`). The file is
6
+ served natively: no guest code runs, and the response is forced to `no-store`.
7
+
8
+ Like the `authenticated-json-api` recipe, this project declares the extension
9
+ and needs an operator host file outside the project plus the canonical origin
10
+ for every command:
11
+
12
+ ```sh
13
+ urlcode validate --local --project . --host-file /operator/host.mjs --origin https://files.example.com
14
+ urlcode test --project . --host-file /operator/host.mjs --origin https://files.example.com
15
+ urlcode audit --project . --expect-routes 1 --host-file /operator/host.mjs --origin https://files.example.com
16
+ ```
17
+
18
+ Use the host file from the `authenticated-json-api` README with the config
19
+ schema accepting `realm: downloads`; the bundled fixtures expect the demo token
20
+ `demo-token`. A real deployment registers `urlcode-auth` instead. Replace
21
+ `files/report.txt` with the real attachment and adjust `filename` and
22
+ `contentType`. Cloudflare refuses extensions.
@@ -0,0 +1 @@
1
+ Quarterly report (placeholder). Replace this file with the real attachment.
@@ -0,0 +1,31 @@
1
+ id: protected-download
2
+ description: Native attachment download that only the operator-installed auth extension lets through.
3
+ tags: [download, attachment, protected, auth, authenticated, file, private, extension, content-disposition, "401"]
4
+ complexity: advanced
5
+ capabilities: [download, enabled, extension, methods, policies.extensions]
6
+ targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: refused}
7
+ routes: 1
8
+ services:
9
+ - name: auth extension
10
+ description: An operator registry providing the `auth` extension; the authenticated-json-api README shows a minimal protocol fixture.
11
+ grants:
12
+ - kind: extension
13
+ description: The registration pins projectSha256 to this exact revision, including the attachment.
14
+ - kind: origin
15
+ description: The canonical origin passed as --origin at every command that activates the project.
16
+ inputs:
17
+ - name: attachment
18
+ file: files/report.txt
19
+ description: Replace the placeholder and adjust filename and contentType in urlcode.yaml.
20
+ files: [urlcode.yaml, files/report.txt, tests/requests.json, README.md]
21
+ tests:
22
+ fixtures: tests/requests.json
23
+ commands:
24
+ - urlcode validate --local --project . --host-file /operator/host.mjs --origin https://files.example.com
25
+ - urlcode test --project . --host-file /operator/host.mjs --origin https://files.example.com
26
+ - urlcode audit --project . --expect-routes 1 --host-file /operator/host.mjs --origin https://files.example.com
27
+ behavior:
28
+ - GET /downloads/report without credentials answers 401
29
+ - GET with the accepted credential answers 200, Content-Disposition attachment and Cache-Control no-store
30
+ - HEAD mirrors both cases with an empty body
31
+ - no guest code runs; the file is served natively and snapshotted at activation
@@ -0,0 +1,32 @@
1
+ [
2
+ {
3
+ "path": "/downloads/report",
4
+ "status": 401
5
+ },
6
+ {
7
+ "path": "/downloads/report",
8
+ "headers": {
9
+ "authorization": "Bearer demo-token"
10
+ },
11
+ "status": 200,
12
+ "expectBody": "Quarterly report (placeholder). Replace this file with the real attachment.\n",
13
+ "expectHeaders": {
14
+ "content-disposition": "attachment; filename=quarterly-report.txt",
15
+ "cache-control": "no-store"
16
+ }
17
+ },
18
+ {
19
+ "path": "/downloads/report",
20
+ "method": "HEAD",
21
+ "headers": {
22
+ "authorization": "Bearer demo-token"
23
+ },
24
+ "status": 200,
25
+ "expectBody": ""
26
+ },
27
+ {
28
+ "path": "/downloads/report",
29
+ "method": "HEAD",
30
+ "status": 401
31
+ }
32
+ ]
@@ -0,0 +1,15 @@
1
+ version: "1"
2
+ extensions:
3
+ auth:
4
+ version: "1"
5
+ config:
6
+ realm: downloads
7
+ routes:
8
+ /downloads/report:
9
+ description: Attachment served natively, only after the auth extension authorizes the caller.
10
+ download:
11
+ file: files/report.txt
12
+ filename: quarterly-report.txt
13
+ contentType: text/plain
14
+ cacheControl: no-store
15
+ auth: true
@@ -0,0 +1,7 @@
1
+ # Permanent documentation redirect
2
+
3
+ Run `urlcode validate --local --project .` and `urlcode serve --project .`.
4
+ `/docs?campaign=launch&private=discarded` redirects to
5
+ `https://example.com/documentation?campaign=launch` with status 301.
6
+ Replace the example destination before deploying. Incoming query parameters are
7
+ not forwarded unless explicitly allowlisted.
@@ -0,0 +1,25 @@
1
+ id: redirect
2
+ description: Permanent redirect that forwards only an allowlisted query key.
3
+ tags: [redirect, permanent, "301", query, passthrough, documentation, native]
4
+ complexity: starter
5
+ capabilities: [enabled, methods, redirect]
6
+ targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: compatible}
7
+ routes: 1
8
+ inputs:
9
+ - name: destination
10
+ file: urlcode.yaml
11
+ description: Replace https://example.com/documentation with the real target.
12
+ - name: query.pass
13
+ file: urlcode.yaml
14
+ description: The query keys forwarded to the destination; everything else is dropped.
15
+ files: [urlcode.yaml, tests/requests.json, README.md]
16
+ tests:
17
+ fixtures: tests/requests.json
18
+ commands:
19
+ - urlcode validate --local --project .
20
+ - urlcode test --project .
21
+ - urlcode audit --project . --expect-routes 1
22
+ behavior:
23
+ - GET /docs?campaign=launch&private=x answers 301 to https://example.com/documentation?campaign=launch
24
+ - HEAD answers the same status with an empty body
25
+ - unknown paths answer 404
@@ -0,0 +1,19 @@
1
+ [
2
+ {
3
+ "path": "/docs?campaign=launch&private=discarded",
4
+ "status": 301,
5
+ "expectHeaders": {
6
+ "location": "https://example.com/documentation?campaign=launch"
7
+ }
8
+ },
9
+ {
10
+ "path": "/docs",
11
+ "method": "HEAD",
12
+ "status": 301,
13
+ "expectBody": ""
14
+ },
15
+ {
16
+ "path": "/missing",
17
+ "status": 404
18
+ }
19
+ ]
@@ -0,0 +1,9 @@
1
+ version: "1"
2
+ routes:
3
+ /docs:
4
+ description: Move documentation and preserve only the declared campaign key.
5
+ redirect:
6
+ url: https://example.com/documentation
7
+ status: 301
8
+ query:
9
+ pass: [campaign]
@@ -0,0 +1,15 @@
1
+ # Static site with a JSON API
2
+
3
+ Run `urlcode validate --local --project .`, `urlcode test --project .` and
4
+ `urlcode audit --project . --expect-routes 3`.
5
+
6
+ `/` serves `public/index.html`, `/assets/*` serves everything under
7
+ `public/assets` (with `index.html` for the directory itself), and `/api/info`
8
+ is a sandboxed function returning JSON built from literal `args`. Pages and
9
+ assets are native: no guest code runs for them, and they are snapshotted at
10
+ activation, so new files need a reload. Edit the HTML, add files under
11
+ `public/assets`, and change or extend `functions/info.mjs`.
12
+
13
+ The function needs the self-hosted runtime; drop `/api/info` to deploy the
14
+ static part on a serverless target. Static mounts cannot escape their
15
+ directory, and the recipe sets explicit caching so nothing is guessed.
@@ -0,0 +1,3 @@
1
+ export default function info(request, {args}) {
2
+ return Response.json({name: args.name, version: args.version});
3
+ }
@@ -0,0 +1,3 @@
1
+ <!doctype html>
2
+ <html lang="en"><head><meta charset="utf-8"><title>Assets</title></head>
3
+ <body><p>Asset directory index.</p></body></html>
@@ -0,0 +1 @@
1
+ body { font-family: system-ui, sans-serif; margin: 2rem; }
@@ -0,0 +1,8 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head><meta charset="utf-8"><title>Example site</title><link rel="stylesheet" href="/assets/site.css"></head>
4
+ <body>
5
+ <h1>Example site</h1>
6
+ <p>Static pages are served natively; <a href="/api/info">/api/info</a> is a sandboxed function.</p>
7
+ </body>
8
+ </html>
@@ -0,0 +1,29 @@
1
+ id: static-plus-api
2
+ description: A native static site (home page and an assets directory) next to one sandboxed JSON endpoint.
3
+ tags: [static, site, page, html, assets, css, api, json, function, cache-control, website]
4
+ complexity: intermediate
5
+ capabilities: [enabled, function, methods, page, response.headers, static]
6
+ targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
7
+ routes: 3
8
+ inputs:
9
+ - name: pages
10
+ file: public/index.html
11
+ description: The home page and everything under public/assets.
12
+ - name: args
13
+ file: urlcode.yaml
14
+ description: The literal name and version /api/info reports.
15
+ - name: handler
16
+ file: functions/info.mjs
17
+ description: Replace the literal JSON with the real endpoint.
18
+ files: [urlcode.yaml, public/index.html, public/assets/index.html, public/assets/site.css, functions/info.mjs, tests/requests.json, README.md]
19
+ tests:
20
+ fixtures: tests/requests.json
21
+ commands:
22
+ - urlcode validate --local --project .
23
+ - urlcode test --project .
24
+ - urlcode audit --project . --expect-routes 3
25
+ behavior:
26
+ - GET / answers the home page as text/html with Cache-Control no-cache
27
+ - GET /assets/site.css answers text/css cached for one hour; /assets/ answers the directory index; missing files answer 404
28
+ - GET /api/info answers 200 {"name":"Example site","version":"1"} with no-store
29
+ - drop /api/info to deploy the static part on a serverless target
@@ -0,0 +1,56 @@
1
+ [
2
+ {
3
+ "path": "/",
4
+ "status": 200,
5
+ "expectHeaders": {
6
+ "content-type": "text/html; charset=utf-8",
7
+ "cache-control": "no-cache"
8
+ }
9
+ },
10
+ {
11
+ "path": "/",
12
+ "method": "HEAD",
13
+ "status": 200,
14
+ "expectBody": ""
15
+ },
16
+ {
17
+ "path": "/assets/site.css",
18
+ "status": 200,
19
+ "expectBody": "body { font-family: system-ui, sans-serif; margin: 2rem; }\n",
20
+ "expectHeaders": {
21
+ "content-type": "text/css; charset=utf-8",
22
+ "cache-control": "public, max-age=3600"
23
+ }
24
+ },
25
+ {
26
+ "path": "/assets/",
27
+ "status": 200,
28
+ "expectHeaders": {
29
+ "content-type": "text/html; charset=utf-8"
30
+ }
31
+ },
32
+ {
33
+ "path": "/assets/site.css",
34
+ "method": "HEAD",
35
+ "status": 200,
36
+ "expectBody": ""
37
+ },
38
+ {
39
+ "path": "/assets/missing.css",
40
+ "status": 404
41
+ },
42
+ {
43
+ "path": "/api/info",
44
+ "status": 200,
45
+ "expectBody": "{\"name\":\"Example site\",\"version\":\"1\"}",
46
+ "expectHeaders": {
47
+ "cache-control": "no-store"
48
+ }
49
+ },
50
+ {
51
+ "path": "/api/info",
52
+ "method": "HEAD",
53
+ "status": 200,
54
+ "expectBody": ""
55
+ }
56
+ ]
@@ -0,0 +1,23 @@
1
+ version: "1"
2
+ routes:
3
+ /:
4
+ description: The site's home page served natively from public/index.html.
5
+ page:
6
+ file: public/index.html
7
+ cacheControl: no-cache
8
+ /assets/*:
9
+ description: Every file under public/assets, with a directory index and one-hour caching.
10
+ static:
11
+ directory: public/assets
12
+ index: index.html
13
+ cacheControl: public, max-age=3600
14
+ /api/info:
15
+ description: A small JSON endpoint next to the static site, computed in the sandbox.
16
+ function:
17
+ source: functions/info.mjs
18
+ args:
19
+ name: Example site
20
+ version: "1"
21
+ response:
22
+ headers:
23
+ Cache-Control: no-store
@@ -0,0 +1,7 @@
1
+ # Build-time TypeScript guest
2
+
3
+ Run `urlcode build-typescript --project . --out ../hello-built` with a new output
4
+ directory, then `urlcode validate --local --project ../hello-built` and
5
+ `urlcode serve --project ../hello-built`. GET `/hello` returns JSON.
6
+ TypeScript is transpiled ahead of time. The runtime executes only the emitted
7
+ JavaScript in QuickJS/WASM. The build does not type-check or read tsconfig.json.
@@ -0,0 +1,5 @@
1
+ interface Greeting { message: string }
2
+ export default function hello(): Response {
3
+ const greeting: Greeting = {message: 'Hello from a compiled TypeScript guest'};
4
+ return Response.json(greeting);
5
+ }
@@ -0,0 +1,23 @@
1
+ id: typescript
2
+ description: Typed guest function transpiled ahead of time; only the emitted JavaScript runs in the sandbox.
3
+ tags: [typescript, function, build, typed, compile, sandbox]
4
+ complexity: intermediate
5
+ capabilities: [enabled, function, methods]
6
+ targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
7
+ routes: 1
8
+ inputs:
9
+ - name: handler
10
+ file: functions/hello.ts
11
+ description: The typed guest; keep to the Request/Response sandbox API.
12
+ files: [urlcode.yaml, functions/hello.ts, tests/requests.json, README.md]
13
+ tests:
14
+ fixtures: tests/requests.json
15
+ commands:
16
+ - urlcode build-typescript --project . --out ../hello-built
17
+ - urlcode validate --local --project ../hello-built
18
+ - urlcode test --project ../hello-built
19
+ - urlcode audit --project ../hello-built --expect-routes 1
20
+ behavior:
21
+ - after the build, GET /hello answers 200 JSON from the compiled guest
22
+ - the unbuilt project does not activate; .ts sources never run
23
+ - POST answers 405
@@ -0,0 +1,18 @@
1
+ [
2
+ {
3
+ "path": "/hello",
4
+ "status": 200,
5
+ "expectBody": "{\"message\":\"Hello from a compiled TypeScript guest\"}"
6
+ },
7
+ {
8
+ "path": "/hello",
9
+ "method": "HEAD",
10
+ "status": 200,
11
+ "expectBody": ""
12
+ },
13
+ {
14
+ "path": "/hello",
15
+ "method": "POST",
16
+ "status": 405
17
+ }
18
+ ]
@@ -0,0 +1,5 @@
1
+ version: "1"
2
+ routes:
3
+ /hello:
4
+ function:
5
+ source: functions/hello.ts
@@ -0,0 +1,16 @@
1
+ # Webhook receiver
2
+
3
+ Run `urlcode validate --local --project .`, `urlcode test --project .` and
4
+ `urlcode audit --project . --expect-routes 1`.
5
+
6
+ `POST /webhook` accepts at most 64 KiB of `application/json`. The runtime
7
+ rejects other methods (405), other content types (415), oversized bodies (413)
8
+ and malformed JSON (400) before the sandbox runs. The function then requires an
9
+ `X-Webhook-Event` header and a JSON object carrying a string `id`, and answers
10
+ `202 {"received":true,...}`.
11
+
12
+ This recipe verifies shape, not origin. Nothing here checks a signature: the
13
+ guest has no crypto API and no network access, so signed webhooks belong behind
14
+ an operator-installed extension or a trusted host in front of this route. Edit
15
+ `functions/receive.mjs` to check the fields your sender guarantees, and keep
16
+ the body limit as small as the sender allows.
@@ -0,0 +1,16 @@
1
+ // The runtime has already enforced method, content type, size and JSON syntax.
2
+ // The guest checks the shape it cares about and acknowledges; it has no network
3
+ // access, so forwarding happens through a declared route signal or a host process
4
+ // that reads the runtime log, never from here.
5
+ const eventPattern = /^[a-z][a-z0-9_.-]{0,63}$/;
6
+ export default async function receive(request) {
7
+ const event = request.headers.get('x-webhook-event');
8
+ if (!event || !eventPattern.test(event)) {
9
+ return Response.json({error: 'missing or invalid X-Webhook-Event header'}, {status: 400});
10
+ }
11
+ const payload = await request.json();
12
+ if (payload === null || typeof payload !== 'object' || Array.isArray(payload) || typeof payload.id !== 'string') {
13
+ return Response.json({error: 'body must be a JSON object with a string id'}, {status: 422});
14
+ }
15
+ return Response.json({received: true, event, id: payload.id}, {status: 202});
16
+ }
@@ -0,0 +1,26 @@
1
+ id: webhook-receiver
2
+ description: Accept a bounded JSON event that names its type in a header, check its shape and acknowledge with 202.
3
+ tags: [webhook, receiver, event, post, json, body, "202", callback, ingest, function]
4
+ complexity: starter
5
+ capabilities: [enabled, function, methods, request.body, response.headers]
6
+ targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
7
+ routes: 1
8
+ inputs:
9
+ - name: shape check
10
+ file: functions/receive.mjs
11
+ description: The header name, event pattern and payload fields the sender guarantees.
12
+ - name: maxBytes
13
+ file: urlcode.yaml
14
+ description: The body limit (64 KiB); keep it as small as the sender allows.
15
+ files: [urlcode.yaml, functions/receive.mjs, tests/requests.json, README.md]
16
+ tests:
17
+ fixtures: tests/requests.json
18
+ commands:
19
+ - urlcode validate --local --project .
20
+ - urlcode test --project .
21
+ - urlcode audit --project . --expect-routes 1
22
+ behavior:
23
+ - POST /webhook with application/json, X-Webhook-Event and a string id answers 202 {"received":true,"event":...,"id":...}
24
+ - a missing event header answers 400; a body that is not an object with a string id answers 422
25
+ - other methods 405, other content types 415, oversized bodies 413 and malformed JSON 400 before the sandbox runs
26
+ - the guest has no crypto or network API, so signatures are not verified here; put signed webhooks behind an operator extension or trusted host
@@ -0,0 +1,59 @@
1
+ [
2
+ {
3
+ "path": "/webhook",
4
+ "method": "POST",
5
+ "headers": {
6
+ "content-type": "application/json",
7
+ "x-webhook-event": "order.created"
8
+ },
9
+ "body": "{\"id\":\"evt_123\",\"total\":42}",
10
+ "status": 202,
11
+ "expectBody": "{\"received\":true,\"event\":\"order.created\",\"id\":\"evt_123\"}",
12
+ "expectHeaders": {
13
+ "cache-control": "no-store"
14
+ }
15
+ },
16
+ {
17
+ "path": "/webhook",
18
+ "method": "POST",
19
+ "headers": {
20
+ "content-type": "application/json"
21
+ },
22
+ "body": "{\"id\":\"evt_123\"}",
23
+ "status": 400
24
+ },
25
+ {
26
+ "path": "/webhook",
27
+ "method": "POST",
28
+ "headers": {
29
+ "content-type": "application/json",
30
+ "x-webhook-event": "order.created"
31
+ },
32
+ "body": "[1,2,3]",
33
+ "status": 422
34
+ },
35
+ {
36
+ "path": "/webhook",
37
+ "method": "POST",
38
+ "headers": {
39
+ "content-type": "application/json",
40
+ "x-webhook-event": "order.created"
41
+ },
42
+ "body": "{not json",
43
+ "status": 400
44
+ },
45
+ {
46
+ "path": "/webhook",
47
+ "method": "POST",
48
+ "headers": {
49
+ "content-type": "text/plain",
50
+ "x-webhook-event": "order.created"
51
+ },
52
+ "body": "id=1",
53
+ "status": 415
54
+ },
55
+ {
56
+ "path": "/webhook",
57
+ "status": 405
58
+ }
59
+ ]
@@ -0,0 +1,16 @@
1
+ version: "1"
2
+ routes:
3
+ /webhook:
4
+ description: Accept a bounded JSON event body that names its event type in a header.
5
+ methods: [POST]
6
+ request:
7
+ body:
8
+ required: true
9
+ maxBytes: 65536
10
+ contentTypes: [application/json]
11
+ format: json
12
+ function:
13
+ source: functions/receive.mjs
14
+ response:
15
+ headers:
16
+ Cache-Control: no-store