@jimhoyd/urlcode 0.4.1 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +60 -7
  2. package/.claude/skills/urlcode-operations/SKILL.md +4 -0
  3. package/README.md +19 -15
  4. package/SECURITY.md +5 -3
  5. package/dist/BUILD-MANIFEST.json +31 -28
  6. package/dist/agent-context.js +82 -0
  7. package/dist/agents-guide.js +42 -42
  8. package/dist/authoring.js +12 -2
  9. package/dist/body-schema.js +159 -0
  10. package/dist/build-cloudflare.js +2 -0
  11. package/dist/capabilities.js +1 -1
  12. package/dist/cli.js +29 -12
  13. package/dist/config.js +74 -6
  14. package/dist/context.js +4 -6
  15. package/dist/errors.js +3 -1
  16. package/dist/examples.js +1 -1
  17. package/dist/extensions.js +138 -3
  18. package/dist/http-policy.js +19 -4
  19. package/dist/http-response.js +2 -2
  20. package/dist/init-with.js +71 -9
  21. package/dist/mcp.js +15 -2
  22. package/dist/pattern-guard.js +32 -0
  23. package/dist/policies/security.js +0 -0
  24. package/dist/policy.js +16 -0
  25. package/dist/project-tests.js +35 -11
  26. package/dist/readiness.js +188 -32
  27. package/dist/recipes.js +1 -1
  28. package/dist/router.js +17 -0
  29. package/dist/runtime.js +26 -3
  30. package/dist/scaffold.js +0 -0
  31. package/dist/server.js +26 -3
  32. package/dist/site.js +0 -0
  33. package/dist/tooling.js +3 -1
  34. package/dist/types/agent-context.d.ts +44 -0
  35. package/dist/types/authoring.d.ts +3 -1
  36. package/dist/types/body-schema.d.ts +58 -0
  37. package/dist/types/config.d.ts +10 -2
  38. package/dist/types/context.d.ts +1 -1
  39. package/dist/types/errors.d.ts +9 -1
  40. package/dist/types/examples.d.ts +1 -1
  41. package/dist/types/extensions.d.ts +105 -2
  42. package/dist/types/http-policy.d.ts +3 -0
  43. package/dist/types/init-with.d.ts +10 -1
  44. package/dist/types/pattern-guard.d.ts +10 -0
  45. package/dist/types/project-tests.d.ts +9 -0
  46. package/dist/types/readiness.d.ts +71 -0
  47. package/dist/types/recipes.d.ts +1 -1
  48. package/dist/types/runtime.d.ts +4 -0
  49. package/dist/types/server.d.ts +9 -1
  50. package/dist/types/tooling.d.ts +2 -0
  51. package/dist/types/types.d.ts +13 -0
  52. package/dist/types.js +10 -2
  53. package/dist/typescript-authoring.js +5 -3
  54. package/dist/verify-deployment.js +16 -10
  55. package/examples/body-validation/README.md +16 -0
  56. package/examples/body-validation/example.yaml +17 -0
  57. package/examples/body-validation/tests/requests.json +15 -0
  58. package/examples/body-validation/urlcode.yaml +34 -0
  59. package/examples/coverage-waiver/README.md +8 -0
  60. package/examples/coverage-waiver/example.yaml +16 -0
  61. package/examples/coverage-waiver/functions/notes.mjs +2 -0
  62. package/examples/coverage-waiver/tests/requests.json +3 -0
  63. package/examples/coverage-waiver/urlcode.yaml +10 -0
  64. package/examples/data-dir/README.md +39 -0
  65. package/examples/data-dir/data/welcome.txt +1 -0
  66. package/examples/data-dir/example.yaml +22 -0
  67. package/examples/data-dir/functions/note.mjs +18 -0
  68. package/examples/data-dir/tests/requests.json +6 -0
  69. package/examples/data-dir/urlcode.yaml +6 -0
  70. package/examples/lifecycle/README.md +15 -0
  71. package/examples/lifecycle/example.yaml +19 -0
  72. package/examples/lifecycle/functions/notes.mjs +29 -0
  73. package/examples/lifecycle/tests/requests.json +12 -0
  74. package/examples/lifecycle/urlcode.yaml +29 -0
  75. package/examples/not-found/README.md +10 -0
  76. package/examples/not-found/example.yaml +17 -0
  77. package/examples/not-found/public/404.html +3 -0
  78. package/examples/not-found/public/index.html +3 -0
  79. package/examples/not-found/tests/requests.json +7 -0
  80. package/examples/not-found/urlcode.yaml +7 -0
  81. package/examples/shared-blocks/README.md +11 -0
  82. package/examples/shared-blocks/example.yaml +17 -0
  83. package/examples/shared-blocks/tests/requests.json +7 -0
  84. package/examples/shared-blocks/urlcode.yaml +37 -0
  85. package/llms-full.txt +436 -149
  86. package/llms.txt +44 -6
  87. package/package.json +15 -22
  88. package/recipes/static-page/README.md +9 -0
  89. package/recipes/static-page/public/index.html +11 -0
  90. package/recipes/static-page/recipe.yaml +21 -0
  91. package/recipes/static-page/tests/requests.json +22 -0
  92. package/recipes/static-page/urlcode.yaml +7 -0
  93. package/recipes/static-plus-api/README.md +6 -0
  94. package/recipes/static-plus-api/urlcode.yaml +4 -0
  95. package/recipes/store-crud/README.md +53 -0
  96. package/recipes/store-crud/recipe.yaml +31 -0
  97. package/recipes/store-crud/tests/requests.json +18 -0
  98. package/recipes/store-crud/urlcode.yaml +18 -0
  99. package/schemas/urlcode.schema.json +125 -60
  100. package/skills/urlcode/SKILL.md +53 -26
  101. package/starters/default/AGENTS.md +43 -43
  102. package/starters/page/README.md +14 -0
  103. package/starters/page/public/index.html +12 -0
  104. package/starters/page/tests/requests.json +17 -0
  105. package/starters/page/urlcode.yaml +6 -0
  106. package/.claude-plugin/marketplace.json +0 -18
  107. package/CONTRIBUTING.md +0 -112
  108. package/ROADMAP.md +0 -66
  109. package/docs/AI-AUTHORING.md +0 -338
  110. package/docs/ASSETS.md +0 -107
  111. package/docs/AUTH-BACKUP.md +0 -32
  112. package/docs/AWS.md +0 -86
  113. package/docs/BEST-PRACTICES.md +0 -276
  114. package/docs/BULK.md +0 -79
  115. package/docs/CAPABILITIES.md +0 -192
  116. package/docs/CAPACITY.md +0 -305
  117. package/docs/CI-FOLLOWUP-2026-09-19.md +0 -97
  118. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +0 -322
  119. package/docs/CI.md +0 -147
  120. package/docs/CLOUDFLARE.md +0 -109
  121. package/docs/CODEBASE-AUDIT-2026-09-20.md +0 -284
  122. package/docs/COMPLIANCE.md +0 -239
  123. package/docs/COMPOSING-A-SITE.md +0 -278
  124. package/docs/CONDITIONS.md +0 -74
  125. package/docs/DEPLOYMENT-CHECKS.md +0 -108
  126. package/docs/DEVELOPMENT-PIPELINE.md +0 -270
  127. package/docs/EGRESS.md +0 -125
  128. package/docs/EXTENSIONS.md +0 -438
  129. package/docs/FRAMEWORK.md +0 -217
  130. package/docs/FUNCTION-SECURITY.md +0 -254
  131. package/docs/HTTP.md +0 -129
  132. package/docs/INSTALL.md +0 -128
  133. package/docs/INTERCHANGE.md +0 -134
  134. package/docs/LOAD-TESTING.md +0 -91
  135. package/docs/LOCAL-DEVELOPMENT.md +0 -102
  136. package/docs/MIDDLEWARE-EXAMPLES.md +0 -75
  137. package/docs/MIDDLEWARE.md +0 -102
  138. package/docs/MONITORING.md +0 -115
  139. package/docs/OBSERVABILITY.md +0 -222
  140. package/docs/OPEN-DECISIONS.md +0 -224
  141. package/docs/OPERATIONAL-PROOF.md +0 -41
  142. package/docs/OPERATIONS.md +0 -201
  143. package/docs/ORGANIZATION.md +0 -135
  144. package/docs/PERFORMANCE.md +0 -72
  145. package/docs/PLUGINS.md +0 -271
  146. package/docs/POLICIES.md +0 -211
  147. package/docs/PRERENDER.md +0 -245
  148. package/docs/PROJECT-DIRECTION.md +0 -118
  149. package/docs/PROVIDER-VERIFICATION.md +0 -84
  150. package/docs/READINESS.md +0 -150
  151. package/docs/README.md +0 -87
  152. package/docs/RECIPES.md +0 -99
  153. package/docs/RELEASE-0.4.0-alpha.3.md +0 -50
  154. package/docs/RELEASE-0.4.1.md +0 -73
  155. package/docs/RELEASE-READINESS.md +0 -117
  156. package/docs/RELEASE-SECURITY.md +0 -96
  157. package/docs/RESILIENCE.md +0 -161
  158. package/docs/ROUTING.md +0 -92
  159. package/docs/SANDBOX-REVIEW.md +0 -72
  160. package/docs/SCAFFOLDING.md +0 -70
  161. package/docs/SECURITY-AUDIT.md +0 -164
  162. package/docs/SITE.md +0 -150
  163. package/docs/SPECIFICATION.md +0 -359
  164. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +0 -288
  165. package/docs/SPIKE-BUSINESS-SUITE.md +0 -1029
  166. package/docs/SPIKE-CORE-LAYERING.md +0 -368
  167. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +0 -211
  168. package/docs/STANDARDS.md +0 -311
  169. package/docs/STARTERS.md +0 -83
  170. package/docs/STATIC.md +0 -105
  171. package/docs/TOOLING.md +0 -298
  172. package/docs/TUNNELS.md +0 -72
  173. package/docs/TYPESCRIPT-AUTHORING.md +0 -87
  174. package/docs/TYPESCRIPT.md +0 -123
  175. package/docs/VERCEL.md +0 -114
  176. package/docs/VERSION-ALIGNMENT.md +0 -80
  177. package/docs/YAML-GUIDE.md +0 -57
  178. package/docs/YAML-REFERENCE.md +0 -449
  179. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +0 -68
  180. package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +0 -102
  181. package/docs/archive/2026-09-19/NEXT-PHASE-PLAN.md +0 -108
  182. package/docs/archive/2026-09-19/NEXT-STEPS.md +0 -646
  183. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +0 -277
  184. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +0 -186
  185. package/docs/archive/2026-09-19/ROADMAP.md +0 -387
  186. package/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md +0 -430
  187. package/docs/archive/2026-09-19/SPIKE-EXTENSIONS.md +0 -492
  188. package/docs/archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md +0 -365
  189. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +0 -778
  190. package/docs/archive/2026-09-19/USABILITY-REVIEW.md +0 -139
  191. package/docs/archive/README.md +0 -28
  192. package/docs/policies/agents.md +0 -182
  193. package/docs/policies/cache.md +0 -152
  194. package/docs/policies/compression.md +0 -169
  195. package/docs/policies/contract.md +0 -52
  196. package/docs/policies/hardened.md +0 -56
  197. package/docs/policies/interoperability.md +0 -169
  198. package/docs/policies/operations.md +0 -45
  199. package/docs/policies/security.md +0 -161
  200. package/docs/policies/throttle.md +0 -103
  201. package/docs/yaml/assets.md +0 -36
  202. package/docs/yaml/conditions.md +0 -20
  203. package/docs/yaml/functions.md +0 -168
  204. package/docs/yaml/middleware.md +0 -31
  205. package/docs/yaml/organization.md +0 -74
  206. package/docs/yaml/policies.md +0 -37
  207. package/docs/yaml/redirects.md +0 -64
  208. package/docs/yaml/responses.md +0 -57
  209. package/docs/yaml/site.md +0 -24
  210. package/packaging/claude-plugin/.claude-plugin/plugin.json +0 -19
  211. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +0 -120
  212. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +0 -108
@@ -1,103 +0,0 @@
1
- # Throttle policy
2
-
3
- `policies.throttle` gives each client, route or client-route pair a request
4
- budget: `quota` requests per `window` seconds. The runtime counts requests in
5
- a sliding window (two fixed windows blended by elapsed time, the usual middle
6
- ground between a fixed window that bursts at its edges and a token bucket
7
- that is hard to explain in a header) and refuses the request that would
8
- exceed the budget before it reads a body or reaches the sandbox. Refused
9
- requests are not counted, so a retrying client cannot keep its own window
10
- from clearing.
11
-
12
- ```yaml
13
- version: "1"
14
- policies:
15
- throttle:
16
- quota: 120 # requests
17
- window: 60 # seconds
18
- partition: client # client | route | client-route (default client)
19
- status: 429 # 4xx or 5xx answered on refusal (default 429)
20
- mode: enforce # enforce | report (default enforce)
21
- maxKeys: 100000 # bounded counter table, LRU eviction
22
-
23
- routes:
24
- /api/lookup/{id}:
25
- function: { source: functions/lookup.mjs }
26
- policies:
27
- throttle: { quota: 10, window: 60 } # tighter budget with its own counter
28
- ```
29
-
30
- A route override that restates `quota` and `window` gets its own counters; a
31
- route that inherits the project budget shares the client's counter with every
32
- other route on the same budget. `throttle: false` on a route turns it off there.
33
-
34
- ## Standards
35
-
36
- - [RFC 6585](https://www.rfc-editor.org/rfc/rfc6585) status 429 by default;
37
- any 4xx or 5xx `status` is accepted and the body is a short plain-text line.
38
- - [RFC 9110 `Retry-After`](https://www.rfc-editor.org/rfc/rfc9110#field.retry-after),
39
- integer seconds, on every refusal.
40
- - [IETF httpapi RateLimit header fields](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/)
41
- on every response, allowed or refused, in structured-field syntax:
42
- `RateLimit-Policy: "default";q=<quota>;w=<window>` and
43
- `RateLimit: "default";r=<remaining>;t=<seconds until reset>`.
44
-
45
- A refusal also carries `content-type: text/plain; charset=utf-8` and
46
- `cache-control: no-store` so no intermediary keeps it.
47
-
48
- ## Targets
49
-
50
- | Target | `partition: route` | `client` / `client-route` |
51
- |---|---|---|
52
- | node (`urlcode serve`) | native | native |
53
- | vercel | native, per instance | refused at activation |
54
- | aws | native, per instance | refused at activation |
55
- | cloudflare | refused | refused |
56
-
57
- Counters live in the process. On serverless targets a route counter is at
58
- least an honest per-instance ceiling, while a client counter would silently
59
- become `quota × instances`, so those partitions are refused with the route
60
- named. The Cloudflare build refuses the policy; map the same `quota` and
61
- `window` to a provider rate rule instead.
62
-
63
- ## Client identity
64
-
65
- `client` is the socket peer address. Behind a load balancer every request
66
- would share one address, so name the proxies allowed to speak for a client:
67
- `urlcode serve --trusted-proxies 10.0.0.0/8,::1`. Only then is
68
- `X-Forwarded-For` consulted, walking from the right past trusted hops. When no
69
- client can be resolved (a caller that passed none, an adapter without a peer)
70
- requests share a single bucket rather than escaping the budget; the policy
71
- inventory (`urlcode audit`, `testPlan().policies`) reports this as
72
- `unresolvedClient: "shared key"`.
73
-
74
- ## Report before enforce
75
-
76
- Run a release with `mode: report`: headers are emitted and every request logs
77
- `{ event: "throttle", route, outcome: "allowed" | "exceeded", remaining }`
78
- (never the client address), but nothing is refused. Read the `exceeded` lines
79
- against real traffic, set `quota` from what you saw, then switch to `enforce`.
80
- In enforce mode only `exceeded` is logged.
81
-
82
- ## What it does not do
83
-
84
- - No shared state across processes or instances. Two `urlcode serve`
85
- processes behind one balancer each enforce the full budget. A host plugin
86
- (`onRequest` returning a refusal, backed by whatever store you run) is the
87
- place for a cluster-wide budget; see the plugin contract in
88
- `docs/SPIKE-EXTENSIONS.md`.
89
- - No per-user or per-token keys. The partition is address or route; a token
90
- bucket per API key is again a plugin.
91
- - Counters do not survive a reload: a new snapshot starts empty.
92
- - `maxKeys` bounds memory with least-recently-used eviction; an evicted key
93
- starts fresh, so a table sized below the number of concurrent clients
94
- under-counts rather than blocks.
95
-
96
- ## Interaction with other policies
97
-
98
- Request order is agents, throttle, cache. A denied agent never touches a
99
- counter; a throttled request never reaches the cache lookup or the handler.
100
- Security headers and compression still apply to a refusal. A cache hit skips
101
- only the cache's own response hook, so a cached response still carries the
102
- client's `RateLimit` headers; the request was counted. A 405 is counted too
103
- and carries them.
@@ -1,36 +0,0 @@
1
- # YAML guide: Pages, static folders and downloads
2
-
3
- Snippets are entries under `routes:` unless stated otherwise; the [guide index](../YAML-GUIDE.md) lists every page.
4
-
5
- ## 10. Pages, static folders, downloads and MIME
6
-
7
- ```yaml
8
- /about:
9
- page:
10
- file: public/about.html
11
- cacheControl: no-cache
12
- /assets/*:
13
- static:
14
- directory: public/assets
15
- index: index.html
16
- cacheControl: public, max-age=3600
17
- /download:
18
- download:
19
- file: public/guide.txt
20
- filename: urlcode-guide.txt
21
- contentType: text/plain
22
- cacheControl: no-store
23
- ```
24
-
25
- All files must exist. MIME is detected by extension, not content sniffing; unknown
26
- extensions become application/octet-stream. `contentType` overrides detection
27
- without MIME parameters. An override on a static mount affects all its files.
28
- The download name defaults to the source basename. `index` is opt-in and only
29
- applies to slash-terminated requests. No automatic slash redirect or SPA fallback.
30
-
31
- Cache choices: `no-cache` (asset default), `no-store`, `public, max-age=3600`,
32
- `public, max-age=31536000, immutable`. Reserve immutable caching for versioned
33
- URLs. GET/HEAD, ETag/date validation and single byte ranges are supported.
34
- Files stay snapshotted until reload/restart. See [assets](../ASSETS.md) for complete
35
- conditional/range semantics and publication safety. Files are limited to 16 MiB
36
- each and 64 MiB total unique bytes per snapshot.
@@ -1,20 +0,0 @@
1
- # YAML guide: Enable, disable and expire
2
-
3
- Snippets are entries under `routes:` unless stated otherwise; the [guide index](../YAML-GUIDE.md) lists every page.
4
-
5
- ## 11. Enable, disable and expire
6
-
7
- ```yaml
8
- /paused:
9
- enabled: false
10
- redirect: {url: 'https://example.com/'}
11
- /campaign:
12
- description: A scheduled end, no scheduled start
13
- expires: "2030-01-01T00:00:00Z"
14
- redirect: {url: 'https://example.com/'}
15
- ```
16
-
17
- Quote timestamps so they remain strings. Disabled routes return 404; expired
18
- routes return 410. Expiry is an absolute UTC timestamp, not a TTL. There is no
19
- start-time scheduler. `description` is authoring metadata. Changing YAML activates
20
- through dev reload or production restart/deployment; it is not an HTTP mutation.
@@ -1,168 +0,0 @@
1
- # YAML guide: Functions, inputs and methods
2
-
3
- Snippets are entries under `routes:` unless stated otherwise; the [guide index](../YAML-GUIDE.md) lists every page.
4
-
5
- ## 1. A URL that runs code
6
-
7
- A complete `urlcode.yaml`:
8
-
9
- ```yaml
10
- version: "1"
11
- routes:
12
- /hello/{name}:
13
- function: functions/hello.mjs
14
- env:
15
- GREETING: {value: Hello}
16
- ```
17
-
18
- Create `functions/hello.mjs`:
19
-
20
- ```js
21
- export default function hello(request, {args, env}) {
22
- return Response.json({message: `${env.GREETING}, ${args.name}!`});
23
- }
24
- ```
25
-
26
- GET `/hello/Ada` returns JSON. HEAD invokes the function and suppresses the body.
27
-
28
- `function: functions/hello.mjs` is the short form. The loader expands it to the
29
- long form before anything else reads the route: every `{param}` in the path
30
- becomes a required string input (`minLength: 1`, `maxLength: 128`) and a
31
- matching `args` entry, so the route above is the same route as
32
-
33
- ```yaml
34
- /hello/{name}:
35
- parameters:
36
- - name: name
37
- in: path
38
- required: true
39
- schema: {type: string, minLength: 1, maxLength: 128}
40
- function:
41
- source: functions/hello.mjs
42
- args:
43
- name: {from: path, name: name}
44
- env:
45
- GREETING: {value: Hello}
46
- ```
47
-
48
- A path parameter the route declares itself keeps its own schema; only the
49
- undeclared ones get the default. Use the long form when you need a named
50
- `export`, query, header, `env` or `secret` arguments, or a tighter path schema.
51
- `routes`, `audit` and `explain` show the expansion. Middleware has the same
52
- short form: `middleware: [middleware/headers.mjs]` means
53
- `middleware: [{source: middleware/headers.mjs}]`. The path must be
54
- project-relative, end in `.mjs` or `.js` and contain no `..` segment.
55
- Methods default to GET and HEAD. Function paths resolve from the project root,
56
- not the YAML file's directory. Modules are ES modules either way: `.mjs` always
57
- works, while a trusted `.js` module is loaded by Node and therefore needs the
58
- nearest `package.json` to say `"type": "module"` (a `sandbox: true` route reads
59
- `.js` as ESM regardless). Serving never transpiles TypeScript; build it first
60
- with [TypeScript authoring](../TYPESCRIPT-AUTHORING.md). The route above
61
- declares no `sandbox`, so it runs trusted and in-process: Node APIs, npm
62
- imports, network and filesystem access are all available to it, exactly as they
63
- are to any other Node code in the host. Add `sandbox: true` to trade them away
64
- for isolation -- inside the guest none of them exist. See
65
- [trust model and sandbox opt-in](../FUNCTION-SECURITY.md).
66
-
67
- ## 4. Input types and constraints
68
-
69
- Use this list under a route's `parameters` when those inputs are needed:
70
-
71
- ```yaml
72
- parameters:
73
- - name: search
74
- in: query
75
- required: true
76
- schema: {type: string, minLength: 1, maxLength: 200}
77
- - name: page
78
- in: query
79
- schema: {type: integer, minimum: 1, default: 1}
80
- - name: weight
81
- in: query
82
- schema: {type: number, minimum: 0, maximum: 1}
83
- - name: preview
84
- in: query
85
- schema: {type: boolean, default: false}
86
- - name: category
87
- in: query
88
- schema: {type: string, enum: [docs, news], default: docs}
89
- - name: ids
90
- in: query
91
- schema: {type: array, items: {type: integer}, maxItems: 10}
92
- ```
93
-
94
- Path inputs must be required strings with no default. Query/header scalar types
95
- are string, integer, number and boolean; arrays are query-only. Booleans are
96
- exactly `true`/`false`; numbers do not accept exponent notation or whitespace.
97
- Defaults apply to absence, not empty strings. Duplicate scalar values fail.
98
- Required, missing and invalid inputs return 400. This is a documented subset,
99
- not full OpenAPI/JSON Schema: no `pattern`, `format`, nested input objects,
100
- `oneOf`, `style` or `explode` in parameter schemas.
101
-
102
- ## 5. Methods and body validation
103
-
104
- ```yaml
105
- /echo:
106
- methods: [POST]
107
- request:
108
- body:
109
- required: true
110
- maxBytes: 4096
111
- contentTypes: [application/json]
112
- format: json
113
- function:
114
- source: functions/echo.mjs
115
- ```
116
-
117
- ```js
118
- export default async function echo(request) {
119
- return Response.json(await request.json());
120
- }
121
- ```
122
-
123
- This validates JSON syntax/media type/UTF-8 and body size, not an application
124
- object schema. Validate business fields in code. Empty required body: 400;
125
- oversized body: 413; wrong media type: 415. For text, use `contentTypes:
126
- [text/plain]`, `format: text`, and `request.text()`; see the runnable `/text`
127
- recipe. `maxBytes: 0` can reject nonempty bodies. Bodies are buffered, not streamed.
128
-
129
- Allowed methods: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS. An explicit list
130
- replaces the defaults: `[GET]` does not add HEAD. Wrong method returns 405 with
131
- Allow. Asset handlers accept only GET/HEAD. Body bytes are not automatically
132
- mapped to function arguments.
133
-
134
- ## 6. All function argument sources
135
-
136
- Within `function.args`, supported values are:
137
-
138
- ```yaml
139
- args:
140
- label: campaign
141
- attempts: 3
142
- preview: false
143
- code: {from: path, name: code}
144
- page: {from: query, name: page}
145
- channel: {from: header, name: x-channel}
146
- greeting: {env: GREETING}
147
- token: {secret: TOKEN}
148
- ```
149
-
150
- This is a field-shape illustration: declare the referenced path/query/header
151
- inputs and route binding aliases before using it. Null, array and arbitrary
152
- object arguments are not supported. Read `context.args` or directly access
153
- `context.inputs.path/query/header`, `context.env` and `context.secrets`.
154
- `function.export` selects a named export; omit it for `default`.
155
-
156
- For a dynamic redirect, use validated choices instead of accepting any URL:
157
-
158
- ```js
159
- export default function choice(request, {args}) {
160
- const destinations = {docs: 'https://example.com/docs', home: 'https://example.com/'};
161
- return Response.redirect(destinations[args.destination], 302);
162
- }
163
- ```
164
-
165
- The runnable `/choice` recipe declares an enum query input and binds it to args.
166
- Functions can return `Response.json(...)`, `new Response('text', {status, headers})`,
167
- or `Response.redirect(...)`. HTML is a string response with Content-Type text/html;
168
- escape untrusted values yourself. See the exact [guest API](../SPECIFICATION.md#functions).
@@ -1,31 +0,0 @@
1
- # YAML guide: Middleware
2
-
3
- Snippets are entries under `routes:` unless stated otherwise; the [guide index](../YAML-GUIDE.md) lists every page.
4
-
5
- ## 7. Middleware before and after a handler
6
-
7
- ```yaml
8
- /go-with-header:
9
- middleware:
10
- - source: middleware/headers.mjs
11
- export: decorate
12
- redirect: {url: 'https://example.com/'}
13
- ```
14
-
15
- ```js
16
- export async function decorate(request, context, next) {
17
- context.state.example = 'cookbook';
18
- const response = await next();
19
- response.headers.set('x-middleware', context.state.example);
20
- return response;
21
- }
22
- ```
23
-
24
- Entries run in declared order before the handler and reverse order afterward.
25
- Return a Response early to skip downstream code; call `next()` at most once.
26
- Up to 16 middleware entries share the route's execution mode, one deadline and
27
- one set of approved route bindings; the mode is the route's `sandbox` field, not
28
- a per-entry choice. On a `sandbox: true` route native bodies are opaque;
29
- preserving them requires retaining original status/headers. Return a new
30
- Response to replace native content or destination.
31
- YAML response headers apply last. See [middleware](../MIDDLEWARE.md) for details.
@@ -1,74 +0,0 @@
1
- # YAML guide: Bindings, split files and tests
2
-
3
- Snippets are entries under `routes:` unless stated otherwise; the [guide index](../YAML-GUIDE.md) lists every page.
4
-
5
- ## 12. Environment and secret references
6
-
7
- Route-level shape (references only, never secret values):
8
-
9
- ```yaml
10
- env:
11
- GREETING: {value: Hello}
12
- REGION: {env: APP_REGION}
13
- secrets:
14
- TOKEN: {secret: APP_TOKEN}
15
- ```
16
-
17
- Literal non-secret env needs no grant. External env and secrets require an
18
- operator-owned policy outside the checkout, granting exact names to the route
19
- and pinning the reviewed config/code digest. `urlcode permissions --project
20
- ./my-links` prints a proposed policy; review it and store it outside the app.
21
- Then pass `--policy /operator/path/policy.json` to validate/dev/test/serve.
22
- This inspection does not authorize the project or execute its code.
23
-
24
- Use ignored `.env.local` for local values; process environment wins. Production
25
- `serve` reads process environment, never `.env.local`. Let your supervisor resolve
26
- provider secrets and inject them; direct provider secret-store adapters do not
27
- exist yet. Every config/code change invalidates the grant; rotate values by
28
- restarting/redeploying. Never return a secret in an example response. Middleware
29
- and functions on an approved route can read its bindings. See [policy setup](../FUNCTION-SECURITY.md).
30
-
31
- ## 13. Split files and folders
32
-
33
- Complete entry point:
34
-
35
- ```yaml
36
- version: "1"
37
- includes:
38
- - routes/code.yaml
39
- - routes/marketing/links.yaml
40
- routes: {}
41
- ```
42
-
43
- Each included file contains `version: "1"` and `routes`. No nested includes,
44
- globs, anchors, merge keys or remote includes. References always use project-root
45
- paths. Duplicate routes fail; include order is not priority. See [organization](../ORGANIZATION.md)
46
- and [matching/regex/wildcard rules](../ROUTING.md).
47
-
48
- ## 14. Assert inputs and outputs
49
-
50
- Save a JSON array as `tests/requests.json`:
51
-
52
- ```json
53
- [
54
- {"path":"/hello/Ada","status":200,"expectBody":"{\"message\":\"Hello, Ada!\"}"},
55
- {"path":"/hello/Ada","method":"HEAD","status":200,"expectBody":""},
56
- {"path":"/go","status":302,"expectHeaders":{"location":"https://example.com/"}},
57
- {"path":"/go","method":"POST","status":405,"expectHeaders":{"allow":"GET, HEAD"}}
58
- ]
59
- ```
60
-
61
- This fixture targets recipes 1 and 2 together; the runnable cookbook has its own
62
- matching expectations. Supported test fields: `path`, optional `method`, request
63
- `headers` and string `body`, required `status`, optional exact string `expectBody`
64
- and string-map `expectHeaders`. Tests do not follow external redirects. There
65
- are no route-local YAML test fields or JSON-path assertions yet.
66
-
67
- Run validate, test, routes, and audit with an intentional expected count. Test
68
- both positive and negative inputs, every allowed method, HEAD, middleware short
69
- circuits and relevant asset conditions. Audit needs meaningful body/header
70
- assertions; a status-only success is insufficient. Benchmarks and recovery drills
71
- are separate from functional correctness. See [readiness](../READINESS.md).
72
-
73
- See [organization and readability practices](../BEST-PRACTICES.md) for conventions
74
- that keep larger projects easy to maintain.
@@ -1,37 +0,0 @@
1
- # YAML guide: Policies and profiles
2
-
3
- Snippets are entries under `routes:` unless stated otherwise; the [guide index](../YAML-GUIDE.md) lists every page.
4
-
5
- ## 16. Hardened profile and per-route overrides
6
-
7
- ```yaml
8
- version: "1"
9
- policies:
10
- profile: hardened # security, agents, throttle, compression, cache
11
- throttle: { quota: 60, window: 60 } # tighten one number; the rest stays
12
- profiles:
13
- public-api:
14
- security: { headers: oshp-no-csp, set: { x-robots-tag: noindex } }
15
- cache: { strategy: swr, maxAge: 30, staleWhileRevalidate: 300 }
16
- routes:
17
- /:
18
- page: {source: pages/index.html}
19
- /api/lookup/{id}:
20
- parameters:
21
- - {name: id, in: path, required: true, schema: {type: string, maxLength: 64}}
22
- function: {source: functions/lookup.mjs}
23
- policies:
24
- profile: public-api # merges over the project layer
25
- throttle: {quota: 10, window: 60, partition: client-route}
26
- /healthz:
27
- respond: {text: ok}
28
- policies: {throttle: false, agents: false}
29
- ```
30
-
31
- Everything under `policies` is optional and off unless declared. The project
32
- block sets defaults, a route block adjusts them, `false` removes one policy for
33
- that route and an object merges shallowly over what is below it. `hardened`
34
- is the only built-in profile; `profiles` defines your own. Serve with
35
- `--trusted-proxies` when a proxy sits in front so `client` partitioning sees
36
- the real peer. Not every target accepts every policy: see the per-target
37
- table in [policies](../POLICIES.md) before deploying the same YAML to an adapter.
@@ -1,64 +0,0 @@
1
- # YAML guide: Redirects
2
-
3
- Snippets are entries under `routes:` unless stated otherwise; the [guide index](../YAML-GUIDE.md) lists every page.
4
-
5
- ## 2. Ordinary and permanent redirects
6
-
7
- The following snippets are **entries under `routes:`** unless stated otherwise:
8
-
9
- ```yaml
10
- /go:
11
- redirect:
12
- url: https://example.com/
13
- /moved:
14
- redirect:
15
- url: https://example.com/new
16
- status: 308
17
- response:
18
- headers:
19
- Cache-Control: public, max-age=60
20
- ```
21
-
22
- 302 is the default. Allowed codes are 301, 302, 303, 307 and 308. Choose status
23
- and cache policy deliberately: a cached permanent redirect can outlive a server
24
- rollback. Requests' query strings are not forwarded by default. No function or
25
- middleware means no sandbox execution for these routes.
26
-
27
- ## 3. Parameterized redirects and explicit query forwarding
28
-
29
- ```yaml
30
- /product/{id}:
31
- parameters:
32
- - name: id
33
- in: path
34
- required: true
35
- schema: {type: string, minLength: 1, maxLength: 64}
36
- - name: page
37
- in: query
38
- schema: {type: integer, minimum: 1, maximum: 100, default: 1}
39
- - name: tag
40
- in: query
41
- schema: {type: array, items: {type: string}, maxItems: 3}
42
- - name: x-channel
43
- in: header
44
- schema: {type: string, enum: [web, email], default: web}
45
- redirect:
46
- url: https://example.com/products/{id}
47
- query:
48
- map:
49
- page: {from: query, name: page}
50
- label: {from: query, name: tag}
51
- channel: {from: header, name: x-channel}
52
- pass: [utm_source]
53
- ```
54
-
55
- `/product/abc?page=2&tag=red&tag=blue&utm_source=news&ignored=no` redirects to
56
- `https://example.com/products/abc?page=2&label=red&label=blue&channel=web&utm_source=news`.
57
- The unknown `ignored` key is dropped. Missing `page` becomes 1. Arrays produce
58
- repeated destination keys. Invalid page values return 400 before redirecting.
59
-
60
- Destination hosts/schemes are literal HTTP(S); path placeholders are safely
61
- encoded. No arbitrary input-controlled host, credentials, secret interpolation,
62
- or unrestricted `pass: true`. Mapping/passthrough keys must not collide with
63
- existing destination keys. Header inputs are client-supplied values, not proof
64
- of identity. See [input and redirect semantics](../SPECIFICATION.md).
@@ -1,57 +0,0 @@
1
- # YAML guide: Declared responses, headers and cookies
2
-
3
- Snippets are entries under `routes:` unless stated otherwise; the [guide index](../YAML-GUIDE.md) lists every page.
4
-
5
- ## 8. Native responses, headers and cookies
6
-
7
- ```yaml
8
- /status:
9
- respond:
10
- json: {ok: true, service: my-links}
11
- /notice:
12
- respond:
13
- status: 503
14
- text: Temporarily unavailable
15
- response:
16
- headers:
17
- Retry-After: "60"
18
- /empty:
19
- respond: {status: 204}
20
- /cookie-demo:
21
- respond: {text: Non-secret preferences only}
22
- response:
23
- headers:
24
- Set-Cookie:
25
- - "theme=light; Path=/; Secure; SameSite=Lax"
26
- - "notice=seen; Path=/; HttpOnly; Secure; SameSite=Lax"
27
- ```
28
-
29
- `respond` defaults to 200. Use text or JSON, never both. Omit both for an empty
30
- body; 204/205 must have no body. 206/304 belong to native asset handling.
31
- Header values are literal strings; quote numeric-looking values. Only Set-Cookie
32
- accepts arrays. Do not put live session tokens in YAML. Secure cookies require
33
- HTTPS at the browser. Functions can create dynamic cookies, but no cookie
34
- parsing/signing/authentication framework is built in.
35
-
36
- Do not set Content-Length, Location, Allow, ETag, Content-Range or other
37
- runtime-owned headers in YAML. Use the corresponding handler. The full reserved
38
- list and precedence rules are in [HTTP](../HTTP.md).
39
-
40
- ## 9. Explicit OPTIONS response (not automatic CORS)
41
-
42
- ```yaml
43
- /preflight:
44
- methods: [OPTIONS]
45
- respond: {status: 204}
46
- response:
47
- headers:
48
- Access-Control-Allow-Origin: https://app.example.com
49
- Access-Control-Allow-Methods: GET, HEAD
50
- Access-Control-Allow-Headers: Content-Type
51
- ```
52
-
53
- This teaches declared headers only; it is not a working cross-origin GET API.
54
- For a real API, OPTIONS and the actual methods must be handled on the same URL,
55
- and actual responses also need the appropriate CORS headers. Because one path
56
- has one handler, use a function with `[GET, HEAD, OPTIONS]` to branch on method.
57
- Never reflect arbitrary Origin with credentials. Automatic CORS is unsupported.
package/docs/yaml/site.md DELETED
@@ -1,24 +0,0 @@
1
- # YAML guide: Site conventions
2
-
3
- Snippets are entries under `routes:` unless stated otherwise; the [guide index](../YAML-GUIDE.md) lists every page.
4
-
5
- ## 17. Site conventions
6
-
7
- Generate the well-known files from a top-level `site` block instead of writing
8
- them by hand. Each declared key becomes one native route counted by
9
- `routes`/`audit`; a declared route at the same path wins.
10
-
11
- ```yaml
12
- site:
13
- robots:
14
- disallow: [ai-crawlers, /admin]
15
- sitemap: true # Sitemap line needs --origin
16
- favicon: public/favicon.svg # served at /favicon.ico
17
- securityTxt:
18
- contact: [mailto:security@example.com]
19
- expires: "2099-01-01T00:00:00Z"
20
- llms: public/llms.txt # served at /llms.txt
21
- # sitemap: true # /sitemap.xml; refuses to start without --origin
22
- ```
23
-
24
- See [site conventions](../SITE.md) for every field, exclusions and target support.
@@ -1,19 +0,0 @@
1
- {
2
- "name": "urlcode",
3
- "description": "Authoring and operating URLCode projects: the implemented YAML contract, capability limits, deployment and verification commands for the pinned runtime revision.",
4
- "version": "0.4.1",
5
- "author": {
6
- "name": "jimhoyd-com",
7
- "url": "https://github.com/jimhoyd-com"
8
- },
9
- "homepage": "https://github.com/jimhoyd-com/urlcode",
10
- "repository": "https://github.com/jimhoyd-com/urlcode.git",
11
- "license": "Apache-2.0",
12
- "keywords": [
13
- "urlcode",
14
- "routing",
15
- "yaml",
16
- "redirects",
17
- "middleware"
18
- ]
19
- }