create-daloy 0.1.18 → 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/README.md +8 -5
- package/bin/create-daloy.mjs +14 -1
- package/package.json +1 -1
- package/templates/bun-basic/AGENTS.md +29 -8
- package/templates/bun-basic/README.md +1 -1
- package/templates/bun-basic/_agents/skills/daloyjs-best-practices/SKILL.md +233 -0
- package/templates/cloudflare-worker/AGENTS.md +29 -7
- package/templates/cloudflare-worker/README.md +6 -0
- package/templates/cloudflare-worker/_agents/skills/daloyjs-best-practices/SKILL.md +236 -0
- package/templates/deno-basic/AGENTS.md +29 -8
- package/templates/deno-basic/README.md +1 -1
- package/templates/deno-basic/_agents/skills/daloyjs-best-practices/SKILL.md +225 -0
- package/templates/node-basic/AGENTS.md +27 -6
- package/templates/node-basic/README.md +1 -1
- package/templates/node-basic/_agents/skills/daloyjs-best-practices/SKILL.md +298 -0
- package/templates/vercel-edge/AGENTS.md +27 -7
- package/templates/vercel-edge/README.md +11 -0
- package/templates/vercel-edge/_agents/skills/daloyjs-best-practices/SKILL.md +204 -0
- package/templates/bun-basic/SKILL.md +0 -68
- package/templates/cloudflare-worker/SKILL.md +0 -68
- package/templates/deno-basic/SKILL.md +0 -71
- package/templates/node-basic/SKILL.md +0 -70
- package/templates/vercel-edge/SKILL.md +0 -64
package/README.md
CHANGED
|
@@ -68,6 +68,7 @@ A production-ready Node.js HTTP server using `@daloyjs/core` with:
|
|
|
68
68
|
A minimal Cloudflare Worker bootstrap using `@daloyjs/core/cloudflare` with:
|
|
69
69
|
|
|
70
70
|
- `wrangler.toml` ready to deploy.
|
|
71
|
+
- `secureHeaders` and `requestId` enabled by default, with smaller edge-friendly body and timeout limits.
|
|
71
72
|
- Zod-validated route exposed as `fetch`.
|
|
72
73
|
- A sample test that exercises `app.request(...)`.
|
|
73
74
|
|
|
@@ -78,6 +79,7 @@ A Vercel Edge API bootstrap using `@daloyjs/core/vercel` with:
|
|
|
78
79
|
- `api/[...path].ts` catch-all routing so DaloyJS owns the API surface.
|
|
79
80
|
- `export const config = { runtime: "edge" }` ready for Vercel Edge.
|
|
80
81
|
- `vercel dev` / `vercel deploy` scripts.
|
|
82
|
+
- `secureHeaders` and `requestId` enabled by default, with smaller edge-friendly body and timeout limits.
|
|
81
83
|
- A health route and bookstore route mirroring the Node starter.
|
|
82
84
|
|
|
83
85
|
### `bun-basic`
|
|
@@ -86,7 +88,7 @@ A [Bun](https://bun.sh) runtime starter using `@daloyjs/core/bun` with:
|
|
|
86
88
|
|
|
87
89
|
- `bun --hot src/index.ts` for instant reloads.
|
|
88
90
|
- `bun test` wired to in-process `app.request(...)` checks.
|
|
89
|
-
- The same
|
|
91
|
+
- The same starter security middleware as the Node template (`secureHeaders`, `requestId`, `rateLimit`).
|
|
90
92
|
- A health route and contract-first `/books/:id` route with Zod validation.
|
|
91
93
|
- Hey API codegen wired to `bun run gen:openapi` + `bun run gen:client`.
|
|
92
94
|
|
|
@@ -97,6 +99,7 @@ A [Deno](https://deno.com) runtime starter using `@daloyjs/core/deno` with:
|
|
|
97
99
|
- A `deno.json` with `deno task dev`, `test`, and `gen:openapi` tasks.
|
|
98
100
|
- `@daloyjs/core` and Zod loaded via `npm:` import-map specifiers.
|
|
99
101
|
- Minimum-permissions dev script (`--allow-net --allow-env --allow-read`).
|
|
102
|
+
- The same starter security middleware as the Node template (`secureHeaders`, `requestId`, `rateLimit`).
|
|
100
103
|
- A health route and contract-first `/books/:id` route with Zod validation.
|
|
101
104
|
- The CLI skips Node-style installs for this template (no `package.json`).
|
|
102
105
|
|
|
@@ -120,16 +123,16 @@ re-run with `--minimal`, or delete the marked blocks by hand later.
|
|
|
120
123
|
- Zero runtime dependencies (uses only Node built-ins) for a clean supply-chain footprint.
|
|
121
124
|
- A modern terminal experience with Unicode/color capability detection and ASCII fallbacks.
|
|
122
125
|
- Templates are copied verbatim from this package's `templates/` directory.
|
|
123
|
-
- Files prefixed with `_` are renamed (`_gitignore` → `.gitignore`, `_npmrc` → `.npmrc`) to survive npm packing.
|
|
126
|
+
- Files and folders prefixed with `_` are renamed on copy (`_gitignore` → `.gitignore`, `_npmrc` → `.npmrc`, `_agents/` → `.agents/`) to survive npm packing.
|
|
124
127
|
- pnpm-specific `.npmrc` hardening is kept only when you choose `pnpm`; other package managers get a clean project without unsupported config warnings.
|
|
125
128
|
- pnpm projects ship with `ignore-scripts=true`, `minimum-release-age=1440`, `verify-store-integrity=true`, `prefer-frozen-lockfile=true`, and `strict-peer-dependencies=true` by default.
|
|
126
129
|
- The CLI never executes template scripts and never makes network calls beyond the package manager you select.
|
|
127
130
|
|
|
128
131
|
## AI agent helper files
|
|
129
132
|
|
|
130
|
-
Every scaffolded project ships with two files
|
|
133
|
+
Every scaffolded project ships with two files that help AI coding agents (Copilot, Claude Code, Cursor, Codex, etc.) understand and work in your project:
|
|
131
134
|
|
|
132
|
-
- `AGENTS.md` — a small, top-of-context file (per the open [AGENTS.md](https://agents.md) convention): one-line project description, package manager / runtime, and the few commands an agent needs. It
|
|
133
|
-
-
|
|
135
|
+
- `AGENTS.md` (repo root) — a small, top-of-context file (per the open [AGENTS.md](https://agents.md) convention): one-line project description, package manager / runtime, project shape, core rules, and the few commands an agent needs. It links to the full skill below.
|
|
136
|
+
- `.agents/skills/daloyjs-best-practices/SKILL.md` — comprehensive operational guidance following the open `agents/skills/<skill-name>/SKILL.md` convention: when to use the skill, project structure, core workflows (adding routes, regenerating the OpenAPI spec and client), schema and validation conventions, error-handling patterns, middleware order, testing best practices (happy and unhappy paths), security best practices, logging and observability notes, configuration and secrets handling, deployment notes, pitfalls and guardrails, and process expectations.
|
|
134
137
|
|
|
135
138
|
Both files are tailored to the chosen template (Node, Bun, Deno, Vercel Edge, or Cloudflare Workers), and Node-style templates rewrite their commands to match your selected package manager. They follow the "instruction budget" advice — small root file, progressive disclosure for the rest — so they don't waste agent tokens. Edit or delete them freely; the framework does not depend on them at runtime.
|
package/bin/create-daloy.mjs
CHANGED
|
@@ -56,6 +56,10 @@ const RENAME_ON_COPY = new Map([
|
|
|
56
56
|
["_gitignore", ".gitignore"],
|
|
57
57
|
["_npmrc", ".npmrc"],
|
|
58
58
|
["_env.example", ".env.example"],
|
|
59
|
+
// Directory: holds skill files for AI coding agents under
|
|
60
|
+
// `.agents/skills/<skill-name>/SKILL.md`. Templates author this as
|
|
61
|
+
// `_agents/` so npm pack does not drop the dotfolder during publish.
|
|
62
|
+
["_agents", ".agents"],
|
|
59
63
|
]);
|
|
60
64
|
|
|
61
65
|
// Templates that target a runtime instead of an npm package manager.
|
|
@@ -436,7 +440,16 @@ async function patchPackageJson(dir, projectName, packageManager) {
|
|
|
436
440
|
|
|
437
441
|
async function patchTemplateTextFiles(dir, packageManager) {
|
|
438
442
|
if (packageManager === "pnpm") return;
|
|
439
|
-
|
|
443
|
+
// README.md and AGENTS.md sit at the repo root; SKILL.md lives under
|
|
444
|
+
// `.agents/skills/daloyjs-best-practices/` so it follows the open
|
|
445
|
+
// "agents/skills" convention. After copyTemplate runs, the `_agents`
|
|
446
|
+
// template folder has already been renamed to `.agents`.
|
|
447
|
+
const targets = [
|
|
448
|
+
"README.md",
|
|
449
|
+
"AGENTS.md",
|
|
450
|
+
path.join(".agents", "skills", "daloyjs-best-practices", "SKILL.md"),
|
|
451
|
+
];
|
|
452
|
+
for (const fileName of targets) {
|
|
440
453
|
const file = path.join(dir, fileName);
|
|
441
454
|
if (!existsSync(file)) continue;
|
|
442
455
|
const raw = await readFile(file, "utf8");
|
package/package.json
CHANGED
|
@@ -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
|
|
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` —
|
|
12
|
-
- `bun run gen:openapi`
|
|
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
|
-
##
|
|
16
|
+
## Project shape
|
|
15
17
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
##
|
|
16
|
+
## Project shape
|
|
16
17
|
|
|
17
|
-
-
|
|
18
|
-
- `wrangler.toml`
|
|
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
|
-
|
|
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.
|