@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.
- package/.claude/skills/urlcode-authoring/SKILL.md +24 -8
- package/.claude/skills/urlcode-operations/SKILL.md +10 -16
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +75 -62
- package/ROADMAP.md +61 -16
- package/SECURITY.md +23 -8
- package/dist/BUILD-MANIFEST.json +38 -43
- package/dist/agents-guide.js +18 -18
- package/dist/build-static.js +134 -0
- package/dist/capabilities.js +85 -31
- package/dist/capability-query.js +0 -1
- package/dist/cli.js +23 -31
- package/dist/compliance-rules/baseline.js +2 -10
- package/dist/compliance-rules/privacy.js +5 -16
- package/dist/compliance-rules/shared.js +0 -2
- package/dist/compliance.js +6 -8
- package/dist/config.js +15 -8
- package/dist/context.js +9 -10
- package/dist/examples.js +2 -2
- package/dist/explain-cli.js +3 -3
- package/dist/explain.js +5 -6
- package/dist/extensions.js +61 -2
- package/dist/function-sources.js +34 -2
- package/dist/function-worker.js +3 -1
- package/dist/functions.js +84 -13
- package/dist/guest-api.js +29 -3
- package/dist/index.js +3 -6
- package/dist/manifest.js +5 -7
- package/dist/mcp.js +1 -1
- package/dist/observability.js +1 -21
- package/dist/policies/cache.js +8 -3
- package/dist/policy.js +28 -9
- package/dist/project-tests.js +3 -3
- package/dist/readiness.js +32 -9
- package/dist/router.js +5 -7
- package/dist/runtime.js +77 -58
- package/dist/sandbox.js +48 -0
- package/dist/scaffold.js +0 -0
- package/dist/scripts/operational-drills.js +12 -54
- package/dist/server.js +3 -29
- package/dist/tooling.js +1 -1
- package/dist/trusted-functions.js +210 -0
- package/dist/types/build-static.d.ts +43 -0
- package/dist/types/capabilities.d.ts +14 -5
- package/dist/types/compliance-rules/shared.d.ts +0 -2
- package/dist/types/compliance.d.ts +0 -3
- package/dist/types/config.d.ts +2 -1
- package/dist/types/context.d.ts +2 -1
- package/dist/types/examples.d.ts +1 -1
- package/dist/types/extensions.d.ts +55 -0
- package/dist/types/function-sources.d.ts +4 -0
- package/dist/types/functions.d.ts +48 -5
- package/dist/types/guest-api.d.ts +1 -0
- package/dist/types/index.d.ts +3 -6
- package/dist/types/manifest.d.ts +1 -3
- package/dist/types/observability.d.ts +1 -14
- package/dist/types/project-tests.d.ts +1 -2
- package/dist/types/readiness.d.ts +10 -3
- package/dist/types/router.d.ts +2 -1
- package/dist/types/runtime.d.ts +0 -27
- package/dist/types/sandbox.d.ts +12 -0
- package/dist/types/scaffold.d.ts +0 -2
- package/dist/types/server.d.ts +1 -4
- package/dist/types/tooling.d.ts +3 -3
- package/dist/types/trusted-functions.d.ts +29 -0
- package/dist/types/types.d.ts +14 -7
- package/dist/types/verify-deployment.d.ts +2 -2
- package/dist/types.js +17 -4
- package/dist/typescript-authoring.js +55 -17
- package/dist/verify-deployment.js +3 -3
- package/docs/AI-AUTHORING.md +104 -9
- package/docs/AWS.md +4 -4
- package/docs/BEST-PRACTICES.md +3 -2
- package/docs/CAPABILITIES.md +29 -16
- package/docs/CAPACITY.md +128 -31
- package/docs/CLOUDFLARE.md +1 -2
- package/docs/COMPLIANCE.md +6 -9
- package/docs/DEPLOYMENT-CHECKS.md +1 -1
- package/docs/EXTENSIONS.md +183 -11
- package/docs/FRAMEWORK.md +35 -19
- package/docs/FUNCTION-SECURITY.md +129 -32
- package/docs/INSTALL.md +0 -5
- package/docs/LOAD-TESTING.md +4 -4
- package/docs/MIDDLEWARE.md +27 -16
- package/docs/MONITORING.md +2 -19
- package/docs/NEXT-PHASE-PLAN.md +12 -4
- package/docs/NEXT-STEPS.md +259 -40
- package/docs/OBSERVABILITY.md +7 -16
- package/docs/OPEN-DECISIONS.md +212 -0
- package/docs/OPERATIONAL-PROOF.md +26 -30
- package/docs/OPERATIONS.md +23 -32
- package/docs/POLICIES.md +15 -4
- package/docs/PRERENDER.md +2 -2
- package/docs/PROJECT-DIRECTION.md +34 -9
- package/docs/README.md +13 -11
- package/docs/RELEASE-READINESS.md +49 -29
- package/docs/RELEASE-SECURITY.md +89 -3
- package/docs/RESILIENCE.md +16 -15
- package/docs/ROUTING.md +8 -10
- package/docs/SANDBOX-REVIEW.md +19 -6
- package/docs/SCAFFOLDING.md +0 -2
- package/docs/SECURITY-AUDIT.md +40 -0
- package/docs/SPECIFICATION.md +60 -26
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +287 -0
- package/docs/SPIKE-BUSINESS-SUITE.md +8 -0
- package/docs/SPIKE-CORE-LAYERING.md +337 -0
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +209 -0
- package/docs/SPIKE-EXTENSION-MODEL.md +2 -2
- package/docs/SPIKE-EXTENSIONS.md +5 -0
- package/docs/SPIKE-LAMBDA-COMPILE.md +4 -2
- package/docs/SPIKE-MONOREPO.md +322 -0
- package/docs/STARTERS.md +1 -1
- package/docs/STATIC.md +94 -0
- package/docs/TOOLING.md +13 -9
- package/docs/TUNNELS.md +0 -3
- package/docs/TYPESCRIPT-AUTHORING.md +27 -12
- package/docs/TYPESCRIPT.md +25 -4
- package/docs/USABILITY-REVIEW.md +7 -1
- package/docs/VERCEL.md +4 -5
- package/docs/VERSION-ALIGNMENT.md +205 -0
- package/docs/YAML-GUIDE.md +0 -3
- package/docs/YAML-REFERENCE.md +16 -6
- package/docs/policies/cache.md +13 -0
- package/examples/assets/example.yaml +1 -1
- package/examples/aws/example.yaml +1 -1
- package/examples/cloudflare/example.yaml +1 -1
- package/examples/conditions/example.yaml +1 -1
- package/examples/cookbook/example.yaml +1 -1
- package/examples/cookbook/middleware/bucket.mjs +12 -2
- package/examples/cookbook/middleware/locale.mjs +7 -3
- package/examples/egress/example.yaml +1 -1
- package/examples/extensions/example.yaml +1 -1
- package/examples/prerender/README.md +2 -2
- package/examples/prerender/example.yaml +1 -1
- package/examples/provider-conformance/example.yaml +1 -1
- package/examples/vercel/example.yaml +1 -1
- package/llms-full.txt +594 -219
- package/llms.txt +23 -12
- package/package.json +10 -3
- package/packaging/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +24 -8
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +10 -16
- package/recipes/authenticated-json-api/recipe.yaml +1 -1
- package/recipes/contact-form/recipe.yaml +1 -1
- package/recipes/cors-api/recipe.yaml +1 -1
- package/recipes/health-page/recipe.yaml +1 -1
- package/recipes/json-api/recipe.yaml +1 -1
- package/recipes/middleware/middleware/bucket.mjs +12 -2
- package/recipes/middleware/middleware/locale.mjs +7 -3
- package/recipes/middleware/recipe.yaml +1 -1
- package/recipes/protected-download/recipe.yaml +1 -1
- package/recipes/redirect/recipe.yaml +1 -1
- package/recipes/static-plus-api/recipe.yaml +1 -1
- package/recipes/typescript/README.md +2 -1
- package/recipes/typescript/recipe.yaml +1 -1
- package/recipes/webhook-receiver/README.md +5 -1
- package/recipes/webhook-receiver/recipe.yaml +2 -1
- package/recipes/webhook-receiver/urlcode.yaml +7 -0
- package/schemas/recipe.schema.json +4 -3
- package/schemas/urlcode.schema.json +14 -41
- package/skills/urlcode/SKILL.md +29 -8
- package/starters/default/AGENTS.md +18 -18
- package/starters/default/urlcode.yaml +0 -1
- package/dist/link-api.js +0 -136
- package/dist/link-cli.js +0 -141
- package/dist/link-events.js +0 -76
- package/dist/link-records.js +0 -31
- package/dist/link-store-worker.js +0 -150
- package/dist/link-store.js +0 -250
- package/dist/management-policy.js +0 -40
- package/dist/sqlite-version.js +0 -6
- package/dist/types/link-api.d.ts +0 -30
- package/dist/types/link-cli.d.ts +0 -37
- package/dist/types/link-events.d.ts +0 -27
- package/dist/types/link-records.d.ts +0 -11
- package/dist/types/link-store-worker.d.ts +0 -1
- package/dist/types/link-store.d.ts +0 -130
- package/dist/types/management-policy.d.ts +0 -8
- package/dist/types/sqlite-version.d.ts +0 -1
- package/docs/DYNAMIC-LINKS.md +0 -61
- package/docs/MANAGEMENT-SECURITY.md +0 -82
- package/docs/links/cli.md +0 -110
- package/docs/links/limits.md +0 -175
- package/docs/links/management-api.md +0 -80
- package/docs/links/pools.md +0 -75
- package/docs/links/setup.md +0 -135
- package/docs/yaml/links.md +0 -30
- package/examples/live-links/README.md +0 -11
- package/examples/live-links/example.yaml +0 -21
- package/examples/live-links/tests/requests.json +0 -6
- 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.
|
package/docs/YAML-GUIDE.md
CHANGED
|
@@ -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).
|
package/docs/YAML-REFERENCE.md
CHANGED
|
@@ -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 | — |
|
package/docs/policies/cache.md
CHANGED
|
@@ -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
|
|
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',
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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:
|
|
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
|
|
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
|