create-daloy 0.1.17 → 0.1.19

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-daloy",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
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",
@@ -1,6 +1,6 @@
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 Zod schemas and OpenAPI 3.1 is generated from them.
3
+ A [DaloyJS](https://daloyjs.dev) REST API for the [Bun](https://bun.sh) runtime. **Contract-first**: routes are defined with Zod schemas and OpenAPI 3.1 is generated from them.
4
4
 
5
5
  - Package manager / runtime: Bun.
6
6
 
@@ -8,13 +8,34 @@ A [DaloyJS](https://daloyjs.dev) REST API for the [Bun](https://bun.sh) runtime.
8
8
 
9
9
  - `bun run dev` — hot-reload server on http://localhost:3000
10
10
  - `bun run typecheck` — `tsc --noEmit`
11
- - `bun test` — run tests with Bun's native test runner
12
- - `bun run gen:openapi` then `bun run gen:client` regenerate `generated/openapi.json` + the Hey API client
11
+ - `bun test` — Bun's native test runner
12
+ - `bun run gen:openapi` — write `generated/openapi.json`
13
+ - `bun run gen:client` — write the typed Hey API client
14
+ - `bun run build` — produce `dist/`
13
15
 
14
- ## Structure hints
16
+ ## Project shape
15
17
 
16
- - Route + middleware wiring lives in `src/build-app.ts` (`buildApp()` factory; pure, no side effects).
17
- - The HTTP listener is started in `src/index.ts` via `@daloyjs/core/bun`.
18
- - Codegen reads from `buildApp()` only — never import `src/index.ts` from scripts.
18
+ - `src/build-app.ts` `buildApp()` factory. Routes, schemas, and middleware live here. **Pure, no side effects.**
19
+ - `src/index.ts` — calls `buildApp()` and starts the listener via `@daloyjs/core/bun`. The only file that opens a port.
20
+ - `scripts/dump-openapi.ts` — imports `buildApp()` and writes `generated/openapi.json`. Codegen reads from `buildApp()` only — never import `src/index.ts` from scripts.
21
+ - `generated/` — machine-written. Do not edit by hand.
22
+ - `tests/` — Bun test files.
19
23
 
20
- For workflows (adding routes, regenerating the SDK, common pitfalls) see [SKILL.md](SKILL.md).
24
+ ## Core rules
25
+
26
+ 1. The route definition is the contract. Method, path, request schemas, and response schemas live in one place — `app.route({...})`.
27
+ 2. Validate every input with Zod. Use `.strict()` on top-level object schemas to reject unknown keys at the boundary.
28
+ 3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields. Codegen depends on these.
29
+ 4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core` — never return raw error responses.
30
+ 5. Keep `requestId()`, `secureHeaders()`, and `rateLimit()` enabled. They are the project's secure defaults.
31
+ 6. Every new route ships with a test that covers a happy path and at least one unhappy path.
32
+ 7. After any route change: `bun run gen:openapi && bun run gen:client && bun run typecheck && bun test`.
33
+
34
+ ## Process expectations
35
+
36
+ - Quality gates must pass before declaring work done: `bun run typecheck` and `bun test`.
37
+ - Regenerate the OpenAPI spec and typed client whenever route shapes change.
38
+ - Bug fixes include a regression test.
39
+ - Never bypass safety checks without a clear reason.
40
+
41
+ For the full workflow — adding routes step-by-step, schema conventions, testing patterns, security guidance, and deployment notes — read [.agents/skills/daloyjs-best-practices/SKILL.md](.agents/skills/daloyjs-best-practices/SKILL.md).
@@ -40,7 +40,7 @@ bun test
40
40
 
41
41
  ## What's included
42
42
 
43
- - `@daloyjs/core` with `secureHeaders`, `requestId`, and `rateLimit` enabled.
43
+ - `@daloyjs/core` with starter security middleware: `secureHeaders`, `requestId`, and `rateLimit`.
44
44
  <!-- daloy-minimal:strip-start books -->
45
45
  - A health route and contract-first `/books/:id` route with Zod validation.
46
46
  <!-- daloy-minimal:strip-end books -->
@@ -0,0 +1,233 @@
1
+ # SKILL.md — DaloyJS best practices (Bun)
2
+
3
+ Operational guidance and best practices for AI coding agents working in this
4
+ DaloyJS [Bun](https://bun.sh) project. This is the project's **single source
5
+ of truth** for how to add routes, write tests, ship secure defaults, and run
6
+ the quality gates. Read this in full before making non-trivial changes.
7
+
8
+ ## When to use this skill
9
+
10
+ Use this skill when you need to:
11
+
12
+ - Add, modify, or remove HTTP routes in this project.
13
+ - Regenerate the OpenAPI spec or the typed Hey API SDK in `generated/`.
14
+ - Wire up new middleware, validation, or error handling.
15
+ - Add or update tests, run typecheck, or build the project.
16
+ - Harden the API (auth, CORS, rate limits, secrets, dependency hygiene).
17
+
18
+ Do **not** use this skill for tasks unrelated to the API itself.
19
+
20
+ ## Core principles
21
+
22
+ DaloyJS is a **contract-first** framework. Internalize these rules — every
23
+ recommendation below follows from them:
24
+
25
+ 1. **The route definition is the contract.** Method, path, request schemas,
26
+ and response schemas live in one place (`app.route({...})`). The OpenAPI
27
+ spec, the typed client, and the runtime validation are all derived from
28
+ it.
29
+ 2. **Zod schemas validate at every boundary.** Body, params, query, and
30
+ headers go through Zod.
31
+ 3. **Preserve literal types.** Return `status: 200 as const` and use
32
+ `z.literal(...)` / `as const` on discriminator fields so the typed
33
+ client can narrow responses.
34
+ 4. **`buildApp()` is pure.** Construction never opens sockets. The HTTP
35
+ listener lives in `src/index.ts` via `@daloyjs/core/bun`. This lets
36
+ codegen and tests import `buildApp()` without side effects.
37
+ 5. **Secure by default.** `requestId()`, `secureHeaders()`, and
38
+ `rateLimit()` are registered before route definitions.
39
+
40
+ ## Project shape
41
+
42
+ - `src/build-app.ts` — exports `buildApp()`. All routes and middleware
43
+ registered here. **Pure factory.**
44
+ - `src/index.ts` — calls `buildApp()` and starts the Bun HTTP listener via
45
+ `@daloyjs/core/bun`. The only file allowed to open a port.
46
+ - `scripts/dump-openapi.ts` — imports `buildApp()` and writes
47
+ `generated/openapi.json`.
48
+ - `openapi-ts.config.ts` — Hey API config; reads `generated/openapi.json`
49
+ and writes `generated/client/`.
50
+ - `tests/` — Bun test files (`*.test.ts`).
51
+ - `generated/` — **machine-written**. Never edit by hand.
52
+
53
+ ## Commands cheat-sheet
54
+
55
+ ```bash
56
+ bun run dev # hot-reload server on http://localhost:3000
57
+ bun run typecheck # tsc --noEmit
58
+ bun test # Bun's native test runner
59
+ bun run gen:openapi # write generated/openapi.json
60
+ bun run gen:client # write generated/client/
61
+ bun run build # produce dist/
62
+ ```
63
+
64
+ Always run `bun run typecheck` and `bun test` before declaring a task done.
65
+ If a change touches route shapes, also rerun `bun run gen:openapi && bun
66
+ run gen:client` so the client stays in sync.
67
+
68
+ ## Workflow: add a new route
69
+
70
+ 1. **Open `src/build-app.ts`.**
71
+ 2. **Design schemas first.** Define request body/params/query/headers and a
72
+ response body per status code. Prefer `z.object({...}).strict()` for
73
+ inputs so unknown keys are rejected at the boundary.
74
+ 3. **Call `app.route({...})`** with `method`, `path`, `operationId`, `tags`,
75
+ `responses`, `handler` (plus `request` when accepting input).
76
+ 4. **Return `{ status, body, headers? }` from the handler.** Always use
77
+ `status: 200 as const` so the typed client can narrow.
78
+ 5. **Throw typed errors**, do not return raw error responses. Use
79
+ `NotFoundError`, `BadRequestError`, `UnauthorizedError`,
80
+ `ForbiddenError`, `ConflictError`, etc.
81
+ 6. **Add a test in `tests/<route>.test.ts`** using `app.request(...)` for
82
+ in-process testing — no port needed.
83
+ 7. **Regenerate the contract**: `bun run gen:openapi && bun run gen:client`.
84
+ Inspect `generated/openapi.json` to confirm the operation shows up.
85
+ 8. **Run the quality gates**: `bun run typecheck && bun test`.
86
+
87
+ ### Example: a typed route
88
+
89
+ ```ts
90
+ import { z } from "zod";
91
+ import { NotFoundError } from "@daloyjs/core";
92
+
93
+ const Book = z.object({ id: z.string(), title: z.string() }).strict();
94
+ const BookParams = z.object({ id: z.string().min(1) }).strict();
95
+
96
+ app.route({
97
+ method: "GET",
98
+ path: "/books/:id",
99
+ operationId: "getBookById",
100
+ tags: ["Books"],
101
+ request: { params: BookParams },
102
+ responses: {
103
+ 200: { description: "Found", body: Book },
104
+ 404: { description: "Not found" },
105
+ },
106
+ handler: async ({ params }) => {
107
+ const book = await store.find(params.id);
108
+ if (!book) throw new NotFoundError(`Book ${params.id} not found`);
109
+ return { status: 200 as const, body: book };
110
+ },
111
+ });
112
+ ```
113
+
114
+ ## Validation & schema conventions
115
+
116
+ - **Inputs**: use `.strict()` on top-level object schemas to reject unknown
117
+ keys at the API boundary.
118
+ - **IDs**: prefer `z.string().min(1)`; use `z.string().uuid()` or
119
+ `z.string().regex(...)` when the shape is known.
120
+ - **Numbers from query strings**: use `z.coerce.number().int().min(...)`.
121
+ - **Optional vs nullable**: `.optional()` for "may be absent",
122
+ `.nullable()` for "explicitly null". They differ in OpenAPI output.
123
+ - **Pagination**: standardize on `{ items, nextCursor }` cursor pagination.
124
+ - **Discriminated unions**: use `z.discriminatedUnion("kind", [...])`.
125
+ - **Never** call `JSON.parse` or read `req.body` directly. Let the
126
+ framework validate and pass the typed object to your handler.
127
+
128
+ ## Error handling
129
+
130
+ - Throw typed errors from `@daloyjs/core` — they serialize to RFC 7807
131
+ problem responses.
132
+ - Add a `responses[code]` entry for every error you throw.
133
+ - Do not swallow errors. Log via `ctx.log.error(err, "context")` and
134
+ rethrow if recovery is impossible.
135
+
136
+ ## Middleware
137
+
138
+ Register middleware **before** route definitions. Order matters.
139
+
140
+ Keep the secure baseline:
141
+
142
+ ```ts
143
+ app.use(requestId()); // x-request-id for log correlation
144
+ app.use(secureHeaders()); // strict security headers
145
+ app.use(rateLimit({ windowMs: 60_000, max: 120 }));
146
+ ```
147
+
148
+ Add CORS only when needed, with an explicit `origin` allowlist.
149
+
150
+ ## Testing best practices
151
+
152
+ Tests run with `bun test`. Use **in-process** requests through
153
+ `app.request()` — no HTTP server, no port flakiness.
154
+
155
+ ```ts
156
+ import { test, expect } from "bun:test";
157
+ import { buildApp } from "../src/build-app";
158
+
159
+ test("GET /healthz returns ok", async () => {
160
+ const app = buildApp();
161
+ const res = await app.request("/healthz");
162
+ expect(res.status).toBe(200);
163
+ const body = await res.json();
164
+ expect(body.ok).toBe(true);
165
+ expect(typeof body.uptime).toBe("number");
166
+ });
167
+ ```
168
+
169
+ Cover both **happy paths and unhappy paths** for every route: valid input,
170
+ validation failures (400), auth failures (401/403), not-found (404),
171
+ conflicts (409), rate limiting (429). For external services, inject an
172
+ in-memory fake via `buildApp({ store })` rather than mocking `fetch`.
173
+
174
+ Aim for **100% line and function coverage** on routes you add.
175
+
176
+ ## Security best practices
177
+
178
+ - Keep `secureHeaders()`, `requestId()`, and `rateLimit()` enabled.
179
+ - Never log secrets — filter `authorization`, `cookie`, and any
180
+ token-bearing fields.
181
+ - Validate secrets from `process.env` (or `Bun.env`) through a Zod schema
182
+ at boot. Fail fast on missing config.
183
+ - For auth, verify JWT signatures against an allowlist of keys, never
184
+ trust the `alg` header, always check `exp` / `nbf`.
185
+ - Validate redirects against an allowlist.
186
+ - Set `bodyLimitBytes` and `requestTimeoutMs` on `new App({...})` to
187
+ mitigate DoS.
188
+ - Use parameterized queries for database access — never interpolate user
189
+ input into SQL.
190
+ - Bun ships its own audit story; check `bun pm audit` periodically and
191
+ pin versions in `bun.lockb`.
192
+
193
+ ## Logging & observability
194
+
195
+ - Use the framework logger via `ctx.log` — it carries the request id
196
+ automatically.
197
+ - Avoid `console.log` in production code paths; the structured logger
198
+ emits JSON for log aggregators.
199
+
200
+ ## Configuration & secrets
201
+
202
+ - Centralize config parsing in `src/config.ts`, validated by Zod.
203
+ - `.env.example` documents required variables; `.env` is gitignored.
204
+ - Treat config as immutable at runtime.
205
+
206
+ ## Pitfalls and guardrails
207
+
208
+ - Never import `@daloyjs/core/bun` from `src/build-app.ts` or any script
209
+ under `scripts/`. That would boot an HTTP listener during codegen.
210
+ - Do not edit files under `generated/` by hand — they are overwritten.
211
+ - Do not weaken response literal types (`as const`); the typed client
212
+ depends on them.
213
+ - Do not return errors as `{ status: 4xx, body: {...} }`. Throw a typed
214
+ error.
215
+ - Do not add runtime dependencies without checking the hardened `.npmrc` (installs wait 24h after publish by default).
216
+ - Avoid Node-only APIs in code that may also run on the Cloudflare/Vercel
217
+ templates; the Bun runtime is web-standard friendly but check before
218
+ reaching for `node:fs` etc.
219
+
220
+ ## Process expectations
221
+
222
+ - Every new feature ships with happy-path and unhappy-path tests.
223
+ - Bug fixes include a regression test.
224
+ - `bun run typecheck` and `bun test` must pass before completion.
225
+ - Run `bun run gen:openapi && bun run gen:client` when route shapes
226
+ change; commit the updated artifacts.
227
+ - Keep `README.md`, this `SKILL.md`, and `AGENTS.md` consistent with the
228
+ code.
229
+
230
+ ## More
231
+
232
+ - Framework docs: <https://daloyjs.dev/docs>
233
+ - Issues: <https://github.com/daloyjs/daloy/issues>
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md
2
2
 
3
- A [DaloyJS](https://daloyjs.dev) REST API deployed to **Cloudflare Workers**. Contract-first: routes are defined with Zod schemas and OpenAPI 3.1 is generated from them.
3
+ A [DaloyJS](https://daloyjs.dev) REST API deployed to **Cloudflare Workers**. **Contract-first**: routes are defined with Zod schemas and OpenAPI 3.1 is generated from them.
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`).
@@ -8,13 +8,35 @@ A [DaloyJS](https://daloyjs.dev) REST API deployed to **Cloudflare Workers**. Co
8
8
  ## Commands
9
9
 
10
10
  - `pnpm dev` — `wrangler dev` on http://localhost:8787
11
- - `pnpm typecheck`
12
- - `pnpm test`
11
+ - `pnpm typecheck` — `tsc --noEmit`
12
+ - `pnpm test` — run test suite
13
13
  - `pnpm deploy` — `wrangler deploy`
14
+ - `pnpm audit` — supply-chain audit
14
15
 
15
- ## Structure hints
16
+ ## Project shape
16
17
 
17
- - Worker entrypoint: `src/index.ts`. Builds the `App`, registers routes/middleware, and exports `default { fetch: toFetchHandler(app) }` from `@daloyjs/core/cloudflare`.
18
- - `wrangler.toml` controls runtime config, bindings, and deploy targets.
18
+ - `src/index.ts` — Worker entrypoint. Builds the `App`, registers routes/middleware, and exports `default { fetch: toFetchHandler(app) }` from `@daloyjs/core/cloudflare`.
19
+ - `wrangler.toml` Worker configuration (name, compatibility date, bindings, routes).
20
+ - `tests/` — test files.
19
21
 
20
- For workflows (adding routes, deploy, common pitfalls) see [SKILL.md](SKILL.md).
22
+ ## Core rules
23
+
24
+ 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. Use `.strict()` on top-level object schemas to reject unknown keys at the boundary.
26
+ 3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields.
27
+ 4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core`.
28
+ 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
+ 6. Stay on the Workers runtime: only Web Standards APIs + Cloudflare bindings. No `node:` modules unless `nodejs_compat` is enabled and required.
30
+ 7. Bindings flow through `env`. Read KV/D1/R2/secrets from the `env` argument; never read them via globals.
31
+ 8. Long-running work belongs in `ctx.waitUntil(...)`, not blocking the response.
32
+ 9. Every new route ships with a test that covers a happy path and at least one unhappy path.
33
+
34
+ ## Process expectations
35
+
36
+ - Quality gates must pass before declaring work done: `pnpm typecheck` and `pnpm test`.
37
+ - Bug fixes include a regression test.
38
+ - Pin `compatibility_date` in `wrangler.toml`; only bump it deliberately.
39
+ - For deploys, ensure the user has run `wrangler login`; do not authenticate on their behalf.
40
+ - Never bypass safety checks without a clear reason.
41
+
42
+ For the full workflow — adding routes step-by-step, bindings, testing patterns, security guidance, and deployment notes — read [.agents/skills/daloyjs-best-practices/SKILL.md](.agents/skills/daloyjs-best-practices/SKILL.md).
@@ -16,3 +16,9 @@ pnpm deploy
16
16
  ```
17
17
 
18
18
  `@daloyjs/core/cloudflare` exposes `toFetchHandler(app)`, so the same `App` you would use on Node also runs on Workers.
19
+
20
+ ## What's included
21
+
22
+ - `@daloyjs/core/cloudflare` with starter security middleware: `secureHeaders` and `requestId`.
23
+ - Smaller edge-friendly body and timeout limits in the generated app.
24
+ - `wrangler.toml` ready for local development and deploys.
@@ -0,0 +1,236 @@
1
+ # SKILL.md — DaloyJS best practices (Cloudflare Workers)
2
+
3
+ Operational guidance and best practices for AI coding agents working in this
4
+ DaloyJS **Cloudflare Workers** project. This is the project's **single
5
+ source of truth** for how to add routes, write tests, ship secure defaults,
6
+ and run the quality gates. Read this in full before making non-trivial
7
+ changes.
8
+
9
+ ## When to use this skill
10
+
11
+ Use this skill when you need to:
12
+
13
+ - Add, modify, or remove HTTP routes in this Worker.
14
+ - Adjust middleware, validation, or error handling.
15
+ - Change Worker bindings (KV, D1, R2, Queues, env vars) in `wrangler.toml`.
16
+ - Run tests/typecheck or deploy the Worker.
17
+ - Harden the API (auth, CORS, rate limits, secrets, dependency hygiene).
18
+
19
+ Do **not** use this skill for tasks unrelated to the API itself.
20
+
21
+ ## Core principles
22
+
23
+ DaloyJS is a **contract-first** framework. On Workers, additionally:
24
+
25
+ 1. **Stay on the Workers runtime.** Only Web Standards APIs and
26
+ Cloudflare-specific bindings. No `node:` modules unless
27
+ `nodejs_compat` is enabled in `wrangler.toml` and the user explicitly
28
+ opts in.
29
+ 2. **The route definition is the contract.** Method, path, request
30
+ schemas, and response schemas live in one place (`app.route({...})`).
31
+ 3. **Zod schemas validate at every boundary.**
32
+ 4. **Preserve literal types.** Return `status: 200 as const`.
33
+ 5. **Secure by default.** `requestId()`, `secureHeaders()`, and
34
+ `rateLimit()` are registered. Note: the in-memory rate limiter resets
35
+ per isolate — for production traffic, prefer Cloudflare's native
36
+ rate-limit binding.
37
+ 6. **Bindings flow through `env`.** Read KV/D1/R2/secrets from the
38
+ `env` argument to `fetch`, never from globals.
39
+
40
+ ## Project shape
41
+
42
+ - `src/index.ts` — the Worker entrypoint. Builds the `App`, registers
43
+ routes/middleware, and exports `default { fetch: toFetchHandler(app) }`
44
+ from `@daloyjs/core/cloudflare`.
45
+ - `wrangler.toml` — Worker config (name, compatibility date, bindings,
46
+ routes).
47
+ - `tests/` — test files using Workers-compatible test runners (e.g.
48
+ `vitest` + `@cloudflare/vitest-pool-workers`) or in-process
49
+ `app.request(...)` for pure logic.
50
+
51
+ ## Commands cheat-sheet
52
+
53
+ ```bash
54
+ pnpm dev # wrangler dev on http://localhost:8787
55
+ pnpm typecheck # tsc --noEmit
56
+ pnpm test # run test suite
57
+ pnpm deploy # wrangler deploy
58
+ pnpm audit # supply-chain audit
59
+ ```
60
+
61
+ Always run `pnpm typecheck` and `pnpm test` before declaring a task done.
62
+
63
+ ## Workflow: add a new route
64
+
65
+ 1. **Open `src/index.ts`.**
66
+ 2. **Design schemas first.** Use `z.object({...}).strict()` for inputs.
67
+ 3. **Call `app.route({...})`** with `method`, `path`, `operationId`,
68
+ `tags`, `responses`, `handler` (plus `request` when accepting input).
69
+ 4. **Return `{ status, body, headers? }`** with `status: 200 as const`.
70
+ 5. **Throw typed errors** (`NotFoundError`, `BadRequestError`, etc.).
71
+ 6. **Add a test** under `tests/`. Use `app.request(...)` for pure logic;
72
+ use `unstable_dev` (Wrangler) or `@cloudflare/vitest-pool-workers`
73
+ when you need bindings.
74
+ 7. **Run the quality gates**: `pnpm typecheck && pnpm test`.
75
+
76
+ ### Example: a typed route with bindings
77
+
78
+ ```ts
79
+ import { z } from "zod";
80
+ import { App, NotFoundError, rateLimit, requestId, secureHeaders } from "@daloyjs/core";
81
+ import { toFetchHandler } from "@daloyjs/core/cloudflare";
82
+
83
+ interface Env {
84
+ BOOKS: KVNamespace;
85
+ JWT_SECRET: string;
86
+ }
87
+
88
+ const Book = z.object({ id: z.string(), title: z.string() }).strict();
89
+
90
+ function buildApp(env: Env) {
91
+ const app = new App({ bodyLimitBytes: 1024 * 1024, requestTimeoutMs: 5_000 });
92
+ app.use(requestId());
93
+ app.use(secureHeaders());
94
+ app.use(rateLimit({ windowMs: 60_000, max: 120 }));
95
+
96
+ app.route({
97
+ method: "GET",
98
+ path: "/books/:id",
99
+ operationId: "getBookById",
100
+ tags: ["Books"],
101
+ request: { params: z.object({ id: z.string().min(1) }).strict() },
102
+ responses: {
103
+ 200: { description: "Found", body: Book },
104
+ 404: { description: "Not found" },
105
+ },
106
+ handler: async ({ params }) => {
107
+ const raw = await env.BOOKS.get(params.id, "json");
108
+ if (!raw) throw new NotFoundError(`Book ${params.id} not found`);
109
+ return { status: 200 as const, body: Book.parse(raw) };
110
+ },
111
+ });
112
+
113
+ return app;
114
+ }
115
+
116
+ export default {
117
+ fetch: (req: Request, env: Env, ctx: ExecutionContext) =>
118
+ toFetchHandler(buildApp(env))(req, env, ctx),
119
+ };
120
+ ```
121
+
122
+ ## Validation & schema conventions
123
+
124
+ - **Inputs**: use `.strict()` on top-level object schemas.
125
+ - **IDs**: prefer `z.string().min(1)`; use `z.string().uuid()` when
126
+ applicable.
127
+ - **Numbers from query strings**: `z.coerce.number().int().min(...)`.
128
+ - **Optional vs nullable**: differ in OpenAPI output.
129
+ - **Pagination**: standardize on `{ items, nextCursor }` cursor
130
+ pagination.
131
+ - **Discriminated unions**: `z.discriminatedUnion("kind", [...])`.
132
+
133
+ ## Error handling
134
+
135
+ - Throw typed errors from `@daloyjs/core` — they serialize to RFC 7807
136
+ problem responses.
137
+ - Add a `responses[code]` entry for every error you throw.
138
+ - For unexpected errors, let them bubble. The framework's error
139
+ middleware converts them to a 500 problem response.
140
+
141
+ ## Middleware
142
+
143
+ Register middleware **before** route definitions. Order matters.
144
+
145
+ Keep the secure baseline (`requestId`, `secureHeaders`, `rateLimit`).
146
+ Add CORS only when needed, with an explicit `origin` allowlist.
147
+
148
+ ## Working with bindings
149
+
150
+ 1. Add the binding (`[[kv_namespaces]]`, `[[d1_databases]]`, `[vars]`,
151
+ etc.) to `wrangler.toml`.
152
+ 2. Type the binding in the `Env` interface inside `src/index.ts`.
153
+ 3. Pass `env` into `buildApp(env)` so handlers receive bindings via
154
+ closure or factory argument. **Never read bindings via globals.**
155
+ 4. Store secrets via `wrangler secret put` — they appear on `env` but
156
+ are not committed to `wrangler.toml`.
157
+
158
+ ## Testing best practices
159
+
160
+ Two patterns:
161
+
162
+ - **In-process** with `app.request(...)` for pure logic that does not
163
+ need bindings.
164
+ - **Workers-aware** runners (`@cloudflare/vitest-pool-workers` or
165
+ Wrangler `unstable_dev`) when KV/D1/etc. are involved.
166
+
167
+ Cover **happy paths and unhappy paths** for every route: valid input,
168
+ validation failures (400), auth failures (401/403), not-found (404),
169
+ conflict (409), rate limiting (429). For external services, inject an
170
+ in-memory fake into `buildApp(env)` during tests.
171
+
172
+ Aim for **100% line and function coverage** on the routes you add.
173
+
174
+ ## Security best practices
175
+
176
+ - Keep `secureHeaders()`, `requestId()`, and `rateLimit()` enabled. For
177
+ high-traffic routes, attach Cloudflare's native rate-limit binding so
178
+ limits are shared across isolates.
179
+ - Never log secrets — filter `authorization`, `cookie`, etc.
180
+ - Read secrets via `wrangler secret put`, never via plain `[vars]` in
181
+ `wrangler.toml`.
182
+ - For auth, verify JWT signatures with the Web Crypto API
183
+ (`crypto.subtle`). Never trust the `alg` header from the token.
184
+ - Validate redirects against an allowlist.
185
+ - Set `bodyLimitBytes` and `requestTimeoutMs` on `new App({...})` to
186
+ mitigate DoS.
187
+ - Workers have CPU and bundle-size limits; be cautious about adding
188
+ heavy dependencies. Run `wrangler deploy --dry-run --outdir=dist` to
189
+ inspect bundle size.
190
+ - Use `ctx.waitUntil(...)` for fire-and-forget work so the response
191
+ returns promptly.
192
+ - Pin a `compatibility_date` in `wrangler.toml` and only bump it
193
+ deliberately. New compat flags can change runtime semantics.
194
+
195
+ ## Logging & observability
196
+
197
+ - Use `ctx.log` — it carries the request id.
198
+ - `console.log` in Workers shows up in `wrangler tail`. Prefer
199
+ structured logs through the framework logger.
200
+ - For tracing, the `tracing()` middleware emits OpenTelemetry-compatible
201
+ spans; wire up a Workers-friendly exporter when needed.
202
+
203
+ ## Configuration & secrets
204
+
205
+ - Centralize env shape in an `Env` interface.
206
+ - Validate env via Zod once per request (cheap with Workers) or on first
207
+ access via a memoized helper.
208
+ - Treat env as immutable during a request.
209
+
210
+ ## Pitfalls and guardrails
211
+
212
+ - Use `toFetchHandler(app)` from `@daloyjs/core/cloudflare` — never
213
+ hand-roll a `fetch(req, env, ctx)` adapter.
214
+ - Do not import `@daloyjs/core/node`, `@daloyjs/core/bun`, etc. — only
215
+ `@daloyjs/core` and `@daloyjs/core/cloudflare`.
216
+ - Avoid Node-only APIs (`Buffer`, `fs`, `process` beyond
217
+ `process.env`) unless `nodejs_compat` is enabled and required.
218
+ - Do not weaken response literal types (`as const`).
219
+ - Do not return errors as `{ status: 4xx, body }`. Throw a typed error.
220
+ - Do not add runtime dependencies without checking the hardened `.npmrc` (installs wait 24h after publish by default).
221
+ - Long-running work belongs in `ctx.waitUntil(...)`, not blocking the
222
+ response.
223
+
224
+ ## Process expectations
225
+
226
+ - Every new feature ships with happy-path and unhappy-path tests.
227
+ - Bug fixes include a regression test.
228
+ - `pnpm typecheck` and `pnpm test` must pass before completion.
229
+ - For deploys, ask the user to run `wrangler login` first if needed —
230
+ do not attempt to authenticate on their behalf.
231
+ - Keep `README.md`, this `SKILL.md`, and `AGENTS.md` consistent.
232
+
233
+ ## More
234
+
235
+ - Framework docs: <https://daloyjs.dev/docs>
236
+ - Issues: <https://github.com/daloyjs/daloy/issues>
@@ -1,8 +1,8 @@
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 Zod schemas and OpenAPI 3.1 is generated from them.
3
+ A [DaloyJS](https://daloyjs.dev) REST API for the [Deno](https://deno.com) runtime. **Contract-first**: routes are defined with Zod schemas and OpenAPI 3.1 is generated from them.
4
4
 
5
- - Runtime: Deno (no Node package manager). Dependencies are loaded via `npm:` specifiers in `deno.json`.
5
+ - Runtime: Deno (no Node package manager). Dependencies are loaded via `npm:` and `jsr:` specifiers in `deno.json`.
6
6
 
7
7
  ## Commands
8
8
 
@@ -11,12 +11,33 @@ A [DaloyJS](https://daloyjs.dev) REST API for the [Deno](https://deno.com) runti
11
11
  - `deno task test`
12
12
  - `deno task gen:openapi` — write `generated/openapi.json`
13
13
 
14
- The typed Hey API SDK is generated outside Deno (Hey API has no Deno entrypoint yet). Run `npx @hey-api/openapi-ts` against `generated/openapi.json` if you need the client.
14
+ 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
15
 
16
- ## Structure hints
16
+ ## Project shape
17
17
 
18
- - Route + middleware wiring lives in `src/build-app.ts` (`buildApp()` factory; pure, no side effects).
19
- - The HTTP listener is started in `src/main.ts` via `@daloyjs/core/deno`.
20
- - Codegen reads from `buildApp()` only — never import `src/main.ts` from scripts.
18
+ - `src/build-app.ts` `buildApp()` factory. Routes, schemas, and middleware live here. **Pure, no side effects.**
19
+ - `src/main.ts` — calls `buildApp()` and starts the listener via `@daloyjs/core/deno`. The only file that opens a port.
20
+ - `scripts/dump-openapi.ts` — imports `buildApp()` and writes `generated/openapi.json`. Codegen reads from `buildApp()` only — never import `src/main.ts` from scripts.
21
+ - `deno.json` — tasks, import map, and `npm:` specifiers. There is no `package.json` in this project.
22
+ - `generated/` — machine-written. Do not edit by hand.
23
+ - `tests/` — Deno test files.
21
24
 
22
- For workflows (adding routes, regenerating the spec, common pitfalls) see [SKILL.md](SKILL.md).
25
+ ## Core rules
26
+
27
+ 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. Use `.strict()` on top-level object schemas to reject unknown keys at the boundary.
29
+ 3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields.
30
+ 4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core`.
31
+ 5. Keep `requestId()`, `secureHeaders()`, and `rateLimit()` enabled.
32
+ 6. Deno permissions are part of the contract — keep `--allow-net --allow-env --allow-read` narrow; never use `--allow-all`.
33
+ 7. Every new route ships with a test that covers a happy path and at least one unhappy path.
34
+ 8. After any route change: `deno task gen:openapi && deno task typecheck && deno task test`.
35
+
36
+ ## Process expectations
37
+
38
+ - Quality gates must pass before declaring work done: `deno task typecheck` and `deno task test`.
39
+ - Regenerate the OpenAPI spec whenever route shapes change.
40
+ - Bug fixes include a regression test.
41
+ - Use `deno task ...`, not `npm`/`pnpm`. There is no `package.json` here.
42
+
43
+ For the full workflow — adding routes step-by-step, schema conventions, testing patterns, security guidance, and deployment notes — read [.agents/skills/daloyjs-best-practices/SKILL.md](.agents/skills/daloyjs-best-practices/SKILL.md).
@@ -44,7 +44,7 @@ deno task test
44
44
  ## What's included
45
45
 
46
46
  - `@daloyjs/core` (loaded via `npm:` specifiers in `deno.json`).
47
- - `secureHeaders`, `requestId`, and `rateLimit` enabled by default.
47
+ - Starter security middleware: `secureHeaders`, `requestId`, and `rateLimit`.
48
48
  <!-- daloy-minimal:strip-start books -->
49
49
  - A health route and contract-first `/books/:id` route with Zod validation.
50
50
  <!-- daloy-minimal:strip-end books -->