@jimhoyd/urlcode 0.3.0 → 0.4.0-alpha.2

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 (380) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +122 -0
  2. package/.claude/skills/urlcode-operations/SKILL.md +108 -0
  3. package/.claude-plugin/marketplace.json +18 -0
  4. package/CONTRIBUTING.md +30 -2
  5. package/README.md +195 -255
  6. package/ROADMAP.md +143 -15
  7. package/SECURITY.md +31 -9
  8. package/dist/BUILD-MANIFEST.json +72 -47
  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/build-static.js +134 -0
  17. package/dist/bulk.js +37 -0
  18. package/dist/capabilities.js +262 -0
  19. package/dist/capability-query.js +71 -0
  20. package/dist/catalog.js +105 -0
  21. package/dist/cli.js +165 -34
  22. package/dist/client-address.js +1 -1
  23. package/dist/compliance-rules/baseline.js +9 -17
  24. package/dist/compliance-rules/privacy.js +7 -18
  25. package/dist/compliance-rules/shared.js +0 -2
  26. package/dist/compliance-rules/strict.js +5 -5
  27. package/dist/compliance.js +6 -8
  28. package/dist/conditions.js +88 -0
  29. package/dist/config.js +69 -6
  30. package/dist/context.js +155 -0
  31. package/dist/ecosystem-cli.js +88 -0
  32. package/dist/egress.js +98 -0
  33. package/dist/examples.js +92 -0
  34. package/dist/explain-cli.js +64 -0
  35. package/dist/explain.js +131 -0
  36. package/dist/extensions.js +231 -0
  37. package/dist/function-sources.js +49 -5
  38. package/dist/function-worker.js +3 -1
  39. package/dist/functions.js +84 -13
  40. package/dist/guest-api.js +29 -3
  41. package/dist/index.js +40 -6
  42. package/dist/init-with.js +165 -0
  43. package/dist/interchange-cli.js +42 -0
  44. package/dist/interchange.js +189 -0
  45. package/dist/manifest.js +109 -0
  46. package/dist/match.js +2 -2
  47. package/dist/mcp-authoring.js +147 -0
  48. package/dist/mcp.js +97 -0
  49. package/dist/observability.js +7 -21
  50. package/dist/operator-host.js +29 -0
  51. package/dist/plugins.js +12 -0
  52. package/dist/policies/agents.js +2 -2
  53. package/dist/policies/cache.js +8 -3
  54. package/dist/policies/compression.js +2 -1
  55. package/dist/policies/security.js +0 -0
  56. package/dist/policies.js +1 -1
  57. package/dist/policy.js +56 -15
  58. package/dist/prerender.js +100 -41
  59. package/dist/project-tests.js +3 -3
  60. package/dist/provider-verification.js +92 -0
  61. package/dist/proxy.js +44 -0
  62. package/dist/readiness.js +34 -11
  63. package/dist/recipes.js +41 -0
  64. package/dist/route-diff.js +106 -0
  65. package/dist/router.js +45 -7
  66. package/dist/runtime.js +164 -64
  67. package/dist/sandbox.js +48 -0
  68. package/dist/scaffold.js +0 -0
  69. package/dist/schema-query.js +62 -0
  70. package/dist/scripts/operational-drills.js +12 -54
  71. package/dist/server.js +3 -29
  72. package/dist/signals.js +24 -0
  73. package/dist/site.js +0 -0
  74. package/dist/tooling.js +96 -0
  75. package/dist/trusted-functions.js +210 -0
  76. package/dist/types/adapters.d.ts +7 -4
  77. package/dist/types/agent-lists.d.ts +0 -1
  78. package/dist/types/agents-guide.d.ts +17 -0
  79. package/dist/types/authoring-files.d.ts +10 -0
  80. package/dist/types/aws.d.ts +3 -1
  81. package/dist/types/build-cloudflare.d.ts +1 -0
  82. package/dist/types/build-static.d.ts +43 -0
  83. package/dist/types/bulk.d.ts +27 -0
  84. package/dist/types/capabilities.d.ts +64 -0
  85. package/dist/types/capability-query.d.ts +24 -0
  86. package/dist/types/catalog.d.ts +65 -0
  87. package/dist/types/client-address.d.ts +0 -1
  88. package/dist/types/compliance-rules/baseline.d.ts +1 -9
  89. package/dist/types/compliance-rules/privacy.d.ts +1 -4
  90. package/dist/types/compliance-rules/shared.d.ts +0 -2
  91. package/dist/types/compliance-rules/strict.d.ts +0 -5
  92. package/dist/types/compliance.d.ts +0 -3
  93. package/dist/types/conditions.d.ts +19 -0
  94. package/dist/types/config.d.ts +21 -2
  95. package/dist/types/context.d.ts +66 -0
  96. package/dist/types/ecosystem-cli.d.ts +17 -0
  97. package/dist/types/egress.d.ts +46 -0
  98. package/dist/types/examples.d.ts +50 -0
  99. package/dist/types/explain-cli.d.ts +11 -0
  100. package/dist/types/explain.d.ts +95 -0
  101. package/dist/types/extensions.d.ts +177 -0
  102. package/dist/types/function-sources.d.ts +9 -0
  103. package/dist/types/functions.d.ts +48 -5
  104. package/dist/types/guest-api.d.ts +1 -0
  105. package/dist/types/index.d.ts +36 -6
  106. package/dist/types/init-with.d.ts +30 -0
  107. package/dist/types/interchange-cli.d.ts +16 -0
  108. package/dist/types/interchange.d.ts +42 -0
  109. package/dist/types/manifest.d.ts +79 -0
  110. package/dist/types/match.d.ts +1 -0
  111. package/dist/types/mcp-authoring.d.ts +92 -0
  112. package/dist/types/mcp.d.ts +12 -0
  113. package/dist/types/observability.d.ts +3 -14
  114. package/dist/types/operator-host.d.ts +8 -0
  115. package/dist/types/plugins.d.ts +2 -0
  116. package/dist/types/policies/agents.d.ts +0 -2
  117. package/dist/types/policies/compression.d.ts +2 -0
  118. package/dist/types/policies/security.d.ts +0 -1
  119. package/dist/types/policy.d.ts +15 -4
  120. package/dist/types/project-tests.d.ts +3 -2
  121. package/dist/types/provider-verification.d.ts +53 -0
  122. package/dist/types/proxy.d.ts +21 -0
  123. package/dist/types/readiness.d.ts +10 -3
  124. package/dist/types/recipes.d.ts +30 -0
  125. package/dist/types/route-diff.d.ts +27 -0
  126. package/dist/types/router.d.ts +2 -1
  127. package/dist/types/runtime.d.ts +11 -27
  128. package/dist/types/sandbox.d.ts +12 -0
  129. package/dist/types/scaffold.d.ts +0 -2
  130. package/dist/types/schema-query.d.ts +12 -0
  131. package/dist/types/server.d.ts +1 -4
  132. package/dist/types/signals.d.ts +25 -0
  133. package/dist/types/site.d.ts +0 -1
  134. package/dist/types/tooling.d.ts +115 -0
  135. package/dist/types/trusted-functions.d.ts +29 -0
  136. package/dist/types/types.d.ts +71 -7
  137. package/dist/types/typescript-authoring.d.ts +12 -0
  138. package/dist/types/vercel.d.ts +3 -1
  139. package/dist/types/verify-deployment.d.ts +47 -0
  140. package/dist/types.js +37 -5
  141. package/dist/typescript-authoring.js +142 -0
  142. package/dist/vercel.js +4 -3
  143. package/dist/verify-deployment.js +270 -0
  144. package/docs/AI-AUTHORING.md +232 -15
  145. package/docs/AWS.md +4 -4
  146. package/docs/BEST-PRACTICES.md +3 -2
  147. package/docs/BULK.md +79 -0
  148. package/docs/CAPABILITIES.md +192 -0
  149. package/docs/CAPACITY.md +129 -32
  150. package/docs/CI.md +142 -0
  151. package/docs/CLOUDFLARE.md +1 -2
  152. package/docs/COMPLIANCE.md +6 -9
  153. package/docs/CONDITIONS.md +74 -0
  154. package/docs/DEPLOYMENT-CHECKS.md +108 -0
  155. package/docs/EGRESS.md +125 -0
  156. package/docs/EXTENSIONS.md +398 -0
  157. package/docs/FRAMEWORK.md +198 -0
  158. package/docs/FUNCTION-SECURITY.md +129 -32
  159. package/docs/INSTALL.md +45 -12
  160. package/docs/INTERCHANGE.md +134 -0
  161. package/docs/LOAD-TESTING.md +4 -4
  162. package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
  163. package/docs/MIDDLEWARE.md +29 -16
  164. package/docs/MONITORING.md +2 -19
  165. package/docs/NEXT-PHASE-PLAN.md +98 -0
  166. package/docs/NEXT-STEPS.md +634 -0
  167. package/docs/OBSERVABILITY.md +11 -18
  168. package/docs/OPEN-DECISIONS.md +212 -0
  169. package/docs/OPERATIONAL-PROOF.md +30 -31
  170. package/docs/OPERATIONS.md +29 -35
  171. package/docs/PLUGINS.md +37 -0
  172. package/docs/POLICIES.md +23 -309
  173. package/docs/PRERENDER.md +41 -1
  174. package/docs/PROJECT-DIRECTION.md +75 -8
  175. package/docs/PROVIDER-VERIFICATION.md +84 -0
  176. package/docs/READINESS.md +21 -1
  177. package/docs/README.md +87 -34
  178. package/docs/RECIPES.md +99 -0
  179. package/docs/RELEASE-READINESS.md +57 -35
  180. package/docs/RELEASE-SECURITY.md +116 -7
  181. package/docs/RESILIENCE.md +16 -15
  182. package/docs/ROUTING.md +8 -10
  183. package/docs/SANDBOX-REVIEW.md +19 -6
  184. package/docs/SCAFFOLDING.md +0 -2
  185. package/docs/SECURITY-AUDIT.md +41 -1
  186. package/docs/SPECIFICATION.md +150 -29
  187. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +287 -0
  188. package/docs/SPIKE-BUSINESS-SUITE.md +1021 -0
  189. package/docs/SPIKE-CORE-LAYERING.md +337 -0
  190. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +209 -0
  191. package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
  192. package/docs/SPIKE-EXTENSIONS.md +6 -0
  193. package/docs/SPIKE-LAMBDA-COMPILE.md +201 -0
  194. package/docs/SPIKE-MONOREPO.md +322 -0
  195. package/docs/STANDARDS.md +150 -142
  196. package/docs/STARTERS.md +21 -1
  197. package/docs/STATIC.md +94 -0
  198. package/docs/TOOLING.md +295 -0
  199. package/docs/TUNNELS.md +0 -3
  200. package/docs/TYPESCRIPT-AUTHORING.md +82 -0
  201. package/docs/TYPESCRIPT.md +25 -4
  202. package/docs/USABILITY-REVIEW.md +129 -0
  203. package/docs/VERCEL.md +4 -5
  204. package/docs/VERSION-ALIGNMENT.md +205 -0
  205. package/docs/YAML-GUIDE.md +15 -479
  206. package/docs/YAML-REFERENCE.md +143 -22
  207. package/docs/policies/agents.md +1 -1
  208. package/docs/policies/cache.md +13 -0
  209. package/docs/policies/contract.md +52 -0
  210. package/docs/policies/hardened.md +56 -0
  211. package/docs/policies/interoperability.md +169 -0
  212. package/docs/policies/operations.md +45 -0
  213. package/docs/yaml/assets.md +36 -0
  214. package/docs/yaml/conditions.md +20 -0
  215. package/docs/yaml/functions.md +160 -0
  216. package/docs/yaml/middleware.md +29 -0
  217. package/docs/yaml/organization.md +74 -0
  218. package/docs/yaml/policies.md +37 -0
  219. package/docs/yaml/redirects.md +64 -0
  220. package/docs/yaml/responses.md +57 -0
  221. package/docs/yaml/site.md +24 -0
  222. package/examples/assets/example.yaml +17 -0
  223. package/examples/aws/example.yaml +20 -0
  224. package/examples/cloudflare/example.yaml +19 -0
  225. package/examples/compliance/example.yaml +11 -0
  226. package/examples/conditions/README.md +12 -0
  227. package/examples/conditions/example.yaml +19 -0
  228. package/examples/conditions/tests/requests.json +13 -0
  229. package/examples/conditions/urlcode.yaml +24 -0
  230. package/examples/cookbook/README.md +8 -4
  231. package/examples/cookbook/example.yaml +17 -0
  232. package/examples/cookbook/functions/catalog.mjs +3 -0
  233. package/examples/cookbook/functions/fail.mjs +4 -0
  234. package/examples/cookbook/functions/items.mjs +3 -0
  235. package/examples/cookbook/functions/profile.mjs +3 -0
  236. package/examples/cookbook/functions/resource.mjs +3 -0
  237. package/examples/cookbook/functions/status.mjs +3 -0
  238. package/examples/cookbook/middleware/auth.mjs +48 -0
  239. package/examples/cookbook/middleware/body.mjs +15 -0
  240. package/examples/cookbook/middleware/bucket.mjs +29 -0
  241. package/examples/cookbook/middleware/cors.mjs +21 -0
  242. package/examples/cookbook/middleware/debug.mjs +13 -0
  243. package/examples/cookbook/middleware/envelope.mjs +11 -0
  244. package/examples/cookbook/middleware/errors.mjs +11 -0
  245. package/examples/cookbook/middleware/etag.mjs +18 -0
  246. package/examples/cookbook/middleware/locale.mjs +20 -0
  247. package/examples/cookbook/middleware/maintenance.mjs +10 -0
  248. package/examples/cookbook/middleware/methods.mjs +15 -0
  249. package/examples/cookbook/middleware/negotiate.mjs +20 -0
  250. package/examples/cookbook/middleware/referer.mjs +12 -0
  251. package/examples/cookbook/middleware/request-id.mjs +16 -0
  252. package/examples/cookbook/route-index.json +676 -0
  253. package/examples/cookbook/routes/middleware.yaml +126 -0
  254. package/examples/cookbook/tests/requests.json +526 -0
  255. package/examples/cookbook/urlcode.yaml +1 -0
  256. package/examples/egress/README.md +22 -0
  257. package/examples/egress/example.yaml +19 -0
  258. package/examples/egress/urlcode.yaml +19 -0
  259. package/examples/extensions/README.md +7 -0
  260. package/examples/extensions/example.yaml +21 -0
  261. package/examples/extensions/urlcode.yaml +25 -0
  262. package/examples/monitoring/example.yaml +8 -0
  263. package/examples/prerender/README.md +2 -2
  264. package/examples/prerender/example.yaml +16 -0
  265. package/examples/provider-conformance/README.md +12 -0
  266. package/examples/provider-conformance/example.yaml +14 -0
  267. package/examples/provider-conformance/urlcode.yaml +34 -0
  268. package/examples/tunnel/example.yaml +8 -0
  269. package/examples/vercel/example.yaml +19 -0
  270. package/llms-full.txt +3084 -0
  271. package/llms.txt +61 -21
  272. package/package.json +36 -7
  273. package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
  274. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +122 -0
  275. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +108 -0
  276. package/recipes/authenticated-json-api/README.md +51 -0
  277. package/recipes/authenticated-json-api/functions/profile.mjs +5 -0
  278. package/recipes/authenticated-json-api/recipe.yaml +34 -0
  279. package/recipes/authenticated-json-api/tests/requests.json +39 -0
  280. package/recipes/authenticated-json-api/urlcode.yaml +12 -0
  281. package/recipes/contact-form/README.md +25 -0
  282. package/recipes/contact-form/functions/contact.mjs +17 -0
  283. package/recipes/contact-form/recipe.yaml +33 -0
  284. package/recipes/contact-form/tests/requests.json +47 -0
  285. package/recipes/contact-form/urlcode.yaml +18 -0
  286. package/recipes/cors-api/README.md +16 -0
  287. package/recipes/cors-api/functions/items.mjs +3 -0
  288. package/recipes/cors-api/middleware/cors.mjs +21 -0
  289. package/recipes/cors-api/recipe.yaml +26 -0
  290. package/recipes/cors-api/tests/requests.json +65 -0
  291. package/recipes/cors-api/urlcode.yaml +12 -0
  292. package/recipes/health-page/README.md +13 -0
  293. package/recipes/health-page/recipe.yaml +23 -0
  294. package/recipes/health-page/tests/requests.json +36 -0
  295. package/recipes/health-page/urlcode.yaml +19 -0
  296. package/recipes/json-api/README.md +6 -0
  297. package/recipes/json-api/functions/echo.mjs +3 -0
  298. package/recipes/json-api/recipe.yaml +25 -0
  299. package/recipes/json-api/tests/requests.json +34 -0
  300. package/recipes/json-api/urlcode.yaml +12 -0
  301. package/recipes/middleware/README.md +34 -0
  302. package/recipes/middleware/functions/catalog.mjs +3 -0
  303. package/recipes/middleware/functions/fail.mjs +4 -0
  304. package/recipes/middleware/functions/items.mjs +3 -0
  305. package/recipes/middleware/functions/profile.mjs +3 -0
  306. package/recipes/middleware/functions/resource.mjs +3 -0
  307. package/recipes/middleware/functions/status.mjs +3 -0
  308. package/recipes/middleware/middleware/auth.mjs +48 -0
  309. package/recipes/middleware/middleware/body.mjs +15 -0
  310. package/recipes/middleware/middleware/bucket.mjs +29 -0
  311. package/recipes/middleware/middleware/cors.mjs +21 -0
  312. package/recipes/middleware/middleware/debug.mjs +13 -0
  313. package/recipes/middleware/middleware/envelope.mjs +11 -0
  314. package/recipes/middleware/middleware/errors.mjs +11 -0
  315. package/recipes/middleware/middleware/etag.mjs +18 -0
  316. package/recipes/middleware/middleware/locale.mjs +20 -0
  317. package/recipes/middleware/middleware/maintenance.mjs +10 -0
  318. package/recipes/middleware/middleware/methods.mjs +15 -0
  319. package/recipes/middleware/middleware/negotiate.mjs +20 -0
  320. package/recipes/middleware/middleware/referer.mjs +12 -0
  321. package/recipes/middleware/middleware/request-id.mjs +16 -0
  322. package/recipes/middleware/public/guide.txt +1 -0
  323. package/recipes/middleware/recipe.yaml +50 -0
  324. package/recipes/middleware/tests/requests.json +528 -0
  325. package/recipes/middleware/urlcode.yaml +127 -0
  326. package/recipes/protected-download/README.md +22 -0
  327. package/recipes/protected-download/files/report.txt +1 -0
  328. package/recipes/protected-download/recipe.yaml +31 -0
  329. package/recipes/protected-download/tests/requests.json +32 -0
  330. package/recipes/protected-download/urlcode.yaml +15 -0
  331. package/recipes/redirect/README.md +7 -0
  332. package/recipes/redirect/recipe.yaml +25 -0
  333. package/recipes/redirect/tests/requests.json +19 -0
  334. package/recipes/redirect/urlcode.yaml +9 -0
  335. package/recipes/static-plus-api/README.md +15 -0
  336. package/recipes/static-plus-api/functions/info.mjs +3 -0
  337. package/recipes/static-plus-api/public/assets/index.html +3 -0
  338. package/recipes/static-plus-api/public/assets/site.css +1 -0
  339. package/recipes/static-plus-api/public/index.html +8 -0
  340. package/recipes/static-plus-api/recipe.yaml +29 -0
  341. package/recipes/static-plus-api/tests/requests.json +56 -0
  342. package/recipes/static-plus-api/urlcode.yaml +23 -0
  343. package/recipes/typescript/README.md +8 -0
  344. package/recipes/typescript/functions/hello.ts +5 -0
  345. package/recipes/typescript/recipe.yaml +23 -0
  346. package/recipes/typescript/tests/requests.json +18 -0
  347. package/recipes/typescript/urlcode.yaml +5 -0
  348. package/recipes/webhook-receiver/README.md +20 -0
  349. package/recipes/webhook-receiver/functions/receive.mjs +16 -0
  350. package/recipes/webhook-receiver/recipe.yaml +27 -0
  351. package/recipes/webhook-receiver/tests/requests.json +59 -0
  352. package/recipes/webhook-receiver/urlcode.yaml +23 -0
  353. package/schemas/recipe.schema.json +139 -0
  354. package/schemas/urlcode.schema.json +659 -110
  355. package/skills/urlcode/SKILL.md +119 -0
  356. package/starters/default/.github/workflows/urlcode.yml +23 -0
  357. package/starters/default/.mcp.json +12 -0
  358. package/starters/default/AGENTS.md +79 -0
  359. package/starters/default/urlcode.yaml +0 -1
  360. package/dist/link-api.js +0 -136
  361. package/dist/link-cli.js +0 -141
  362. package/dist/link-events.js +0 -76
  363. package/dist/link-records.js +0 -31
  364. package/dist/link-store-worker.js +0 -150
  365. package/dist/link-store.js +0 -250
  366. package/dist/management-policy.js +0 -41
  367. package/dist/sqlite-version.js +0 -6
  368. package/dist/types/link-api.d.ts +0 -30
  369. package/dist/types/link-cli.d.ts +0 -36
  370. package/dist/types/link-events.d.ts +0 -27
  371. package/dist/types/link-records.d.ts +0 -11
  372. package/dist/types/link-store-worker.d.ts +0 -1
  373. package/dist/types/link-store.d.ts +0 -130
  374. package/dist/types/management-policy.d.ts +0 -9
  375. package/dist/types/sqlite-version.d.ts +0 -1
  376. package/docs/DYNAMIC-LINKS.md +0 -561
  377. package/docs/MANAGEMENT-SECURITY.md +0 -82
  378. package/examples/live-links/README.md +0 -11
  379. package/examples/live-links/tests/requests.json +0 -6
  380. package/examples/live-links/urlcode.yaml +0 -16
@@ -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: conditional, aws: conditional, vercel: conditional, cloudflare: refused, static: 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, static: 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, static: 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,8 @@
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 for a `sandbox: true` route and in-process for a
8
+ trusted one. 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, static: 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,20 @@
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 route declares
9
+ `sandbox: true`: functions/middleware run trusted by default, but a
10
+ third-party sender's payload is exactly the input a project doesn't fully
11
+ trust, so this recipe isolates parsing it (docs/FUNCTION-SECURITY.md). The
12
+ function then requires an
13
+ `X-Webhook-Event` header and a JSON object carrying a string `id`, and answers
14
+ `202 {"received":true,...}`.
15
+
16
+ This recipe verifies shape, not origin. Nothing here checks a signature: the
17
+ guest has no crypto API and no network access, so signed webhooks belong behind
18
+ an operator-installed extension or a trusted host in front of this route. Edit
19
+ `functions/receive.mjs` to check the fields your sender guarantees, and keep
20
+ 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,27 @@
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, static: 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
27
+ - "the route declares sandbox: true, isolating the third-party payload from the trusted-by-default execution every other function/middleware route gets"
@@ -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,23 @@
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
+ # A third-party sender's payload is exactly the kind of input this
15
+ # project does not fully trust (docs/AI-AUTHORING.md, "Deciding when a
16
+ # route needs sandbox: true"), even after body/content-type validation:
17
+ # isolate parsing it from the rest of the process.
18
+ sandbox: true
19
+ sandboxReason: >-
20
+ Third-party webhook payload; isolate parsing it even after body/content-type validation.
21
+ response:
22
+ headers:
23
+ Cache-Control: no-store
@@ -0,0 +1,139 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/jimhoyd-com/urlcode/main/schemas/recipe.schema.json",
4
+ "title": "URLCode recipe and example metadata",
5
+ "description": "The recipe.yaml and example.yaml shape searched by `urlcode recipes search` and `urlcode examples search`. capabilities, targets and routes are derived from the capability preflight and checked by `npm run check`; the rest is written by hand.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["id", "description", "tags", "complexity", "files"],
9
+ "properties": {
10
+ "id": {
11
+ "description": "The directory name under recipes/ or examples/.",
12
+ "type": "string",
13
+ "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
14
+ "maxLength": 64
15
+ },
16
+ "description": {
17
+ "description": "One sentence; searched.",
18
+ "type": "string",
19
+ "minLength": 1,
20
+ "maxLength": 300
21
+ },
22
+ "tags": {
23
+ "description": "Lower-case search words; searched.",
24
+ "type": "array",
25
+ "minItems": 1,
26
+ "maxItems": 32,
27
+ "uniqueItems": true,
28
+ "items": {"type": "string", "pattern": "^[a-z0-9][a-z0-9.-]{0,39}$"}
29
+ },
30
+ "complexity": {
31
+ "enum": ["starter", "intermediate", "advanced"]
32
+ },
33
+ "runnable": {
34
+ "description": "false for a directory with no urlcode.yaml (operator rules, monitoring configuration, scripts). Such entries carry no capabilities, targets or routes.",
35
+ "type": "boolean",
36
+ "default": true
37
+ },
38
+ "capabilities": {
39
+ "description": "Derived: every capability name the preflight reports for the project, sorted; searched.",
40
+ "type": "array",
41
+ "uniqueItems": true,
42
+ "items": {"type": "string", "pattern": "^[a-z][a-zA-Z.]{0,39}$"}
43
+ },
44
+ "targets": {
45
+ "description": "Derived: the capability preflight verdict per target. compatible means no issue; otherwise the strongest issue (refused, unknown, conditional).",
46
+ "type": "object",
47
+ "additionalProperties": false,
48
+ "required": ["self-hosted", "aws", "vercel", "cloudflare", "static"],
49
+ "properties": {
50
+ "self-hosted": {"$ref": "#/$defs/verdict"},
51
+ "aws": {"$ref": "#/$defs/verdict"},
52
+ "vercel": {"$ref": "#/$defs/verdict"},
53
+ "cloudflare": {"$ref": "#/$defs/verdict"},
54
+ "static": {"$ref": "#/$defs/verdict"}
55
+ }
56
+ },
57
+ "routes": {
58
+ "description": "Derived: the number of routes after site expansion; the value for --expect-routes.",
59
+ "type": "integer",
60
+ "minimum": 0
61
+ },
62
+ "services": {
63
+ "description": "External services the project needs at activation: an operator-installed extension package, a hook endpoint, a store.",
64
+ "type": "array",
65
+ "maxItems": 16,
66
+ "items": {
67
+ "type": "object",
68
+ "additionalProperties": false,
69
+ "required": ["name", "description"],
70
+ "properties": {
71
+ "name": {"type": "string", "minLength": 1, "maxLength": 80},
72
+ "description": {"type": "string", "minLength": 1, "maxLength": 300}
73
+ }
74
+ }
75
+ },
76
+ "grants": {
77
+ "description": "Operator grants the project needs before it activates; none of them come from project files.",
78
+ "type": "array",
79
+ "maxItems": 16,
80
+ "items": {
81
+ "type": "object",
82
+ "additionalProperties": false,
83
+ "required": ["kind", "description"],
84
+ "properties": {
85
+ "kind": {"enum": ["extension", "signals", "proxy", "env", "secret", "origin"]},
86
+ "description": {"type": "string", "minLength": 1, "maxLength": 300}
87
+ }
88
+ }
89
+ },
90
+ "inputs": {
91
+ "description": "What to edit before use.",
92
+ "type": "array",
93
+ "maxItems": 32,
94
+ "items": {
95
+ "type": "object",
96
+ "additionalProperties": false,
97
+ "required": ["name", "file", "description"],
98
+ "properties": {
99
+ "name": {"type": "string", "minLength": 1, "maxLength": 80},
100
+ "file": {"type": "string", "minLength": 1, "maxLength": 200},
101
+ "description": {"type": "string", "minLength": 1, "maxLength": 300}
102
+ }
103
+ }
104
+ },
105
+ "files": {
106
+ "description": "Every file the recipe copies (recipes) or the files worth reading first (examples), project-relative.",
107
+ "type": "array",
108
+ "minItems": 1,
109
+ "maxItems": 256,
110
+ "uniqueItems": true,
111
+ "items": {"type": "string", "minLength": 1, "maxLength": 200, "pattern": "^[A-Za-z0-9_][A-Za-z0-9_./-]*$"}
112
+ },
113
+ "tests": {
114
+ "type": "object",
115
+ "additionalProperties": false,
116
+ "required": ["commands"],
117
+ "properties": {
118
+ "fixtures": {"description": "The request fixture file, when the project has one.", "type": "string", "maxLength": 200},
119
+ "commands": {
120
+ "description": "The exact commands that validate, test and audit the project, run from its directory.",
121
+ "type": "array",
122
+ "minItems": 1,
123
+ "maxItems": 12,
124
+ "items": {"type": "string", "minLength": 1, "maxLength": 300}
125
+ }
126
+ }
127
+ },
128
+ "behavior": {
129
+ "description": "Expected behavior, one observable statement per line.",
130
+ "type": "array",
131
+ "minItems": 1,
132
+ "maxItems": 32,
133
+ "items": {"type": "string", "minLength": 1, "maxLength": 300}
134
+ }
135
+ },
136
+ "$defs": {
137
+ "verdict": {"enum": ["compatible", "conditional", "refused", "unknown"]}
138
+ }
139
+ }