@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,129 @@
1
+ # Usability review: URLCode against the tools people reach for instead
2
+
3
+ Status: an honest assessment written 2026-09-18 against the source in the four
4
+ repositories at that date. It is opinion grounded in the code and docs, not a
5
+ benchmark. Its purpose is to name the friction a first-time user or an AI
6
+ agent meets, compare it with what competing tools do, and rank the changes
7
+ that would matter most. It does not change any contract by itself.
8
+
9
+ > **Update:** written the day before `0.4.0-alpha.2` reversed the default
10
+ > execution mode. Where the table below credits functions and middleware with
11
+ > isolation, that now describes a route declaring `sandbox: true`; the default
12
+ > is trusted, in-process execution with full Node, npm and filesystem access.
13
+ > See [SPIKE-DEFAULT-TRUST-MODEL.md](SPIKE-DEFAULT-TRUST-MODEL.md).
14
+
15
+ ## 1. Who the competitors are, by rung
16
+
17
+ URLCode is not one product; it competes with a different tool on each rung
18
+ of [the ladder](FRAMEWORK.md#the-ladder).
19
+
20
+ | Rung | What people use today | Where URLCode is better | Where it is worse today |
21
+ |---|---|---|---|
22
+ | Redirects, responses | Netlify `_redirects`, Cloudflare `_redirects`/Rules, Vercel `vercel.json`, nginx | One portable file that validates before deploy, tests with fixtures, counts routes, converts from those formats and refuses lossy conversions | Those files are two columns; `urlcode.yaml` needs `version`, a route key and a handler object. Nobody needs a validator for ten redirects |
23
+ | Pages, files | Any static host, Astro, Hugo | Same file, no build step, native ranges/ETags, `site` conventions | No content pipeline, no templating for pages outside the extension kit |
24
+ | Functions, middleware | Cloudflare Workers, Vercel Functions, Hono, Express | Real isolation with a fresh heap per call; typed args from YAML; secrets only by revision-pinned grant; runs the same on a laptop | No `fetch`, no timers, no streaming, no npm packages in guests. Every framework above lets you call an API from a function; here you declare a `proxy` or stop |
25
+ | Live links | Bitly, Dub, Short.io, a Postgres table | Records without reloads, versioned writes, private management API, no account system to run | Single host SQLite that needs a patched Node build; no dashboard until you install admin |
26
+ | Accounts | Clerk, Auth0, Auth.js, Better Auth, Lucia, Supabase Auth, Devise | Everything is operator-owned and reviewable: no vendor, no callback URLs on someone else's dashboard, no per-MAU bill; passkeys, OIDC, TOTP, recovery and an account page in one package | Install is a reviewed-tarball ritual, a host file, a SHA-256 pin and a JSON-on-stdin bootstrap. Clerk is `npm i` and one env var. Better Auth is a config object and a CLI migration |
27
+ | Administration | Django admin, Rails Administrate, Retool, Forest Admin, Supabase Studio | Built on the same service with two-person cases and audited reveal, which none of the generic admins give you | Only manages auth's entities; there is no way to expose the project's own data. Django admin is one line per model |
28
+ | Full app in one place | Rails, Django, Laravel, Next.js + Clerk + Prisma, Supabase | The whole thing is declarative and portable; an agent can generate and validate it without a build; the trust boundary between generated code and the operator is explicit | Everything that is not a route, a function or an account is out of scope: no data model, no ORM, no forms for your own tables, no client bundle, no email templates for your app |
29
+
30
+ The honest summary: URLCode wins on portability, validation, isolation and
31
+ operator ownership, and loses on the first fifteen minutes and on anything
32
+ that needs application data.
33
+
34
+ ## 2. The first fifteen minutes
35
+
36
+ What a new person does today, compared with the fastest competitor at each
37
+ step. Times are what the docs imply, not measurements.
38
+
39
+ | Step | URLCode today | Fastest competitor | Gap |
40
+ |---|---|---|---|
41
+ | Install | `npm i -g @jimhoyd/urlcode` or brew or a checksum script; three channels documented at equal weight | `npx create-next-app` | Too many equal choices on the first screen. Pick one, put the rest in the install guide |
42
+ | First project | `urlcode init` writes a function route, a redirect and fixtures | `wrangler init` | Equal |
43
+ | First redirect | 5 lines of YAML for one redirect | 1 line in `_redirects` | Acceptable once, heavy for 200. `bulk-import` exists but a person starting small never learns it |
44
+ | First function | Must know: `parameters` with `schema`, `args` with `from: path`, the Request/Response subset, that `fetch` is absent | Hono: `app.get('/x', c => c.json(...))` | The typed-args design is right but needs a two-line minimal form. Today the smallest function route is 10 lines |
45
+ | Add accounts | Clone two private repos, build tarballs with `pack-sources.mjs --core --auth --ui --admin --core-revision SHA`, install four tarballs, `urlcode-auth init`, compute `inspectExtensionRevision`, paste the SHA into an env var, `bootstrap` with JSON on stdin, `serve --host-file --origin` | Clerk: `npm i @clerk/nextjs`, one env var, wrap the app | This is the largest gap in the framework. Most of it is a consequence of the packages being unpublished and of the revision pin, and it is the step the README leads with |
46
+ | Add admin | `urlcode-admin init` writes everything | Django `admin.site.register(Model)` | Comparable once installed |
47
+ | Deploy | Container, Node process, or an adapter that refuses functions, links and extensions | `vercel deploy` | The refusals are correct but the person learns them at deploy time. `urlcode capabilities` exists and is not in the quick start |
48
+
49
+ ## 3. What an AI agent meets
50
+
51
+ The framework's claim is that an agent should build from a handful of
52
+ redirects to a full application without rebuilding the core. Measured
53
+ against that claim:
54
+
55
+ - **Strong:** one JSON Schema, a generated field reference, fixtures the agent
56
+ must write, `validate`, `test` and `audit` that give exact failures with the
57
+ route named, an MCP server for read-only inspection, and a capability
58
+ matrix that lists what does not exist. Few frameworks tell an agent what it
59
+ cannot do. This is the right foundation.
60
+ - **Weak:** the documentation was organized by feature history rather than by
61
+ task. Before this review, `llms.txt` listed 40 documents at equal weight,
62
+ three of them status logs, and the README opened with release history and
63
+ "unreleased source" caveats. An agent reading it spent its context on
64
+ provenance rather than on the shape of a project. The framework page and the
65
+ reorganized index in this change address that; the remaining cost is the
66
+ size of the reference documents themselves (the YAML guide, policies and
67
+ dynamic links are each over 400 lines).
68
+ - **Missing:** an agent cannot yet discover the extension packages' YAML from
69
+ the core schema. `extensions.auth.config` is validated by auth's schema at
70
+ activation, but there is no way to ask the installed runtime "what config
71
+ does `auth` accept" without the host file. A `urlcode extensions --schema`
72
+ command that reads the host file and prints each extension's configuration
73
+ and policy schemas would close this, and would let `urlcode mcp` serve them.
74
+ - **Missing:** no single command creates the whole layered project. Today it
75
+ is `urlcode init`, then `urlcode-auth init`, then `urlcode-admin init`, each
76
+ with its own directory conventions. One `urlcode init --with auth,admin`
77
+ that delegates to the installed extension packages' scaffolds would make
78
+ the ladder real for an agent.
79
+
80
+ ## 4. Ranked recommendations
81
+
82
+ Ordered by how much each would change the experience per unit of work, and
83
+ whether it touches a contract.
84
+
85
+ 1. **Publish the three extension packages** (even as `0.1.0-alpha` with the
86
+ caveats their status files carry). Every install step in section 2's
87
+ "add accounts" row except the revision pin existed because they were
88
+ unpublished. This was a decision, not code; the repositories already had
89
+ the release checks. No contract change. Done 2026-09-18: all three are on
90
+ npm as `0.1.0-alpha.x` (review still pending, issue 58).
91
+ 2. **`urlcode init --with auth,admin,ui`.** Delegate to each installed
92
+ package's existing scaffold; write one host file and one README. No contract
93
+ change; a CLI addition in core that calls into optional peers.
94
+ 3. **Print extension schemas.** `urlcode extensions --host-file … --json`
95
+ listing each registered extension's name, version, configuration schema and
96
+ policy schema, and expose it through `urlcode mcp`. No contract change.
97
+ 4. **A short form for the common function route.** Allow `function:
98
+ functions/hello.mjs` as a string with path parameters inferred as required
99
+ strings of bounded length, expanding to today's long form. This is a schema
100
+ addition (`version: "1"` stays valid) and the single largest cut in YAML
101
+ for first-time users and agents. Needs the usual generated-reference and
102
+ cookbook updates.
103
+ 5. **Lead the README with the ladder, not the release history.** Done in this
104
+ change; keep it that way. Move status caveats to the readiness register.
105
+ 6. **One install channel on the first screen.** npm first; brew, script and
106
+ container in the install guide.
107
+ 7. **Fold the `presentation`/`ui` split.** Auth and admin still render through
108
+ the primitives while the kit is the documented way to restyle. Finishing
109
+ kit adoption (already listed in the ui status file) removes the one place
110
+ where the framework's story and its code differ.
111
+ 8. **Later, and a real contract question:** application data. The thing every
112
+ competitor on the last rung has and URLCode does not is a place for the
113
+ project's own records with an admin view. The runtime already has one
114
+ bounded store (links) and one admin surface (auth's entities). Whether a
115
+ declared `collection` handler with operator-owned SQLite and an admin
116
+ registration belongs in the framework is the next spike worth writing.
117
+ Without it, "full-fledged application" means "site with accounts".
118
+
119
+ ## 5. What is fine and should stay
120
+
121
+ - The operator host file. It is the reason the project can be untrusted and
122
+ portable at the same time; Clerk's convenience is bought with a vendor in
123
+ the loop. Keep the boundary, make the file generated.
124
+ - The revision pin. It is unusual and it is what makes "an agent changed the
125
+ YAML" a reviewable event rather than a silent grant. Keep it, print it
126
+ loudly, and make `init` write it.
127
+ - Refusing instead of degrading on every target. The failure names the route.
128
+ - The capability matrix in the AI guide. Extend it to the extension packages
129
+ rather than softening it.
package/docs/VERCEL.md CHANGED
@@ -5,8 +5,8 @@ The Vercel adapter runs a URLCode project as a Node function. The same
5
5
  that is the point of the project format.
6
6
 
7
7
  **This adapter serves native handlers only:** redirects, validated responses,
8
- pages, static assets and downloads. Isolated functions, middleware and stored
9
- live links are refused at activation, not per request, so a deployment cannot
8
+ pages, static assets and downloads. `function` and `middleware` routes are
9
+ refused at activation, trusted or sandboxed alike, not per request, so a deployment cannot
10
10
  half-work. See [what is not supported](#what-this-adapter-does-not-do).
11
11
 
12
12
  A working project is in [`examples/vercel/`](../examples/vercel/).
@@ -71,9 +71,8 @@ and want it in generated URLs.
71
71
 
72
72
  | Not supported | Why |
73
73
  |---|---|
74
- | Isolated functions | Every cold start would spawn worker threads and load the WASM engine. Correctness is not the issue; predictable latency is, and it is unmeasured. |
75
- | Middleware | Runs in the same sandbox as functions. |
76
- | Stored live links | SQLite needs a durable writable file. A serverless filesystem is ephemeral and per-instance, so records would silently diverge between instances. |
74
+ | `function` routes | They need the self-hosted Node lifecycle; a `sandbox: true` route would additionally spawn worker threads and load the WASM engine on every cold start. Correctness is not the issue; predictable latency is, and it is unmeasured. |
75
+ | Middleware | Runs in the same execution mode as the route's function, and is refused with it. |
77
76
  | `urlcode serve` operational endpoints | `/_urlcode/health` and `/_urlcode/ready` describe a long-lived process. Use Vercel's own observability. |
78
77
 
79
78
  Each refusal happens at activation with a message naming the route, so you find
@@ -0,0 +1,205 @@
1
+ # Core version alignment across repositories
2
+
3
+ Which core version each downstream package supports, how it says so, and the
4
+ order in which a core behavior change reaches downstream repositories. This
5
+ page is a mechanism and an invariant, not a schedule: it states no release
6
+ cadence, no LTS line and no support window. Those are not decided.
7
+
8
+ It exists because "the current core" had come to mean five different things at
9
+ once, and one of them did not resolve. The `0.4.0-alpha.2` release closes that;
10
+ the table below is where every repository lands, and it is the register to
11
+ change whenever a version changes anywhere.
12
+
13
+ | Repository | How it names core | Value (read from its own `package.json`/`peers.json`) |
14
+ |---|---|---|
15
+ | `urlcode` | source version | `0.4.0-alpha.2` |
16
+ | `urlcode-auth`, `urlcode-admin` | peer range plus a reviewed SHA | `>=0.4.0-alpha.1 <0.5.0`; `peers.json` `urlcode` = `d5e86017e93b96ec24bfdbf840692b95fc323151` in both |
17
+ | `urlcode-dynamic-link` | peer range | `>=0.4.0-alpha.1 <0.5.0` |
18
+ | `urlcode-middleware` | peer range | `>=0.4.0-alpha.2 <0.5.0` |
19
+ | `urlcode-short`, `urlcode-template`, `urlcode-docs` | exact dependency pin | `0.4.0-alpha.2` |
20
+
21
+ Only `urlcode-middleware` requires `0.4.0-alpha.2` specifically: it uses the
22
+ `middleware()` extension hook, `ExtensionActivation.root` and
23
+ `RuntimeExtension.cacheSensitive`, none of which exist in `0.4.0-alpha.1`. The
24
+ other extension packages work against either alpha and keep the wider floor,
25
+ which is what the supported-floor definition below requires of them.
26
+
27
+ The npm dist-tags for `@jimhoyd/urlcode` are `latest` = `0.3.0` and `alpha` =
28
+ `0.4.0-alpha.2`. `latest` deliberately stays on the `0.3.0` Apache-2.0
29
+ self-hosted baseline: the `0.4.0` line is a prerelease and must not become the
30
+ default install. Every release workflow derives its dist-tag from the version
31
+ rather than defaulting, so a prerelease can only publish under `alpha`.
32
+
33
+ The sibling packages are `@jimhoyd/urlcode-ui` `0.1.0-alpha.5`,
34
+ `@jimhoyd/urlcode-auth` and `@jimhoyd/urlcode-admin` `0.1.0-alpha.3`, and
35
+ `@jimhoyd/urlcode-dynamic-link`, `@jimhoyd/urlcode-middleware` and
36
+ `@jimhoyd/urlcode-short` `0.1.0-alpha.2`. For the extension line, `latest` and
37
+ `alpha` point at the same version — see the second invariant below.
38
+
39
+ Every one of those is a new version in this release. Each package's previous
40
+ release sat at the same version number as a source tree that had moved well
41
+ past it — 43 merged commits in `urlcode-auth`, 40 in `urlcode-ui`, 28 in
42
+ `urlcode-admin` — so the published version number identified nothing. A
43
+ version number that does not change when the source does is the same class of
44
+ defect as a peer range that cannot resolve, and the rule is the same: change
45
+ the version in the pull request that changes the source.
46
+
47
+ ## The supported floor
48
+
49
+ The supported core floor for an extension package is the **lowest core version
50
+ published to npm that contains every core API the extension calls**. It is a
51
+ property of the code, not of a calendar: raise it when the extension starts
52
+ using a core API that older published cores do not have, and not otherwise.
53
+
54
+ Everything below follows from that one definition.
55
+
56
+ ## How a package expresses its core requirement
57
+
58
+ Three forms are in use. They are not interchangeable.
59
+
60
+ **Peer range (`peerDependencies`), for an extension package.** An extension is
61
+ installed alongside core by the operator, so it must not carry its own copy;
62
+ `peerDependencies` is the correct field. Write it as a floor plus the next
63
+ breaking bound — `">=<floor> <0.5.0"` — where the floor is the supported floor
64
+ above. `urlcode-auth` and `urlcode-admin` use this form.
65
+
66
+ **Exact pin (`dependencies`), for an application or a starter.** A project that
67
+ is deployed or cloned rather than composed — `urlcode-short`, `urlcode-template`,
68
+ `urlcode-docs` — depends on one core version and pins it exactly. This is the
69
+ right form when the repository's tests, generated files and documentation were
70
+ all produced against one runtime and are only claimed to hold for that runtime.
71
+
72
+ **Reviewed SHA (`peers.json`), for source CI on top of a range.** A reviewed
73
+ commit is not a substitute for the peer range; it is an addition to it, used
74
+ where the repository must build and test against an exact reviewed core
75
+ checkout rather than whatever the registry resolves. `urlcode-auth` and
76
+ `urlcode-admin` each keep one `peers.json` naming the reviewed core commit;
77
+ each file states in its own `$comment` that published releases do not use those
78
+ SHAs and resolve peers from the registry by the `package.json` range instead.
79
+ Use a reviewed SHA when source CI needs reproducibility; do not use it to
80
+ express what an installing operator will get.
81
+
82
+ ## The invariant: a published peer range must be satisfiable
83
+
84
+ **A package published to npm must never declare a peer range that no published
85
+ core version satisfies.** At publish time, at least one version on the registry
86
+ must fall inside the range. A range that points at an unpublished core is not a
87
+ forward-looking declaration — it is an install failure for everyone who takes
88
+ the package from the registry.
89
+
90
+ The worked example came from this project. `@jimhoyd/urlcode-middleware`
91
+ `0.1.0-alpha.1` was published declaring `peerDependencies`
92
+ `{"@jimhoyd/urlcode": ">=0.4.0-alpha.2"}` at a time when the registry held only
93
+ `0.3.0` and `0.4.0-alpha.1`, so nothing satisfied it. The range was correct —
94
+ the package genuinely needs APIs that first appear in `0.4.0-alpha.2` — so the
95
+ fix was never to widen the range. The publication order was wrong: the package
96
+ was published before the core it requires, and it could be installed only from
97
+ source against a vendored core tarball carried for exactly that reason.
98
+
99
+ Publishing core `0.4.0-alpha.2` resolves it without any change to the already
100
+ published package: the range becomes satisfiable the moment core is on the
101
+ registry. The vendored tarball and the source-only install path go away with
102
+ it.
103
+
104
+ That is what the invariant prevents, and it is the only ordering rule that
105
+ cannot be relaxed.
106
+
107
+ ## The second invariant: `latest` must not fall below a sibling's floor
108
+
109
+ **Where a package line publishes under a prerelease dist-tag, `latest` must
110
+ still resolve to a version that satisfies every peer floor its siblings
111
+ declare.** `latest` is what a plain `npm install <package>` resolves, so a
112
+ `latest` left behind hands the installing operator a build that another
113
+ package in the same install refuses.
114
+
115
+ The worked example, again live in this project: `@jimhoyd/urlcode-auth`
116
+ published `alpha` = `0.1.0-alpha.2` while leaving `latest` = `0.1.0-alpha.1`.
117
+ `@jimhoyd/urlcode-admin` declares a peer floor of `>=0.1.0-alpha.2` on auth, so
118
+ `npm install @jimhoyd/urlcode-auth` resolved a build below the floor admin
119
+ requires. Nothing in the source is wrong; the dist-tag is.
120
+
121
+ The fix is a registry operation rather than a source change:
122
+
123
+ ```sh
124
+ npm dist-tag add @jimhoyd/urlcode-auth@0.1.0-alpha.2 latest
125
+ ```
126
+
127
+ Core is the deliberate exception. Its `latest` stays on `0.3.0` because no
128
+ sibling declares a floor above it — the extension packages name core through
129
+ `peerDependencies`, which resolve by range and never by dist-tag.
130
+
131
+ ## A deliberate older pin is a position, not drift
132
+
133
+ `urlcode-template`, `urlcode-docs` and `urlcode-short` now all pin
134
+ `0.4.0-alpha.2`, so no downstream repository is currently behind. An older pin
135
+ remains a legitimate position, and the rule for it does not change: it is
136
+ recorded where a reader will meet it. The repository's README says which core
137
+ version it pins, and every statement about runtime behavior in that repository
138
+ is read against that version. Where a statement is only true for the pinned version, it
139
+ says so and names the version, rather than being silently corrected to match
140
+ core's unreleased `main`.
141
+
142
+ This matters most for the trust model. Core `0.4.0-alpha.2` runs `function` and
143
+ `middleware` routes trusted and unsandboxed by default, with `sandbox: true` as
144
+ a per-route opt-in ([decision record](SPIKE-DEFAULT-TRUST-MODEL.md)).
145
+ `0.4.0-alpha.1` and earlier sandbox all such code unconditionally and have no
146
+ `sandbox` field in the schema. This is why moving a pin to `0.4.0-alpha.2` is
147
+ a behavior change even when no YAML changes: every `function`/`middleware`
148
+ route that does not declare `sandbox` becomes trusted on upgrade. Review those
149
+ routes before raising a pin, and add `sandbox: true` to the ones that handle
150
+ input or code you would not trust with full Node/filesystem/network access.
151
+ A repository still pinned to `0.4.0-alpha.1` or `0.3.0` that documents
152
+ sandbox-by-default is **describing its pin correctly**. Its
153
+ generated files — field references, schemas, scaffolding output, vendored agent
154
+ skills — are likewise correct for that pin. Divergence from core's current
155
+ `main` is not by itself staleness, and it is not a defect to be "fixed" by
156
+ importing behavior the pinned runtime does not have. The same point is made
157
+ about the advisory `npm run check:downstream-skills` report in
158
+ [release readiness](RELEASE-READINESS.md).
159
+
160
+ What is a defect: a repository that does not say which core version it pins, or
161
+ that states pin-dependent behavior as an unconditional property of URLCode.
162
+
163
+ ## Order of operations when core changes behavior
164
+
165
+ A core behavior change reaches downstream repositories in this order. Each step
166
+ depends on the one before it; skipping ahead is what produced the
167
+ `urlcode-middleware` breakage above.
168
+
169
+ 1. **Publish core.** The version carrying the change goes to npm first. Until it
170
+ is on the registry, no downstream peer range may reference it (the invariant
171
+ above).
172
+ 2. **Raise downstream floors.** Extension packages move their peer range floor
173
+ to the published version; applications and starters move their exact pin.
174
+ A repository that chooses to stay on the older core does nothing here — that
175
+ is a valid outcome, and step 3 does not apply to it.
176
+ 3. **Regenerate downstream generated files.** Field references, schemas,
177
+ scaffolding output and vendored agent skills are regenerated *after* the
178
+ floor moves, against the version now pinned — never before, or they describe
179
+ a runtime the repository does not depend on.
180
+
181
+ Reviewed-SHA repositories take the same three steps, and additionally update
182
+ `peers.json` to the reviewed commit for the new core when source CI is expected
183
+ to build against it. The SHA and the published floor answer different questions
184
+ and are updated independently.
185
+
186
+ ## Open: two publishing conventions
187
+
188
+ The repositories publish under two different conventions, and the maintainer has
189
+ not settled which one the project uses. Both are recorded here neutrally; this
190
+ page does not pick one.
191
+
192
+ - **Private until release.** `urlcode-dynamic-link` and `urlcode-middleware`
193
+ keep `"private": true` in their `package.json` on `main` and drop it in the
194
+ release commit. Publication is an explicit, visible act in the release diff,
195
+ and an accidental `npm publish` from `main` fails closed. Both packages are
196
+ nonetheless published on npm, so the convention has been exercised.
197
+ - **Publishable on main.** `urlcode-auth`, `urlcode-admin` and `urlcode-ui`
198
+ carry no `private` field and declare `publishConfig.access = "public"`
199
+ instead. The committed manifest is
200
+ the manifest that publishes, so what is on the registry can be diffed against
201
+ `main` without accounting for a release-only edit.
202
+
203
+ The split is currently by repository, not by package kind, and nothing records
204
+ why. Settling it is a maintainer decision; no `package.json` is changed on the
205
+ strength of this page.