@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,134 @@
1
+ # Redirect interchange and bulk authoring
2
+
3
+ `importRoutes` and `exportRoutes` return a conversion report without writing files,
4
+ activating a runtime, reading bindings, loading function sources or running code.
5
+ The CLI can preview the report before creating a new output file. Failed reports
6
+ never contain a partial document or output; duplicate paths never overwrite a row.
7
+
8
+ ```js
9
+ import { importRoutes, exportRoutes } from '@jimhoyd/urlcode';
10
+ const report = await importRoutes({
11
+ format: 'csv', source: 'migration.csv',
12
+ text: 'path,url,status\n/old,https://example.test/new,301\n'
13
+ });
14
+ if (!report.ok) throw new Error(JSON.stringify(report.diagnostics));
15
+ // report.output is URLCode project YAML; report.document is the validated project.
16
+ const csv = await exportRoutes({ format: 'csv', document: report.document });
17
+ ```
18
+
19
+ Each report has `ok`, `lossless`, `routeCount` and `diagnostics`. Diagnostics name
20
+ an input source, physical CSV/text row or JSON/YAML array index where available,
21
+ a path where appropriate, a code, severity and explanation. Destinations and
22
+ credentials are not echoed in validation errors. Output is sorted by literal
23
+ path, independent of input order. Import does not merge an existing project;
24
+ normal project loading rejects collisions across includes when output is added.
25
+
26
+ ## CLI usage
27
+
28
+ ```sh
29
+ # Bulk row formats can be inferred from the input extension or named explicitly.
30
+ urlcode import csv migration.csv --out routes.yaml --dry-run
31
+ urlcode import csv migration.csv --out routes.yaml
32
+ urlcode import migration.json --report json --dry-run
33
+
34
+ # Provider migrations require explicit acknowledgment of the reported differences.
35
+ urlcode import netlify _redirects --out imported.yaml --dry-run
36
+ urlcode import netlify _redirects --out imported.yaml --accept-provider-differences
37
+ urlcode export --target netlify --project ./project --out _redirects --accept-provider-differences
38
+ urlcode export --target cloudflare --project ./project --dry-run
39
+ urlcode export --target vercel --project ./project --dry-run
40
+ urlcode export --target netlify-toml --project ./project --dry-run
41
+ ```
42
+
43
+ General import syntax is `urlcode import [format] FILE`; `--format` explicitly
44
+ overrides the positional format or extension. Exports use `--target` for a
45
+ provider format, or `--format csv|json|yaml` for row data. `--out FILE` creates a
46
+ new file with mode 0600 and never overwrites one. `--dry-run` validates and
47
+ returns a report without creating the requested output. `--report json` always
48
+ emits the report. Failures exit nonzero and leave no converted output.
49
+
50
+ Reports include source/row diagnostics and the successful candidate output.
51
+ Acknowledged provider conversions always emit a report with their warnings,
52
+ even when `--out` is provided; raw provider output is never silently printed as
53
+ if it were lossless. Project export flattens validated includes, but refuses
54
+ other unsupported project behavior instead of dropping it. For a large input
55
+ that needs a complete sharded project, use [bulk import](BULK.md).
56
+
57
+ ## Supported forms
58
+
59
+ | Format | Input/output subset |
60
+ | --- | --- |
61
+ | `csv` | Header exactly `path,url,status`; optional empty status defaults to 302; quoted fields and escaped quotes |
62
+ | `json` | Array of `{path,url,status?}`; status must be a number |
63
+ | `yaml` | The same row array in strict URLCode YAML syntax; not a full project file |
64
+ | `netlify` | `_redirects`: literal source, absolute HTTP(S) destination, optional status (default 301) |
65
+ | `cloudflare` | Pages `_redirects`: same columns, default 302; 2,000 static rules, 1,000 characters per rule |
66
+ | `vercel` | `vercel.json` containing only `redirects`; literal `source`, absolute `destination`, and either boolean `permanent` (308/307) or `statusCode` |
67
+ | `netlify-toml` | Only `[[redirects]]` blocks with `from`, `to`, optional numeric `status` (default 301); unescaped double-quoted strings |
68
+
69
+ All statuses are restricted to 301, 302, 303, 307 and 308. Literal ASCII paths
70
+ and absolute HTTP(S) destinations are required. The existing schema and semantic
71
+ compiler validate resulting routes. No provider pattern compiler is introduced.
72
+
73
+ The TOML subset deliberately rejects general TOML constructs, build settings,
74
+ escapes, inline comments, nested conditions, force flags and other tables. A full
75
+ `netlify.toml` must be separated into a redirects-only input by the operator. This
76
+ avoids silently discarding build settings or reinterpreting unsupported syntax.
77
+
78
+ Functions, assets, middleware, conditions, parameters, query maps/allowlists,
79
+ headers, policies, includes and any other extra route/project fields cannot be
80
+ exported by this subset. Relative destinations, wildcards, provider placeholder
81
+ syntax, rewrites and duplicate paths fail. Use a runtime adapter for richer
82
+ behavior. CSV/JSON/YAML round-trips are lossless within the declared simple subset.
83
+
84
+ ## Provider semantics require explicit acknowledgment
85
+
86
+ Provider conversions fail by default. `acceptProviderDifferences: true` (CLI
87
+ `--accept-provider-differences`) permits a **non-lossless migration candidate**;
88
+ it does not suppress invalid rules, discard unsupported fields or claim exact
89
+ behavior. Every such successful report retains a warning and `lossless: false`.
90
+
91
+ URLCode drops incoming query parameters and defaults to GET/HEAD. Native
92
+ redirect systems can apply to other methods and have different normalization
93
+ and request-query behavior. Netlify automatically forwards queries for common
94
+ redirect statuses and can give existing files precedence. Cloudflare Pages
95
+ redirects override assets, but Pages Functions can bypass `_redirects`.
96
+ Review behavior for requests with queries, non-GET methods, slash/URL normalization
97
+ and conflicting assets/functions before deploying. The acknowledged subset is
98
+ literal GET/HEAD requests without these conflicts. No provider deployment was
99
+ performed as part of interchange unit tests.
100
+
101
+ Provider references checked 2026-09-17:
102
+
103
+ - [Netlify redirect options](https://docs.netlify.com/manage/routing/redirects/redirect-options/)
104
+ - [Netlify TOML configuration](https://docs.netlify.com/build/configure-builds/file-based-configuration/)
105
+ - [Cloudflare Pages redirects](https://developers.cloudflare.com/pages/configuration/redirects/)
106
+ - [Vercel redirect configuration](https://vercel.com/docs/project-configuration/vercel-json)
107
+
108
+ ## Resource limits
109
+
110
+ Imports reject more than 32 MiB of input or 100,000 rows. Diagnostic collection
111
+ stops after 100 invalid rows; normal route compilation retains its deadline and
112
+ validation rules. Large output can still exceed the runtime YAML worker's heap
113
+ or deadline: conversion success does not promise activation of a single 100k
114
+ route YAML file. Split large migration inputs into independently reviewed files
115
+ and use explicit `includes`; aggregate project limits continue to apply.
116
+
117
+ Reports also expose bounded classification counts:
118
+
119
+ - `convertedRoutes`: routes actually returned in successful output; zero when
120
+ any error prevents output.
121
+ - `nativeEquivalentRoutes`: returned routes with no conversion warnings; always
122
+ zero for acknowledged provider conversions.
123
+ - `runtimeRequiredRoutes`: route diagnostics identifying behavior outside the
124
+ simple export subset.
125
+ - `unsupportedRows`: known invalid/duplicate input-row diagnostics.
126
+ - `providerDifferenceRoutes`: candidate routes subject to the provider semantic
127
+ warning; these overlap converted routes after acknowledgment.
128
+ - `fullyScanned`: false on parser/global failures or truncated diagnostics. Counts
129
+ then describe only examined input, never an inferred total.
130
+
131
+ These counts are not a partition of arbitrary malformed input. `routeCount`
132
+ retains its original meaning: routes in the candidate table, or rows
133
+ parsed before an early global failure. A failed report contains no candidate
134
+ output even if some rows were convertible.
@@ -0,0 +1,75 @@
1
+ # Middleware examples
2
+
3
+ Fourteen reusable middleware modules covering fifteen patterns (`auth.mjs`
4
+ exports both `bearer` and `basic`), each in
5
+ [`examples/cookbook/middleware`](../examples/cookbook/middleware) with a route in
6
+ [`routes/middleware.yaml`](../examples/cookbook/routes/middleware.yaml) and request
7
+ fixtures in the cookbook tests. The same modules ship as the `middleware`
8
+ [local recipe](RECIPES.md):
9
+
10
+ ```sh
11
+ urlcode recipes add middleware --out ./my-middleware
12
+ urlcode test --project ./my-middleware
13
+ ```
14
+
15
+ They cover what Express, Koa, Fastify, Hono, Next.js and edge runtimes usually
16
+ ship as middleware, rewritten for URLCode's [guest API](MIDDLEWARE.md): text/JSON
17
+ `Request`/`Response`, `Headers`, `context.inputs/args/env/secrets/state`, timers
18
+ and nothing else. There is no `crypto`, `URL`, `fetch`, storage or console, and
19
+ `context.state` dies with the request. Patterns that need any of those are listed
20
+ at the end so nobody wastes time porting them.
21
+
22
+ | Pattern | Module | Framework equivalent | Demonstrates |
23
+ | --- | --- | --- | --- |
24
+ | Bearer token gate | `auth.mjs` `bearer` | Hono `bearerAuth`, `express-bearer-token` | Early 401, `www-authenticate`, constant-time compare against an `env` binding |
25
+ | Basic authentication | `auth.mjs` `basic` | Hono `basicAuth`, `express-basic-auth` | Hand-written base64 (no `atob`), both checks always evaluated, `state.user` |
26
+ | CORS | `cors.mjs` | Express `cors`, Hono `cors` | `OPTIONS` answered before the handler, origin allowlist, `vary: origin` |
27
+ | Correlation id and timing | `request-id.mjs` | Hono `requestId`/`timing`, Express `response-time` | Validating a caller header, `server-timing`, coexisting with the runtime's own `x-request-id` |
28
+ | Maintenance switch | `maintenance.mjs` | Next.js and Netlify Edge maintenance examples | 503 with `retry-after`, bypass header, flipping behavior from a binding |
29
+ | Error boundary | `errors.mjs` | Koa `onerror`, Express error handlers | Catching a downstream throw, JSON 500 instead of a bare 502 |
30
+ | JSON envelope | `envelope.mjs` | Response transformers | Reading a function body once, passing native bodies through untouched |
31
+ | Content negotiation | `negotiate.mjs` | Express `res.format` | Parsing `accept` with q-values, 406, `vary: accept` |
32
+ | Method override | `methods.mjs` `override` | Express `method-override` | Bounded tunneling through POST, 405 with `allow` |
33
+ | ETag and 304 | `etag.mjs` | Express `etag`, Fastify `@fastify/etag` | FNV-1a weak tag, `if-none-match`, null-body 304 |
34
+ | A/B bucket | `bucket.mjs` | Vercel and Cloudflare A/B examples | Cookie parsing, `set-cookie`, replacing a native redirect |
35
+ | Locale redirect | `locale.mjs` | Next.js i18n middleware | `accept-language` ranking, allowlisted languages, `vary` |
36
+ | Referer allowlist | `referer.mjs` | Hotlink protection rules | Gating a native download without reading it |
37
+ | Body validation | `body.mjs` | `express-validator`, Fastify schemas | Single-use body, 422 error list, handoff through `state` |
38
+ | Debug echo | `debug.mjs` | Request loggers | Inspecting inputs, args and redacted headers when the console is silent |
39
+
40
+ ## Reading the modules
41
+
42
+ Every module follows the shape in [middleware](MIDDLEWARE.md): read the request,
43
+ optionally return early, otherwise `await next()` once and return a `Response`.
44
+ Three habits recur and are worth copying:
45
+
46
+ - **Configuration lives in bindings.** Tokens, allowlists and switches are read
47
+ from `context.env`. The cookbook binds literal values so it runs without grants;
48
+ a deployed project uses `{secret: name}` for credentials and an operator grant.
49
+ - **Native bodies stay opaque.** `envelope`, `negotiate` and `etag` only rewrite a
50
+ body when the downstream response is a function response with a readable
51
+ content type. `bucket`, `locale` and `referer` wrap native redirects and
52
+ downloads without touching their bytes; to change the destination they return
53
+ a new `Response` instead.
54
+ - **Chains compose through `state`.** `/fragile` runs `request-id` before
55
+ `errors`, so the fallback JSON carries the correlation id. `/profile` parses
56
+ the body once in middleware and the function reads `context.state.body`.
57
+
58
+ ## Limits these examples respect
59
+
60
+ - **No `crypto`.** The auth modules compare a shared token; they cannot verify
61
+ HMAC signatures, JWTs or password hashes. Signed URLs would be a runtime
62
+ feature, not a middleware example.
63
+ - **No cross-request state.** Rate limiting, caching, sessions and CSRF tokens
64
+ need storage the guest does not have. Throttling and cache headers exist as
65
+ native [policies](POLICIES.md) instead.
66
+ - **No logging target.** The guest console is a no-op; `debug.mjs` returns the
67
+ information to the caller instead, and only when both a binding and a header
68
+ ask for it. Remove that route before publishing a project.
69
+ - **Runtime headers win.** The runtime stamps `x-request-id` on every response,
70
+ which is why the tracing example uses `x-correlation-id`. YAML
71
+ `response.headers` also override middleware headers.
72
+
73
+ Each cookbook route has fixtures for its success path, its early responses, its
74
+ validation failures and every declared method, which is what `urlcode audit`
75
+ expects before it reports a middleware-wrapped route as covered.
@@ -28,6 +28,8 @@ Paths resolve from the project root. Up to 16 entries are allowed; each accepts
28
28
  `source` and optional `export` (default `default`). Reuse a module across routes.
29
29
  There are no global middleware settings or middleware-specific argument fields.
30
30
  This is URLCode's small portable API, not Express/Node middleware compatibility.
31
+ Fourteen ready-to-copy patterns (auth, CORS, tracing, error boundary, ETag and
32
+ more) are in [middleware examples](MIDDLEWARE-EXAMPLES.md).
31
33
 
32
34
  ## Order and responses
33
35
 
@@ -0,0 +1,90 @@
1
+ # Next-phase implementation status
2
+
3
+ Repository review and source implementation, 2026-09-17. Phase A established
4
+ the capability catalog; the subsequent source work implements the bounded
5
+ Phase B–D features below. This describes the source additions after `0.3.0` that ship in
6
+ `0.4.0-alpha.1`, not a claim that provider deployments have been verified.
7
+
8
+ ## Implementation and evidence
9
+
10
+ | Phase | Implemented source | Evidence and remaining limits |
11
+ | --- | --- | --- |
12
+ | A: capabilities and normalized representation | Shared catalog, route/project analysis, CLI/SDK and adapter preflight reuse the existing compiled IR | Capability tests and target refusals; compiled routes with secrets/closures are never a portable public artifact |
13
+ | B1: provider conformance | Synthetic 12-case common-subset fixture; local self-hosted/AWS/Vercel/Cloudflare replay; bounded HTTPS deployment runner and versioned reports | Local adapter evidence exists; actual AWS/Vercel/Cloudflare deployments and provider-specific transport/policy guarantees remain unverified |
14
+ | B2: Netlify/Cloudflare conversion | Strict literal redirect import/export, source diagnostics, dry-run, no-clobber output and explicit provider-difference acknowledgment | Provider normalization, query forwarding, method coverage and asset precedence differ; acknowledged migrations are explicitly non-lossless |
15
+ | B3: Vercel/TOML conversion | Conservative Vercel redirect subset and redirects-only Netlify TOML grammar | Unsupported fields, patterns, conditions, forced rules and general TOML syntax are rejected rather than discarded |
16
+ | C1: bounded proxy | Self-hosted native proxy and external revision-pinned HTTPS-origin grants, connection-pinned public DNS, body/time/concurrency limits and header filtering | No guest fetch, host execution fallback, automatic redirects or retries; providers refuse proxy; independent security review remains open |
17
+ | C2: conditions | Exact bounded query/header/cookie/host/method predicates; explicit disjoint redirect/respond cases and fallback; no-store | Duplicate YAML keys remain errors; ambiguous cases fail; self-hosted/AWS/Vercel share logic, Cloudflare refuses pending artifact support |
18
+ | C3: best-effort signals | Self-hosted bounded webhook broker, external pinned grants, fixed redacted event shape, accepted/delivered/failed/dropped counters and shutdown handling | No queue, retry, ordering or durability guarantee; saturation drops; providers refuse signals |
19
+ | D: recipes | Three ordinary local Git-owned recipe projects with list/show/add and new-directory dry-run publication | Runtime integration tests exercise redirect, JSON API and built TypeScript recipe; no remote registry or implicit project merge |
20
+ | D: bulk | Strict CSV/JSON/YAML conversion, input fingerprint/source provenance, sorted 1,000-route include shards | 1k/10k/100k local measurements pass without relaxing loader limits; no implicit merge or arbitrary bulk mutation |
21
+ | D: TypeScript guests | Fixed trusted build-time compiler, bounded relative graph, rewritten JavaScript imports, referenced-asset snapshot and safe new output | Transpilation is not type checking; no tsconfig/plugins/package execution or dotenv copying; runtime remains QuickJS JavaScript only |
22
+ | D: consolidated SDK/MCP | Inspection, semantic validation, path explanation, compatibility, conversion previews and recipe discovery; operator-rooted stdio MCP | Read-only tooling; no arbitrary path, credential, guest execution or write authority; not a remote authenticated service |
23
+
24
+ See [interchange](INTERCHANGE.md), [provider evidence](PROVIDER-VERIFICATION.md),
25
+ [egress](EGRESS.md), [conditions](CONDITIONS.md), [recipes](RECIPES.md),
26
+ [bulk measurements](BULK.md), [TypeScript authoring](TYPESCRIPT-AUTHORING.md), and
27
+ [tooling/MCP](TOOLING.md) for the executable interfaces and exact restrictions.
28
+
29
+ ## Preserved architecture
30
+
31
+ The schema and semantic compiler remain the behavior contract. `CompiledRoute`
32
+ and `CompiledRouteTable` remain the runtime IR; `MatchableRoute` remains the
33
+ shared matching representation. New handlers and conditions extend those paths
34
+ rather than introducing a second route compiler. Capability analysis precedes
35
+ binding resolution and activation and distinguishes implementation support from
36
+ actual deployment evidence. Unknown or unsupported targets fail closed.
37
+
38
+ Project YAML describes route behavior. Provider infrastructure and outbound
39
+ origin grants stay in operator configuration. Functions remain untrusted
40
+ QuickJS/WASM guests with no host-code fallback. Existing explicit external
41
+ bindings and new egress grants remain pinned to the exact configuration/source
42
+ revision. Compilation and conversion do not resolve credentials or manufacture
43
+ grants. File authors publish new projects without overwriting unrelated work.
44
+
45
+ The five policy modules remain the authority for their target-sensitive
46
+ compatibility. Delegated compression does not imply identical edge behavior;
47
+ coalesced headers and normalized URLs remain transport limitations. Conditional
48
+ routes are no-store to prevent cross-branch shared-cache leakage. Conditions are
49
+ selection rules, not authentication or authority.
50
+
51
+ ## Validation and outstanding external work
52
+
53
+ New regression suites cover conservative conversion refusals, source diagnostics,
54
+ local provider replay, conditions/ambiguity, proxy and signal security boundaries,
55
+ recipe execution, TypeScript graph limits, bulk sharding and MCP authority limits.
56
+ Package smoke exercises actual archive installation with production dependencies,
57
+ CLI authoring/conversion/MCP and the public declaration surface. Schema changes
58
+ require regenerated reference documentation and executable examples. Local integration passed `npm run verify` (384 tests: 383 passed, one existing
59
+ TLS-fixture skip) and `npm run test:package`, including a production-only install
60
+ with TypeScript 6.0.3. Required CI checks and normal pull-request review still
61
+ apply to each exact proposed revision.
62
+
63
+ The bulk benchmark uses fresh sequential processes and records conversion,
64
+ normal runtime activation, memory samples and checked runtime lookups for
65
+ 1,000, 10,000 and 100,000 synthetic redirects. Splitting the last dataset into
66
+ 100 includes avoids the earlier single-document worker memory failure without
67
+ increasing the 256 MiB worker heap or ten-second loader deadline. This is local
68
+ capacity evidence, not a cross-platform SLO or peak-memory bound.
69
+
70
+ ### Deferred follow-up: live provider testing (non-blocking)
71
+
72
+ Decision, 2026-09-17: defer live Cloudflare, AWS and Vercel testing and return to
73
+ it later. This does not block the current implementation work or pull-request
74
+ review and merge, subject to the normal required checks and authorization.
75
+ Provider deployment status remains **unverified** until real tests are recorded;
76
+ deferral does not change capability claims or remove other release/security gates.
77
+
78
+ When resumed, choose a provider and supply an operator-owned test account/project
79
+ with normal local login access and authorization for a temporary deployment
80
+ (including any hosting charges), or supply an already deployed conformance
81
+ fixture URL. Run the existing provider verification tool and record the results.
82
+ No credentials or provider setup are needed from the user for the current work.
83
+
84
+ Actual provider provisioning/deployment observations require operator-owned
85
+ accounts and explicit fixture URLs. Real ingress normalization, repeated header
86
+ and cookie behavior, distributed policy guarantees, soak/recovery tests and
87
+ independent assessment of the new network bridge remain separate release and
88
+ operational gates. No implementation test, capability report, benchmark, or CI
89
+ pass substitutes for that evidence. The self-hosted release remains useful and
90
+ portable without requiring provider accounts or a paid control plane.