create-daloy 1.0.0-beta.3 → 1.0.0-beta.5
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/package.json +2 -2
- package/sbom.cdx.json +9 -9
- package/sbom.spdx.json +5 -5
- package/templates/bun-basic/AGENTS.md +17 -7
- package/templates/bun-basic/_agents/skills/daloyjs-best-practices/SKILL.md +50 -13
- package/templates/bun-basic/package.json +1 -1
- package/templates/cloudflare-worker/AGENTS.md +17 -6
- package/templates/cloudflare-worker/_agents/skills/daloyjs-best-practices/SKILL.md +45 -8
- package/templates/cloudflare-worker/package.json +1 -1
- package/templates/deno-basic/AGENTS.md +17 -7
- package/templates/deno-basic/_agents/skills/daloyjs-best-practices/SKILL.md +46 -9
- package/templates/deno-basic/deno.json +5 -5
- package/templates/node-basic/AGENTS.md +20 -8
- package/templates/node-basic/_agents/skills/daloyjs-best-practices/SKILL.md +50 -13
- package/templates/node-basic/package.json +2 -2
- package/templates/vercel/AGENTS.md +25 -13
- package/templates/vercel/_agents/skills/daloyjs-best-practices/SKILL.md +44 -7
- package/templates/vercel/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-daloy",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.5",
|
|
4
4
|
"description": "Scaffold a new DaloyJS project. Run with `pnpm create daloy`, `npm create daloy@latest`, `yarn create daloy`, or `bun create daloy`.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"openapi"
|
|
28
28
|
],
|
|
29
29
|
"engines": {
|
|
30
|
-
"node": "
|
|
30
|
+
"node": "^24.0.0 || >=26.0.0"
|
|
31
31
|
},
|
|
32
32
|
"bin": {
|
|
33
33
|
"create-daloy": "bin/create-daloy.mjs"
|
package/sbom.cdx.json
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.5",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:b7fc0806-0eba-57f4-8949-d430e24d329a",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-
|
|
7
|
+
"timestamp": "2026-07-01T13:38:30.022Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "DaloyJS",
|
|
11
11
|
"name": "daloy-generate-sbom",
|
|
12
|
-
"version": "1.0.0-beta.
|
|
12
|
+
"version": "1.0.0-beta.5"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"authors": [],
|
|
16
16
|
"component": {
|
|
17
17
|
"type": "library",
|
|
18
|
-
"bom-ref": "pkg:npm/create-daloy@1.0.0-beta.
|
|
18
|
+
"bom-ref": "pkg:npm/create-daloy@1.0.0-beta.5",
|
|
19
19
|
"name": "create-daloy",
|
|
20
|
-
"version": "1.0.0-beta.
|
|
20
|
+
"version": "1.0.0-beta.5",
|
|
21
21
|
"description": "Scaffold a new DaloyJS project. Run with `pnpm create daloy`, `npm create daloy@latest`, `yarn create daloy`, or `bun create daloy`.",
|
|
22
|
-
"purl": "pkg:npm/create-daloy@1.0.0-beta.
|
|
22
|
+
"purl": "pkg:npm/create-daloy@1.0.0-beta.5",
|
|
23
23
|
"licenses": [
|
|
24
24
|
{
|
|
25
25
|
"license": {
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
}
|
|
43
43
|
],
|
|
44
44
|
"swid": {
|
|
45
|
-
"tagId": "swidtag-create-daloy-1.0.0-beta.
|
|
45
|
+
"tagId": "swidtag-create-daloy-1.0.0-beta.5",
|
|
46
46
|
"name": "create-daloy",
|
|
47
|
-
"version": "1.0.0-beta.
|
|
47
|
+
"version": "1.0.0-beta.5",
|
|
48
48
|
"tagVersion": 0,
|
|
49
49
|
"patch": false
|
|
50
50
|
}
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"components": [],
|
|
54
54
|
"dependencies": [
|
|
55
55
|
{
|
|
56
|
-
"ref": "pkg:npm/create-daloy@1.0.0-beta.
|
|
56
|
+
"ref": "pkg:npm/create-daloy@1.0.0-beta.5",
|
|
57
57
|
"dependsOn": []
|
|
58
58
|
}
|
|
59
59
|
]
|
package/sbom.spdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"spdxVersion": "SPDX-2.3",
|
|
3
3
|
"dataLicense": "CC0-1.0",
|
|
4
4
|
"SPDXID": "SPDXRef-DOCUMENT",
|
|
5
|
-
"name": "create-daloy-1.0.0-beta.
|
|
6
|
-
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-1.0.0-beta.
|
|
5
|
+
"name": "create-daloy-1.0.0-beta.5",
|
|
6
|
+
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-1.0.0-beta.5-b7fc0806-0eba-57f4-8949-d430e24d329a",
|
|
7
7
|
"creationInfo": {
|
|
8
|
-
"created": "2026-
|
|
8
|
+
"created": "2026-07-01T13:38:30.022Z",
|
|
9
9
|
"creators": [
|
|
10
10
|
"Tool: daloy-generate-sbom",
|
|
11
11
|
"Organization: DaloyJS"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
"SPDXID": "SPDXRef-Package-create-daloy",
|
|
18
18
|
"name": "create-daloy",
|
|
19
|
-
"versionInfo": "1.0.0-beta.
|
|
19
|
+
"versionInfo": "1.0.0-beta.5",
|
|
20
20
|
"downloadLocation": "https://github.com/daloyjs/daloy",
|
|
21
21
|
"filesAnalyzed": false,
|
|
22
22
|
"licenseConcluded": "MIT",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
{
|
|
28
28
|
"referenceCategory": "PACKAGE-MANAGER",
|
|
29
29
|
"referenceType": "purl",
|
|
30
|
-
"referenceLocator": "pkg:npm/create-daloy@1.0.0-beta.
|
|
30
|
+
"referenceLocator": "pkg:npm/create-daloy@1.0.0-beta.5"
|
|
31
31
|
}
|
|
32
32
|
]
|
|
33
33
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
A [DaloyJS](https://daloyjs.dev) REST API for the [Bun](https://bun.sh) runtime. **Contract-first**: routes are defined with
|
|
3
|
+
A [DaloyJS](https://daloyjs.dev) REST API for the [Bun](https://bun.sh) runtime. **Contract-first**: routes are defined with validation schemas (Zod in this template; DaloyJS also supports Standard Schema-compatible validators) and OpenAPI 3.1 is generated from them. When `docs: true` is set in `new App({...})`, three routes are auto-mounted: `GET /openapi.json`, `GET /openapi.yaml`, and `GET /docs` (Scalar UI).
|
|
4
4
|
|
|
5
5
|
- Package manager / runtime: Bun.
|
|
6
6
|
|
|
7
|
+
## Agent guidance
|
|
8
|
+
|
|
9
|
+
- Treat this file as the short, durable project contract for AI coding agents.
|
|
10
|
+
- Use `.agents/skills/daloyjs-best-practices/SKILL.md` for the detailed DaloyJS workflow; keep this file concise and do not duplicate that skill.
|
|
11
|
+
- If instructions conflict, follow the user's latest prompt first, then the nearest `AGENTS.md`, then the skill.
|
|
12
|
+
- Change route definitions, schemas, metadata, and tests first; regenerate generated files instead of hand-editing OpenAPI or typed-client output.
|
|
13
|
+
|
|
7
14
|
## Commands
|
|
8
15
|
|
|
9
16
|
- `bun run dev` — hot-reload server on http://localhost:3000
|
|
@@ -11,6 +18,8 @@ A [DaloyJS](https://daloyjs.dev) REST API for the [Bun](https://bun.sh) runtime.
|
|
|
11
18
|
- `bun test` — Bun's native test runner
|
|
12
19
|
- `bun run gen:openapi` — write `generated/openapi.json`
|
|
13
20
|
- `bun run gen:client` — write the typed Hey API client
|
|
21
|
+
- `bun run contract` — run the focused OpenAPI contract test
|
|
22
|
+
- `bun run hooks:install` — enable the optional pre-push contract gate
|
|
14
23
|
- `bun run build` — produce `dist/`
|
|
15
24
|
|
|
16
25
|
## Project shape
|
|
@@ -34,19 +43,20 @@ Do not write `.js` here — that's the Node NodeNext convention and will fail to
|
|
|
34
43
|
## Core rules
|
|
35
44
|
|
|
36
45
|
1. The route definition is the contract. Method, path, request schemas, and response schemas live in one place — `app.route({...})`.
|
|
37
|
-
2. Validate every input with Zod
|
|
46
|
+
2. Validate every input with Zod or another Standard Schema-compatible validator. For Zod object schemas, use `.strict()` to reject unknown keys at the boundary.
|
|
38
47
|
3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields. Codegen depends on these.
|
|
39
48
|
4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core` — never return raw error responses.
|
|
40
49
|
5. Keep `requestId()`, `secureHeaders()`, and `rateLimit()` enabled. They are the project's secure defaults.
|
|
41
|
-
6.
|
|
42
|
-
7.
|
|
50
|
+
6. Keep operation IDs stable and examples schema-valid; `bun run contract` must pass after route, metadata, or OpenAPI-facing changes.
|
|
51
|
+
7. Every new route ships with a test that covers a happy path and at least one unhappy path.
|
|
52
|
+
8. After any route change: `bun run gen:openapi && bun run gen:client && bun run contract && bun run typecheck && bun test`.
|
|
43
53
|
|
|
44
54
|
## Secure-by-default (do not let an AI strip these)
|
|
45
55
|
|
|
46
|
-
Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/blog/supabase-approach-to-secure-by-default-development):
|
|
56
|
+
Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/blog/supabase-approach-to-secure-by-default-development): _"If you tell an AI to make something work, it might remove the very security checks that protect you."_ When a guard rejects a request, **satisfy it, do not delete it.**
|
|
47
57
|
|
|
48
58
|
- Keep `secureHeaders()`, `requestId()`, `rateLimit()` registered, and `bodyLimitBytes` / `requestTimeoutMs` set on `new App({...})`. Tighten per-route; never raise globally to pass a test.
|
|
49
|
-
- Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`. Keep `responses[N].body` schemas tight; never widen to `z.any()` to let a privileged field escape.
|
|
59
|
+
- Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`. For other validators, use the strict / no-extra-keys equivalent. Keep `responses[N].body` schemas tight; never widen to `z.any()` to let a privileged field escape.
|
|
50
60
|
- Every protected route attaches an auth `beforeHandle` and ships an unhappy-path test proving an unauthenticated request returns `401` (and wrong scope returns `403`) — the HTTP-boundary equivalent of Supabase's pgTAP policy tests.
|
|
51
61
|
- JWT verifiers keep an explicit `algorithms` allowlist; never trust the token's `alg` header, never allow `none`, always check `exp` / `nbf`.
|
|
52
62
|
- Credential / HMAC comparisons use `timingSafeEqual`, never `===`. Throw typed errors from `@daloyjs/core` so problem+json redacts in prod; never return raw stack traces.
|
|
@@ -56,7 +66,7 @@ Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/
|
|
|
56
66
|
## Process expectations
|
|
57
67
|
|
|
58
68
|
- Quality gates must pass before declaring work done: `bun run typecheck` and `bun test`.
|
|
59
|
-
- Regenerate the OpenAPI spec and typed client whenever route shapes change
|
|
69
|
+
- Regenerate the OpenAPI spec and typed client whenever route shapes change, then run `bun run contract`.
|
|
60
70
|
- Bug fixes include a regression test.
|
|
61
71
|
- Never bypass safety checks without a clear reason.
|
|
62
72
|
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
name: daloyjs-best-practices
|
|
3
3
|
description: >-
|
|
4
4
|
Best practices for building, testing, and hardening this DaloyJS REST API on
|
|
5
|
-
the Bun runtime. Use when adding or changing HTTP routes, Zod
|
|
6
|
-
middleware, or error handling;
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
the Bun runtime. Use when adding or changing HTTP routes, Zod/Standard Schema
|
|
6
|
+
validation schemas, middleware, route metadata, or error handling;
|
|
7
|
+
regenerating the OpenAPI spec or typed Hey API client; running contract
|
|
8
|
+
gates; or working on auth, rate limits, secrets, and security defaults.
|
|
9
9
|
license: MIT
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -37,8 +37,9 @@ recommendation below follows from them:
|
|
|
37
37
|
and response schemas live in one place (`app.route({...})`). The OpenAPI
|
|
38
38
|
spec, the typed client, and the runtime validation are all derived from
|
|
39
39
|
it.
|
|
40
|
-
2. **
|
|
41
|
-
|
|
40
|
+
2. **Validation schemas protect every boundary.** This template uses Zod,
|
|
41
|
+
and Daloy accepts any Standard Schema-compatible library. Body, params,
|
|
42
|
+
query, and headers go through the declared schema.
|
|
42
43
|
3. **Preserve literal types.** Return `status: 200 as const` and use
|
|
43
44
|
`z.literal(...)` / `as const` on discriminator fields so the typed
|
|
44
45
|
client can narrow responses.
|
|
@@ -47,6 +48,9 @@ recommendation below follows from them:
|
|
|
47
48
|
codegen and tests import `buildApp()` without side effects.
|
|
48
49
|
5. **Secure by default.** `requestId()`, `secureHeaders()`, and
|
|
49
50
|
`rateLimit()` are registered before route definitions.
|
|
51
|
+
6. **Contract gates are part of done.** Keep `operationId` values stable,
|
|
52
|
+
examples schema-valid, declared error responses accurate, and generated
|
|
53
|
+
OpenAPI / client artifacts in sync with the live route table.
|
|
50
54
|
|
|
51
55
|
## Project shape
|
|
52
56
|
|
|
@@ -69,12 +73,15 @@ bun run typecheck # tsc --noEmit
|
|
|
69
73
|
bun test # Bun's native test runner
|
|
70
74
|
bun run gen:openapi # write generated/openapi.json
|
|
71
75
|
bun run gen:client # write generated/client/
|
|
76
|
+
bun run contract # run the focused contract test
|
|
72
77
|
bun run build # produce dist/
|
|
73
78
|
```
|
|
74
79
|
|
|
75
80
|
Always run `bun run typecheck` and `bun test` before declaring a task done.
|
|
76
|
-
|
|
77
|
-
run
|
|
81
|
+
`bun test` includes the contract gate; if you need a focused contract check,
|
|
82
|
+
run `bun run contract`. If a change touches route shapes, also rerun
|
|
83
|
+
`bun run gen:openapi && bun run gen:client` so the OpenAPI spec and client
|
|
84
|
+
stay in sync.
|
|
78
85
|
|
|
79
86
|
## OpenAPI & docs routes
|
|
80
87
|
|
|
@@ -93,6 +100,18 @@ mount only outside production, or `docs: false` to disable all three.
|
|
|
93
100
|
For hand-rolled mounting, `openapiToYAML` is exported from
|
|
94
101
|
`@daloyjs/core/openapi`.
|
|
95
102
|
|
|
103
|
+
## AI-ready contract metadata
|
|
104
|
+
|
|
105
|
+
Daloy can expose route metadata to OpenAPI and agent tooling. Add metadata
|
|
106
|
+
when it helps consumers understand or safely automate the route:
|
|
107
|
+
|
|
108
|
+
- Use `summary`, `description`, and `tags` for concise human-facing docs.
|
|
109
|
+
- Use `meta.examples` for realistic happy-path and unhappy-path examples.
|
|
110
|
+
Examples must match the declared schemas; the contract gate rejects drift.
|
|
111
|
+
- Use `meta.extensions` for stable `x-*` fields consumed by internal tools.
|
|
112
|
+
- Use `deprecated` and `sunset` when changing API lifecycle. Do not remove
|
|
113
|
+
a route or response shape silently if generated clients may depend on it.
|
|
114
|
+
|
|
96
115
|
## Workflow: add a new route
|
|
97
116
|
|
|
98
117
|
1. **Open `src/build-app.ts`.**
|
|
@@ -100,7 +119,9 @@ For hand-rolled mounting, `openapiToYAML` is exported from
|
|
|
100
119
|
response body per status code. Prefer `z.object({...}).strict()` for
|
|
101
120
|
inputs so unknown keys are rejected at the boundary.
|
|
102
121
|
3. **Call `app.route({...})`** with `method`, `path`, `operationId`, `tags`,
|
|
103
|
-
`responses`, `handler` (plus `request` when accepting input).
|
|
122
|
+
`responses`, `handler` (plus `request` when accepting input). Add `meta`
|
|
123
|
+
examples / descriptions when the route is user-facing or consumed by
|
|
124
|
+
agents.
|
|
104
125
|
4. **Return `{ status, body, headers? }` from the handler.** Always use
|
|
105
126
|
`status: 200 as const` so the typed client can narrow.
|
|
106
127
|
5. **Throw typed errors**, do not return raw error responses. Use
|
|
@@ -108,9 +129,10 @@ For hand-rolled mounting, `openapiToYAML` is exported from
|
|
|
108
129
|
`ForbiddenError`, `ConflictError`, etc.
|
|
109
130
|
6. **Add a test in `tests/<route>.test.ts`** using `app.request(...)` for
|
|
110
131
|
in-process testing — no port needed.
|
|
111
|
-
7. **
|
|
132
|
+
7. **Run the contract gate**: `bun run contract` or `bun test`.
|
|
133
|
+
8. **Regenerate the contract artifacts**: `bun run gen:openapi && bun run gen:client`.
|
|
112
134
|
Inspect `generated/openapi.json` to confirm the operation shows up.
|
|
113
|
-
|
|
135
|
+
9. **Run the quality gates**: `bun run typecheck && bun test`.
|
|
114
136
|
|
|
115
137
|
### Example: a typed route
|
|
116
138
|
|
|
@@ -150,6 +172,8 @@ app.route({
|
|
|
150
172
|
`.nullable()` for "explicitly null". They differ in OpenAPI output.
|
|
151
173
|
- **Pagination**: standardize on `{ items, nextCursor }` cursor pagination.
|
|
152
174
|
- **Discriminated unions**: use `z.discriminatedUnion("kind", [...])`.
|
|
175
|
+
- Keep response examples close to the route definition and schema-valid.
|
|
176
|
+
The contract test intentionally fails invalid examples.
|
|
153
177
|
- **Never** call `JSON.parse` or read `req.body` directly. Let the
|
|
154
178
|
framework validate and pass the typed object to your handler.
|
|
155
179
|
|
|
@@ -168,8 +192,8 @@ Register middleware **before** route definitions. Order matters.
|
|
|
168
192
|
Keep the secure baseline:
|
|
169
193
|
|
|
170
194
|
```ts
|
|
171
|
-
app.use(requestId());
|
|
172
|
-
app.use(secureHeaders());
|
|
195
|
+
app.use(requestId()); // x-request-id for log correlation
|
|
196
|
+
app.use(secureHeaders()); // strict security headers
|
|
173
197
|
app.use(rateLimit({ windowMs: 60_000, max: 120 }));
|
|
174
198
|
```
|
|
175
199
|
|
|
@@ -198,12 +222,18 @@ Cover both **happy paths and unhappy paths** for every route: valid input,
|
|
|
198
222
|
validation failures (400), auth failures (401/403), not-found (404),
|
|
199
223
|
conflicts (409), rate limiting (429). For external services, inject an
|
|
200
224
|
in-memory fake via `buildApp({ store })` rather than mocking `fetch`.
|
|
225
|
+
The shipped contract test should fail invalid examples, duplicate/missing
|
|
226
|
+
`operationId`, or missing responses.
|
|
201
227
|
|
|
202
228
|
Aim for **100% line and function coverage** on routes you add.
|
|
203
229
|
|
|
204
230
|
## Security best practices
|
|
205
231
|
|
|
206
232
|
- Keep `secureHeaders()`, `requestId()`, and `rateLimit()` enabled.
|
|
233
|
+
- Never make a failing test pass by deleting or weakening a security guard.
|
|
234
|
+
If a guard blocks a legitimate route, add the narrowest per-route
|
|
235
|
+
override or configuration knob and cover both the allowed and rejected
|
|
236
|
+
paths in tests.
|
|
207
237
|
- Never log secrets — filter `authorization`, `cookie`, and any
|
|
208
238
|
token-bearing fields.
|
|
209
239
|
- Validate secrets from `process.env` (or `Bun.env`) through a Zod schema
|
|
@@ -215,6 +245,9 @@ Aim for **100% line and function coverage** on routes you add.
|
|
|
215
245
|
mitigate DoS.
|
|
216
246
|
- Use parameterized queries for database access — never interpolate user
|
|
217
247
|
input into SQL.
|
|
248
|
+
- For outbound HTTP, prefer `fetchGuard()` or a transport layered on top
|
|
249
|
+
of it when URLs can be influenced by users or tenants. SSRF protections
|
|
250
|
+
should fail closed for private ranges and cloud metadata endpoints.
|
|
218
251
|
- Bun ships its own audit story; check `bun pm audit` periodically and
|
|
219
252
|
pin versions in `bun.lockb`.
|
|
220
253
|
|
|
@@ -236,6 +269,8 @@ Aim for **100% line and function coverage** on routes you add.
|
|
|
236
269
|
- Never import `@daloyjs/core/bun` from `src/build-app.ts` or any script
|
|
237
270
|
under `scripts/`. That would boot an HTTP listener during codegen.
|
|
238
271
|
- Do not edit files under `generated/` by hand — they are overwritten.
|
|
272
|
+
- Do not hand-edit OpenAPI paths or client types. Fix the route definition,
|
|
273
|
+
schema, or metadata and regenerate.
|
|
239
274
|
- Do not weaken response literal types (`as const`); the typed client
|
|
240
275
|
depends on them.
|
|
241
276
|
- Do not return errors as `{ status: 4xx, body: {...} }`. Throw a typed
|
|
@@ -252,6 +287,8 @@ Aim for **100% line and function coverage** on routes you add.
|
|
|
252
287
|
- `bun run typecheck` and `bun test` must pass before completion.
|
|
253
288
|
- Run `bun run gen:openapi && bun run gen:client` when route shapes
|
|
254
289
|
change; commit the updated artifacts.
|
|
290
|
+
- When route metadata, examples, lifecycle flags, or operation IDs change,
|
|
291
|
+
run the contract gate and inspect the relevant generated OpenAPI diff.
|
|
255
292
|
- Keep `README.md`, this `SKILL.md`, and `AGENTS.md` consistent with the
|
|
256
293
|
code.
|
|
257
294
|
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
A [DaloyJS](https://daloyjs.dev) REST API deployed to **Cloudflare Workers**. **Contract-first**: routes are defined with
|
|
3
|
+
A [DaloyJS](https://daloyjs.dev) REST API deployed to **Cloudflare Workers**. **Contract-first**: routes are defined with validation schemas (Zod in this template; DaloyJS also supports Standard Schema-compatible validators) and OpenAPI 3.1 is generated from them. `docs: true` is set in `new App({...})`, so `GET /openapi.json`, `GET /openapi.yaml`, and `GET /docs` (Scalar UI) are auto-mounted. DaloyJS is dependency-free and the Scalar UI loads from a CDN, so this adds negligible Worker bundle size; drop `docs` (and the `openapi` block) if you want the smallest possible bundle.
|
|
4
4
|
|
|
5
5
|
- Package manager: pnpm (use `pnpm` unless the project's `package.json` was rewritten for npm/yarn/bun).
|
|
6
6
|
- Runtime: Cloudflare Workers (Web Standard `Request`/`Response`).
|
|
7
7
|
|
|
8
|
+
## Agent guidance
|
|
9
|
+
|
|
10
|
+
- Treat this file as the short, durable project contract for AI coding agents.
|
|
11
|
+
- Use `.agents/skills/daloyjs-best-practices/SKILL.md` for the detailed DaloyJS workflow; keep this file concise and do not duplicate that skill.
|
|
12
|
+
- If instructions conflict, follow the user's latest prompt first, then the nearest `AGENTS.md`, then the skill.
|
|
13
|
+
- Change route definitions, schemas, metadata, and tests first; regenerate generated files instead of hand-editing OpenAPI output.
|
|
14
|
+
|
|
8
15
|
## Commands
|
|
9
16
|
|
|
10
17
|
- `pnpm dev` — `wrangler dev` on http://localhost:8787
|
|
11
18
|
- `pnpm typecheck` — `tsc --noEmit`
|
|
12
19
|
- `pnpm test` — run test suite
|
|
20
|
+
- `pnpm contract` — run `daloy inspect --check src/index.ts`
|
|
21
|
+
- `pnpm hooks:install` — enable the optional pre-push contract gate
|
|
13
22
|
- `pnpm deploy` — `wrangler deploy`
|
|
14
23
|
- `pnpm audit` — supply-chain audit
|
|
15
24
|
|
|
@@ -22,31 +31,33 @@ A [DaloyJS](https://daloyjs.dev) REST API deployed to **Cloudflare Workers**. **
|
|
|
22
31
|
## Core rules
|
|
23
32
|
|
|
24
33
|
1. The route definition is the contract. Method, path, request schemas, and response schemas live in one place — `app.route({...})`.
|
|
25
|
-
2. Validate every input with Zod
|
|
34
|
+
2. Validate every input with Zod or another Standard Schema-compatible validator. For Zod object schemas, use `.strict()` to reject unknown keys at the boundary.
|
|
26
35
|
3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields.
|
|
27
36
|
4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core`.
|
|
28
37
|
5. Keep `requestId()`, `secureHeaders()`, and `rateLimit()` enabled. For high-traffic routes, attach Cloudflare's native rate-limit binding (the in-memory limiter resets per isolate).
|
|
29
38
|
6. Stay on the Workers runtime: only Web Standards APIs + Cloudflare bindings. No `node:` modules unless you explicitly add `nodejs_compat` and require it.
|
|
30
39
|
7. Bindings flow through `env`. Read KV/D1/R2/secrets from the `env` argument; never read them via globals.
|
|
31
40
|
8. Long-running work belongs in `ctx.waitUntil(...)`, not blocking the response.
|
|
32
|
-
9.
|
|
41
|
+
9. Keep operation IDs stable and examples schema-valid; `pnpm contract` must pass after route, metadata, or OpenAPI-facing changes.
|
|
42
|
+
10. Every new route ships with a test that covers a happy path and at least one unhappy path.
|
|
33
43
|
|
|
34
44
|
## Secure-by-default (do not let an AI strip these)
|
|
35
45
|
|
|
36
|
-
Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/blog/supabase-approach-to-secure-by-default-development):
|
|
46
|
+
Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/blog/supabase-approach-to-secure-by-default-development): _"If you tell an AI to make something work, it might remove the very security checks that protect you."_ When a guard rejects a request, **satisfy it, do not delete it.**
|
|
37
47
|
|
|
38
48
|
- Keep `secureHeaders()`, `requestId()`, `rateLimit()` registered, and `bodyLimitBytes` / `requestTimeoutMs` set on `new App({...})`. For production, add Cloudflare's native rate-limit binding **in addition to** the in-memory limiter, not instead of it.
|
|
39
49
|
- Read secrets and bindings (KV, D1, R2) from the `env` argument; never hard-code, never log them.
|
|
40
|
-
- Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`. Keep `responses[N].body` schemas tight; never widen to `z.any()` to let a privileged field escape.
|
|
50
|
+
- Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`. For other validators, use the strict / no-extra-keys equivalent. Keep `responses[N].body` schemas tight; never widen to `z.any()` to let a privileged field escape.
|
|
41
51
|
- Every protected route attaches an auth `beforeHandle` and ships an unhappy-path test proving an unauthenticated request returns `401` (and wrong scope returns `403`) — the HTTP-boundary equivalent of Supabase's pgTAP policy tests.
|
|
42
52
|
- JWT verifiers keep an explicit `algorithms` allowlist; never trust the token's `alg` header, never allow `none`, always check `exp` / `nbf`.
|
|
43
|
-
- Credential / HMAC comparisons use
|
|
53
|
+
- Credential / HMAC comparisons use constant-time verification, never `===`. Prefer Web Crypto verification APIs or the framework timing-safe helper where available. Throw typed errors from `@daloyjs/core` so problem+json redacts in prod; never return raw stack traces.
|
|
44
54
|
- Keep `compatibility_date` pinned; do not enable `nodejs_compat` unless a feature requires it.
|
|
45
55
|
- `.env`, `.dev.vars`, secrets, private keys: never commit. Use `wrangler secret put` for production secrets.
|
|
46
56
|
|
|
47
57
|
## Process expectations
|
|
48
58
|
|
|
49
59
|
- Quality gates must pass before declaring work done: `pnpm typecheck` and `pnpm test`.
|
|
60
|
+
- Run the contract gate (`pnpm contract`) whenever route shapes, examples, operation IDs, or OpenAPI metadata change.
|
|
50
61
|
- Bug fixes include a regression test.
|
|
51
62
|
- Pin `compatibility_date` in `wrangler.toml`; only bump it deliberately.
|
|
52
63
|
- For deploys, ensure the user has run `wrangler login`; do not authenticate on their behalf.
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
name: daloyjs-best-practices
|
|
3
3
|
description: >-
|
|
4
4
|
Best practices for building, testing, and hardening this DaloyJS REST API on
|
|
5
|
-
Cloudflare Workers. Use when adding or changing HTTP routes, Zod
|
|
6
|
-
middleware, or error handling;
|
|
7
|
-
|
|
8
|
-
gates.
|
|
5
|
+
Cloudflare Workers. Use when adding or changing HTTP routes, Zod/Standard
|
|
6
|
+
Schema validation schemas, middleware, route metadata, or error handling;
|
|
7
|
+
wiring Worker bindings (KV, D1, R2, Queues, env, secrets); running contract
|
|
8
|
+
gates; or working on auth, rate limits, and security defaults.
|
|
9
9
|
license: MIT
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -34,11 +34,12 @@ Do **not** use this skill for tasks unrelated to the API itself.
|
|
|
34
34
|
DaloyJS is a **contract-first** framework. On Workers, additionally:
|
|
35
35
|
|
|
36
36
|
1. **Stay on the Workers runtime.** Only Web Standards APIs and
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
Cloudflare-specific bindings. No `node:` modules unless the user
|
|
38
|
+
explicitly adds `nodejs_compat` to `wrangler.toml` and opts in.
|
|
39
39
|
2. **The route definition is the contract.** Method, path, request
|
|
40
40
|
schemas, and response schemas live in one place (`app.route({...})`).
|
|
41
|
-
3. **
|
|
41
|
+
3. **Validation schemas protect every boundary.** This template uses Zod,
|
|
42
|
+
and Daloy accepts any Standard Schema-compatible library.
|
|
42
43
|
4. **Preserve literal types.** Return `status: 200 as const`.
|
|
43
44
|
5. **Secure by default.** `requestId()`, `secureHeaders()`, and
|
|
44
45
|
`rateLimit()` are registered. Note: the in-memory rate limiter resets
|
|
@@ -46,6 +47,9 @@ DaloyJS is a **contract-first** framework. On Workers, additionally:
|
|
|
46
47
|
rate-limit binding.
|
|
47
48
|
6. **Bindings flow through `env`.** Read KV/D1/R2/secrets from the
|
|
48
49
|
`env` argument to `fetch`, never from globals.
|
|
50
|
+
7. **Contract gates are part of done.** Keep `operationId` values stable,
|
|
51
|
+
examples schema-valid, declared error responses accurate, and the
|
|
52
|
+
generated OpenAPI contract in sync with the live route table.
|
|
49
53
|
|
|
50
54
|
## Project shape
|
|
51
55
|
|
|
@@ -64,11 +68,14 @@ DaloyJS is a **contract-first** framework. On Workers, additionally:
|
|
|
64
68
|
pnpm dev # wrangler dev on http://localhost:8787
|
|
65
69
|
pnpm typecheck # tsc --noEmit
|
|
66
70
|
pnpm test # run test suite
|
|
71
|
+
pnpm contract # daloy inspect --check src/index.ts
|
|
67
72
|
pnpm deploy # wrangler deploy
|
|
68
73
|
pnpm audit # supply-chain audit
|
|
69
74
|
```
|
|
70
75
|
|
|
71
76
|
Always run `pnpm typecheck` and `pnpm test` before declaring a task done.
|
|
77
|
+
`pnpm test` includes the contract gate; if you need a focused contract
|
|
78
|
+
check, run `pnpm contract`.
|
|
72
79
|
|
|
73
80
|
## OpenAPI & docs routes
|
|
74
81
|
|
|
@@ -89,18 +96,33 @@ On Workers the Scalar UI adds the most weight; consider
|
|
|
89
96
|
For hand-rolled mounting, `openapiToYAML` is exported from
|
|
90
97
|
`@daloyjs/core/openapi`.
|
|
91
98
|
|
|
99
|
+
## AI-ready contract metadata
|
|
100
|
+
|
|
101
|
+
Daloy can expose route metadata to OpenAPI and agent tooling. Add metadata
|
|
102
|
+
when it helps consumers understand or safely automate the route:
|
|
103
|
+
|
|
104
|
+
- Use `summary`, `description`, and `tags` for concise human-facing docs.
|
|
105
|
+
- Use `meta.examples` for realistic happy-path and unhappy-path examples.
|
|
106
|
+
Examples must match the declared schemas; the contract gate rejects drift.
|
|
107
|
+
- Use `meta.extensions` for stable `x-*` fields consumed by internal tools.
|
|
108
|
+
- Use `deprecated` and `sunset` when changing API lifecycle. Do not remove
|
|
109
|
+
a route or response shape silently if generated clients may depend on it.
|
|
110
|
+
|
|
92
111
|
## Workflow: add a new route
|
|
93
112
|
|
|
94
113
|
1. **Open `src/index.ts`.**
|
|
95
114
|
2. **Design schemas first.** Use `z.object({...}).strict()` for inputs.
|
|
96
115
|
3. **Call `app.route({...})`** with `method`, `path`, `operationId`,
|
|
97
116
|
`tags`, `responses`, `handler` (plus `request` when accepting input).
|
|
117
|
+
Add `meta` examples / descriptions when the route is user-facing or
|
|
118
|
+
consumed by agents.
|
|
98
119
|
4. **Return `{ status, body, headers? }`** with `status: 200 as const`.
|
|
99
120
|
5. **Throw typed errors** (`NotFoundError`, `BadRequestError`, etc.).
|
|
100
121
|
6. **Add a test** under `tests/`. Use `app.request(...)` for pure logic;
|
|
101
122
|
use `unstable_dev` (Wrangler) or `@cloudflare/vitest-pool-workers`
|
|
102
123
|
when you need bindings.
|
|
103
|
-
7. **Run the
|
|
124
|
+
7. **Run the contract gate**: `pnpm contract` or `pnpm test`.
|
|
125
|
+
8. **Run the quality gates**: `pnpm typecheck && pnpm test`.
|
|
104
126
|
|
|
105
127
|
### Example: a typed route with bindings
|
|
106
128
|
|
|
@@ -158,6 +180,8 @@ export default {
|
|
|
158
180
|
- **Pagination**: standardize on `{ items, nextCursor }` cursor
|
|
159
181
|
pagination.
|
|
160
182
|
- **Discriminated unions**: `z.discriminatedUnion("kind", [...])`.
|
|
183
|
+
- Keep response examples close to the route definition and schema-valid.
|
|
184
|
+
The contract test intentionally fails invalid examples.
|
|
161
185
|
|
|
162
186
|
## Error handling
|
|
163
187
|
|
|
@@ -197,6 +221,8 @@ Cover **happy paths and unhappy paths** for every route: valid input,
|
|
|
197
221
|
validation failures (400), auth failures (401/403), not-found (404),
|
|
198
222
|
conflict (409), rate limiting (429). For external services, inject an
|
|
199
223
|
in-memory fake into `buildApp(env)` during tests.
|
|
224
|
+
The shipped contract test should fail invalid examples, duplicate/missing
|
|
225
|
+
`operationId`, or missing responses.
|
|
200
226
|
|
|
201
227
|
Aim for **100% line and function coverage** on the routes you add.
|
|
202
228
|
|
|
@@ -205,6 +231,10 @@ Aim for **100% line and function coverage** on the routes you add.
|
|
|
205
231
|
- Keep `secureHeaders()`, `requestId()`, and `rateLimit()` enabled. For
|
|
206
232
|
high-traffic routes, attach Cloudflare's native rate-limit binding so
|
|
207
233
|
limits are shared across isolates.
|
|
234
|
+
- Never make a failing test pass by deleting or weakening a security guard.
|
|
235
|
+
If a guard blocks a legitimate route, add the narrowest per-route
|
|
236
|
+
override or configuration knob and cover both the allowed and rejected
|
|
237
|
+
paths in tests.
|
|
208
238
|
- Never log secrets — filter `authorization`, `cookie`, etc.
|
|
209
239
|
- Read secrets via `wrangler secret put`, never via plain `[vars]` in
|
|
210
240
|
`wrangler.toml`.
|
|
@@ -213,6 +243,9 @@ Aim for **100% line and function coverage** on the routes you add.
|
|
|
213
243
|
- Validate redirects against an allowlist.
|
|
214
244
|
- Set `bodyLimitBytes` and `requestTimeoutMs` on `new App({...})` to
|
|
215
245
|
mitigate DoS.
|
|
246
|
+
- For outbound HTTP, prefer `fetchGuard()` or a transport layered on top
|
|
247
|
+
of it when URLs can be influenced by users or tenants. SSRF protections
|
|
248
|
+
should fail closed for private ranges and cloud metadata endpoints.
|
|
216
249
|
- Workers have CPU and bundle-size limits; be cautious about adding
|
|
217
250
|
heavy dependencies. Run `wrangler deploy --dry-run --outdir=dist` to
|
|
218
251
|
inspect bundle size.
|
|
@@ -242,6 +275,8 @@ Aim for **100% line and function coverage** on the routes you add.
|
|
|
242
275
|
hand-roll a `fetch(req, env, ctx)` adapter.
|
|
243
276
|
- Do not import `@daloyjs/core/node`, `@daloyjs/core/bun`, etc. — only
|
|
244
277
|
`@daloyjs/core` and `@daloyjs/core/cloudflare`.
|
|
278
|
+
- Do not hand-edit OpenAPI paths or client types. Fix the route definition,
|
|
279
|
+
schema, or metadata and regenerate.
|
|
245
280
|
- Avoid Node-only APIs (`Buffer`, `fs`, `process` beyond
|
|
246
281
|
`process.env`) unless `nodejs_compat` is enabled and required.
|
|
247
282
|
- Do not weaken response literal types (`as const`).
|
|
@@ -255,6 +290,8 @@ Aim for **100% line and function coverage** on the routes you add.
|
|
|
255
290
|
- Every new feature ships with happy-path and unhappy-path tests.
|
|
256
291
|
- Bug fixes include a regression test.
|
|
257
292
|
- `pnpm typecheck` and `pnpm test` must pass before completion.
|
|
293
|
+
- When route metadata, examples, lifecycle flags, or operation IDs change,
|
|
294
|
+
run the contract gate and inspect the relevant generated OpenAPI diff.
|
|
258
295
|
- For deploys, ask the user to run `wrangler login` first if needed —
|
|
259
296
|
do not attempt to authenticate on their behalf.
|
|
260
297
|
- Keep `README.md`, this `SKILL.md`, and `AGENTS.md` consistent.
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
A [DaloyJS](https://daloyjs.dev) REST API for the [Deno](https://deno.com) runtime. **Contract-first**: routes are defined with
|
|
3
|
+
A [DaloyJS](https://daloyjs.dev) REST API for the [Deno](https://deno.com) runtime. **Contract-first**: routes are defined with validation schemas (Zod in this template; DaloyJS also supports Standard Schema-compatible validators) and OpenAPI 3.1 is generated from them. When `docs: true` is set in `new App({...})`, three routes are auto-mounted: `GET /openapi.json`, `GET /openapi.yaml`, and `GET /docs` (Scalar UI).
|
|
4
4
|
|
|
5
5
|
- Runtime: Deno (no Node package manager). DaloyJS loads from `jsr:` and third-party packages such as Zod load from `npm:` in `deno.json`.
|
|
6
6
|
|
|
7
|
+
## Agent guidance
|
|
8
|
+
|
|
9
|
+
- Treat this file as the short, durable project contract for AI coding agents.
|
|
10
|
+
- Use `.agents/skills/daloyjs-best-practices/SKILL.md` for the detailed DaloyJS workflow; keep this file concise and do not duplicate that skill.
|
|
11
|
+
- If instructions conflict, follow the user's latest prompt first, then the nearest `AGENTS.md`, then the skill.
|
|
12
|
+
- Change route definitions, schemas, metadata, and tests first; regenerate generated files instead of hand-editing OpenAPI output.
|
|
13
|
+
|
|
7
14
|
## Commands
|
|
8
15
|
|
|
9
16
|
- `deno task dev` — watch-mode server on http://localhost:3000
|
|
10
17
|
- `deno task typecheck`
|
|
11
18
|
- `deno task test`
|
|
12
19
|
- `deno task gen:openapi` — write `generated/openapi.json`
|
|
20
|
+
- `deno task contract` — run the focused OpenAPI contract test
|
|
21
|
+
- `deno task hooks:install` — enable the optional pre-push contract gate
|
|
13
22
|
|
|
14
23
|
The typed Hey API SDK is generated outside Deno (Hey API has no Deno entrypoint yet). Run `npx @hey-api/openapi-ts -i generated/openapi.json -o generated/client` if you need the client.
|
|
15
24
|
|
|
@@ -25,21 +34,22 @@ The typed Hey API SDK is generated outside Deno (Hey API has no Deno entrypoint
|
|
|
25
34
|
## Core rules
|
|
26
35
|
|
|
27
36
|
1. The route definition is the contract. Method, path, request schemas, and response schemas live in one place — `app.route({...})`.
|
|
28
|
-
2. Validate every input with Zod
|
|
37
|
+
2. Validate every input with Zod or another Standard Schema-compatible validator. For Zod object schemas, use `.strict()` to reject unknown keys at the boundary.
|
|
29
38
|
3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields.
|
|
30
39
|
4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core`.
|
|
31
40
|
5. Keep `requestId()`, `secureHeaders()`, and `rateLimit()` enabled.
|
|
32
41
|
6. Deno permissions are part of the contract — keep `--allow-net --allow-env --allow-read` narrow; never use `--allow-all`.
|
|
33
|
-
7.
|
|
34
|
-
8.
|
|
42
|
+
7. Keep operation IDs stable and examples schema-valid; `deno task contract` must pass after route, metadata, or OpenAPI-facing changes.
|
|
43
|
+
8. Every new route ships with a test that covers a happy path and at least one unhappy path.
|
|
44
|
+
9. After any route change: `deno task gen:openapi && deno task contract && deno task typecheck && deno task test`.
|
|
35
45
|
|
|
36
46
|
## Secure-by-default (do not let an AI strip these)
|
|
37
47
|
|
|
38
|
-
Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/blog/supabase-approach-to-secure-by-default-development):
|
|
48
|
+
Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/blog/supabase-approach-to-secure-by-default-development): _"If you tell an AI to make something work, it might remove the very security checks that protect you."_ When a guard rejects a request, **satisfy it, do not delete it.**
|
|
39
49
|
|
|
40
50
|
- Keep `secureHeaders()`, `requestId()`, `rateLimit()` registered, and `bodyLimitBytes` / `requestTimeoutMs` set on `new App({...})`. Tighten per-route; never raise globally to pass a test.
|
|
41
51
|
- Keep Deno permissions narrow. Never add `--allow-all`; never broaden `--allow-net` / `--allow-read` / `--allow-env` to silence a prompt — add the specific host / path / var.
|
|
42
|
-
- Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`. Keep `responses[N].body` schemas tight; never widen to `z.any()` to let a privileged field escape.
|
|
52
|
+
- Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`. For other validators, use the strict / no-extra-keys equivalent. Keep `responses[N].body` schemas tight; never widen to `z.any()` to let a privileged field escape.
|
|
43
53
|
- Every protected route attaches an auth `beforeHandle` and ships an unhappy-path test proving an unauthenticated request returns `401` (and wrong scope returns `403`) — the HTTP-boundary equivalent of Supabase's pgTAP policy tests.
|
|
44
54
|
- JWT verifiers keep an explicit `algorithms` allowlist; never trust the token's `alg` header, never allow `none`, always check `exp` / `nbf`.
|
|
45
55
|
- Credential / HMAC comparisons use a constant-time comparison, never `===`. Throw typed errors from `@daloyjs/core` so problem+json redacts in prod; never return raw stack traces.
|
|
@@ -48,7 +58,7 @@ Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/
|
|
|
48
58
|
## Process expectations
|
|
49
59
|
|
|
50
60
|
- Quality gates must pass before declaring work done: `deno task typecheck` and `deno task test`.
|
|
51
|
-
- Regenerate the OpenAPI spec whenever route shapes change
|
|
61
|
+
- Regenerate the OpenAPI spec whenever route shapes change, then run `deno task contract`.
|
|
52
62
|
- Bug fixes include a regression test.
|
|
53
63
|
- Use `deno task ...`, not `npm`/`pnpm`. There is no `package.json` here.
|
|
54
64
|
|