@jimhoyd/urlcode 0.4.0-alpha.1 → 0.4.0-alpha.3
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 +36 -22
- package/.claude/skills/urlcode-operations/SKILL.md +16 -22
- package/.claude-plugin/marketplace.json +1 -1
- package/CONTRIBUTING.md +20 -16
- package/README.md +83 -80
- package/ROADMAP.md +61 -331
- package/SECURITY.md +23 -8
- package/dist/BUILD-MANIFEST.json +42 -47
- package/dist/agents-guide.js +18 -18
- package/dist/build-static.js +136 -0
- package/dist/capabilities.js +85 -31
- package/dist/capability-query.js +0 -1
- package/dist/cli.js +24 -32
- 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 +7 -5
- package/dist/explain.js +10 -5
- 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 +11 -7
- package/dist/mcp-authoring.js +2 -2
- package/dist/mcp.js +1 -1
- package/dist/observability.js +1 -21
- package/dist/policies/cache.js +8 -3
- package/dist/policies.js +3 -1
- package/dist/policy.js +28 -9
- package/dist/prerender.js +4 -0
- package/dist/project-tests.js +3 -3
- package/dist/readiness.js +35 -9
- package/dist/route-diff.js +12 -5
- 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/explain.d.ts +6 -0
- 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 +5 -3
- package/dist/types/observability.d.ts +1 -14
- package/dist/types/project-tests.d.ts +1 -2
- package/dist/types/readiness.d.ts +12 -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 +22 -8
- package/dist/types/verify-deployment.d.ts +2 -2
- package/dist/types.js +25 -5
- package/dist/typescript-authoring.js +67 -18
- package/dist/verify-deployment.js +3 -3
- package/docs/AI-AUTHORING.md +109 -10
- package/docs/ASSETS.md +2 -1
- package/docs/AUTH-BACKUP.md +32 -0
- package/docs/AWS.md +4 -4
- package/docs/BEST-PRACTICES.md +17 -8
- package/docs/CAPABILITIES.md +30 -17
- package/docs/CAPACITY.md +128 -31
- package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
- package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
- package/docs/CI.md +8 -3
- package/docs/CLOUDFLARE.md +1 -2
- package/docs/CODEBASE-AUDIT-2026-09-20.md +278 -0
- package/docs/COMPLIANCE.md +6 -9
- package/docs/DEPLOYMENT-CHECKS.md +1 -1
- package/docs/DEVELOPMENT-PIPELINE.md +181 -0
- package/docs/EXTENSIONS.md +193 -11
- package/docs/FRAMEWORK.md +53 -33
- package/docs/FUNCTION-SECURITY.md +173 -32
- package/docs/INSTALL.md +0 -5
- package/docs/LOAD-TESTING.md +4 -4
- package/docs/LOCAL-DEVELOPMENT.md +3 -0
- package/docs/MIDDLEWARE.md +27 -16
- package/docs/MONITORING.md +2 -19
- package/docs/OBSERVABILITY.md +7 -16
- package/docs/OPEN-DECISIONS.md +184 -0
- package/docs/OPERATIONAL-PROOF.md +26 -30
- package/docs/OPERATIONS.md +23 -32
- package/docs/POLICIES.md +27 -8
- package/docs/PRERENDER.md +25 -13
- package/docs/PROJECT-DIRECTION.md +35 -10
- package/docs/READINESS.md +6 -2
- package/docs/README.md +20 -41
- package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
- package/docs/RELEASE-READINESS.md +50 -30
- package/docs/RELEASE-SECURITY.md +72 -86
- package/docs/RESILIENCE.md +16 -15
- package/docs/ROUTING.md +7 -10
- package/docs/SANDBOX-REVIEW.md +19 -6
- package/docs/SCAFFOLDING.md +0 -2
- package/docs/SECURITY-AUDIT.md +42 -2
- package/docs/SPECIFICATION.md +63 -27
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +288 -0
- package/docs/SPIKE-BUSINESS-SUITE.md +16 -0
- package/docs/SPIKE-CORE-LAYERING.md +368 -0
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +211 -0
- package/docs/SPIKE-LAMBDA-COMPILE.md +12 -2
- package/docs/STARTERS.md +1 -1
- package/docs/STATIC.md +105 -0
- package/docs/TOOLING.md +17 -12
- package/docs/TUNNELS.md +0 -3
- package/docs/TYPESCRIPT-AUTHORING.md +32 -12
- package/docs/TYPESCRIPT.md +25 -4
- package/docs/VERCEL.md +4 -5
- package/docs/VERSION-ALIGNMENT.md +46 -0
- package/docs/YAML-GUIDE.md +0 -3
- package/docs/YAML-REFERENCE.md +16 -6
- package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
- package/docs/{MANAGEMENT-SECURITY.md → archive/2026-09-19/MANAGEMENT-SECURITY.md} +20 -0
- package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +26 -8
- package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +274 -43
- package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
- package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
- package/docs/archive/2026-09-19/ROADMAP.md +386 -0
- package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +12 -2
- package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +28 -12
- package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +776 -0
- package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +18 -2
- package/docs/archive/README.md +27 -0
- package/docs/policies/agents.md +1 -1
- package/docs/policies/cache.md +13 -0
- package/docs/policies/compression.md +3 -2
- package/docs/policies/security.md +3 -2
- package/examples/assets/example.yaml +2 -2
- 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/cookbook/route-index.json +1 -1
- package/examples/cookbook/routes/middleware.yaml +1 -1
- package/examples/egress/example.yaml +1 -1
- package/examples/extensions/example.yaml +1 -1
- package/examples/prerender/README.md +14 -6
- package/examples/prerender/example.yaml +1 -1
- package/examples/prerender/functions/page.mjs +4 -2
- package/examples/prerender/middleware/template.mjs +1 -1
- package/examples/prerender/prerender.mjs +1 -1
- package/examples/prerender/urlcode.yaml +8 -4
- package/examples/provider-conformance/example.yaml +1 -1
- package/examples/vercel/example.yaml +1 -1
- package/llms-full.txt +686 -239
- package/llms.txt +27 -15
- package/package.json +32 -5
- package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +36 -22
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +16 -22
- package/recipes/authenticated-json-api/README.md +4 -3
- package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
- package/recipes/authenticated-json-api/recipe.yaml +2 -2
- package/recipes/contact-form/functions/contact.mjs +2 -1
- package/recipes/contact-form/recipe.yaml +3 -3
- package/recipes/cors-api/README.md +2 -2
- package/recipes/cors-api/recipe.yaml +2 -2
- package/recipes/health-page/README.md +1 -1
- package/recipes/health-page/recipe.yaml +1 -1
- package/recipes/json-api/README.md +1 -1
- package/recipes/json-api/recipe.yaml +4 -4
- package/recipes/middleware/README.md +8 -4
- 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/README.md +1 -1
- package/recipes/protected-download/recipe.yaml +2 -2
- package/recipes/redirect/recipe.yaml +1 -1
- package/recipes/static-plus-api/README.md +2 -2
- package/recipes/static-plus-api/public/index.html +1 -1
- package/recipes/static-plus-api/recipe.yaml +2 -2
- package/recipes/static-plus-api/urlcode.yaml +1 -1
- package/recipes/typescript/README.md +2 -1
- package/recipes/typescript/recipe.yaml +5 -5
- 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 +32 -11
- 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/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
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
# Historical record
|
|
2
|
+
|
|
3
|
+
Archived 2026-09-19. This records an earlier implementation or proposal, not
|
|
4
|
+
current instructions. See the [current roadmap](../../../ROADMAP.md),
|
|
5
|
+
[current contract](../../SPECIFICATION.md) and [open decisions](../../OPEN-DECISIONS.md).
|
|
6
|
+
Remaining acceptance work is not declared complete by archiving this record.
|
|
7
|
+
|
|
8
|
+
<!-- trust-model-prose: historical-file -->
|
|
9
|
+
<!-- guidance-claims: ignore-file -->
|
|
10
|
+
|
|
1
11
|
# Usability review: URLCode against the tools people reach for instead
|
|
2
12
|
|
|
3
13
|
Status: an honest assessment written 2026-09-18 against the source in the four
|
|
@@ -6,10 +16,16 @@ benchmark. Its purpose is to name the friction a first-time user or an AI
|
|
|
6
16
|
agent meets, compare it with what competing tools do, and rank the changes
|
|
7
17
|
that would matter most. It does not change any contract by itself.
|
|
8
18
|
|
|
19
|
+
> **Update:** written the day before `0.4.0-alpha.2` reversed the default
|
|
20
|
+
> execution mode. Where the table below credits functions and middleware with
|
|
21
|
+
> isolation, that now describes a route declaring `sandbox: true`; the default
|
|
22
|
+
> is trusted, in-process execution with full Node, npm and filesystem access.
|
|
23
|
+
> See [SPIKE-DEFAULT-TRUST-MODEL.md](../../SPIKE-DEFAULT-TRUST-MODEL.md).
|
|
24
|
+
|
|
9
25
|
## 1. Who the competitors are, by rung
|
|
10
26
|
|
|
11
27
|
URLCode is not one product; it competes with a different tool on each rung
|
|
12
|
-
of [the ladder](FRAMEWORK.md#the-ladder).
|
|
28
|
+
of [the ladder](../../FRAMEWORK.md#the-ladder).
|
|
13
29
|
|
|
14
30
|
| Rung | What people use today | Where URLCode is better | Where it is worse today |
|
|
15
31
|
|---|---|---|---|
|
|
@@ -81,7 +97,7 @@ whether it touches a contract.
|
|
|
81
97
|
"add accounts" row except the revision pin existed because they were
|
|
82
98
|
unpublished. This was a decision, not code; the repositories already had
|
|
83
99
|
the release checks. No contract change. Done 2026-09-18: all three are on
|
|
84
|
-
npm as `0.1.0-alpha.
|
|
100
|
+
npm as `0.1.0-alpha.x` (review still pending, issue 58).
|
|
85
101
|
2. **`urlcode init --with auth,admin,ui`.** Delegate to each installed
|
|
86
102
|
package's existing scaffold; write one host file and one README. No contract
|
|
87
103
|
change; a CLI addition in core that calls into optional peers.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Historical records
|
|
2
|
+
|
|
3
|
+
These documents retain earlier reasoning and evidence. They are not authoring
|
|
4
|
+
instructions or an active backlog. Read [the current roadmap](../../ROADMAP.md),
|
|
5
|
+
[open decisions](../OPEN-DECISIONS.md) and [implemented contract](../SPECIFICATION.md)
|
|
6
|
+
first. An old example can describe a removed feature or an earlier trust model.
|
|
7
|
+
|
|
8
|
+
Archived on 2026-09-19 after checking the current code:
|
|
9
|
+
|
|
10
|
+
| Record | Why archived | Current owner of remaining work |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| [Monorepo spike](2026-09-19/SPIKE-MONOREPO.md) | The migration it proposed is complete: ui, auth and admin are workspace packages and all three have been released from this repository | [Development pipeline](../DEVELOPMENT-PIPELINE.md) and [release security](../RELEASE-SECURITY.md) |
|
|
13
|
+
| [Previous roadmap](2026-09-19/ROADMAP.md) | Release chronology mixed with current planning | Current roadmap and release readiness |
|
|
14
|
+
| [Detailed phases](2026-09-19/NEXT-STEPS.md) | Most authoring/tooling/UI work delivered; repeated stale backlog | Current roadmap, issues and open decisions |
|
|
15
|
+
| [Previous decisions](2026-09-19/OPEN-DECISIONS.md) | Mixed settled releases, obsolete repository counts and live questions | Current open decisions |
|
|
16
|
+
| [Next-phase implementation](2026-09-19/NEXT-PHASE-PLAN.md) | Delivered source snapshot for alpha.1 | Specification and provider evidence |
|
|
17
|
+
| [Extension delivery sequence](2026-09-19/EXTENSION-IMPLEMENTATION.md) | Registration, auth/admin and UI now implemented | Package status files and core issue 58 |
|
|
18
|
+
| [Policy/extension exploration](2026-09-19/SPIKE-EXTENSIONS.md) | Main design delivered; obsolete link APIs | Policies, plugins and extensions guides |
|
|
19
|
+
| [Extension model review](2026-09-19/SPIKE-EXTENSION-MODEL.md) | Historical design rationale | Current extension contract |
|
|
20
|
+
| [Usability review](2026-09-19/USABILITY-REVIEW.md) | Predates trusted execution and retired packages | New benchmark observations |
|
|
21
|
+
| [Management security](2026-09-19/MANAGEMENT-SECURITY.md) | Describes the removed core link-management API | No current core replacement |
|
|
22
|
+
|
|
23
|
+
Open proposals stay outside the archive with their status stated at the top.
|
|
24
|
+
The accepted trust-by-default decision remains directly linked from the current
|
|
25
|
+
security contract; its `SPIKE-` filename is retained for existing references.
|
|
26
|
+
Security audits and operational evidence remain available: “implemented” never
|
|
27
|
+
means their unperformed review, deployment or recovery work is complete.
|
package/docs/policies/agents.md
CHANGED
|
@@ -65,7 +65,7 @@ Matching is string matching. A client can claim any `User-Agent`, and the
|
|
|
65
65
|
genuine search crawlers publish the way to check a claim (reverse DNS for
|
|
66
66
|
Googlebot, bingbot and Applebot; the IETF `web-bot-auth` HTTP Message
|
|
67
67
|
Signature drafts for newer agents). That verification needs network calls and
|
|
68
|
-
vendor data, so it belongs in a [host plugin](../SPIKE-EXTENSIONS.md) that
|
|
68
|
+
vendor data, so it belongs in a [host plugin](../archive/2026-09-19/SPIKE-EXTENSIONS.md) that
|
|
69
69
|
runs after this policy, not in the runtime.
|
|
70
70
|
|
|
71
71
|
## Bundled lists
|
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 |
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Compression policy
|
|
2
2
|
|
|
3
3
|
`policies.compression` negotiates a content coding (RFC 9110 §12.5.3) on the
|
|
4
|
-
host, outside
|
|
5
|
-
bodies, function results, asset
|
|
4
|
+
host, outside function/middleware execution — trusted or sandboxed alike — for
|
|
5
|
+
every result a route produces: `respond` bodies, function results, asset
|
|
6
|
+
responses, cache hits and early denials.
|
|
6
7
|
It runs last in the response phase, after the cache store and the security
|
|
7
8
|
headers, so every header it reads is final. Asset snapshots are compressed
|
|
8
9
|
once at load and served by reference (the NGINX `gzip_static` / Caddy
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Security headers policy
|
|
2
2
|
|
|
3
|
-
`policies.security` adds response security headers on the host, outside
|
|
4
|
-
|
|
3
|
+
`policies.security` adds response security headers on the host, outside
|
|
4
|
+
function/middleware execution -- trusted or sandboxed alike -- to every result
|
|
5
|
+
a route produces: redirects, `respond` bodies,
|
|
5
6
|
function results, asset responses, cache hits and early denials from the
|
|
6
7
|
`agents` and `throttle` policies. Values are the OWASP Secure Headers
|
|
7
8
|
Project best-practice recommendations (OSHP 2024 best practices,
|
|
@@ -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:
|
|
@@ -14,4 +14,4 @@ tests:
|
|
|
14
14
|
- node src/cli.ts audit --project examples/assets --expect-routes 5
|
|
15
15
|
behavior:
|
|
16
16
|
- native page, static and download routes serve snapshotted files with explicit caching
|
|
17
|
-
- one
|
|
17
|
+
- one trusted function shows the two kinds of route side by side
|
|
@@ -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
|
}
|
|
@@ -12,7 +12,7 @@ routes:
|
|
|
12
12
|
export: bearer
|
|
13
13
|
respond: {json: {private: true}}
|
|
14
14
|
/admin/panel:
|
|
15
|
-
description: HTTP Basic authentication decoded
|
|
15
|
+
description: HTTP Basic authentication decoded in trusted middleware
|
|
16
16
|
env:
|
|
17
17
|
ADMIN_USER: {value: admin}
|
|
18
18
|
ADMIN_PASSWORD: {value: cookbook-password}
|
|
@@ -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
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
# Prerender a dynamic project into a native static one
|
|
2
2
|
|
|
3
3
|
Three pages rendered by a function and one shared template middleware, then
|
|
4
|
-
rendered once at build time into a project that serves the same bytes with
|
|
5
|
-
|
|
4
|
+
rendered once at build time into a project that serves the same bytes with no
|
|
5
|
+
code on the request path at all.
|
|
6
|
+
|
|
7
|
+
The source routes run trusted and in-process, which is the default for
|
|
8
|
+
`function`/`middleware` and the right choice here: the page code is reviewed
|
|
9
|
+
first-party code that reads nothing but the literal arguments in
|
|
10
|
+
`urlcode.yaml`. Prerendering does not depend on that — a route declaring
|
|
11
|
+
`sandbox: true` prerenders the same way — and either way the generated project
|
|
12
|
+
runs no code at all.
|
|
6
13
|
|
|
7
14
|
From the runtime checkout:
|
|
8
15
|
|
|
@@ -15,17 +22,18 @@ node src/cli.ts audit --project /absolute/out --expect-routes 3
|
|
|
15
22
|
```
|
|
16
23
|
|
|
17
24
|
The same three URLs answer identically before and after. The difference is what
|
|
18
|
-
runs to serve them: the source project executes
|
|
19
|
-
the generated project reads a prevalidated byte buffer
|
|
25
|
+
runs to serve them: the source project executes the function and its middleware
|
|
26
|
+
per request, the generated project reads a prevalidated byte buffer and runs no
|
|
27
|
+
project code at all.
|
|
20
28
|
|
|
21
29
|
| | Source project | Generated project |
|
|
22
30
|
|---|---|---|
|
|
23
31
|
| Handlers | 3 × `function` | 3 × `page` |
|
|
24
32
|
| Middleware | shared template | none |
|
|
25
|
-
|
|
|
33
|
+
| Project code per request | function + middleware | none |
|
|
26
34
|
| Content | reviewed literals in YAML | rendered HTML files |
|
|
27
35
|
|
|
28
|
-
`prerender.mjs` is operator build tooling
|
|
36
|
+
`prerender.mjs` is operator build tooling, not a route handler. The
|
|
29
37
|
orchestration lives in the runtime's build helper:
|
|
30
38
|
|
|
31
39
|
```js
|
|
@@ -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:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// One page's content, supplied as reviewed literal arguments in urlcode.yaml.
|
|
2
|
-
// This runs in the
|
|
3
|
-
//
|
|
2
|
+
// This runs trusted and in-process -- the default for a function route -- at
|
|
3
|
+
// build time exactly as it would at request time. It reads nothing but its
|
|
4
|
+
// own args, so there is nothing here for `sandbox: true` to isolate; adding it
|
|
5
|
+
// would cost worker-pool capacity for no gain.
|
|
4
6
|
export function page(request, {args}) {
|
|
5
7
|
return new Response(args.body, {headers: {'content-type': 'text/html; charset=utf-8'}});
|
|
6
8
|
}
|
|
@@ -22,7 +22,7 @@ export async function template(request, context, next) {
|
|
|
22
22
|
<h1>${title}</h1>
|
|
23
23
|
${content}
|
|
24
24
|
</main>
|
|
25
|
-
<footer>Prerendered at build time. No
|
|
25
|
+
<footer>Prerendered at build time. No project code runs to serve this page.</footer>
|
|
26
26
|
</body>
|
|
27
27
|
</html>
|
|
28
28
|
`, {status: response.status, headers: {'content-type': 'text/html; charset=utf-8'}});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Prerender recipe: render this project's function/middleware routes once, at
|
|
3
3
|
// build time, into a native page project that serves the same bytes with no
|
|
4
|
-
//
|
|
4
|
+
// project code on the request path.
|
|
5
5
|
//
|
|
6
6
|
// The orchestration that is easy to get wrong — runtime lifecycle, response
|
|
7
7
|
// validation, budgets, byte fidelity and output-path safety — lives in the
|
|
@@ -2,6 +2,10 @@ version: "1"
|
|
|
2
2
|
# The dynamic source of the site. Every page is a function response wrapped by
|
|
3
3
|
# one shared template middleware, so `dev` serves it live while you edit. The
|
|
4
4
|
# prerender recipe renders these routes once and emits a native page project.
|
|
5
|
+
# No route declares `sandbox: true`: this is reviewed first-party code taking
|
|
6
|
+
# literal arguments from this file, which is the trusted default's case, not an
|
|
7
|
+
# exception to it (docs/AI-AUTHORING.md, "Deciding when a route needs
|
|
8
|
+
# `sandbox: true`").
|
|
5
9
|
routes:
|
|
6
10
|
/:
|
|
7
11
|
description: Home page rendered by a function and the shared template
|
|
@@ -16,7 +20,7 @@ routes:
|
|
|
16
20
|
body: |
|
|
17
21
|
<p>This page is generated by a function and wrapped by shared template
|
|
18
22
|
middleware. Running the prerender recipe turns it into a native page
|
|
19
|
-
route that serves the same bytes with no
|
|
23
|
+
route that serves the same bytes with no code on the request path.</p>
|
|
20
24
|
<ul>
|
|
21
25
|
<li><a href="/guide">Guide</a></li>
|
|
22
26
|
<li><a href="/about">About</a></li>
|
|
@@ -32,9 +36,9 @@ routes:
|
|
|
32
36
|
args:
|
|
33
37
|
title: Guide
|
|
34
38
|
body: |
|
|
35
|
-
<p>Content is reviewed literal input in YAML
|
|
36
|
-
|
|
37
|
-
content before rendering.</p>
|
|
39
|
+
<p>Content is reviewed literal input in YAML rather than anything this
|
|
40
|
+
code goes and reads, so a larger site generates this project from
|
|
41
|
+
host-prepared content before rendering.</p>
|
|
38
42
|
/about:
|
|
39
43
|
description: A third page proving the template is shared, not copied
|
|
40
44
|
middleware:
|
|
@@ -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
|