@jimhoyd/urlcode 0.4.0-alpha.1 → 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 (191) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +24 -8
  2. package/.claude/skills/urlcode-operations/SKILL.md +10 -16
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/README.md +75 -62
  5. package/ROADMAP.md +61 -16
  6. package/SECURITY.md +23 -8
  7. package/dist/BUILD-MANIFEST.json +38 -43
  8. package/dist/agents-guide.js +18 -18
  9. package/dist/build-static.js +134 -0
  10. package/dist/capabilities.js +85 -31
  11. package/dist/capability-query.js +0 -1
  12. package/dist/cli.js +23 -31
  13. package/dist/compliance-rules/baseline.js +2 -10
  14. package/dist/compliance-rules/privacy.js +5 -16
  15. package/dist/compliance-rules/shared.js +0 -2
  16. package/dist/compliance.js +6 -8
  17. package/dist/config.js +15 -8
  18. package/dist/context.js +9 -10
  19. package/dist/examples.js +2 -2
  20. package/dist/explain-cli.js +3 -3
  21. package/dist/explain.js +5 -6
  22. package/dist/extensions.js +61 -2
  23. package/dist/function-sources.js +34 -2
  24. package/dist/function-worker.js +3 -1
  25. package/dist/functions.js +84 -13
  26. package/dist/guest-api.js +29 -3
  27. package/dist/index.js +3 -6
  28. package/dist/manifest.js +5 -7
  29. package/dist/mcp.js +1 -1
  30. package/dist/observability.js +1 -21
  31. package/dist/policies/cache.js +8 -3
  32. package/dist/policy.js +28 -9
  33. package/dist/project-tests.js +3 -3
  34. package/dist/readiness.js +32 -9
  35. package/dist/router.js +5 -7
  36. package/dist/runtime.js +77 -58
  37. package/dist/sandbox.js +48 -0
  38. package/dist/scaffold.js +0 -0
  39. package/dist/scripts/operational-drills.js +12 -54
  40. package/dist/server.js +3 -29
  41. package/dist/tooling.js +1 -1
  42. package/dist/trusted-functions.js +210 -0
  43. package/dist/types/build-static.d.ts +43 -0
  44. package/dist/types/capabilities.d.ts +14 -5
  45. package/dist/types/compliance-rules/shared.d.ts +0 -2
  46. package/dist/types/compliance.d.ts +0 -3
  47. package/dist/types/config.d.ts +2 -1
  48. package/dist/types/context.d.ts +2 -1
  49. package/dist/types/examples.d.ts +1 -1
  50. package/dist/types/extensions.d.ts +55 -0
  51. package/dist/types/function-sources.d.ts +4 -0
  52. package/dist/types/functions.d.ts +48 -5
  53. package/dist/types/guest-api.d.ts +1 -0
  54. package/dist/types/index.d.ts +3 -6
  55. package/dist/types/manifest.d.ts +1 -3
  56. package/dist/types/observability.d.ts +1 -14
  57. package/dist/types/project-tests.d.ts +1 -2
  58. package/dist/types/readiness.d.ts +10 -3
  59. package/dist/types/router.d.ts +2 -1
  60. package/dist/types/runtime.d.ts +0 -27
  61. package/dist/types/sandbox.d.ts +12 -0
  62. package/dist/types/scaffold.d.ts +0 -2
  63. package/dist/types/server.d.ts +1 -4
  64. package/dist/types/tooling.d.ts +3 -3
  65. package/dist/types/trusted-functions.d.ts +29 -0
  66. package/dist/types/types.d.ts +14 -7
  67. package/dist/types/verify-deployment.d.ts +2 -2
  68. package/dist/types.js +17 -4
  69. package/dist/typescript-authoring.js +55 -17
  70. package/dist/verify-deployment.js +3 -3
  71. package/docs/AI-AUTHORING.md +104 -9
  72. package/docs/AWS.md +4 -4
  73. package/docs/BEST-PRACTICES.md +3 -2
  74. package/docs/CAPABILITIES.md +29 -16
  75. package/docs/CAPACITY.md +128 -31
  76. package/docs/CLOUDFLARE.md +1 -2
  77. package/docs/COMPLIANCE.md +6 -9
  78. package/docs/DEPLOYMENT-CHECKS.md +1 -1
  79. package/docs/EXTENSIONS.md +183 -11
  80. package/docs/FRAMEWORK.md +35 -19
  81. package/docs/FUNCTION-SECURITY.md +129 -32
  82. package/docs/INSTALL.md +0 -5
  83. package/docs/LOAD-TESTING.md +4 -4
  84. package/docs/MIDDLEWARE.md +27 -16
  85. package/docs/MONITORING.md +2 -19
  86. package/docs/NEXT-PHASE-PLAN.md +12 -4
  87. package/docs/NEXT-STEPS.md +259 -40
  88. package/docs/OBSERVABILITY.md +7 -16
  89. package/docs/OPEN-DECISIONS.md +212 -0
  90. package/docs/OPERATIONAL-PROOF.md +26 -30
  91. package/docs/OPERATIONS.md +23 -32
  92. package/docs/POLICIES.md +15 -4
  93. package/docs/PRERENDER.md +2 -2
  94. package/docs/PROJECT-DIRECTION.md +34 -9
  95. package/docs/README.md +13 -11
  96. package/docs/RELEASE-READINESS.md +49 -29
  97. package/docs/RELEASE-SECURITY.md +89 -3
  98. package/docs/RESILIENCE.md +16 -15
  99. package/docs/ROUTING.md +8 -10
  100. package/docs/SANDBOX-REVIEW.md +19 -6
  101. package/docs/SCAFFOLDING.md +0 -2
  102. package/docs/SECURITY-AUDIT.md +40 -0
  103. package/docs/SPECIFICATION.md +60 -26
  104. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +287 -0
  105. package/docs/SPIKE-BUSINESS-SUITE.md +8 -0
  106. package/docs/SPIKE-CORE-LAYERING.md +337 -0
  107. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +209 -0
  108. package/docs/SPIKE-EXTENSION-MODEL.md +2 -2
  109. package/docs/SPIKE-EXTENSIONS.md +5 -0
  110. package/docs/SPIKE-LAMBDA-COMPILE.md +4 -2
  111. package/docs/SPIKE-MONOREPO.md +322 -0
  112. package/docs/STARTERS.md +1 -1
  113. package/docs/STATIC.md +94 -0
  114. package/docs/TOOLING.md +13 -9
  115. package/docs/TUNNELS.md +0 -3
  116. package/docs/TYPESCRIPT-AUTHORING.md +27 -12
  117. package/docs/TYPESCRIPT.md +25 -4
  118. package/docs/USABILITY-REVIEW.md +7 -1
  119. package/docs/VERCEL.md +4 -5
  120. package/docs/VERSION-ALIGNMENT.md +205 -0
  121. package/docs/YAML-GUIDE.md +0 -3
  122. package/docs/YAML-REFERENCE.md +16 -6
  123. package/docs/policies/cache.md +13 -0
  124. package/examples/assets/example.yaml +1 -1
  125. package/examples/aws/example.yaml +1 -1
  126. package/examples/cloudflare/example.yaml +1 -1
  127. package/examples/conditions/example.yaml +1 -1
  128. package/examples/cookbook/example.yaml +1 -1
  129. package/examples/cookbook/middleware/bucket.mjs +12 -2
  130. package/examples/cookbook/middleware/locale.mjs +7 -3
  131. package/examples/egress/example.yaml +1 -1
  132. package/examples/extensions/example.yaml +1 -1
  133. package/examples/prerender/README.md +2 -2
  134. package/examples/prerender/example.yaml +1 -1
  135. package/examples/provider-conformance/example.yaml +1 -1
  136. package/examples/vercel/example.yaml +1 -1
  137. package/llms-full.txt +594 -219
  138. package/llms.txt +23 -12
  139. package/package.json +10 -3
  140. package/packaging/claude-plugin/.claude-plugin/plugin.json +1 -1
  141. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +24 -8
  142. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +10 -16
  143. package/recipes/authenticated-json-api/recipe.yaml +1 -1
  144. package/recipes/contact-form/recipe.yaml +1 -1
  145. package/recipes/cors-api/recipe.yaml +1 -1
  146. package/recipes/health-page/recipe.yaml +1 -1
  147. package/recipes/json-api/recipe.yaml +1 -1
  148. package/recipes/middleware/middleware/bucket.mjs +12 -2
  149. package/recipes/middleware/middleware/locale.mjs +7 -3
  150. package/recipes/middleware/recipe.yaml +1 -1
  151. package/recipes/protected-download/recipe.yaml +1 -1
  152. package/recipes/redirect/recipe.yaml +1 -1
  153. package/recipes/static-plus-api/recipe.yaml +1 -1
  154. package/recipes/typescript/README.md +2 -1
  155. package/recipes/typescript/recipe.yaml +1 -1
  156. package/recipes/webhook-receiver/README.md +5 -1
  157. package/recipes/webhook-receiver/recipe.yaml +2 -1
  158. package/recipes/webhook-receiver/urlcode.yaml +7 -0
  159. package/schemas/recipe.schema.json +4 -3
  160. package/schemas/urlcode.schema.json +14 -41
  161. package/skills/urlcode/SKILL.md +29 -8
  162. package/starters/default/AGENTS.md +18 -18
  163. package/starters/default/urlcode.yaml +0 -1
  164. package/dist/link-api.js +0 -136
  165. package/dist/link-cli.js +0 -141
  166. package/dist/link-events.js +0 -76
  167. package/dist/link-records.js +0 -31
  168. package/dist/link-store-worker.js +0 -150
  169. package/dist/link-store.js +0 -250
  170. package/dist/management-policy.js +0 -40
  171. package/dist/sqlite-version.js +0 -6
  172. package/dist/types/link-api.d.ts +0 -30
  173. package/dist/types/link-cli.d.ts +0 -37
  174. package/dist/types/link-events.d.ts +0 -27
  175. package/dist/types/link-records.d.ts +0 -11
  176. package/dist/types/link-store-worker.d.ts +0 -1
  177. package/dist/types/link-store.d.ts +0 -130
  178. package/dist/types/management-policy.d.ts +0 -8
  179. package/dist/types/sqlite-version.d.ts +0 -1
  180. package/docs/DYNAMIC-LINKS.md +0 -61
  181. package/docs/MANAGEMENT-SECURITY.md +0 -82
  182. package/docs/links/cli.md +0 -110
  183. package/docs/links/limits.md +0 -175
  184. package/docs/links/management-api.md +0 -80
  185. package/docs/links/pools.md +0 -75
  186. package/docs/links/setup.md +0 -135
  187. package/docs/yaml/links.md +0 -30
  188. package/examples/live-links/README.md +0 -11
  189. package/examples/live-links/example.yaml +0 -21
  190. package/examples/live-links/tests/requests.json +0 -6
  191. package/examples/live-links/urlcode.yaml +0 -16
@@ -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.
@@ -37,7 +37,6 @@ Each page holds the recipes for one task; the section numbers continue across pa
37
37
  | [Pages, static folders and downloads](yaml/assets.md) | 10. Pages, static folders, downloads and MIME |
38
38
  | [Enable, disable and expire](yaml/conditions.md) | 11. Enable, disable and expire |
39
39
  | [Bindings, split files and tests](yaml/organization.md) | 12. Environment and secret references; 13. Split files and folders; 14. Assert inputs and outputs |
40
- | [Live short-link records](yaml/links.md) | 15. Live short-link records |
41
40
  | [Policies and profiles](yaml/policies.md) | 16. Hardened profile and per-route overrides |
42
41
  | [Site conventions](yaml/site.md) | 17. Site conventions |
43
42
 
@@ -56,5 +55,3 @@ Each page holds the recipes for one task; the section numbers continue across pa
56
55
  | Automatic hot updates in `serve` | Deploy/restart or use the embedding reload API deliberately |
57
56
  | “All examples are production-ready” | Validate your security, load and deployment requirements separately |
58
57
 
59
- Live-link recipes require `dynamicLinks: true` in the entry `urlcode.yaml`; see
60
- [live short-link records](yaml/links.md) and [dynamic-link opt-in](DYNAMIC-LINKS.md#explicit-project-opt-in).
@@ -20,6 +20,8 @@ schema-valid combinations activate successfully.
20
20
  | `routes.*.methods` | array | no | default: ["GET","HEAD"]; minItems: 1; uniqueItems: true |
21
21
  | `routes.*.methods[]` | string | no | enum: ["GET","HEAD","POST","PUT","PATCH","DELETE","OPTIONS"] |
22
22
  | `routes.*.enabled` | boolean | no | — |
23
+ | `routes.*.sandbox` | boolean | no | default: false |
24
+ | `routes.*.sandboxReason` | string | no | maxLength: 500 |
23
25
  | `routes.*.expires` | string | no | — |
24
26
  | `routes.*.description` | string | no | maxLength: 1024 |
25
27
  | `routes.*.parameters` | array | no | maxItems: 64 |
@@ -113,11 +115,6 @@ schema-valid combinations activate successfully.
113
115
  | `routes.*.middleware[] (option 2)` | object | no | unknown keys rejected |
114
116
  | `routes.*.middleware[] (option 2).source` | string | yes | maxLength: 1024 |
115
117
  | `routes.*.middleware[] (option 2).export` | string | no | pattern: "^[A-Za-z_][A-Za-z0-9_]*$" |
116
- | `routes.*.link` | object | no | unknown keys rejected |
117
- | `routes.*.link.collection` | string | yes | pattern: "^[A-Za-z][A-Za-z0-9_-]{0,63}$" |
118
- | `routes.*.link.code` | object | yes | unknown keys rejected |
119
- | `routes.*.link.code.from` | constant | yes | const: "path" |
120
- | `routes.*.link.code.name` | string | yes | pattern: "^[A-Za-z_][A-Za-z0-9_]*$" |
121
118
  | `routes.*.policies` | object | no | unknown keys rejected |
122
119
  | `routes.*.policies.profile` | string | no | pattern: "^[a-z][a-z0-9-]{0,63}$" |
123
120
  | `routes.*.policies.throttle` | one of the shapes below | no | — |
@@ -268,9 +265,22 @@ schema-valid combinations activate successfully.
268
265
  | `routes.*.auth (option 2).verified` | boolean | no | — |
269
266
  | `routes.*.auth (option 2).freshWithinSeconds` | integer | no | minimum: 1; maximum: 3600 |
270
267
  | `routes.*.auth (option 2).onDeny` | number / string | no | enum: [401,403,404,"sign-in"] |
268
+ | `routes.*.cache` | object | no | unknown keys rejected |
269
+ | `routes.*.cache.strategy` | string | no | enum: ["no-store","revalidate","public","immutable","swr","sie","micro","cdn-only","private"] |
270
+ | `routes.*.cache.maxAge` | integer | no | minimum: 0; maximum: 31536000 |
271
+ | `routes.*.cache.staleWhileRevalidate` | integer | no | minimum: 0; maximum: 31536000 |
272
+ | `routes.*.cache.staleIfError` | integer | no | minimum: 0; maximum: 31536000 |
273
+ | `routes.*.cache.cdnMaxAge` | integer | no | minimum: 0; maximum: 31536000 |
274
+ | `routes.*.cache.originTtl` | integer | no | minimum: 0; maximum: 86400 |
275
+ | `routes.*.cache.vary` | array | no | maxItems: 8; uniqueItems: true |
276
+ | `routes.*.cache.vary[]` | string | no | minLength: 1; maxLength: 128 |
277
+ | `routes.*.cache.statuses` | array | no | maxItems: 16; uniqueItems: true |
278
+ | `routes.*.cache.statuses[]` | integer | no | minimum: 200; maximum: 599 |
279
+ | `routes.*.cache.maxBytes` | integer | no | minimum: 0; maximum: 16777216 |
280
+ | `routes.*.cache.maxEntries` | integer | no | minimum: 1; maximum: 1000000 |
281
+ | `routes.*.cache.force` | boolean | no | default: false |
271
282
  | `includes` | array | no | maxItems: 256; uniqueItems: true |
272
283
  | `includes[]` | string | no | maxLength: 1024 |
273
- | `dynamicLinks` | boolean | no | default: false |
274
284
  | `policies` | object | no | unknown keys rejected |
275
285
  | `policies.profile` | string | no | pattern: "^[a-z][a-z0-9-]{0,63}$" |
276
286
  | `policies.throttle` | one of the shapes below | no | — |
@@ -17,6 +17,19 @@ routes:
17
17
  cache: { strategy: swr, maxAge: 30, staleWhileRevalidate: 300, vary: [Accept-Language] }
18
18
  ```
19
19
 
20
+ A route may also write `cache` directly, next to `function` or `middleware`,
21
+ instead of nesting it under `policies`. The compiler expands it to
22
+ `policies.cache` before anything else reads the project ([policies](../POLICIES.md)),
23
+ so it is pure sugar over the form above and is refused alongside a direct
24
+ `policies.cache` on the same route:
25
+
26
+ ```yaml
27
+ routes:
28
+ /feed:
29
+ function: { source: feed.mjs }
30
+ cache: { strategy: swr, maxAge: 30 } # same as policies: { cache: {...} }
31
+ ```
32
+
20
33
  ## Strategies
21
34
 
22
35
  | `strategy` | Emitted headers | Origin memory cache | Typical use |
@@ -3,7 +3,7 @@ description: Pages, a static directory, a download and one function next to them
3
3
  tags: [assets, static, page, download, html, files, cache, snapshot, function]
4
4
  complexity: intermediate
5
5
  capabilities: [bindings, download, enabled, function, methods, page, parameters, redirect, static]
6
- targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
6
+ targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused, static: refused}
7
7
  routes: 5
8
8
  files: [urlcode.yaml, functions/hello.mjs, public/about.html, public/guide.txt, public/assets/example.txt, tests/requests.json, README.md]
9
9
  tests:
@@ -3,7 +3,7 @@ description: Native-handler project served by an AWS Lambda Function URL through
3
3
  tags: [aws, lambda, serverless, deploy, adapter, native, page, static, redirect, respond, sam]
4
4
  complexity: intermediate
5
5
  capabilities: [enabled, methods, page, parameters, redirect, respond, static]
6
- targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: refused}
6
+ targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: refused, static: refused}
7
7
  routes: 5
8
8
  services:
9
9
  - name: AWS Lambda
@@ -3,7 +3,7 @@ description: Declarative redirect and respond project compiled into a Cloudflare
3
3
  tags: [cloudflare, workers, wrangler, compile, deploy, serverless, redirect, respond, native]
4
4
  complexity: intermediate
5
5
  capabilities: [enabled, methods, parameters, redirect, respond, response.headers]
6
- targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: compatible}
6
+ targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: compatible, static: refused}
7
7
  routes: 4
8
8
  services:
9
9
  - name: Cloudflare Workers
@@ -3,7 +3,7 @@ description: Exact query, header, cookie, host and method conditions choosing be
3
3
  tags: [conditions, conditional, match, query, header, cookie, host, method, redirect, respond, disjoint]
4
4
  complexity: intermediate
5
5
  capabilities: [conditional, conditions, enabled, methods, redirect, respond]
6
- targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: refused}
6
+ targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: refused, static: refused}
7
7
  routes: 4
8
8
  grants:
9
9
  - kind: origin
@@ -3,7 +3,7 @@ description: Forty runnable routes covering every handler, middleware, inputs, b
3
3
  tags: [cookbook, reference, routes, handlers, middleware, policies, site, includes, parameters, cookies, methods, expiry, assets, everything]
4
4
  complexity: advanced
5
5
  capabilities: [bindings, download, enabled, expires, function, methods, middleware, page, parameters, policies.agents, policies.cache, policies.security, policies.throttle, redirect, request.body, respond, response.headers, static]
6
- targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
6
+ targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused, static: refused}
7
7
  routes: 40
8
8
  files: [urlcode.yaml, routes/code.yaml, routes/redirects.yaml, routes/responses.yaml, routes/files.yaml, routes/policies.yaml, routes/middleware.yaml, route-index.json, tests/requests.json, README.md]
9
9
  tests:
@@ -1,5 +1,9 @@
1
1
  // Sticky A/B bucketing through a cookie, as on Vercel or Cloudflare edge examples.
2
2
  // Bucket b gets a different destination; everyone keeps their bucket for a week.
3
+ // `Response.redirect()`'s headers are immutable (per the Fetch standard a
4
+ // trusted route's real `Response` enforces this, unlike the sandbox's guest
5
+ // API), so build that branch's headers up front instead of mutating the
6
+ // result afterward.
3
7
  function cookie(request, name) {
4
8
  for (const part of (request.headers.get('cookie') || '').split(';')) {
5
9
  const [key, ...rest] = part.trim().split('=');
@@ -12,8 +16,14 @@ export default async function bucket(request, context, next) {
12
16
  const fresh = assigned !== 'a' && assigned !== 'b';
13
17
  if (fresh) assigned = Math.random() < 0.5 ? 'a' : 'b';
14
18
  context.state.bucket = assigned;
15
- const response = assigned === 'b' ? Response.redirect(context.env.VARIANT_URL, 302) : await next();
19
+ const setCookie = 'bucket=' + assigned + '; Path=/; Max-Age=604800; SameSite=Lax';
20
+ if (assigned === 'b') {
21
+ const headers = new Headers({ location: context.env.VARIANT_URL, vary: 'cookie' });
22
+ if (fresh) headers.append('set-cookie', setCookie);
23
+ return new Response(null, { status: 302, headers });
24
+ }
25
+ const response = await next();
16
26
  response.headers.set('vary', 'cookie');
17
- if (fresh) response.headers.append('set-cookie', 'bucket=' + assigned + '; Path=/; Max-Age=604800; SameSite=Lax');
27
+ if (fresh) response.headers.append('set-cookie', setCookie);
18
28
  return response;
19
29
  }
@@ -1,5 +1,8 @@
1
1
  // Language redirect from Accept-Language, like Next.js i18n middleware. Only
2
2
  // languages listed in LOCALES are chosen; the native redirect is the default.
3
+ // `Response.redirect()`'s headers are immutable (a trusted route's real
4
+ // `Response` enforces the Fetch standard here, unlike the sandbox's guest
5
+ // API), so that branch builds its own `Response` with headers up front.
3
6
  export default async function locale(request, context, next) {
4
7
  const supported = (context.env.LOCALES || '').split(/\s+/).filter(Boolean);
5
8
  const ranked = (request.headers.get('accept-language') || '').split(',').map((part, index) => {
@@ -8,9 +11,10 @@ export default async function locale(request, context, next) {
8
11
  return {lang: tag.trim().toLowerCase().split('-')[0], q: q ? Number(q.slice(2)) || 0 : 1, index};
9
12
  }).filter(p => p.q > 0).sort((a, b) => b.q - a.q || a.index - b.index);
10
13
  const chosen = ranked.find(p => supported.includes(p.lang))?.lang;
11
- const response = chosen && chosen !== supported[0]
12
- ? Response.redirect(context.env.SITE + '/' + chosen + '/welcome', 302)
13
- : await next();
14
+ if (chosen && chosen !== supported[0]) {
15
+ return new Response(null, { status: 302, headers: { location: context.env.SITE + '/' + chosen + '/welcome', vary: 'accept-language' } });
16
+ }
17
+ const response = await next();
14
18
  response.headers.set('vary', 'accept-language');
15
19
  return response;
16
20
  }
@@ -3,7 +3,7 @@ description: A bounded HTTPS proxy route and a route with a best-effort signal,
3
3
  tags: [egress, proxy, signals, outbound, https, grant, policy, operator, webhook]
4
4
  complexity: advanced
5
5
  capabilities: [enabled, methods, parameters, proxy, respond, signals]
6
- targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
6
+ targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused, static: refused}
7
7
  routes: 2
8
8
  grants:
9
9
  - kind: proxy
@@ -3,7 +3,7 @@ description: Two declared operator extensions, one mount and two protected route
3
3
  tags: [extensions, auth, operator, mount, protected, policies.extensions, registry, revision]
4
4
  complexity: advanced
5
5
  capabilities: [enabled, extension, methods, policies.extensions, respond]
6
- targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: refused}
6
+ targets: {self-hosted: conditional, aws: conditional, vercel: conditional, cloudflare: refused, static: refused}
7
7
  routes: 3
8
8
  services:
9
9
  - name: operator registry
@@ -15,8 +15,8 @@ node src/cli.ts audit --project /absolute/out --expect-routes 3
15
15
  ```
16
16
 
17
17
  The same three URLs answer identically before and after. The difference is what
18
- runs to serve them: the source project executes a QuickJS/WASM guest per request,
19
- the generated project reads a prevalidated byte buffer.
18
+ runs to serve them: the source project runs function and middleware code per
19
+ request, the generated project reads a prevalidated byte buffer.
20
20
 
21
21
  | | Source project | Generated project |
22
22
  |---|---|---|
@@ -3,7 +3,7 @@ description: Pages rendered by a function and a template middleware, then preren
3
3
  tags: [prerender, build, static, pages, template, function, middleware, html, render]
4
4
  complexity: advanced
5
5
  capabilities: [enabled, function, methods, middleware]
6
- targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused}
6
+ targets: {self-hosted: compatible, aws: refused, vercel: refused, cloudflare: refused, static: refused}
7
7
  routes: 3
8
8
  files: [urlcode.yaml, functions/page.mjs, middleware/template.mjs, prerender.mjs, tests/requests.json, README.md]
9
9
  tests:
@@ -3,7 +3,7 @@ description: Synthetic redirect and respond fixture deployed to a disposable env
3
3
  tags: [provider, conformance, verify, deployment, fixture, aws, vercel, cloudflare, self-hosted, probes]
4
4
  complexity: intermediate
5
5
  capabilities: [enabled, methods, parameters, redirect, request.body, respond, response.headers]
6
- targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: compatible}
6
+ targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: compatible, static: refused}
7
7
  routes: 5
8
8
  files: [urlcode.yaml, README.md]
9
9
  tests:
@@ -3,7 +3,7 @@ description: Native-handler project deployed as a Vercel Node function through t
3
3
  tags: [vercel, serverless, deploy, adapter, native, page, static, redirect, respond]
4
4
  complexity: intermediate
5
5
  capabilities: [enabled, methods, page, parameters, redirect, respond, static]
6
- targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: refused}
6
+ targets: {self-hosted: compatible, aws: compatible, vercel: compatible, cloudflare: refused, static: refused}
7
7
  routes: 5
8
8
  services:
9
9
  - name: Vercel