create-daloy 1.0.0-rc.2 → 1.0.0-rc.4
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 +1 -1
- package/sbom.cdx.json +9 -9
- package/sbom.spdx.json +5 -5
- package/templates/bun-basic/AGENTS.md +1 -1
- package/templates/bun-basic/_agents/skills/daloyjs-best-practices/SKILL.md +27 -18
- package/templates/bun-basic/package.json +1 -1
- package/templates/bun-basic/src/build-app.ts +24 -22
- package/templates/cloudflare-worker/AGENTS.md +1 -1
- package/templates/cloudflare-worker/_agents/skills/daloyjs-best-practices/SKILL.md +25 -16
- package/templates/cloudflare-worker/package.json +1 -1
- package/templates/cloudflare-worker/src/index.ts +24 -22
- package/templates/deno-basic/AGENTS.md +1 -1
- package/templates/deno-basic/_agents/skills/daloyjs-best-practices/SKILL.md +25 -15
- package/templates/deno-basic/deno.json +5 -5
- package/templates/deno-basic/src/build-app.ts +24 -22
- package/templates/node-basic/AGENTS.md +1 -4
- package/templates/node-basic/_agents/skills/daloyjs-best-practices/SKILL.md +28 -19
- package/templates/node-basic/package.json +1 -1
- package/templates/node-basic/src/build-app.ts +24 -22
- package/templates/vercel/AGENTS.md +3 -3
- package/templates/vercel/_agents/skills/daloyjs-best-practices/SKILL.md +26 -16
- package/templates/vercel/api/index.ts +24 -22
- 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-rc.
|
|
3
|
+
"version": "1.0.0-rc.4",
|
|
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",
|
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:d3388094-26fe-5e1d-980d-c256c3d08aef",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-07-
|
|
7
|
+
"timestamp": "2026-07-12T20:39:15.641Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "DaloyJS",
|
|
11
11
|
"name": "daloy-generate-sbom",
|
|
12
|
-
"version": "1.0.0-rc.
|
|
12
|
+
"version": "1.0.0-rc.4"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"authors": [],
|
|
16
16
|
"component": {
|
|
17
17
|
"type": "library",
|
|
18
|
-
"bom-ref": "pkg:npm/create-daloy@1.0.0-rc.
|
|
18
|
+
"bom-ref": "pkg:npm/create-daloy@1.0.0-rc.4",
|
|
19
19
|
"name": "create-daloy",
|
|
20
|
-
"version": "1.0.0-rc.
|
|
20
|
+
"version": "1.0.0-rc.4",
|
|
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-rc.
|
|
22
|
+
"purl": "pkg:npm/create-daloy@1.0.0-rc.4",
|
|
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-rc.
|
|
45
|
+
"tagId": "swidtag-create-daloy-1.0.0-rc.4",
|
|
46
46
|
"name": "create-daloy",
|
|
47
|
-
"version": "1.0.0-rc.
|
|
47
|
+
"version": "1.0.0-rc.4",
|
|
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-rc.
|
|
56
|
+
"ref": "pkg:npm/create-daloy@1.0.0-rc.4",
|
|
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-rc.
|
|
6
|
-
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-1.0.0-rc.
|
|
5
|
+
"name": "create-daloy-1.0.0-rc.4",
|
|
6
|
+
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-1.0.0-rc.4-d3388094-26fe-5e1d-980d-c256c3d08aef",
|
|
7
7
|
"creationInfo": {
|
|
8
|
-
"created": "2026-07-
|
|
8
|
+
"created": "2026-07-12T20:39:15.641Z",
|
|
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-rc.
|
|
19
|
+
"versionInfo": "1.0.0-rc.4",
|
|
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-rc.
|
|
30
|
+
"referenceLocator": "pkg:npm/create-daloy@1.0.0-rc.4"
|
|
31
31
|
}
|
|
32
32
|
]
|
|
33
33
|
}
|
|
@@ -42,7 +42,7 @@ Do not write `.js` here — that's the Node NodeNext convention and will fail to
|
|
|
42
42
|
|
|
43
43
|
## Core rules
|
|
44
44
|
|
|
45
|
-
1. The route definition is the contract. Method, path, request schemas, and response schemas live in one place — `app.route({...})
|
|
45
|
+
1. The route definition is the contract. Method, path, request schemas, and response schemas live in one place — `app.get(path, contract, handler)` (or `app.route({...})` for reusable `defineRoute()` contracts or metadata-heavy routes).
|
|
46
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.
|
|
47
47
|
3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields. Codegen depends on these.
|
|
48
48
|
4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core` — never return raw error responses.
|
|
@@ -34,9 +34,13 @@ DaloyJS is a **contract-first** framework. Internalize these rules — every
|
|
|
34
34
|
recommendation below follows from them:
|
|
35
35
|
|
|
36
36
|
1. **The route definition is the contract.** Method, path, request schemas,
|
|
37
|
-
and response schemas live in one place
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
and response schemas live in one place — `app.get(path, contract, handler)`
|
|
38
|
+
(and the matching `app.post`/`put`/`patch`/`delete`/`head` shorthands), or
|
|
39
|
+
`app.route({...})` when you need a reusable `defineRoute()` contract or a
|
|
40
|
+
metadata-heavy route. Both forms produce identical runtime behavior,
|
|
41
|
+
validation, security, and OpenAPI output. The OpenAPI spec, the typed
|
|
42
|
+
client, and the runtime validation are all derived from the route
|
|
43
|
+
definition.
|
|
40
44
|
2. **Validation schemas protect every boundary.** This template uses Zod,
|
|
41
45
|
and Daloy accepts any Standard Schema-compatible library. Body, params,
|
|
42
46
|
query, and headers go through the declared schema.
|
|
@@ -118,10 +122,14 @@ when it helps consumers understand or safely automate the route:
|
|
|
118
122
|
2. **Design schemas first.** Define request body/params/query/headers and a
|
|
119
123
|
response body per status code. Prefer `z.object({...}).strict()` for
|
|
120
124
|
inputs so unknown keys are rejected at the boundary.
|
|
121
|
-
3. **Call `app.
|
|
122
|
-
`
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
3. **Call the method shorthand: `app.get(path, contract, handler)`** (or
|
|
126
|
+
`app.post`/`put`/`patch`/`delete`/`head` for other methods). The contract
|
|
127
|
+
object's required keys are `operationId`, `tags`, `responses`. Add
|
|
128
|
+
`request` when the route accepts input, and add `meta` examples /
|
|
129
|
+
descriptions when the route is user-facing or consumed by agents. Reach
|
|
130
|
+
for the full `app.route({ method, path, ...contract, handler })` form
|
|
131
|
+
instead when the route is built from a reusable `defineRoute()` contract,
|
|
132
|
+
or when composing many routes at once via `registerRoutes()`.
|
|
125
133
|
4. **Return `{ status, body, headers? }` from the handler.** Always use
|
|
126
134
|
`status: 200 as const` so the typed client can narrow.
|
|
127
135
|
5. **Throw typed errors**, do not return raw error responses. Use
|
|
@@ -143,22 +151,23 @@ import { NotFoundError } from "@daloyjs/core";
|
|
|
143
151
|
const Book = z.object({ id: z.string(), title: z.string() }).strict();
|
|
144
152
|
const BookParams = z.object({ id: z.string().min(1) }).strict();
|
|
145
153
|
|
|
146
|
-
app.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
app.get(
|
|
155
|
+
"/books/:id",
|
|
156
|
+
{
|
|
157
|
+
operationId: "getBookById",
|
|
158
|
+
tags: ["Books"],
|
|
159
|
+
request: { params: BookParams },
|
|
160
|
+
responses: {
|
|
161
|
+
200: { description: "Found", body: Book },
|
|
162
|
+
404: { description: "Not found" },
|
|
163
|
+
},
|
|
155
164
|
},
|
|
156
|
-
|
|
165
|
+
async ({ params }) => {
|
|
157
166
|
const book = await store.find(params.id);
|
|
158
167
|
if (!book) throw new NotFoundError(`Book ${params.id} not found`);
|
|
159
168
|
return { status: 200 as const, body: book };
|
|
160
169
|
},
|
|
161
|
-
|
|
170
|
+
);
|
|
162
171
|
```
|
|
163
172
|
|
|
164
173
|
## Validation & schema conventions
|
|
@@ -58,22 +58,23 @@ export function buildApp(): App {
|
|
|
58
58
|
app.use(secureHeaders());
|
|
59
59
|
app.use(rateLimit({ windowMs: 60_000, max: 120 }));
|
|
60
60
|
|
|
61
|
-
app.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
app.get(
|
|
62
|
+
"/healthz",
|
|
63
|
+
{
|
|
64
|
+
operationId: "healthz",
|
|
65
|
+
tags: ["Ops"],
|
|
66
|
+
responses: {
|
|
67
|
+
200: {
|
|
68
|
+
description: "Service is healthy",
|
|
69
|
+
body: z.object({ ok: z.literal(true), runtime: z.literal("bun") }),
|
|
70
|
+
},
|
|
70
71
|
},
|
|
71
72
|
},
|
|
72
|
-
|
|
73
|
+
async () => ({
|
|
73
74
|
status: 200 as const,
|
|
74
75
|
body: { ok: true as const, runtime: "bun" as const },
|
|
75
76
|
}),
|
|
76
|
-
|
|
77
|
+
);
|
|
77
78
|
|
|
78
79
|
// daloy-minimal:strip-start books
|
|
79
80
|
const Book = z.object({ id: z.string(), title: z.string() }).strict();
|
|
@@ -82,22 +83,23 @@ export function buildApp(): App {
|
|
|
82
83
|
["2", { id: "2", title: "El Filibusterismo" }],
|
|
83
84
|
]);
|
|
84
85
|
|
|
85
|
-
app.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
app.get(
|
|
87
|
+
"/books/:id",
|
|
88
|
+
{
|
|
89
|
+
operationId: "getBookById",
|
|
90
|
+
tags: ["Books"],
|
|
91
|
+
request: { params: z.object({ id: z.string().min(1) }).strict() },
|
|
92
|
+
responses: {
|
|
93
|
+
200: { description: "Found", body: Book },
|
|
94
|
+
404: { description: "Not found" },
|
|
95
|
+
},
|
|
94
96
|
},
|
|
95
|
-
|
|
97
|
+
async ({ params }) => {
|
|
96
98
|
const book = books.get(params.id);
|
|
97
99
|
if (!book) throw new NotFoundError(`Book ${params.id} not found`);
|
|
98
100
|
return { status: 200 as const, body: book };
|
|
99
101
|
},
|
|
100
|
-
|
|
102
|
+
);
|
|
101
103
|
// daloy-minimal:strip-end books
|
|
102
104
|
|
|
103
105
|
return app;
|
|
@@ -30,7 +30,7 @@ A [DaloyJS](https://daloyjs.dev) REST API deployed to **Cloudflare Workers**. **
|
|
|
30
30
|
|
|
31
31
|
## Core rules
|
|
32
32
|
|
|
33
|
-
1. The route definition is the contract. Method, path, request schemas, and response schemas live in one place — `app.route({...})
|
|
33
|
+
1. The route definition is the contract. Method, path, request schemas, and response schemas live in one place — `app.get(path, contract, handler)` (or `app.route({...})` for reusable `defineRoute()` contracts or metadata-heavy routes).
|
|
34
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.
|
|
35
35
|
3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields.
|
|
36
36
|
4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core`.
|
|
@@ -37,7 +37,11 @@ DaloyJS is a **contract-first** framework. On Workers, additionally:
|
|
|
37
37
|
Cloudflare-specific bindings. No `node:` modules unless the user
|
|
38
38
|
explicitly adds `nodejs_compat` to `wrangler.toml` and opts in.
|
|
39
39
|
2. **The route definition is the contract.** Method, path, request
|
|
40
|
-
schemas, and response schemas live in one place
|
|
40
|
+
schemas, and response schemas live in one place. Use the shorthand
|
|
41
|
+
`app.get(path, contract, handler)` (`app.post`, `app.put`, `app.patch`,
|
|
42
|
+
`app.delete`, `app.head`) for ordinary routes; use `app.route({...})`
|
|
43
|
+
for reusable `defineRoute()` contracts, metadata-heavy routes, or when
|
|
44
|
+
composing many routes via `registerRoutes()`.
|
|
41
45
|
3. **Validation schemas protect every boundary.** This template uses Zod,
|
|
42
46
|
and Daloy accepts any Standard Schema-compatible library.
|
|
43
47
|
4. **Preserve literal types.** Return `status: 200 as const`.
|
|
@@ -112,10 +116,14 @@ when it helps consumers understand or safely automate the route:
|
|
|
112
116
|
|
|
113
117
|
1. **Open `src/index.ts`.**
|
|
114
118
|
2. **Design schemas first.** Use `z.object({...}).strict()` for inputs.
|
|
115
|
-
3. **Call `app.
|
|
116
|
-
`
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
3. **Call `app.get(path, contract, handler)`** (or the matching
|
|
120
|
+
`app.post`/`app.put`/`app.patch`/`app.delete`/`app.head` shorthand) with
|
|
121
|
+
a contract object holding `operationId`, `tags`, `responses` (plus
|
|
122
|
+
`request` when accepting input). Add `meta` examples / descriptions when
|
|
123
|
+
the route is user-facing or consumed by agents. Reach for the full
|
|
124
|
+
`app.route({...})` form instead when the contract is a reusable
|
|
125
|
+
`defineRoute()` value, is metadata-heavy, or is being composed with many
|
|
126
|
+
other routes via `registerRoutes()`.
|
|
119
127
|
4. **Return `{ status, body, headers? }`** with `status: 200 as const`.
|
|
120
128
|
5. **Throw typed errors** (`NotFoundError`, `BadRequestError`, etc.).
|
|
121
129
|
6. **Add a test** under `tests/`. Use `app.request(...)` for pure logic;
|
|
@@ -144,22 +152,23 @@ function buildApp(env: Env) {
|
|
|
144
152
|
app.use(secureHeaders());
|
|
145
153
|
app.use(rateLimit({ windowMs: 60_000, max: 120 }));
|
|
146
154
|
|
|
147
|
-
app.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
app.get(
|
|
156
|
+
"/books/:id",
|
|
157
|
+
{
|
|
158
|
+
operationId: "getBookById",
|
|
159
|
+
tags: ["Books"],
|
|
160
|
+
request: { params: z.object({ id: z.string().min(1) }).strict() },
|
|
161
|
+
responses: {
|
|
162
|
+
200: { description: "Found", body: Book },
|
|
163
|
+
404: { description: "Not found" },
|
|
164
|
+
},
|
|
156
165
|
},
|
|
157
|
-
|
|
166
|
+
async ({ params }) => {
|
|
158
167
|
const raw = await env.BOOKS.get(params.id, "json");
|
|
159
168
|
if (!raw) throw new NotFoundError(`Book ${params.id} not found`);
|
|
160
169
|
return { status: 200 as const, body: Book.parse(raw) };
|
|
161
170
|
},
|
|
162
|
-
|
|
171
|
+
);
|
|
163
172
|
|
|
164
173
|
return app;
|
|
165
174
|
}
|
|
@@ -32,22 +32,23 @@ app.use(secureHeaders());
|
|
|
32
32
|
// this baseline. Do not remove it to make a test pass; raise `max` per route.
|
|
33
33
|
app.use(rateLimit({ windowMs: 60_000, max: 120 }));
|
|
34
34
|
|
|
35
|
-
app.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
app.get(
|
|
36
|
+
"/healthz",
|
|
37
|
+
{
|
|
38
|
+
operationId: "healthz",
|
|
39
|
+
tags: ["Ops"],
|
|
40
|
+
responses: {
|
|
41
|
+
200: {
|
|
42
|
+
description: "Service is healthy",
|
|
43
|
+
body: z.object({ ok: z.literal(true), runtime: z.literal("cloudflare-worker") }),
|
|
44
|
+
},
|
|
44
45
|
},
|
|
45
46
|
},
|
|
46
|
-
|
|
47
|
+
async () => ({
|
|
47
48
|
status: 200 as const,
|
|
48
49
|
body: { ok: true as const, runtime: "cloudflare-worker" as const },
|
|
49
50
|
}),
|
|
50
|
-
|
|
51
|
+
);
|
|
51
52
|
|
|
52
53
|
// daloy-minimal:strip-start books
|
|
53
54
|
const Book = z.object({ id: z.string(), title: z.string() }).strict();
|
|
@@ -55,22 +56,23 @@ const books = new Map<string, z.infer<typeof Book>>([
|
|
|
55
56
|
["1", { id: "1", title: "Noli Me Tangere" }],
|
|
56
57
|
]);
|
|
57
58
|
|
|
58
|
-
app.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
app.get(
|
|
60
|
+
"/books/:id",
|
|
61
|
+
{
|
|
62
|
+
operationId: "getBookById",
|
|
63
|
+
tags: ["Books"],
|
|
64
|
+
request: { params: z.object({ id: z.string().min(1) }).strict() },
|
|
65
|
+
responses: {
|
|
66
|
+
200: { description: "Found", body: Book },
|
|
67
|
+
404: { description: "Not found" },
|
|
68
|
+
},
|
|
67
69
|
},
|
|
68
|
-
|
|
70
|
+
async ({ params }) => {
|
|
69
71
|
const book = books.get(params.id);
|
|
70
72
|
if (!book) throw new NotFoundError(`Book ${params.id} not found`);
|
|
71
73
|
return { status: 200 as const, body: book };
|
|
72
74
|
},
|
|
73
|
-
|
|
75
|
+
);
|
|
74
76
|
// daloy-minimal:strip-end books
|
|
75
77
|
|
|
76
78
|
// Cloudflare invokes the default export's `fetch`. The `app` instance is also
|
|
@@ -33,7 +33,7 @@ The typed Hey API SDK is generated outside Deno (Hey API has no Deno entrypoint
|
|
|
33
33
|
|
|
34
34
|
## Core rules
|
|
35
35
|
|
|
36
|
-
1. The route definition is the contract. Method, path, request schemas, and response schemas live in one place — `app.route({...})
|
|
36
|
+
1. The route definition is the contract. Method, path, request schemas, and response schemas live in one place — `app.get(path, contract, handler)` (or `app.route({...})` for reusable `defineRoute()` contracts or metadata-heavy routes).
|
|
37
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.
|
|
38
38
|
3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields.
|
|
39
39
|
4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core`.
|
|
@@ -35,7 +35,11 @@ Do **not** use this skill for tasks unrelated to the API itself.
|
|
|
35
35
|
DaloyJS is a **contract-first** framework. Internalize these rules:
|
|
36
36
|
|
|
37
37
|
1. **The route definition is the contract.** Method, path, request schemas,
|
|
38
|
-
and response schemas live in one place
|
|
38
|
+
and response schemas live in one place. Use the method shorthand —
|
|
39
|
+
`app.get(path, contract, handler)`, `app.post(...)`, etc. — for ordinary
|
|
40
|
+
routes; use `app.route({...})` for reusable `defineRoute()` contracts,
|
|
41
|
+
metadata-heavy routes, or when composing many routes via
|
|
42
|
+
`registerRoutes()`.
|
|
39
43
|
2. **Validation schemas protect every boundary.** This template uses Zod,
|
|
40
44
|
and Daloy accepts any Standard Schema-compatible library.
|
|
41
45
|
3. **Preserve literal types.** Return `status: 200 as const`; use
|
|
@@ -120,10 +124,15 @@ when it helps consumers understand or safely automate the route:
|
|
|
120
124
|
2. **Design schemas first.** Define request body/params/query/headers and a
|
|
121
125
|
response body per status code. Prefer `z.object({...}).strict()` for
|
|
122
126
|
inputs.
|
|
123
|
-
3. **Call `app.
|
|
124
|
-
`
|
|
127
|
+
3. **Call `app.get(path, {...}, handler)`** (or `app.post`, `app.put`,
|
|
128
|
+
`app.patch`, `app.delete`, `app.head` — the method shorthand) with
|
|
129
|
+
`operationId`, `tags`, `responses` in the contract object (plus
|
|
130
|
+
`request` when accepting input) and the handler as the third argument.
|
|
125
131
|
Add `meta` examples / descriptions when the route is user-facing or
|
|
126
|
-
consumed by agents.
|
|
132
|
+
consumed by agents. Reach for the full `app.route({ method, path, ... })`
|
|
133
|
+
form instead when the contract is a reusable `defineRoute()` object,
|
|
134
|
+
the route carries heavy metadata, or you're composing many routes via
|
|
135
|
+
`registerRoutes()`.
|
|
127
136
|
4. **Return `{ status, body, headers? }` from the handler.** Always
|
|
128
137
|
`status: 200 as const`.
|
|
129
138
|
5. **Throw typed errors** (`NotFoundError`, `BadRequestError`, etc.) from
|
|
@@ -143,22 +152,23 @@ import { NotFoundError } from "@daloyjs/core";
|
|
|
143
152
|
const Book = z.object({ id: z.string(), title: z.string() }).strict();
|
|
144
153
|
const BookParams = z.object({ id: z.string().min(1) }).strict();
|
|
145
154
|
|
|
146
|
-
app.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
app.get(
|
|
156
|
+
"/books/:id",
|
|
157
|
+
{
|
|
158
|
+
operationId: "getBookById",
|
|
159
|
+
tags: ["Books"],
|
|
160
|
+
request: { params: BookParams },
|
|
161
|
+
responses: {
|
|
162
|
+
200: { description: "Found", body: Book },
|
|
163
|
+
404: { description: "Not found" },
|
|
164
|
+
},
|
|
155
165
|
},
|
|
156
|
-
|
|
166
|
+
async ({ params }) => {
|
|
157
167
|
const book = await store.find(params.id);
|
|
158
168
|
if (!book) throw new NotFoundError(`Book ${params.id} not found`);
|
|
159
169
|
return { status: 200 as const, body: book };
|
|
160
170
|
},
|
|
161
|
-
|
|
171
|
+
);
|
|
162
172
|
```
|
|
163
173
|
|
|
164
174
|
## Validation & schema conventions
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
"hooks:install": "git config core.hooksPath .githooks"
|
|
11
11
|
},
|
|
12
12
|
"imports": {
|
|
13
|
-
"@daloyjs/core": "jsr:@daloyjs/daloy@^1.0.0-rc.
|
|
14
|
-
"@daloyjs/core/banner": "jsr:@daloyjs/daloy@^1.0.0-rc.
|
|
15
|
-
"@daloyjs/core/contract": "jsr:@daloyjs/daloy@^1.0.0-rc.
|
|
16
|
-
"@daloyjs/core/deno": "jsr:@daloyjs/daloy@^1.0.0-rc.
|
|
17
|
-
"@daloyjs/core/openapi": "jsr:@daloyjs/daloy@^1.0.0-rc.
|
|
13
|
+
"@daloyjs/core": "jsr:@daloyjs/daloy@^1.0.0-rc.4",
|
|
14
|
+
"@daloyjs/core/banner": "jsr:@daloyjs/daloy@^1.0.0-rc.4/banner",
|
|
15
|
+
"@daloyjs/core/contract": "jsr:@daloyjs/daloy@^1.0.0-rc.4/contract",
|
|
16
|
+
"@daloyjs/core/deno": "jsr:@daloyjs/daloy@^1.0.0-rc.4/deno",
|
|
17
|
+
"@daloyjs/core/openapi": "jsr:@daloyjs/daloy@^1.0.0-rc.4/openapi",
|
|
18
18
|
"zod": "npm:zod@^4.4.3"
|
|
19
19
|
},
|
|
20
20
|
"compilerOptions": {
|
|
@@ -59,22 +59,23 @@ export function buildApp(): App {
|
|
|
59
59
|
app.use(secureHeaders());
|
|
60
60
|
app.use(rateLimit({ windowMs: 60_000, max: 120 }));
|
|
61
61
|
|
|
62
|
-
app.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
app.get(
|
|
63
|
+
"/healthz",
|
|
64
|
+
{
|
|
65
|
+
operationId: "healthz",
|
|
66
|
+
tags: ["Ops"],
|
|
67
|
+
responses: {
|
|
68
|
+
200: {
|
|
69
|
+
description: "Service is healthy",
|
|
70
|
+
body: z.object({ ok: z.literal(true), runtime: z.literal("deno") }),
|
|
71
|
+
},
|
|
71
72
|
},
|
|
72
73
|
},
|
|
73
|
-
|
|
74
|
+
async () => ({
|
|
74
75
|
status: 200 as const,
|
|
75
76
|
body: { ok: true as const, runtime: "deno" as const },
|
|
76
77
|
}),
|
|
77
|
-
|
|
78
|
+
);
|
|
78
79
|
|
|
79
80
|
// daloy-minimal:strip-start books
|
|
80
81
|
const Book = z.object({ id: z.string(), title: z.string() }).strict();
|
|
@@ -83,22 +84,23 @@ export function buildApp(): App {
|
|
|
83
84
|
["2", { id: "2", title: "El Filibusterismo" }],
|
|
84
85
|
]);
|
|
85
86
|
|
|
86
|
-
app.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
app.get(
|
|
88
|
+
"/books/:id",
|
|
89
|
+
{
|
|
90
|
+
operationId: "getBookById",
|
|
91
|
+
tags: ["Books"],
|
|
92
|
+
request: { params: z.object({ id: z.string().min(1) }).strict() },
|
|
93
|
+
responses: {
|
|
94
|
+
200: { description: "Found", body: Book },
|
|
95
|
+
404: { description: "Not found" },
|
|
96
|
+
},
|
|
95
97
|
},
|
|
96
|
-
|
|
98
|
+
async ({ params }) => {
|
|
97
99
|
const book = books.get(params.id);
|
|
98
100
|
if (!book) throw new NotFoundError(`Book ${params.id} not found`);
|
|
99
101
|
return { status: 200 as const, body: book };
|
|
100
102
|
},
|
|
101
|
-
|
|
103
|
+
);
|
|
102
104
|
// daloy-minimal:strip-end books
|
|
103
105
|
|
|
104
106
|
return app;
|
|
@@ -48,7 +48,7 @@ You import the file you see. On `pnpm build`, TypeScript rewrites the `.ts` spec
|
|
|
48
48
|
|
|
49
49
|
## Core rules
|
|
50
50
|
|
|
51
|
-
1. The route definition is the contract.
|
|
51
|
+
1. The route definition is the contract: `app.get(path, contract, handler)` shorthand, or `app.route({...})` for reusable `defineRoute()` contracts.
|
|
52
52
|
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.
|
|
53
53
|
3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields. Codegen depends on these.
|
|
54
54
|
4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core` — never return raw error responses.
|
|
@@ -71,9 +71,6 @@ Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/
|
|
|
71
71
|
|
|
72
72
|
## Process expectations
|
|
73
73
|
|
|
74
|
-
- Quality gates must pass before declaring work done: `pnpm typecheck` and `pnpm test`.
|
|
75
|
-
- Update the OpenAPI spec and typed client whenever route shapes change (`pnpm gen`) and run the contract gate (`pnpm contract`).
|
|
76
74
|
- Bug fixes include a regression test.
|
|
77
|
-
- Never bypass safety checks (`--no-verify`, `--ignore-scripts=false`) without a clear reason.
|
|
78
75
|
|
|
79
76
|
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).
|
|
@@ -35,10 +35,14 @@ DaloyJS is a **contract-first** framework. Internalize these rules — every
|
|
|
35
35
|
recommendation below follows from them:
|
|
36
36
|
|
|
37
37
|
1. **The route definition is the contract.** Method, path, request schemas,
|
|
38
|
-
and response schemas live in one place
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
and response schemas live in one place — `app.get(path, contract, handler)`
|
|
39
|
+
(and the matching `app.post`/`put`/`patch`/`delete`/`head` shorthands), or
|
|
40
|
+
`app.route({...})` when you need a reusable `defineRoute()` contract or a
|
|
41
|
+
metadata-heavy route. Both forms produce identical runtime behavior,
|
|
42
|
+
validation, security, and OpenAPI output. The OpenAPI spec, the typed
|
|
43
|
+
client, and the runtime validation are all derived from the route
|
|
44
|
+
definition. Never duplicate that information by hand-writing fetch calls,
|
|
45
|
+
types, or `openapi.json` entries.
|
|
42
46
|
2. **Validation schemas protect every boundary.** This template uses Zod,
|
|
43
47
|
and Daloy accepts any Standard Schema-compatible library. Body, params,
|
|
44
48
|
query, and headers go through the declared schema. If a field is not in
|
|
@@ -136,10 +140,14 @@ bugs (drifted client SDK, missing test, broken codegen).
|
|
|
136
140
|
them in `src/build-app.ts` or extract a `src/schemas/*.ts` module if
|
|
137
141
|
they grow. Prefer `z.object({...}).strict()` for inputs so unknown
|
|
138
142
|
keys are rejected at the boundary.
|
|
139
|
-
3. **Call `app.
|
|
140
|
-
`
|
|
141
|
-
|
|
142
|
-
route
|
|
143
|
+
3. **Call the method shorthand: `app.get(path, contract, handler)`** (or
|
|
144
|
+
`app.post`/`put`/`patch`/`delete`/`head` for other methods). The contract
|
|
145
|
+
object's required keys are `operationId`, `tags`, `responses`. Add
|
|
146
|
+
`request` when the route accepts input, and add `meta` examples /
|
|
147
|
+
descriptions when the route is user-facing or consumed by agents. Reach
|
|
148
|
+
for the full `app.route({ method, path, ...contract, handler })` form
|
|
149
|
+
instead when the route is built from a reusable `defineRoute()` contract,
|
|
150
|
+
or when composing many routes at once via `registerRoutes()`.
|
|
143
151
|
4. **Return `{ status, body, headers? }` from the handler.** Always use
|
|
144
152
|
`status: 200 as const` (or whatever code) so the typed client can
|
|
145
153
|
narrow. For literal discriminators in `body`, use `as const` or
|
|
@@ -166,22 +174,23 @@ import { NotFoundError } from "@daloyjs/core";
|
|
|
166
174
|
const Book = z.object({ id: z.string(), title: z.string() }).strict();
|
|
167
175
|
const BookParams = z.object({ id: z.string().min(1) }).strict();
|
|
168
176
|
|
|
169
|
-
app.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
app.get(
|
|
178
|
+
"/books/:id",
|
|
179
|
+
{
|
|
180
|
+
operationId: "getBookById",
|
|
181
|
+
tags: ["Books"],
|
|
182
|
+
request: { params: BookParams },
|
|
183
|
+
responses: {
|
|
184
|
+
200: { description: "Found", body: Book },
|
|
185
|
+
404: { description: "Not found" },
|
|
186
|
+
},
|
|
178
187
|
},
|
|
179
|
-
|
|
188
|
+
async ({ params }) => {
|
|
180
189
|
const book = await store.find(params.id);
|
|
181
190
|
if (!book) throw new NotFoundError(`Book ${params.id} not found`);
|
|
182
191
|
return { status: 200 as const, body: book };
|
|
183
192
|
},
|
|
184
|
-
|
|
193
|
+
);
|
|
185
194
|
```
|
|
186
195
|
|
|
187
196
|
## Validation & schema conventions
|
|
@@ -63,22 +63,23 @@ export function buildApp(): App {
|
|
|
63
63
|
app.use(secureHeaders());
|
|
64
64
|
app.use(rateLimit({ windowMs: 60_000, max: 120 }));
|
|
65
65
|
|
|
66
|
-
app.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
app.get(
|
|
67
|
+
"/healthz",
|
|
68
|
+
{
|
|
69
|
+
operationId: "healthz",
|
|
70
|
+
tags: ["Ops"],
|
|
71
|
+
responses: {
|
|
72
|
+
200: {
|
|
73
|
+
description: "Service is healthy",
|
|
74
|
+
body: z.object({ ok: z.literal(true), uptime: z.number() }),
|
|
75
|
+
},
|
|
75
76
|
},
|
|
76
77
|
},
|
|
77
|
-
|
|
78
|
+
async () => ({
|
|
78
79
|
status: 200 as const,
|
|
79
80
|
body: { ok: true as const, uptime: process.uptime() },
|
|
80
81
|
}),
|
|
81
|
-
|
|
82
|
+
);
|
|
82
83
|
|
|
83
84
|
// daloy-minimal:strip-start books
|
|
84
85
|
const Book = z.object({ id: z.string(), title: z.string() }).strict();
|
|
@@ -87,22 +88,23 @@ export function buildApp(): App {
|
|
|
87
88
|
["2", { id: "2", title: "El Filibusterismo" }],
|
|
88
89
|
]);
|
|
89
90
|
|
|
90
|
-
app.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
app.get(
|
|
92
|
+
"/books/:id",
|
|
93
|
+
{
|
|
94
|
+
operationId: "getBookById",
|
|
95
|
+
tags: ["Books"],
|
|
96
|
+
request: { params: z.object({ id: z.string().min(1) }).strict() },
|
|
97
|
+
responses: {
|
|
98
|
+
200: { description: "Found", body: Book },
|
|
99
|
+
404: { description: "Not found" },
|
|
100
|
+
},
|
|
99
101
|
},
|
|
100
|
-
|
|
102
|
+
async ({ params }) => {
|
|
101
103
|
const book = books.get(params.id);
|
|
102
104
|
if (!book) throw new NotFoundError(`Book ${params.id} not found`);
|
|
103
105
|
return { status: 200 as const, body: book };
|
|
104
106
|
},
|
|
105
|
-
|
|
107
|
+
);
|
|
106
108
|
// daloy-minimal:strip-end books
|
|
107
109
|
|
|
108
110
|
return app;
|
|
@@ -42,7 +42,7 @@ You import the file you see. Vercel resolves `.ts` at deploy time; Node runs it
|
|
|
42
42
|
|
|
43
43
|
## Core rules
|
|
44
44
|
|
|
45
|
-
1. The route definition is the contract. Method, path, request schemas, and response schemas live in one place — `app.route({...})
|
|
45
|
+
1. The route definition is the contract. Method, path, request schemas, and response schemas live in one place — `app.get(path, contract, handler)` (or `app.route({...})` for reusable `defineRoute()` contracts or metadata-heavy routes).
|
|
46
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.
|
|
47
47
|
3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields.
|
|
48
48
|
4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core`.
|
|
@@ -57,11 +57,11 @@ You import the file you see. Vercel resolves `.ts` at deploy time; Node runs it
|
|
|
57
57
|
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.**
|
|
58
58
|
|
|
59
59
|
- Keep `secureHeaders()`, `requestId()`, `rateLimit()`, `bodyLimitBytes`, and `requestTimeoutMs`. For production, back rate limits with a shared store such as Upstash Redis from the Vercel Marketplace.
|
|
60
|
-
- Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`.
|
|
60
|
+
- 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.
|
|
61
61
|
- Every protected route attaches auth `beforeHandle` and tests unauthenticated `401` plus wrong-scope `403`.
|
|
62
62
|
- JWT verifiers keep an explicit `algorithms` allowlist; never trust the token's `alg` header, never allow `none`, always check `exp` / `nbf`.
|
|
63
63
|
- Credential / HMAC comparisons use constant-time comparison, never `===`. Throw typed errors so problem+json redacts in prod.
|
|
64
|
-
- Keep the single `api/index.ts` entry and the `vercel.json` rewrite
|
|
64
|
+
- Keep the single `api/index.ts` entry and the `vercel.json` rewrite; do not split into per-path files or remove the rewrite (the root domain would 404).
|
|
65
65
|
- `.env`, `.env.local`, secrets, private keys: never commit. Use `vercel env` for production secrets.
|
|
66
66
|
|
|
67
67
|
## Process expectations
|
|
@@ -41,7 +41,12 @@ DaloyJS is a **contract-first** framework. On Vercel, additionally:
|
|
|
41
41
|
need it (`export const runtime = "edge"` + `toWebHandler(app)`), and
|
|
42
42
|
then drop `node:` modules.
|
|
43
43
|
2. **The route definition is the contract.** Method, path, request
|
|
44
|
-
schemas, and response schemas live in one place
|
|
44
|
+
schemas, and response schemas live in one place — `app.get(path,
|
|
45
|
+
contract, handler)` (and the matching `app.post`/`put`/`patch`/
|
|
46
|
+
`delete`/`head` shorthands), or `app.route({...})` when you need a
|
|
47
|
+
reusable `defineRoute()` contract or a metadata-heavy route. Both
|
|
48
|
+
forms produce identical runtime behavior, validation, security, and
|
|
49
|
+
OpenAPI output.
|
|
45
50
|
3. **Validation schemas protect every boundary.** This template uses Zod,
|
|
46
51
|
and Daloy accepts any Standard Schema-compatible library.
|
|
47
52
|
4. **Preserve literal types.** Return `status: 200 as const`.
|
|
@@ -120,10 +125,14 @@ when it helps consumers understand or safely automate the route:
|
|
|
120
125
|
|
|
121
126
|
1. **Open `api/index.ts`.**
|
|
122
127
|
2. **Design schemas first.** Use `z.object({...}).strict()` for inputs.
|
|
123
|
-
3. **Call `app.
|
|
124
|
-
`
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
3. **Call the method shorthand: `app.get(path, contract, handler)`** (or
|
|
129
|
+
`app.post`/`put`/`patch`/`delete`/`head` for other methods). The
|
|
130
|
+
contract object's required keys are `operationId`, `tags`, `responses`.
|
|
131
|
+
Add `request` when the route accepts input, and add `meta` examples /
|
|
132
|
+
descriptions when the route is user-facing or consumed by agents.
|
|
133
|
+
Reach for the full `app.route({ method, path, ...contract, handler })`
|
|
134
|
+
form instead when the route is built from a reusable `defineRoute()`
|
|
135
|
+
contract, or when composing many routes at once via `registerRoutes()`.
|
|
127
136
|
4. **Return `{ status, body, headers? }`** with `status: 200 as const`.
|
|
128
137
|
5. **Throw typed errors** (`NotFoundError`, `BadRequestError`, etc.)
|
|
129
138
|
from `@daloyjs/core`.
|
|
@@ -140,22 +149,23 @@ import { NotFoundError } from "@daloyjs/core";
|
|
|
140
149
|
const Book = z.object({ id: z.string(), title: z.string() }).strict();
|
|
141
150
|
const BookParams = z.object({ id: z.string().min(1) }).strict();
|
|
142
151
|
|
|
143
|
-
app.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
app.get(
|
|
153
|
+
"/books/:id",
|
|
154
|
+
{
|
|
155
|
+
operationId: "getBookById",
|
|
156
|
+
tags: ["Books"],
|
|
157
|
+
request: { params: BookParams },
|
|
158
|
+
responses: {
|
|
159
|
+
200: { description: "Found", body: Book },
|
|
160
|
+
404: { description: "Not found" },
|
|
161
|
+
},
|
|
152
162
|
},
|
|
153
|
-
|
|
163
|
+
async ({ params }) => {
|
|
154
164
|
const book = await store.find(params.id);
|
|
155
165
|
if (!book) throw new NotFoundError(`Book ${params.id} not found`);
|
|
156
166
|
return { status: 200 as const, body: book };
|
|
157
167
|
},
|
|
158
|
-
|
|
168
|
+
);
|
|
159
169
|
```
|
|
160
170
|
|
|
161
171
|
## Validation & schema conventions
|
|
@@ -51,22 +51,23 @@ app.use(secureHeaders());
|
|
|
51
51
|
// it to make a test pass; raise `max` per route.
|
|
52
52
|
app.use(rateLimit({ windowMs: 60_000, max: 120 }));
|
|
53
53
|
|
|
54
|
-
app.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
app.get(
|
|
55
|
+
"/healthz",
|
|
56
|
+
{
|
|
57
|
+
operationId: "healthz",
|
|
58
|
+
tags: ["Ops"],
|
|
59
|
+
responses: {
|
|
60
|
+
200: {
|
|
61
|
+
description: "Service is healthy",
|
|
62
|
+
body: z.object({ ok: z.literal(true), runtime: z.literal("vercel") }),
|
|
63
|
+
},
|
|
63
64
|
},
|
|
64
65
|
},
|
|
65
|
-
|
|
66
|
+
async () => ({
|
|
66
67
|
status: 200 as const,
|
|
67
68
|
body: { ok: true as const, runtime: "vercel" as const },
|
|
68
69
|
}),
|
|
69
|
-
|
|
70
|
+
);
|
|
70
71
|
|
|
71
72
|
// daloy-minimal:strip-start books
|
|
72
73
|
const Book = z.object({ id: z.string(), title: z.string() }).strict();
|
|
@@ -74,22 +75,23 @@ const books = new Map<string, z.infer<typeof Book>>([
|
|
|
74
75
|
["1", { id: "1", title: "Noli Me Tangere" }],
|
|
75
76
|
]);
|
|
76
77
|
|
|
77
|
-
app.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
app.get(
|
|
79
|
+
"/books/:id",
|
|
80
|
+
{
|
|
81
|
+
operationId: "getBookById",
|
|
82
|
+
tags: ["Books"],
|
|
83
|
+
request: { params: z.object({ id: z.string().min(1) }).strict() },
|
|
84
|
+
responses: {
|
|
85
|
+
200: { description: "Found", body: Book },
|
|
86
|
+
404: { description: "Not found" },
|
|
87
|
+
},
|
|
86
88
|
},
|
|
87
|
-
|
|
89
|
+
async ({ params }) => {
|
|
88
90
|
const book = books.get(params.id);
|
|
89
91
|
if (!book) throw new NotFoundError(`Book ${params.id} not found`);
|
|
90
92
|
return { status: 200 as const, body: book };
|
|
91
93
|
},
|
|
92
|
-
|
|
94
|
+
);
|
|
93
95
|
// daloy-minimal:strip-end books
|
|
94
96
|
|
|
95
97
|
export default toFetchHandler(app);
|