@jimhoyd/urlcode 0.3.0
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/CONTRIBUTING.md +80 -0
- package/LICENSE +202 -0
- package/NOTICE +20 -0
- package/README.md +342 -0
- package/ROADMAP.md +248 -0
- package/SECURITY.md +40 -0
- package/data/agents/LICENSES/ai-robots-txt.txt +24 -0
- package/data/agents/LICENSES/crawler-user-agents.txt +23 -0
- package/data/agents/ai-crawlers.json +1240 -0
- package/data/agents/crawlers.json +10515 -0
- package/data/agents/index.d.ts +3 -0
- package/data/agents/index.js +2453 -0
- package/data/agents/monitoring.json +1520 -0
- package/data/agents/seo.json +3865 -0
- package/dist/BUILD-MANIFEST.json +60 -0
- package/dist/adapters.js +59 -0
- package/dist/agent-lists.js +59 -0
- package/dist/assets.js +129 -0
- package/dist/authoring.js +68 -0
- package/dist/aws.js +110 -0
- package/dist/build-cloudflare.js +164 -0
- package/dist/cli.js +185 -0
- package/dist/client-address.js +85 -0
- package/dist/cloudflare.js +165 -0
- package/dist/compliance-rules/baseline.js +98 -0
- package/dist/compliance-rules/privacy.js +38 -0
- package/dist/compliance-rules/shared.js +62 -0
- package/dist/compliance-rules/strict.js +61 -0
- package/dist/compliance.js +219 -0
- package/dist/config-worker.js +9 -0
- package/dist/config.js +172 -0
- package/dist/errors.js +8 -0
- package/dist/function-sources.js +54 -0
- package/dist/function-worker.js +105 -0
- package/dist/functions.js +174 -0
- package/dist/guest-api.js +114 -0
- package/dist/header-validation.js +18 -0
- package/dist/http-policy.js +74 -0
- package/dist/http-response.js +84 -0
- package/dist/index.js +11 -0
- package/dist/link-api.js +136 -0
- package/dist/link-cli.js +141 -0
- package/dist/link-events.js +76 -0
- package/dist/link-records.js +31 -0
- package/dist/link-store-worker.js +150 -0
- package/dist/link-store.js +250 -0
- package/dist/logging.js +22 -0
- package/dist/management-policy.js +41 -0
- package/dist/match.js +124 -0
- package/dist/observability.js +242 -0
- package/dist/plugins.js +74 -0
- package/dist/policies/agents.js +248 -0
- package/dist/policies/cache.js +297 -0
- package/dist/policies/compression.js +187 -0
- package/dist/policies/security.js +0 -0
- package/dist/policies/throttle.js +131 -0
- package/dist/policies.js +142 -0
- package/dist/policy.js +55 -0
- package/dist/prerender.js +165 -0
- package/dist/project-tests.js +23 -0
- package/dist/readiness.js +216 -0
- package/dist/router.js +166 -0
- package/dist/runtime.js +244 -0
- package/dist/scaffold.js +0 -0
- package/dist/scripts/operational-drills.js +75 -0
- package/dist/server.js +270 -0
- package/dist/site.js +0 -0
- package/dist/sqlite-version.js +6 -0
- package/dist/types/adapters.d.ts +12 -0
- package/dist/types/agent-lists.d.ts +5 -0
- package/dist/types/assets.d.ts +13 -0
- package/dist/types/authoring.d.ts +2 -0
- package/dist/types/aws.d.ts +35 -0
- package/dist/types/build-cloudflare.d.ts +14 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/client-address.d.ts +9 -0
- package/dist/types/cloudflare.d.ts +75 -0
- package/dist/types/compliance-rules/baseline.d.ts +12 -0
- package/dist/types/compliance-rules/privacy.d.ts +6 -0
- package/dist/types/compliance-rules/shared.d.ts +24 -0
- package/dist/types/compliance-rules/strict.d.ts +9 -0
- package/dist/types/compliance.d.ts +119 -0
- package/dist/types/config-worker.d.ts +1 -0
- package/dist/types/config.d.ts +21 -0
- package/dist/types/errors.d.ts +7 -0
- package/dist/types/function-sources.d.ts +21 -0
- package/dist/types/function-worker.d.ts +1 -0
- package/dist/types/functions.d.ts +92 -0
- package/dist/types/guest-api.d.ts +16 -0
- package/dist/types/header-validation.d.ts +2 -0
- package/dist/types/http-policy.d.ts +39 -0
- package/dist/types/http-response.d.ts +43 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/link-api.d.ts +30 -0
- package/dist/types/link-cli.d.ts +36 -0
- package/dist/types/link-events.d.ts +27 -0
- package/dist/types/link-records.d.ts +11 -0
- package/dist/types/link-store-worker.d.ts +1 -0
- package/dist/types/link-store.d.ts +130 -0
- package/dist/types/logging.d.ts +9 -0
- package/dist/types/management-policy.d.ts +9 -0
- package/dist/types/match.d.ts +81 -0
- package/dist/types/observability.d.ts +104 -0
- package/dist/types/plugins.d.ts +27 -0
- package/dist/types/policies/agents.d.ts +86 -0
- package/dist/types/policies/cache.d.ts +82 -0
- package/dist/types/policies/compression.d.ts +41 -0
- package/dist/types/policies/security.d.ts +37 -0
- package/dist/types/policies/throttle.d.ts +55 -0
- package/dist/types/policies.d.ts +36 -0
- package/dist/types/policy.d.ts +21 -0
- package/dist/types/prerender.d.ts +31 -0
- package/dist/types/project-tests.d.ts +13 -0
- package/dist/types/readiness.d.ts +121 -0
- package/dist/types/router.d.ts +11 -0
- package/dist/types/runtime.d.ts +91 -0
- package/dist/types/scaffold.d.ts +17 -0
- package/dist/types/server.d.ts +37 -0
- package/dist/types/site.d.ts +12 -0
- package/dist/types/sqlite-version.d.ts +1 -0
- package/dist/types/types.d.ts +332 -0
- package/dist/types/vercel.d.ts +12 -0
- package/dist/types.js +177 -0
- package/dist/vercel.js +65 -0
- package/docs/AI-AUTHORING.md +113 -0
- package/docs/ASSETS.md +106 -0
- package/docs/AWS.md +77 -0
- package/docs/BEST-PRACTICES.md +267 -0
- package/docs/CAPACITY.md +208 -0
- package/docs/CLOUDFLARE.md +110 -0
- package/docs/COMPLIANCE.md +242 -0
- package/docs/DYNAMIC-LINKS.md +561 -0
- package/docs/FUNCTION-SECURITY.md +113 -0
- package/docs/HTTP.md +129 -0
- package/docs/INSTALL.md +90 -0
- package/docs/LOAD-TESTING.md +91 -0
- package/docs/LOCAL-DEVELOPMENT.md +99 -0
- package/docs/MANAGEMENT-SECURITY.md +82 -0
- package/docs/MIDDLEWARE.md +83 -0
- package/docs/MONITORING.md +132 -0
- package/docs/OBSERVABILITY.md +229 -0
- package/docs/OPERATIONAL-PROOF.md +42 -0
- package/docs/OPERATIONS.md +207 -0
- package/docs/ORGANIZATION.md +135 -0
- package/docs/PERFORMANCE.md +72 -0
- package/docs/PLUGINS.md +234 -0
- package/docs/POLICIES.md +489 -0
- package/docs/PRERENDER.md +193 -0
- package/docs/PROJECT-DIRECTION.md +51 -0
- package/docs/READINESS.md +125 -0
- package/docs/README.md +58 -0
- package/docs/RELEASE-READINESS.md +95 -0
- package/docs/RELEASE-SECURITY.md +72 -0
- package/docs/RESILIENCE.md +160 -0
- package/docs/ROUTING.md +95 -0
- package/docs/SANDBOX-REVIEW.md +59 -0
- package/docs/SCAFFOLDING.md +72 -0
- package/docs/SECURITY-AUDIT.md +124 -0
- package/docs/SITE.md +150 -0
- package/docs/SPECIFICATION.md +232 -0
- package/docs/SPIKE-EXTENSIONS.md +475 -0
- package/docs/STANDARDS.md +303 -0
- package/docs/STARTERS.md +51 -0
- package/docs/TUNNELS.md +75 -0
- package/docs/TYPESCRIPT.md +102 -0
- package/docs/VERCEL.md +107 -0
- package/docs/YAML-GUIDE.md +521 -0
- package/docs/YAML-REFERENCE.md +328 -0
- package/docs/policies/agents.md +182 -0
- package/docs/policies/cache.md +139 -0
- package/docs/policies/compression.md +168 -0
- package/docs/policies/security.md +160 -0
- package/docs/policies/throttle.md +103 -0
- package/examples/assets/.env.example +1 -0
- package/examples/assets/.gitattributes +2 -0
- package/examples/assets/Makefile +30 -0
- package/examples/assets/README.md +18 -0
- package/examples/assets/functions/hello.mjs +3 -0
- package/examples/assets/gitignore.template +7 -0
- package/examples/assets/public/about.html +2 -0
- package/examples/assets/public/assets/example.txt +1 -0
- package/examples/assets/public/guide.txt +1 -0
- package/examples/assets/tests/requests.json +44 -0
- package/examples/assets/urlcode.yaml +27 -0
- package/examples/aws/README.md +13 -0
- package/examples/aws/handler.mjs +5 -0
- package/examples/aws/public/index.html +3 -0
- package/examples/aws/public/notes.txt +1 -0
- package/examples/aws/template.yaml +33 -0
- package/examples/aws/tests/requests.json +9 -0
- package/examples/aws/urlcode.yaml +19 -0
- package/examples/cloudflare/README.md +18 -0
- package/examples/cloudflare/package.json +14 -0
- package/examples/cloudflare/tests/requests.json +9 -0
- package/examples/cloudflare/urlcode.yaml +24 -0
- package/examples/cloudflare/wrangler.toml +5 -0
- package/examples/compliance/README.md +20 -0
- package/examples/compliance/rules.mjs +65 -0
- package/examples/cookbook/README.md +21 -0
- package/examples/cookbook/functions/choice.mjs +4 -0
- package/examples/cookbook/functions/echo.mjs +3 -0
- package/examples/cookbook/functions/hello.mjs +3 -0
- package/examples/cookbook/functions/text.mjs +3 -0
- package/examples/cookbook/middleware/headers.mjs +6 -0
- package/examples/cookbook/public/about.html +1 -0
- package/examples/cookbook/public/assets/index.html +1 -0
- package/examples/cookbook/public/assets/site.css +1 -0
- package/examples/cookbook/public/favicon.svg +1 -0
- package/examples/cookbook/public/guide.txt +1 -0
- package/examples/cookbook/public/llms.txt +6 -0
- package/examples/cookbook/routes/code.yaml +55 -0
- package/examples/cookbook/routes/files.yaml +17 -0
- package/examples/cookbook/routes/policies.yaml +28 -0
- package/examples/cookbook/routes/redirects.yaml +38 -0
- package/examples/cookbook/routes/responses.yaml +26 -0
- package/examples/cookbook/tests/requests.json +254 -0
- package/examples/cookbook/urlcode.yaml +22 -0
- package/examples/live-links/README.md +11 -0
- package/examples/live-links/tests/requests.json +6 -0
- package/examples/live-links/urlcode.yaml +16 -0
- package/examples/monitoring/blackbox-jobs.yaml +37 -0
- package/examples/monitoring/prometheus-rules.yaml +88 -0
- package/examples/monitoring/prometheus-scrape.yaml +20 -0
- package/examples/monitoring/vector.toml +65 -0
- package/examples/prerender/README.md +48 -0
- package/examples/prerender/functions/page.mjs +6 -0
- package/examples/prerender/middleware/template.mjs +29 -0
- package/examples/prerender/prerender.d.mts +4 -0
- package/examples/prerender/prerender.mjs +63 -0
- package/examples/prerender/tests/requests.json +39 -0
- package/examples/prerender/urlcode.yaml +50 -0
- package/examples/tunnel/dev-with-ngrok.sh +61 -0
- package/examples/vercel/README.md +13 -0
- package/examples/vercel/api/index.js +5 -0
- package/examples/vercel/package.json +8 -0
- package/examples/vercel/public/index.html +3 -0
- package/examples/vercel/public/notes.txt +1 -0
- package/examples/vercel/tests/requests.json +48 -0
- package/examples/vercel/urlcode.yaml +19 -0
- package/examples/vercel/vercel.json +10 -0
- package/llms.txt +46 -0
- package/package.json +123 -0
- package/schemas/urlcode.schema.json +1230 -0
- package/starters/default/.gitattributes +1 -0
- package/starters/default/Makefile +30 -0
- package/starters/default/README.md +34 -0
- package/starters/default/functions/hello.mjs +3 -0
- package/starters/default/gitignore.template +8 -0
- package/starters/default/middleware/headers.mjs +6 -0
- package/starters/default/routes/functions.yaml +20 -0
- package/starters/default/routes/marketing/links.yaml +7 -0
- package/starters/default/starter.json +5 -0
- package/starters/default/tests/requests.json +56 -0
- package/starters/default/urlcode.yaml +8 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Organization and readability
|
|
2
|
+
|
|
3
|
+
These are conventions for URLCode applications, not extra schema requirements.
|
|
4
|
+
Choose the smallest structure that makes a route easy to find, understand and
|
|
5
|
+
test. Preserve an existing project's conventions unless there is a clear reason
|
|
6
|
+
to change them. See [file composition](ORGANIZATION.md) for enforced rules and
|
|
7
|
+
[the YAML guide](YAML-GUIDE.md) for supported features.
|
|
8
|
+
|
|
9
|
+
## Start small, split by responsibility
|
|
10
|
+
|
|
11
|
+
The default starter's two routes do not need a framework or many abstraction
|
|
12
|
+
layers. A small project can keep all definitions in `urlcode.yaml`:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
my-links/
|
|
16
|
+
urlcode.yaml
|
|
17
|
+
functions/
|
|
18
|
+
welcome.mjs
|
|
19
|
+
middleware/
|
|
20
|
+
response-headers.mjs
|
|
21
|
+
public/
|
|
22
|
+
guide.txt
|
|
23
|
+
tests/
|
|
24
|
+
requests.json
|
|
25
|
+
README.md
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Create only the folders you use. When navigation or ownership becomes difficult,
|
|
29
|
+
group by feature, audience or campaign, rather than creating one YAML file per
|
|
30
|
+
short link. There is no universal file-size threshold; split when a reviewer can
|
|
31
|
+
no longer follow the changes comfortably. Thousands of redirects usually belong
|
|
32
|
+
in a few meaningful collections, not thousands of include entries.
|
|
33
|
+
|
|
34
|
+
For a growing application, colocating feature-specific code is often clearer:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
my-links/
|
|
38
|
+
urlcode.yaml
|
|
39
|
+
features/
|
|
40
|
+
welcome/
|
|
41
|
+
routes.yaml
|
|
42
|
+
welcome.mjs
|
|
43
|
+
greeting.mjs
|
|
44
|
+
campaigns/
|
|
45
|
+
routes.yaml
|
|
46
|
+
middleware/
|
|
47
|
+
response-headers.mjs
|
|
48
|
+
public/
|
|
49
|
+
guide.txt
|
|
50
|
+
tests/
|
|
51
|
+
requests.json
|
|
52
|
+
README.md
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The root remains a short explicit table of contents:
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
version: "1"
|
|
59
|
+
includes:
|
|
60
|
+
- features/welcome/routes.yaml
|
|
61
|
+
- features/campaigns/routes.yaml
|
|
62
|
+
routes: {}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Each included file has its own `version: "1"` and `routes`. Do not add recursive
|
|
66
|
+
includes, glob patterns or inherited defaults. Folder names never change public
|
|
67
|
+
URLs. Keep the two styles available: folders by file type for small apps, or by
|
|
68
|
+
feature when it improves ownership. Neither requires forking the runtime.
|
|
69
|
+
|
|
70
|
+
## Make a route readable from top to bottom
|
|
71
|
+
|
|
72
|
+
Use two spaces, no tabs, and one consistent key order. A useful order is:
|
|
73
|
+
`description`, lifecycle (`enabled`/`expires`), `methods`, `parameters`,
|
|
74
|
+
`request`, bindings (`env`/`secrets`), `middleware`, the handler, then `response`.
|
|
75
|
+
This is visual organization, not an execution-order declaration.
|
|
76
|
+
|
|
77
|
+
Use block style for nested behavior. Short scalar lists such as `[GET, HEAD]`
|
|
78
|
+
and small leaf schemas such as `{type: string}` can stay inline. Omit ordinary
|
|
79
|
+
defaults unless making them explicit clarifies a decision. Quote timestamps and
|
|
80
|
+
numeric-looking header values. Avoid unrelated reformatting when changing a route.
|
|
81
|
+
|
|
82
|
+
Example `features/welcome/routes.yaml`:
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
version: "1"
|
|
86
|
+
routes:
|
|
87
|
+
/welcome/{name}:
|
|
88
|
+
description: A personalized welcome message with a consistent response header
|
|
89
|
+
parameters:
|
|
90
|
+
- name: name
|
|
91
|
+
in: path
|
|
92
|
+
required: true
|
|
93
|
+
schema:
|
|
94
|
+
type: string
|
|
95
|
+
minLength: 1
|
|
96
|
+
maxLength: 80
|
|
97
|
+
middleware:
|
|
98
|
+
- source: middleware/response-headers.mjs
|
|
99
|
+
function:
|
|
100
|
+
source: features/welcome/welcome.mjs
|
|
101
|
+
args:
|
|
102
|
+
name: {from: path, name: name}
|
|
103
|
+
response:
|
|
104
|
+
headers:
|
|
105
|
+
Cache-Control: no-store
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Example `features/campaigns/routes.yaml`:
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
version: "1"
|
|
112
|
+
routes:
|
|
113
|
+
/guide:
|
|
114
|
+
# Keep this temporary so a campaign destination can change later.
|
|
115
|
+
redirect:
|
|
116
|
+
url: https://example.com/guide
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Use comments for the reason behind a choice, migration notes or a business rule.
|
|
120
|
+
Do not narrate obvious syntax or include secrets, private customer details or
|
|
121
|
+
stale deployment instructions. `description` is useful authoring metadata; do
|
|
122
|
+
not invent `owner`, `tags` or other unsupported YAML fields. Record ownership in
|
|
123
|
+
the README or repository tooling instead.
|
|
124
|
+
|
|
125
|
+
Order routes consistently within each collection, such as alphabetically or by
|
|
126
|
+
business flow. Do not rely on file order for precedence: exact routes win before
|
|
127
|
+
parameter routes, then static mounts. Avoid ambiguous overlaps; see [routing](ROUTING.md).
|
|
128
|
+
|
|
129
|
+
## Keep handlers short and name their job
|
|
130
|
+
|
|
131
|
+
Name files for behavior (`welcome.mjs`, `select-destination.mjs`) rather than
|
|
132
|
+
`utils2.mjs` or `handler-final.mjs`. Name exports clearly even when using a default
|
|
133
|
+
export. Use descriptive variables and early returns for error cases. Separate
|
|
134
|
+
business transformations from Request/Response handling when that improves clarity.
|
|
135
|
+
Do not extract a one-line helper merely to create more files.
|
|
136
|
+
|
|
137
|
+
`features/welcome/welcome.mjs`:
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
import {formatGreeting} from './greeting.mjs';
|
|
141
|
+
|
|
142
|
+
export default function welcome(request, {args}) {
|
|
143
|
+
return Response.json({message: formatGreeting(args.name)});
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`features/welcome/greeting.mjs`:
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
export function formatGreeting(name) {
|
|
151
|
+
return `Hello, ${name}!`;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
YAML `source` paths are project-root relative. JavaScript imports are relative to
|
|
156
|
+
the importing module. Explicit `.mjs` filenames make that distinction clear.
|
|
157
|
+
Only relative project JavaScript imports are supported; do not introduce npm,
|
|
158
|
+
Node, remote imports or a transpilation requirement accidentally. There is a
|
|
159
|
+
project-wide module/source budget; excessive fragmentation has a real limit.
|
|
160
|
+
|
|
161
|
+
Use validated `args` and `inputs` instead of parsing query strings again. Return
|
|
162
|
+
JSON through `Response.json`; escape user data explicitly when producing HTML.
|
|
163
|
+
For dynamic redirects, map a validated enum to known destinations rather than
|
|
164
|
+
accepting any user-controlled URL. Keep modules free of top-level work other than
|
|
165
|
+
simple definitions: initialization runs during validation and fresh invocations.
|
|
166
|
+
|
|
167
|
+
Prefer pure helpers with explicit inputs and outputs. Module globals are not a
|
|
168
|
+
cache, database, session store or rate limiter: guest state resets per request.
|
|
169
|
+
Review [sandbox constraints](FUNCTION-SECURITY.md) before choosing dependencies.
|
|
170
|
+
|
|
171
|
+
## Middleware should have one clear responsibility
|
|
172
|
+
|
|
173
|
+
`middleware/response-headers.mjs`:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
export default async function responseHeaders(request, context, next) {
|
|
177
|
+
const response = await next();
|
|
178
|
+
response.headers.set('x-app', 'my-links');
|
|
179
|
+
return response;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Use middleware for reusable behavior around a handler, not to conceal the entire
|
|
184
|
+
application flow. Prefer YAML headers for fixed route-specific headers; this
|
|
185
|
+
example demonstrates a shared wrapper, but native YAML avoids sandbox overhead
|
|
186
|
+
when no custom code is needed. Keep middleware order explicit in each route.
|
|
187
|
+
|
|
188
|
+
Always return a Response. Call `await next()` once when continuing, or return
|
|
189
|
+
an early Response when intentionally stopping. Do not launch unawaited work or
|
|
190
|
+
assume background tasks will survive. A body read consumes the request body;
|
|
191
|
+
if downstream code needs parsed data, agree on a documented `context.state` field.
|
|
192
|
+
Use specific field names to avoid collisions among middleware.
|
|
193
|
+
|
|
194
|
+
Catch only errors you can handle meaningfully. Do not turn every failure into a
|
|
195
|
+
200 response or include secrets in errors. Keep native body/status/header
|
|
196
|
+
preservation rules visible in code review. All middleware share the route's
|
|
197
|
+
bindings and execution budget; splitting modules does not create privilege
|
|
198
|
+
separation. See [middleware semantics](MIDDLEWARE.md).
|
|
199
|
+
|
|
200
|
+
## Organize tests around observable behavior
|
|
201
|
+
|
|
202
|
+
Keep runnable HTTP assertions in `tests/requests.json`, currently the single file
|
|
203
|
+
read by the CLI. Group adjacent cases by route: ordinary success, HEAD, boundary
|
|
204
|
+
inputs, invalid input, wrong method and relevant early responses. Add meaningful
|
|
205
|
+
body/header checks instead of relying only on status codes.
|
|
206
|
+
|
|
207
|
+
For the two-route feature layout above:
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
[
|
|
211
|
+
{"path":"/welcome/Ada","status":200,"expectBody":"{\"message\":\"Hello, Ada!\"}","expectHeaders":{"x-app":"my-links"}},
|
|
212
|
+
{"path":"/welcome/Ada","method":"HEAD","status":200,"expectBody":"","expectHeaders":{"x-app":"my-links"}},
|
|
213
|
+
{"path":"/welcome/Ada","method":"POST","status":405,"expectHeaders":{"allow":"GET, HEAD"}},
|
|
214
|
+
{"path":"/guide","status":302,"expectHeaders":{"location":"https://example.com/guide"}},
|
|
215
|
+
{"path":"/guide","method":"HEAD","status":302,"expectBody":"","expectHeaders":{"location":"https://example.com/guide"}}
|
|
216
|
+
]
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Additional ordinary JavaScript unit tests for pure helpers are your project's
|
|
220
|
+
choice. Unit tests alone do not verify sandbox compatibility: always exercise
|
|
221
|
+
HTTP behavior through URLCode too. Keep large fixture generation explicit and
|
|
222
|
+
deterministic if you add your own tooling; nested test directories and JSON
|
|
223
|
+
fragments are not automatically discovered or merged by URLCode.
|
|
224
|
+
|
|
225
|
+
Assert observable contracts, not incidental timings or internal variable names.
|
|
226
|
+
Avoid checking generated request IDs, current Date values or performance numbers
|
|
227
|
+
as fixed functional outputs. Test cache/range semantics with controlled assets.
|
|
228
|
+
Update expected route counts deliberately when adding or removing a route.
|
|
229
|
+
|
|
230
|
+
## Keep configuration, code, assets and operations separate
|
|
231
|
+
|
|
232
|
+
- Git owns behavior and reviewed code. Keep the runtime as a pinned dependency;
|
|
233
|
+
upgrading it should not regenerate or overwrite application files.
|
|
234
|
+
- Publish only intentionally public files in `public/`. File filters cannot
|
|
235
|
+
recognize every secret. A harmless filename is not proof of public content.
|
|
236
|
+
- Put local secrets in ignored `.env.local`; production values come from the
|
|
237
|
+
operator. Keep operator grants outside the application checkout. Never use
|
|
238
|
+
YAML anchors, shell expansion or generated credentials for convenience.
|
|
239
|
+
- Deployment limits, TLS, DNS, DDoS filters and worker tuning belong to operations,
|
|
240
|
+
not invented route fields. Document them separately from portable behavior.
|
|
241
|
+
- Use [stored links](DYNAMIC-LINKS.md) for live short-code records instead of a
|
|
242
|
+
Git/YAML edit per record. General session/application storage remains future work.
|
|
243
|
+
|
|
244
|
+
## Refactor without changing the public contract
|
|
245
|
+
|
|
246
|
+
Move one feature at a time. Update explicit includes and project-root source/asset
|
|
247
|
+
paths, then check relative JavaScript imports. Keep public route paths, methods,
|
|
248
|
+
headers and bodies stable unless the change is intentional. Avoid mixing URL
|
|
249
|
+
renames, dependency upgrades and folder rearrangement in one review.
|
|
250
|
+
|
|
251
|
+
Run validate, HTTP tests and the expected-count audit before and after moving
|
|
252
|
+
files. Re-review operator grants: code/config changes invalidate the digest even
|
|
253
|
+
when intended behavior is unchanged. A successful dev reload is not a production
|
|
254
|
+
deployment. Record activation/rollback steps in the project README.
|
|
255
|
+
|
|
256
|
+
## A useful project README
|
|
257
|
+
|
|
258
|
+
Document how to install the pinned runtime, start locally, run tests/audit, and
|
|
259
|
+
activate a release. Include a small folder map, the owner of each major feature,
|
|
260
|
+
public route behavior and required external binding names (never values). State
|
|
261
|
+
supported Node/runtime versions and link to matching URLCode docs. Explain any
|
|
262
|
+
middleware ordering or surprising defaults that a new maintainer might miss.
|
|
263
|
+
|
|
264
|
+
For AI-generated changes, require the assistant to follow existing conventions,
|
|
265
|
+
keep diffs focused, add response assertions and report checks actually run. Reject
|
|
266
|
+
invented YAML keys, hidden side effects and unnecessary abstractions. The
|
|
267
|
+
[AI authoring guide](AI-AUTHORING.md) provides a reusable prompt and capability list.
|
package/docs/CAPACITY.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Capacity, concurrency and system limits
|
|
2
|
+
|
|
3
|
+
These are 0.2.0 implementation limits and planning models, not a throughput
|
|
4
|
+
SLA. Route count, connections, in-flight requests and sandbox concurrency are
|
|
5
|
+
four different quantities. Always measure the actual application on deployment
|
|
6
|
+
hardware with the intended proxy, TLS, logging and limits enabled.
|
|
7
|
+
|
|
8
|
+
## What happens for each request
|
|
9
|
+
|
|
10
|
+
A single Node process accepts HTTP, parses/validates inputs, matches a compiled
|
|
11
|
+
route and builds the response. Exact routes use a Map lookup (expected O(1)
|
|
12
|
+
lookup after path parsing). Parameter candidates are grouped by segment count
|
|
13
|
+
and scanned in specificity order; matching is O(P × L) in the worst case for P
|
|
14
|
+
candidates and L segments. Static mount prefixes are scanned longest first.
|
|
15
|
+
|
|
16
|
+
Plain redirects, declared responses, stored-link lookups and assets do not enter
|
|
17
|
+
the sandbox. Stored links use a separate bounded database pools. A
|
|
18
|
+
function or any attached middleware occupies one shared worker slot for its
|
|
19
|
+
whole chain. Workers are shared by all programmable routes in that snapshot;
|
|
20
|
+
there is no per-route fairness or reserved capacity. Awaiting guest timers still
|
|
21
|
+
occupies the slot. A fresh guest and module initialization are part of each call.
|
|
22
|
+
|
|
23
|
+
Node's main event loop remains a shared bottleneck for HTTP parsing, logging and
|
|
24
|
+
native responses. Sandboxing contains application code authority and bounds
|
|
25
|
+
individual execution; it does not make all host resources immune to exhaustion.
|
|
26
|
+
|
|
27
|
+
## Enforced limits and defaults
|
|
28
|
+
|
|
29
|
+
| Resource | Current behavior | Scope / configuration |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| Routes | 100,000 combined | Per project snapshot; schema/loader cap |
|
|
32
|
+
| Parameter routes | 1,000 | Per snapshot; not 1,000 concurrent requests |
|
|
33
|
+
| Included files / YAML size | 256 / 32 MiB per file / 64 MiB aggregate | Parser worker: 256 MiB old heap, 10 s deadline, two concurrent loads per isolate |
|
|
34
|
+
| Route path | 2,048 characters, 32 segments | Configured path; no regex or greedy parameters |
|
|
35
|
+
| Request target / headers | 8,192 characters / 16 KiB headers | Target is checked as a JS string; HTTP header limit is bytes |
|
|
36
|
+
| HTTP connections | 1,024 | Per server; includes keep-alive sockets, not worker slots or users |
|
|
37
|
+
| In-flight application requests | 64 default, no queue; excess gets 503 | From body receipt through response finish/disconnect; health probes exempt |
|
|
38
|
+
| Socket inactivity | 15 s | Destroys inactive sockets, including stalled response writers; not an absolute response deadline |
|
|
39
|
+
| Requests per socket | 1,000 | Connection recycling; not a requests-per-second limit |
|
|
40
|
+
| Header / request receipt / keep-alive timeouts | 10 s / 15 s / 5 s | These are not an overall end-to-end response deadline |
|
|
41
|
+
| Request body | 1 MiB default | Buffered; route maxBytes can tighten to 0–1 MiB |
|
|
42
|
+
| Sandbox concurrency | 2 workers, no queue | Shared per snapshot; full pool returns 503 |
|
|
43
|
+
| Execution deadline | 5 s default | Entire middleware + handler invocation; timeout returns 504 |
|
|
44
|
+
| Guest heap / stack | 32 MiB / 512 KiB | Fresh per invocation; not a bound on total process RSS |
|
|
45
|
+
| Outer worker old-generation V8 budget | 128 MiB | Separate from WASM/host/native allocations |
|
|
46
|
+
| Function response | 1 MiB default, 16 KiB / 256 header pairs | Buffered text/JSON; YAML headers also bounded |
|
|
47
|
+
| Middleware | 16 entries per route | One shared slot/deadline, not 16 independent workers |
|
|
48
|
+
| Function sources | 128 modules, 1 MiB/module, 4 MiB total | Project snapshot, including middleware dependencies |
|
|
49
|
+
| Worker startup | 5 s deadline | Failure rejects activation; no untrusted host fallback |
|
|
50
|
+
| Worker replacement | Up to 3 exits/minute per slot trigger replacement | Further churn leaves the slot unavailable until reload/restart |
|
|
51
|
+
| Assets | 16 MiB/file, 64 MiB unique contents | Buffered immutable snapshots; 10,000 static entries, depth 20 |
|
|
52
|
+
| Logger buffering | Drop at 1 MiB stdout buffering | Reports logs_dropped when output recovers |
|
|
53
|
+
|
|
54
|
+
The 1,024-connection cap is not a global memory bound, fairness policy or DDoS
|
|
55
|
+
protection. At the default admission/body limits, accepted uploads can buffer up to
|
|
56
|
+
64 MiB of payload before copies and other allocations. Slow readers can hold
|
|
57
|
+
sockets/response memory until completion/disconnect or the 15-second inactivity
|
|
58
|
+
timeout. A peer that continues making progress can stay connected longer. Use
|
|
59
|
+
proxy admission limits, timeouts and OS/container limits.
|
|
60
|
+
|
|
61
|
+
The CLI and the embedding JS API accept `--workers`/`workers` (1–32),
|
|
62
|
+
`--function-timeout-ms`/`timeoutMs` (10–60,000), `--max-response-bytes`/`maxBytes`
|
|
63
|
+
(response limit, 1–16 MiB), `--max-body-bytes`/`maxBodyBytes` (request limit, 1–16 MiB),
|
|
64
|
+
`--max-in-flight`/`maxInFlightRequests` (1–1,024; default 64) and
|
|
65
|
+
`--max-in-flight-health`/`maxInFlightHealthRequests` (1–1,024; default 16). Measure the effect with
|
|
66
|
+
[load testing](LOAD-TESTING.md) rather than guessing; `shedResponses` names the
|
|
67
|
+
limit that bound. These are
|
|
68
|
+
operator choices on `startServer`, not supported YAML fields or CLI flags.
|
|
69
|
+
Route body policy still cannot exceed 1 MiB. More workers consume memory and CPU;
|
|
70
|
+
increasing a timeout also increases how long an attacker can occupy capacity.
|
|
71
|
+
The CLI uses defaults. Keep settings identical across replicas unless testing a
|
|
72
|
+
controlled rollout. See [operations](OPERATIONS.md).
|
|
73
|
+
|
|
74
|
+
## A useful theoretical model
|
|
75
|
+
|
|
76
|
+
Let W be worker slots, S the measured mean slot occupancy in seconds (including
|
|
77
|
+
sandbox startup and cleanup effects), and lambda the offered programmable
|
|
78
|
+
requests per second. An idealized worker ceiling is:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
worker-limited throughput <= W / S
|
|
82
|
+
mean offered worker load A = lambda * S
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This ignores CPU contention, event-loop work, garbage collection, worker failures
|
|
86
|
+
and network overhead. It is an upper bound under simplified assumptions, not a
|
|
87
|
+
recommended arrival rate. With the default W=2:
|
|
88
|
+
|
|
89
|
+
| Mean slot time S | Idealized ceiling W/S |
|
|
90
|
+
|---|---:|
|
|
91
|
+
| 5 ms | 400 requests/s |
|
|
92
|
+
| 50 ms | 40 requests/s |
|
|
93
|
+
| 500 ms | 4 requests/s |
|
|
94
|
+
| 5 s | 0.4 requests/s (at the timeout boundary; not useful successful capacity) |
|
|
95
|
+
|
|
96
|
+
No queue means requests are rejected when both slots are occupied, even if the
|
|
97
|
+
average arrival rate is below the ceiling. Under a simplified independent
|
|
98
|
+
Poisson-arrival loss model, Erlang B gives blocking probability:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
B(W,A) = (A^W / W!) / sum(k=0..W, A^k / k!)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
For W=2 and S=50 ms, an offered 20 requests/s gives A=1 and B=20%. That is a
|
|
105
|
+
model illustration, not a measured URLCode result. Bursts, correlated traffic and
|
|
106
|
+
CPU-dependent service times can differ substantially. Measure rejection rate as
|
|
107
|
+
well as latency; fast 503 responses must not count as successful throughput.
|
|
108
|
+
An upstream bounded queue may smooth bursts but adds latency and memory; it is
|
|
109
|
+
not included in this runtime. Unbounded queues just move the failure.
|
|
110
|
+
|
|
111
|
+
Little's law, L=lambda*R, describes average in-flight work for a stable system
|
|
112
|
+
using admitted/completed throughput and mean residence time. It does not turn
|
|
113
|
+
1,024 sockets into 1,024 execution slots or predict tail latency. CPU and bandwidth
|
|
114
|
+
put separate ceilings on throughput. For CPU-bound work, adding workers beyond
|
|
115
|
+
available cores cannot produce linear scaling. Bandwidth must also carry asset
|
|
116
|
+
bytes, response headers, TLS and protocol overhead.
|
|
117
|
+
|
|
118
|
+
## Native routes and mixed traffic
|
|
119
|
+
|
|
120
|
+
For native-only traffic, the Node event loop, network, buffers and logging dominate;
|
|
121
|
+
the W/S sandbox model does not apply. Function saturation does not itself consume
|
|
122
|
+
native route worker slots. However all routes share the server/event loop and
|
|
123
|
+
host resources, so a flood can still degrade ordinary redirects and health checks.
|
|
124
|
+
A single expensive function can starve other functions. For stronger isolation,
|
|
125
|
+
use separate processes/containers and proxy routing; there is no per-route pool
|
|
126
|
+
configuration in YAML today.
|
|
127
|
+
|
|
128
|
+
Horizontal replicas can add capacity if balanced well and supplied identical
|
|
129
|
+
runtime/application revisions and bindings. Scaling is not perfectly linear,
|
|
130
|
+
and capacity falls during failures/rollouts. Rate limits must account for all
|
|
131
|
+
replicas. In-memory counters in middleware reset per request and cannot implement
|
|
132
|
+
a shared rate limiter or durable application state.
|
|
133
|
+
|
|
134
|
+
Optional [policies](POLICIES.md) keep their state per runtime instance, and
|
|
135
|
+
their memory bounds are per instance too: the `throttle` counter table is one
|
|
136
|
+
LRU table per runtime capped by the largest declared `maxKeys` (default
|
|
137
|
+
100,000 keys), and the `cache` policy's origin cache is bounded by its
|
|
138
|
+
`maxEntries` and `maxBytes` per route and by 64 MiB of bodies across the
|
|
139
|
+
whole runtime; the `compression` policy holds up to 64 MiB of precompressed
|
|
140
|
+
asset variants per runtime, the same figure as the asset snapshot itself, so
|
|
141
|
+
a fully policied instance can hold three such budgets. Neither is shared between replicas or
|
|
142
|
+
serverless instances, so a client budget across N replicas is up to N times
|
|
143
|
+
the declared quota and a cached response is computed once per replica. Both
|
|
144
|
+
tables are dropped on a snapshot reload. Sharing state across instances is a
|
|
145
|
+
[plugin](PLUGINS.md) concern.
|
|
146
|
+
|
|
147
|
+
## Memory, startup and reload
|
|
148
|
+
|
|
149
|
+
A practical memory budget includes the Node baseline, parsed YAML/compiled route
|
|
150
|
+
objects, asset snapshots, source copies in workers, WASM heaps, active request
|
|
151
|
+
and response buffers, sockets, logs and transient garbage collection allocations.
|
|
152
|
+
These are not all covered by worker heap limits. Production needs measured peak
|
|
153
|
+
RSS with an OS/container ceiling, plus headroom.
|
|
154
|
+
|
|
155
|
+
Reload constructs a complete new snapshot while the old one serves/drains. Old
|
|
156
|
+
and new assets and worker pools can overlap; repeated reloads with in-flight calls
|
|
157
|
+
can retain multiple generations. Host route compilation and snapshot transfer can delay the shared event
|
|
158
|
+
loop even though the HTTP listener is not restarted. Do not equate atomic swap
|
|
159
|
+
with zero latency impact or incremental route updates. Prefer candidate replicas
|
|
160
|
+
and traffic switching for production. `serve` does not watch configuration.
|
|
161
|
+
|
|
162
|
+
Before parser-worker limits were introduced, recorded 100k-route startup RSS was about 621 MiB on one development machine,
|
|
163
|
+
above the illustrative 512 MiB container example. Route limits are acceptance
|
|
164
|
+
caps, not a promise that the maximum fits your deployment. See [measurements](PERFORMANCE.md).
|
|
165
|
+
That short benchmark uses 5,000 measured requests and does not exercise all routes
|
|
166
|
+
in the larger datasets; client/server share a process, logs are off, and no TLS
|
|
167
|
+
or production proxy is involved. No NGINX performance ratio has been measured.
|
|
168
|
+
|
|
169
|
+
## Establish a deployment budget
|
|
170
|
+
|
|
171
|
+
1. Pin runtime, app, dependency locks and image; record CPU, RAM, Node, proxy/TLS
|
|
172
|
+
settings, workers, timeouts and logging. Select representative input/body sizes.
|
|
173
|
+
2. Measure native redirects, parameter hits/misses, functions, middleware and
|
|
174
|
+
assets separately, then use the expected mixed workload and hot-route skew.
|
|
175
|
+
3. Use a separate load generator for deployment tests. Increase offered rate and
|
|
176
|
+
concurrency gradually; record successful throughput, all status counts,
|
|
177
|
+
p50/p95/p99, CPU, peak RSS, sockets, restarts and network bytes.
|
|
178
|
+
4. Include bursts, slow clients, saturation, invalid inputs and one failed replica.
|
|
179
|
+
Sustain tests long enough to observe memory/GC behavior and stable plateaus.
|
|
180
|
+
5. Choose admission limits below the measured failure knee with explicit spare
|
|
181
|
+
capacity for a replica loss. Verify the service recovers after load stops.
|
|
182
|
+
6. Record the accepted load, error and latency budgets and repeat after changes.
|
|
183
|
+
|
|
184
|
+
The built-in local benchmark is a quick correctness-aware signal, not the above
|
|
185
|
+
production exercise. The readiness endpoint can stay 200 while all worker slots
|
|
186
|
+
are busy. Use error/latency signals too. No universal safe RPS can be derived
|
|
187
|
+
from the route count or these defaults alone. See [resilience](RESILIENCE.md).
|
|
188
|
+
|
|
189
|
+
## Optional stored-link capacity
|
|
190
|
+
|
|
191
|
+
Each SQLite store defaults to two read-only worker connections; writable stores
|
|
192
|
+
add one writer. Independent read/write admission caps default to 32 each across
|
|
193
|
+
their pool. Readers are configurable from 1–8, and caps from 1–32. Operations have
|
|
194
|
+
a 5-second deadline and one-second SQLite lock wait. Public serving has no writer. The
|
|
195
|
+
initial cap is 100,000 stored records across collections; this is separate from
|
|
196
|
+
the YAML route count. No lookup cache is used, so visibility does not depend on
|
|
197
|
+
cache invalidation. Store failures/overload return 503 and failed workers need
|
|
198
|
+
reload/restart; healthy readers can continue while readiness is degraded. Management has a separate listener with 64 connections, 8 KiB
|
|
199
|
+
headers and 16 KiB JSON bodies. Rate limiting remains an ingress responsibility.
|
|
200
|
+
Do not extrapolate in-memory redirect benchmark numbers to database lookups;
|
|
201
|
+
measure disk, writes, contention and restoration on the target host.
|
|
202
|
+
|
|
203
|
+
Configuration parsing/schema validation now run in a terminated-on-deadline worker;
|
|
204
|
+
route compilation still runs cooperatively on the host (10 seconds, yields every
|
|
205
|
+
64 routes). Source, AST, structured-clone output, compiled routes, assets, module
|
|
206
|
+
snapshots and overlapping runtimes all consume memory. Worker V8 limits do not cap
|
|
207
|
+
external buffers or aggregate process RSS. Enforce container/process limits and
|
|
208
|
+
operator-controlled activation; see [review scope](SANDBOX-REVIEW.md).
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Deploying to Cloudflare Workers
|
|
2
|
+
|
|
3
|
+
Cloudflare Workers is not a Node host. There is no `worker_threads`, no
|
|
4
|
+
filesystem, and code generation at runtime is forbidden. So this target does not
|
|
5
|
+
adapt the runtime the way [Vercel](VERCEL.md) and [AWS](AWS.md) do — it
|
|
6
|
+
**compiles the project ahead of time** and ships a Worker that reads the result.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
urlcode build --target cloudflare --project . --out dist
|
|
10
|
+
npx wrangler deploy
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
A working project is in [`examples/cloudflare/`](../examples/cloudflare/).
|
|
14
|
+
|
|
15
|
+
## Declarative routes only
|
|
16
|
+
|
|
17
|
+
This target serves **redirects** and **declared responses** (`respond:`), with
|
|
18
|
+
path, query and header parameters, defaults, validation, response headers,
|
|
19
|
+
`enabled` and `expires`. Everything else is refused **at build time**, with the
|
|
20
|
+
route pattern and the reason named:
|
|
21
|
+
|
|
22
|
+
| Handler | Why it is refused |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `function`, `middleware` | need worker threads and the QuickJS WASM engine |
|
|
25
|
+
| `link` | needs a durable writable store the platform does not provide here |
|
|
26
|
+
| `page`, `static`, `download` | need a platform static-asset binding, not an inline copy |
|
|
27
|
+
| `env`, `secrets` | would have to be baked into the artifact |
|
|
28
|
+
|
|
29
|
+
Generated [site conventions](SITE.md) follow the same table: `robots`,
|
|
30
|
+
`sitemap` and `securityTxt` are `respond` routes and compile into the artifact
|
|
31
|
+
(pass `--origin` to `build` for the absolute URLs they contain); `favicon` and
|
|
32
|
+
`llms` are `page` routes and are refused.
|
|
33
|
+
|
|
34
|
+
A build artifact is a file that gets copied, cached and committed by mistake, so
|
|
35
|
+
it never carries a secret. That is why `env` and `secrets` are refused even when
|
|
36
|
+
the value is a literal in the YAML.
|
|
37
|
+
|
|
38
|
+
Refusing at build time rather than at runtime is the point: a project that
|
|
39
|
+
cannot be served fails `urlcode build`, so it never reaches a deployment.
|
|
40
|
+
|
|
41
|
+
## What the build emits
|
|
42
|
+
|
|
43
|
+
`--out` receives three generated files. None of them are edited by hand, and
|
|
44
|
+
`dist/` belongs in `.gitignore`:
|
|
45
|
+
|
|
46
|
+
- `artifact.js` — the compiled routes. **This is an internal build output, not a
|
|
47
|
+
published contract.** Its `format` may change in any release, and the runtime
|
|
48
|
+
refuses a format it does not recognise rather than guessing. Always rebuild
|
|
49
|
+
with the same version of URLCode that the Worker imports.
|
|
50
|
+
- `validators.js` — the parameter schemas, precompiled by Ajv into standalone ES
|
|
51
|
+
modules. The platform forbids runtime code generation, so a validator cannot
|
|
52
|
+
be compiled on the Worker; it has to be compiled by the build.
|
|
53
|
+
- `index.js` — the Worker entry, which is three lines over
|
|
54
|
+
`createFetchHandler` from `@jimhoyd/urlcode/cloudflare`. That import resolves to the
|
|
55
|
+
package's built `dist/cloudflare.js` (and its declarations, for a TypeScript
|
|
56
|
+
Worker); the artifact never depends on the TypeScript sources or on type
|
|
57
|
+
stripping.
|
|
58
|
+
|
|
59
|
+
Ajv's standalone output hardcodes a CommonJS `require` for its runtime helpers
|
|
60
|
+
even in ESM mode, which an ES module cannot evaluate. The build inlines each
|
|
61
|
+
helper from the installed Ajv — the real function, not a copy that could drift —
|
|
62
|
+
and fails if it meets a `require` it does not recognise, rather than emitting a
|
|
63
|
+
Worker that cannot start.
|
|
64
|
+
|
|
65
|
+
`wrangler.toml` needs no `nodejs_compat` flag. The runtime and the generated
|
|
66
|
+
validators use Web standards only.
|
|
67
|
+
|
|
68
|
+
## Portability, and where it stops
|
|
69
|
+
|
|
70
|
+
The Worker shares its route matching, request policy and response policy with
|
|
71
|
+
the self-hosted server: `src/match.ts`, `src/http-policy.ts` and
|
|
72
|
+
`src/http-response.ts` are the same modules, with no Node imports. Two checks
|
|
73
|
+
keep it that way: an ESLint rule forbids `node:` imports in the modules that
|
|
74
|
+
ship to the Worker, and `scripts/check.ts` (part of `npm run verify`) walks the
|
|
75
|
+
import closure of `src/cloudflare.ts` and fails on any `node:` specifier that
|
|
76
|
+
is not an `import type`. `test/cloudflare.test.ts` builds a project, runs the same project on the
|
|
77
|
+
self-hosted server, and asserts both return the same status, body and headers
|
|
78
|
+
(everything but the per-request identifier) — including the example in this
|
|
79
|
+
repository, replayed through the compiled Worker.
|
|
80
|
+
|
|
81
|
+
[Policies](POLICIES.md) follow the same rule: `agents` and `security` are
|
|
82
|
+
compiled into the artifact with project list files embedded as entries,
|
|
83
|
+
`compression` is delegated to the edge, and `throttle` and `cache` are refused
|
|
84
|
+
at build time with the route named. The artifact also carries the project-level
|
|
85
|
+
`security` policy, so the Worker's own 404 and thrown-error responses get the
|
|
86
|
+
same security headers the self-hosted server gives them.
|
|
87
|
+
|
|
88
|
+
Two differences are real and deliberate:
|
|
89
|
+
|
|
90
|
+
- **Duplicate request headers.** The platform joins repeated headers into one
|
|
91
|
+
value before the Worker runs, so per-header counts do not exist. The
|
|
92
|
+
self-hosted server rejects a duplicated scalar header parameter with 400. Here
|
|
93
|
+
that check cannot fire: the parameter sees the joined value (`a, b`) and is
|
|
94
|
+
validated against its schema like any other. A constrained schema still
|
|
95
|
+
rejects it; an unconstrained `type: string` accepts it where the self-hosted
|
|
96
|
+
server would not. Constrain header parameters you care about. The duplicate
|
|
97
|
+
`Content-Type` check on a declared request body is unavailable for the same
|
|
98
|
+
reason; a joined value fails the media-type check instead.
|
|
99
|
+
- **The request target.** The self-hosted server inspects the request line
|
|
100
|
+
verbatim. The Worker only ever sees a parsed `Request`, so the target is
|
|
101
|
+
reconstructed from `URL`, and a malformed target the self-hosted server would
|
|
102
|
+
refuse may have been normalised or rejected by the platform before this code
|
|
103
|
+
runs. Path traversal, control characters, over-long targets and ambiguous `%`
|
|
104
|
+
sequences that do survive are still refused by the shared `parseTarget`.
|
|
105
|
+
|
|
106
|
+
**This has never been deployed to Cloudflare.** Everything above is verified
|
|
107
|
+
against the runtime's own test suite and a local build, not against the
|
|
108
|
+
platform. A first real deployment is the next thing that would change that, and
|
|
109
|
+
until it happens, treat compatibility with a specific `compatibility_date` and
|
|
110
|
+
with Wrangler's bundler as unproven.
|