create-daloy 1.0.0-beta.7 → 1.0.0-rc.0

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": "1.0.0-beta.7",
3
+ "version": "1.0.0-rc.0",
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:e2d088ad-a90f-56b0-a738-fd1d1aa81e82",
4
+ "serialNumber": "urn:uuid:59eb0e74-bb0a-538a-b5e2-6c2d63f0c0ff",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-07-02T15:06:50.370Z",
7
+ "timestamp": "2026-07-03T11:50:58.874Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "DaloyJS",
11
11
  "name": "daloy-generate-sbom",
12
- "version": "1.0.0-beta.7"
12
+ "version": "1.0.0-rc.0"
13
13
  }
14
14
  ],
15
15
  "authors": [],
16
16
  "component": {
17
17
  "type": "library",
18
- "bom-ref": "pkg:npm/create-daloy@1.0.0-beta.7",
18
+ "bom-ref": "pkg:npm/create-daloy@1.0.0-rc.0",
19
19
  "name": "create-daloy",
20
- "version": "1.0.0-beta.7",
20
+ "version": "1.0.0-rc.0",
21
21
  "description": "Scaffold a new DaloyJS project. Run with `pnpm create daloy`, `npm create daloy@latest`, `yarn create daloy`, or `bun create daloy`.",
22
- "purl": "pkg:npm/create-daloy@1.0.0-beta.7",
22
+ "purl": "pkg:npm/create-daloy@1.0.0-rc.0",
23
23
  "licenses": [
24
24
  {
25
25
  "license": {
@@ -42,9 +42,9 @@
42
42
  }
43
43
  ],
44
44
  "swid": {
45
- "tagId": "swidtag-create-daloy-1.0.0-beta.7",
45
+ "tagId": "swidtag-create-daloy-1.0.0-rc.0",
46
46
  "name": "create-daloy",
47
- "version": "1.0.0-beta.7",
47
+ "version": "1.0.0-rc.0",
48
48
  "tagVersion": 0,
49
49
  "patch": false
50
50
  }
@@ -53,7 +53,7 @@
53
53
  "components": [],
54
54
  "dependencies": [
55
55
  {
56
- "ref": "pkg:npm/create-daloy@1.0.0-beta.7",
56
+ "ref": "pkg:npm/create-daloy@1.0.0-rc.0",
57
57
  "dependsOn": []
58
58
  }
59
59
  ]
package/sbom.spdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "create-daloy-1.0.0-beta.7",
6
- "documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-1.0.0-beta.7-e2d088ad-a90f-56b0-a738-fd1d1aa81e82",
5
+ "name": "create-daloy-1.0.0-rc.0",
6
+ "documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-1.0.0-rc.0-59eb0e74-bb0a-538a-b5e2-6c2d63f0c0ff",
7
7
  "creationInfo": {
8
- "created": "2026-07-02T15:06:50.370Z",
8
+ "created": "2026-07-03T11:50:58.874Z",
9
9
  "creators": [
10
10
  "Tool: daloy-generate-sbom",
11
11
  "Organization: DaloyJS"
@@ -16,7 +16,7 @@
16
16
  {
17
17
  "SPDXID": "SPDXRef-Package-create-daloy",
18
18
  "name": "create-daloy",
19
- "versionInfo": "1.0.0-beta.7",
19
+ "versionInfo": "1.0.0-rc.0",
20
20
  "downloadLocation": "https://github.com/daloyjs/daloy",
21
21
  "filesAnalyzed": false,
22
22
  "licenseConcluded": "MIT",
@@ -27,7 +27,7 @@
27
27
  {
28
28
  "referenceCategory": "PACKAGE-MANAGER",
29
29
  "referenceType": "purl",
30
- "referenceLocator": "pkg:npm/create-daloy@1.0.0-beta.7"
30
+ "referenceLocator": "pkg:npm/create-daloy@1.0.0-rc.0"
31
31
  }
32
32
  ]
33
33
  }
@@ -73,8 +73,8 @@ bun run typecheck # tsc --noEmit
73
73
  bun test # Bun's native test runner
74
74
  bun run gen:openapi # write generated/openapi.json
75
75
  bun run gen:client # write generated/client/
76
+ bun run gen # gen:openapi + gen:client
76
77
  bun run contract # run the focused contract test
77
- bun run build # produce dist/
78
78
  ```
79
79
 
80
80
  Always run `bun run typecheck` and `bun test` before declaring a task done.
@@ -91,7 +91,7 @@ definitions:
91
91
 
92
92
  - `GET /openapi.json` — OpenAPI 3.1 spec as JSON.
93
93
  - `GET /openapi.yaml` — OpenAPI 3.1 spec as YAML (served inline as
94
- `text/yaml; charset=utf-8`, since `@daloyjs/core` 0.13.1).
94
+ `text/yaml; charset=utf-8`).
95
95
  - `GET /docs` — Scalar API reference UI that loads the spec.
96
96
 
97
97
  Customize via `docs: { openapiPath, openapiYamlPath, path, ui }`. Set
@@ -179,7 +179,7 @@ app.route({
179
179
 
180
180
  ## Error handling
181
181
 
182
- - Throw typed errors from `@daloyjs/core` — they serialize to RFC 7807
182
+ - Throw typed errors from `@daloyjs/core` — they serialize to RFC 9457
183
183
  problem responses.
184
184
  - Add a `responses[code]` entry for every error you throw.
185
185
  - Do not swallow errors. Log via `ctx.log.error(err, "context")` and
@@ -225,7 +225,7 @@ in-memory fake via `buildApp({ store })` rather than mocking `fetch`.
225
225
  The shipped contract test should fail invalid examples, duplicate/missing
226
226
  `operationId`, or missing responses.
227
227
 
228
- Aim for **100% line and function coverage** on routes you add.
228
+ Aim for complete happy- and unhappy-path test coverage of the routes you add.
229
229
 
230
230
  ## Security best practices
231
231
 
@@ -279,6 +279,11 @@ Aim for **100% line and function coverage** on routes you add.
279
279
  - Avoid Node-only APIs in code that may also run on the Cloudflare/Vercel
280
280
  templates; the Bun runtime is web-standard friendly but check before
281
281
  reaching for `node:fs` etc.
282
+ - If a route intentionally returns a body the contract cannot describe (a
283
+ raw `Response`, HTML, a proxied payload), set
284
+ `acknowledgeNoResponseBodySchema: true` on that route — never silence the
285
+ `security.response.bodySchemaMissing` boot warning by widening a schema
286
+ to `z.any()`.
282
287
 
283
288
  ## Process expectations
284
289
 
@@ -292,6 +297,18 @@ Aim for **100% line and function coverage** on routes you add.
292
297
  - Keep `README.md`, this `SKILL.md`, and `AGENTS.md` consistent with the
293
298
  code.
294
299
 
300
+ ## Exposing this API over MCP
301
+
302
+ `@daloyjs/core` ships a dependency-free Model Context Protocol (Streamable
303
+ HTTP) server helper — also available from the `@daloyjs/core/mcp` subpath.
304
+ To expose selected capabilities to MCP clients (AI agents), build a handler
305
+ with `createMcpHandler({ tools, resources, prompts })` and mount it with
306
+ `mcpRoutes("/mcp", handler)`. Throw `McpToolError` for caller-correctable
307
+ tool failures. The handler ships protocol-level guards (body cap, UTF-8/JSON
308
+ validation, `Origin` checks against DNS rebinding) and composes with the
309
+ existing middleware chain — put `bearerAuth()` / `rateLimit()` in front of
310
+ it like any other route. See <https://daloyjs.dev/docs> for the MCP guide.
311
+
295
312
  ## More
296
313
 
297
314
  - Framework docs: <https://daloyjs.dev/docs>
@@ -19,7 +19,7 @@
19
19
  "hooks:install": "git config core.hooksPath .githooks"
20
20
  },
21
21
  "dependencies": {
22
- "@daloyjs/core": "^1.0.0-beta.7",
22
+ "@daloyjs/core": "^1.0.0-rc.0",
23
23
  "zod": "^4.4.3"
24
24
  },
25
25
  "devDependencies": {
@@ -70,13 +70,13 @@ export function buildApp(): App {
70
70
  },
71
71
  },
72
72
  handler: async () => ({
73
- status: 200,
73
+ status: 200 as const,
74
74
  body: { ok: true as const, runtime: "bun" as const },
75
75
  }),
76
76
  });
77
77
 
78
78
  // daloy-minimal:strip-start books
79
- const Book = z.object({ id: z.string(), title: z.string() });
79
+ const Book = z.object({ id: z.string(), title: z.string() }).strict();
80
80
  const books = new Map<string, z.infer<typeof Book>>([
81
81
  ["1", { id: "1", title: "Noli Me Tangere" }],
82
82
  ["2", { id: "2", title: "El Filibusterismo" }],
@@ -87,7 +87,7 @@ export function buildApp(): App {
87
87
  path: "/books/:id",
88
88
  operationId: "getBookById",
89
89
  tags: ["Books"],
90
- request: { params: z.object({ id: z.string() }) },
90
+ request: { params: z.object({ id: z.string().min(1) }).strict() },
91
91
  responses: {
92
92
  200: { description: "Found", body: Book },
93
93
  404: { description: "Not found" },
@@ -95,7 +95,7 @@ export function buildApp(): App {
95
95
  handler: async ({ params }) => {
96
96
  const book = books.get(params.id);
97
97
  if (!book) throw new NotFoundError(`Book ${params.id} not found`);
98
- return { status: 200, body: book };
98
+ return { status: 200 as const, body: book };
99
99
  },
100
100
  });
101
101
  // daloy-minimal:strip-end books
@@ -87,7 +87,7 @@ smallest possible Worker. The routes:
87
87
 
88
88
  - `GET /openapi.json` — OpenAPI 3.1 spec as JSON.
89
89
  - `GET /openapi.yaml` — OpenAPI 3.1 spec as YAML (served inline as
90
- `text/yaml; charset=utf-8`, since `@daloyjs/core` 0.13.1).
90
+ `text/yaml; charset=utf-8`).
91
91
  - `GET /docs` — Scalar API reference UI that loads the spec.
92
92
 
93
93
  On Workers the Scalar UI adds the most weight; consider
@@ -185,7 +185,7 @@ export default {
185
185
 
186
186
  ## Error handling
187
187
 
188
- - Throw typed errors from `@daloyjs/core` — they serialize to RFC 7807
188
+ - Throw typed errors from `@daloyjs/core` — they serialize to RFC 9457
189
189
  problem responses.
190
190
  - Add a `responses[code]` entry for every error you throw.
191
191
  - For unexpected errors, let them bubble. The framework's error
@@ -224,7 +224,7 @@ in-memory fake into `buildApp(env)` during tests.
224
224
  The shipped contract test should fail invalid examples, duplicate/missing
225
225
  `operationId`, or missing responses.
226
226
 
227
- Aim for **100% line and function coverage** on the routes you add.
227
+ Aim for complete happy- and unhappy-path test coverage of the routes you add.
228
228
 
229
229
  ## Security best practices
230
230
 
@@ -284,6 +284,11 @@ Aim for **100% line and function coverage** on the routes you add.
284
284
  - Do not add runtime dependencies without checking the hardened `.npmrc` (installs wait 24h after publish by default).
285
285
  - Long-running work belongs in `ctx.waitUntil(...)`, not blocking the
286
286
  response.
287
+ - If a route intentionally returns a body the contract cannot describe (a
288
+ raw `Response`, HTML, a proxied payload), set
289
+ `acknowledgeNoResponseBodySchema: true` on that route — never silence the
290
+ `security.response.bodySchemaMissing` boot warning by widening a schema
291
+ to `z.any()`.
287
292
 
288
293
  ## Process expectations
289
294
 
@@ -296,6 +301,18 @@ Aim for **100% line and function coverage** on the routes you add.
296
301
  do not attempt to authenticate on their behalf.
297
302
  - Keep `README.md`, this `SKILL.md`, and `AGENTS.md` consistent.
298
303
 
304
+ ## Exposing this API over MCP
305
+
306
+ `@daloyjs/core` ships a dependency-free Model Context Protocol (Streamable
307
+ HTTP) server helper — also available from the `@daloyjs/core/mcp` subpath.
308
+ To expose selected capabilities to MCP clients (AI agents), build a handler
309
+ with `createMcpHandler({ tools, resources, prompts })` and mount it with
310
+ `mcpRoutes("/mcp", handler)`. Throw `McpToolError` for caller-correctable
311
+ tool failures. The handler ships protocol-level guards (body cap, UTF-8/JSON
312
+ validation, `Origin` checks against DNS rebinding) and composes with the
313
+ existing middleware chain — put `bearerAuth()` / `rateLimit()` in front of
314
+ it like any other route. See <https://daloyjs.dev/docs> for the MCP guide.
315
+
299
316
  ## More
300
317
 
301
318
  - Framework docs: <https://daloyjs.dev/docs>
@@ -13,7 +13,7 @@
13
13
  "hooks:install": "git config core.hooksPath .githooks"
14
14
  },
15
15
  "dependencies": {
16
- "@daloyjs/core": "^1.0.0-beta.7",
16
+ "@daloyjs/core": "^1.0.0-rc.0",
17
17
  "zod": "^4.4.3"
18
18
  },
19
19
  "devDependencies": {
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { App, NotFoundError, requestId, secureHeaders } from "@daloyjs/core";
2
+ import { App, NotFoundError, rateLimit, requestId, secureHeaders } from "@daloyjs/core";
3
3
  import { toFetchHandler } from "@daloyjs/core/cloudflare";
4
4
 
5
5
  const app = new App({
@@ -26,6 +26,11 @@ const app = new App({
26
26
 
27
27
  app.use(requestId());
28
28
  app.use(secureHeaders());
29
+ // The in-memory limiter resets per Worker isolate, so treat it as a
30
+ // per-isolate abuse brake, not a global quota. For high-traffic routes, attach
31
+ // Cloudflare's native rate-limit binding in addition to — not instead of —
32
+ // this baseline. Do not remove it to make a test pass; raise `max` per route.
33
+ app.use(rateLimit({ windowMs: 60_000, max: 120 }));
29
34
 
30
35
  app.route({
31
36
  method: "GET",
@@ -39,13 +44,13 @@ app.route({
39
44
  },
40
45
  },
41
46
  handler: async () => ({
42
- status: 200,
47
+ status: 200 as const,
43
48
  body: { ok: true as const, runtime: "cloudflare-worker" as const },
44
49
  }),
45
50
  });
46
51
 
47
52
  // daloy-minimal:strip-start books
48
- const Book = z.object({ id: z.string(), title: z.string() });
53
+ const Book = z.object({ id: z.string(), title: z.string() }).strict();
49
54
  const books = new Map<string, z.infer<typeof Book>>([
50
55
  ["1", { id: "1", title: "Noli Me Tangere" }],
51
56
  ]);
@@ -55,7 +60,7 @@ app.route({
55
60
  path: "/books/:id",
56
61
  operationId: "getBookById",
57
62
  tags: ["Books"],
58
- request: { params: z.object({ id: z.string() }) },
63
+ request: { params: z.object({ id: z.string().min(1) }).strict() },
59
64
  responses: {
60
65
  200: { description: "Found", body: Book },
61
66
  404: { description: "Not found" },
@@ -63,7 +68,7 @@ app.route({
63
68
  handler: async ({ params }) => {
64
69
  const book = books.get(params.id);
65
70
  if (!book) throw new NotFoundError(`Book ${params.id} not found`);
66
- return { status: 200, body: book };
71
+ return { status: 200 as const, body: book };
67
72
  },
68
73
  });
69
74
  // daloy-minimal:strip-end books
@@ -93,7 +93,7 @@ definitions:
93
93
 
94
94
  - `GET /openapi.json` — OpenAPI 3.1 spec as JSON.
95
95
  - `GET /openapi.yaml` — OpenAPI 3.1 spec as YAML (served inline as
96
- `text/yaml; charset=utf-8`, since `@daloyjs/core` 0.13.1).
96
+ `text/yaml; charset=utf-8`).
97
97
  - `GET /docs` — Scalar API reference UI that loads the spec.
98
98
 
99
99
  Customize via `docs: { openapiPath, openapiYamlPath, path, ui }`. Set
@@ -177,7 +177,7 @@ app.route({
177
177
 
178
178
  ## Error handling
179
179
 
180
- - Throw typed errors from `@daloyjs/core` — they serialize to RFC 7807
180
+ - Throw typed errors from `@daloyjs/core` — they serialize to RFC 9457
181
181
  problem responses.
182
182
  - Add a `responses[code]` entry for every error you throw.
183
183
  - Do not swallow errors. Log via `ctx.log.error(...)` and rethrow.
@@ -221,7 +221,7 @@ limiting (429). For external services, inject an in-memory fake via
221
221
  The shipped contract test should fail invalid examples, duplicate/missing
222
222
  `operationId`, or missing responses.
223
223
 
224
- Aim for **100% line and function coverage** on routes you add.
224
+ Aim for complete happy- and unhappy-path test coverage of the routes you add.
225
225
 
226
226
  ## Security best practices
227
227
 
@@ -271,6 +271,11 @@ Aim for **100% line and function coverage** on routes you add.
271
271
  - Use `deno task ...`, not `npm`/`pnpm`. There is no `package.json`.
272
272
  - If you need a new dependency, add it to `imports` in `deno.json` via
273
273
  `npm:` or `jsr:` specifiers; do not introduce a `package.json`.
274
+ - If a route intentionally returns a body the contract cannot describe (a
275
+ raw `Response`, HTML, a proxied payload), set
276
+ `acknowledgeNoResponseBodySchema: true` on that route — never silence the
277
+ `security.response.bodySchemaMissing` boot warning by widening a schema
278
+ to `z.any()`.
274
279
 
275
280
  ## Process expectations
276
281
 
@@ -284,6 +289,18 @@ Aim for **100% line and function coverage** on routes you add.
284
289
  - Keep `README.md`, this `SKILL.md`, and `AGENTS.md` consistent with the
285
290
  code.
286
291
 
292
+ ## Exposing this API over MCP
293
+
294
+ `@daloyjs/core` ships a dependency-free Model Context Protocol (Streamable
295
+ HTTP) server helper — also available from the `@daloyjs/core/mcp` subpath.
296
+ To expose selected capabilities to MCP clients (AI agents), build a handler
297
+ with `createMcpHandler({ tools, resources, prompts })` and mount it with
298
+ `mcpRoutes("/mcp", handler)`. Throw `McpToolError` for caller-correctable
299
+ tool failures. The handler ships protocol-level guards (body cap, UTF-8/JSON
300
+ validation, `Origin` checks against DNS rebinding) and composes with the
301
+ existing middleware chain — put `bearerAuth()` / `rateLimit()` in front of
302
+ it like any other route. See <https://daloyjs.dev/docs> for the MCP guide.
303
+
287
304
  ## More
288
305
 
289
306
  - Framework docs: <https://daloyjs.dev/docs>
@@ -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-beta.7",
14
- "@daloyjs/core/banner": "jsr:@daloyjs/daloy@^1.0.0-beta.7/banner",
15
- "@daloyjs/core/contract": "jsr:@daloyjs/daloy@^1.0.0-beta.7/contract",
16
- "@daloyjs/core/deno": "jsr:@daloyjs/daloy@^1.0.0-beta.7/deno",
17
- "@daloyjs/core/openapi": "jsr:@daloyjs/daloy@^1.0.0-beta.7/openapi",
13
+ "@daloyjs/core": "jsr:@daloyjs/daloy@^1.0.0-rc.0",
14
+ "@daloyjs/core/banner": "jsr:@daloyjs/daloy@^1.0.0-rc.0/banner",
15
+ "@daloyjs/core/contract": "jsr:@daloyjs/daloy@^1.0.0-rc.0/contract",
16
+ "@daloyjs/core/deno": "jsr:@daloyjs/daloy@^1.0.0-rc.0/deno",
17
+ "@daloyjs/core/openapi": "jsr:@daloyjs/daloy@^1.0.0-rc.0/openapi",
18
18
  "zod": "npm:zod@^4.4.3"
19
19
  },
20
20
  "compilerOptions": {
@@ -71,13 +71,13 @@ export function buildApp(): App {
71
71
  },
72
72
  },
73
73
  handler: async () => ({
74
- status: 200,
74
+ status: 200 as const,
75
75
  body: { ok: true as const, runtime: "deno" as const },
76
76
  }),
77
77
  });
78
78
 
79
79
  // daloy-minimal:strip-start books
80
- const Book = z.object({ id: z.string(), title: z.string() });
80
+ const Book = z.object({ id: z.string(), title: z.string() }).strict();
81
81
  const books = new Map<string, z.infer<typeof Book>>([
82
82
  ["1", { id: "1", title: "Noli Me Tangere" }],
83
83
  ["2", { id: "2", title: "El Filibusterismo" }],
@@ -88,7 +88,7 @@ export function buildApp(): App {
88
88
  path: "/books/:id",
89
89
  operationId: "getBookById",
90
90
  tags: ["Books"],
91
- request: { params: z.object({ id: z.string() }) },
91
+ request: { params: z.object({ id: z.string().min(1) }).strict() },
92
92
  responses: {
93
93
  200: { description: "Found", body: Book },
94
94
  404: { description: "Not found" },
@@ -96,7 +96,7 @@ export function buildApp(): App {
96
96
  handler: async ({ params }) => {
97
97
  const book = books.get(params.id);
98
98
  if (!book) throw new NotFoundError(`Book ${params.id} not found`);
99
- return { status: 200, body: book };
99
+ return { status: 200 as const, body: book };
100
100
  },
101
101
  });
102
102
  // daloy-minimal:strip-end books
@@ -82,6 +82,7 @@ pnpm gen:client # write generated/client/
82
82
  pnpm contract # daloy inspect --check src/build-app.ts
83
83
  pnpm build # emit dist/
84
84
  pnpm audit # supply-chain audit
85
+ pnpm hooks:install # enable the optional pre-push contract gate
85
86
  ```
86
87
 
87
88
  Always run `pnpm typecheck` and `pnpm test` before declaring a task done.
@@ -97,7 +98,7 @@ definitions:
97
98
 
98
99
  - `GET /openapi.json` — OpenAPI 3.1 spec as JSON.
99
100
  - `GET /openapi.yaml` — OpenAPI 3.1 spec as YAML (served inline as
100
- `text/yaml; charset=utf-8`, since `@daloyjs/core` 0.13.1).
101
+ `text/yaml; charset=utf-8`).
101
102
  - `GET /docs` — Scalar API reference UI that loads the spec.
102
103
 
103
104
  Customize via `docs: { openapiPath, openapiYamlPath, path, ui }`. Set
@@ -146,7 +147,7 @@ bugs (drifted client SDK, missing test, broken codegen).
146
147
  5. **Throw typed errors, do not return raw error responses.** Use
147
148
  `NotFoundError`, `BadRequestError`, `UnauthorizedError`,
148
149
  `ForbiddenError`, `ConflictError`, etc. from `@daloyjs/core`. The
149
- framework maps them to RFC 7807 problem responses.
150
+ framework maps them to RFC 9457 problem responses.
150
151
  6. **Add a test in `tests/<route>.test.ts`.** Use `app.request(...)` for
151
152
  in-process tests — no port needed (see "Testing best practices").
152
153
  7. **Run the contract gate.** Run `pnpm contract` or `pnpm test` before
@@ -209,7 +210,7 @@ app.route({
209
210
  ## Error handling
210
211
 
211
212
  - Throw typed errors from `@daloyjs/core` — they carry status codes and
212
- serialize to RFC 7807 problem responses (`application/problem+json`).
213
+ serialize to RFC 9457 problem responses (`application/problem+json`).
213
214
  - Add a `responses[code]` entry for every error you throw, so the OpenAPI
214
215
  spec and the typed client know it can happen.
215
216
  - Do not swallow errors in handlers. If you need to log and rethrow, use
@@ -276,7 +277,7 @@ For routes that touch external services, write a thin in-memory fake
276
277
  inside the test and inject it via the factory pattern (`buildApp({ store })`).
277
278
  Do not mock global `fetch` unless there is no alternative.
278
279
 
279
- Aim for **100% line and function coverage** on routes you add. If a
280
+ Aim for complete happy- and unhappy-path test coverage of the routes you add. If a
280
281
  branch is impractical to test (e.g. defensive `never` arms), refactor it
281
282
  out rather than adding ignore comments — agent coverage tools may not
282
283
  honor them.
@@ -347,6 +348,11 @@ honor them.
347
348
  `--ignore-scripts=false` on install without a clear reason.
348
349
  - Avoid global mutable state in `buildApp()`. If you need shared state,
349
350
  pass it in as a parameter (`buildApp({ store })`).
351
+ - If a route intentionally returns a body the contract cannot describe (a
352
+ raw `Response`, HTML, a proxied payload), set
353
+ `acknowledgeNoResponseBodySchema: true` on that route — never silence the
354
+ `security.response.bodySchemaMissing` boot warning by widening a schema
355
+ to `z.any()`.
350
356
 
351
357
  ## Process expectations
352
358
 
@@ -361,6 +367,18 @@ honor them.
361
367
  - Keep `README.md`, this `SKILL.md`, and `AGENTS.md` consistent with the
362
368
  code. If you add a workflow, document it here.
363
369
 
370
+ ## Exposing this API over MCP
371
+
372
+ `@daloyjs/core` ships a dependency-free Model Context Protocol (Streamable
373
+ HTTP) server helper — also available from the `@daloyjs/core/mcp` subpath.
374
+ To expose selected capabilities to MCP clients (AI agents), build a handler
375
+ with `createMcpHandler({ tools, resources, prompts })` and mount it with
376
+ `mcpRoutes("/mcp", handler)`. Throw `McpToolError` for caller-correctable
377
+ tool failures. The handler ships protocol-level guards (body cap, UTF-8/JSON
378
+ validation, `Origin` checks against DNS rebinding) and composes with the
379
+ existing middleware chain — put `bearerAuth()` / `rateLimit()` in front of
380
+ it like any other route. See <https://daloyjs.dev/docs> for the MCP guide.
381
+
364
382
  ## More
365
383
 
366
384
  - Framework docs: <https://daloyjs.dev/docs>
@@ -20,7 +20,7 @@
20
20
  "hooks:install": "git config core.hooksPath .githooks"
21
21
  },
22
22
  "dependencies": {
23
- "@daloyjs/core": "^1.0.0-beta.7",
23
+ "@daloyjs/core": "^1.0.0-rc.0",
24
24
  "zod": "^4.4.3"
25
25
  },
26
26
  "devDependencies": {
@@ -75,13 +75,13 @@ export function buildApp(): App {
75
75
  },
76
76
  },
77
77
  handler: async () => ({
78
- status: 200,
78
+ status: 200 as const,
79
79
  body: { ok: true as const, uptime: process.uptime() },
80
80
  }),
81
81
  });
82
82
 
83
83
  // daloy-minimal:strip-start books
84
- const Book = z.object({ id: z.string(), title: z.string() });
84
+ const Book = z.object({ id: z.string(), title: z.string() }).strict();
85
85
  const books = new Map<string, z.infer<typeof Book>>([
86
86
  ["1", { id: "1", title: "Noli Me Tangere" }],
87
87
  ["2", { id: "2", title: "El Filibusterismo" }],
@@ -92,7 +92,7 @@ export function buildApp(): App {
92
92
  path: "/books/:id",
93
93
  operationId: "getBookById",
94
94
  tags: ["Books"],
95
- request: { params: z.object({ id: z.string() }) },
95
+ request: { params: z.object({ id: z.string().min(1) }).strict() },
96
96
  responses: {
97
97
  200: { description: "Found", body: Book },
98
98
  404: { description: "Not found" },
@@ -100,7 +100,7 @@ export function buildApp(): App {
100
100
  handler: async ({ params }) => {
101
101
  const book = books.get(params.id);
102
102
  if (!book) throw new NotFoundError(`Book ${params.id} not found`);
103
- return { status: 200, body: book };
103
+ return { status: 200 as const, body: book };
104
104
  },
105
105
  });
106
106
  // daloy-minimal:strip-end books
@@ -46,7 +46,7 @@ You import the file you see. Vercel and tsx resolve `.ts` directly. Bare package
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`.
49
- 5. Keep `requestId()`, `secureHeaders()`, and `rateLimit()` enabled. For production traffic, back rate-limiting with Vercel KV or another shared store (the in-memory limiter resets per instance).
49
+ 5. Keep `requestId()`, `secureHeaders()`, and `rateLimit()` enabled. For production traffic, back rate-limiting with a shared store such as Upstash Redis from the Vercel Marketplace (the in-memory limiter resets per instance).
50
50
  6. Prefer Web Standards (`Request`/`Response`, `fetch`, `Web Crypto`) even though Node APIs are available; Edge runtime code must avoid `node:` modules.
51
51
  7. Keep a single `api/index.ts` entry and the `vercel.json` `/(.*)` → `/api` rewrite so DaloyJS handles all routing at the site root.
52
52
  8. Keep operation IDs stable and examples schema-valid; `pnpm contract` must pass after route, metadata, or OpenAPI-facing changes.
@@ -56,7 +56,7 @@ You import the file you see. Vercel and tsx resolve `.ts` directly. Bare package
56
56
 
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
- - Keep `secureHeaders()`, `requestId()`, `rateLimit()`, `bodyLimitBytes`, and `requestTimeoutMs`. For production, back rate limits with Vercel KV or another shared store.
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
60
  - Keep Zod `.strict()` on top-level request objects; do not switch to `.passthrough()`. For other validators, use the strict / no-extra-keys equivalent. Keep `responses[N].body` schemas tight; never widen to `z.any()` to let a privileged field escape.
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`.
@@ -94,7 +94,7 @@ definitions:
94
94
 
95
95
  - `GET /openapi.json` — OpenAPI 3.1 spec as JSON.
96
96
  - `GET /openapi.yaml` — OpenAPI 3.1 spec as YAML (served inline as
97
- `text/yaml; charset=utf-8`, since `@daloyjs/core` 0.13.1).
97
+ `text/yaml; charset=utf-8`).
98
98
  - `GET /docs` — Scalar API reference UI that loads the spec.
99
99
 
100
100
  Customize via `docs: { openapiPath, openapiYamlPath, path, ui }`. Set
@@ -173,7 +173,7 @@ app.route({
173
173
 
174
174
  ## Error handling
175
175
 
176
- - Throw typed errors from `@daloyjs/core` — they serialize to RFC 7807
176
+ - Throw typed errors from `@daloyjs/core` — they serialize to RFC 9457
177
177
  problem responses.
178
178
  - Add a `responses[code]` entry for every error you throw.
179
179
 
@@ -210,13 +210,13 @@ in-memory fake during tests.
210
210
  The shipped contract test should fail invalid examples, duplicate/missing
211
211
  `operationId`, or missing responses.
212
212
 
213
- Aim for **100% line and function coverage** on the routes you add.
213
+ Aim for complete happy- and unhappy-path test coverage of the routes you add.
214
214
 
215
215
  ## Security best practices
216
216
 
217
217
  - Keep `secureHeaders()`, `requestId()`, and `rateLimit()` enabled. For
218
- production traffic, back rate-limiting with Vercel KV or another
219
- shared store so limits apply across instances.
218
+ production traffic, back rate-limiting with a shared store (e.g. Upstash
219
+ Redis from the Vercel Marketplace) so limits apply across instances.
220
220
  - Never make a failing test pass by deleting or weakening a security guard.
221
221
  If a guard blocks a legitimate route, add the narrowest per-route
222
222
  override or configuration knob and cover both the allowed and rejected
@@ -270,6 +270,11 @@ Aim for **100% line and function coverage** on the routes you add.
270
270
  - Do not weaken response literal types (`as const`).
271
271
  - Do not return errors as `{ status: 4xx, body }`. Throw a typed error.
272
272
  - Do not add runtime dependencies without checking the hardened `.npmrc` (installs wait 24h after publish by default).
273
+ - If a route intentionally returns a body the contract cannot describe (a
274
+ raw `Response`, HTML, a proxied payload), set
275
+ `acknowledgeNoResponseBodySchema: true` on that route — never silence the
276
+ `security.response.bodySchemaMissing` boot warning by widening a schema
277
+ to `z.any()`.
273
278
 
274
279
  ## Process expectations
275
280
 
@@ -282,6 +287,18 @@ Aim for **100% line and function coverage** on the routes you add.
282
287
  authenticate on their behalf.
283
288
  - Keep `README.md`, this `SKILL.md`, and `AGENTS.md` consistent.
284
289
 
290
+ ## Exposing this API over MCP
291
+
292
+ `@daloyjs/core` ships a dependency-free Model Context Protocol (Streamable
293
+ HTTP) server helper — also available from the `@daloyjs/core/mcp` subpath.
294
+ To expose selected capabilities to MCP clients (AI agents), build a handler
295
+ with `createMcpHandler({ tools, resources, prompts })` and mount it with
296
+ `mcpRoutes("/mcp", handler)`. Throw `McpToolError` for caller-correctable
297
+ tool failures. The handler ships protocol-level guards (body cap, UTF-8/JSON
298
+ validation, `Origin` checks against DNS rebinding) and composes with the
299
+ existing middleware chain — put `bearerAuth()` / `rateLimit()` in front of
300
+ it like any other route. See <https://daloyjs.dev/docs> for the MCP guide.
301
+
285
302
  ## More
286
303
 
287
304
  - Framework docs: <https://daloyjs.dev/docs>
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { App, NotFoundError, requestId, secureHeaders } from "@daloyjs/core";
2
+ import { App, NotFoundError, rateLimit, requestId, secureHeaders } from "@daloyjs/core";
3
3
  import { toFetchHandler } from "@daloyjs/core/vercel";
4
4
 
5
5
  // This template targets Vercel's Node.js runtime — the runtime Vercel now
@@ -44,6 +44,12 @@ export const app = new App({
44
44
 
45
45
  app.use(requestId());
46
46
  app.use(secureHeaders());
47
+ // The in-memory limiter resets per function instance on Vercel, so treat it as
48
+ // a per-instance abuse brake, not a global quota. For production traffic, back
49
+ // it with a shared `RateLimitStore` (e.g. Upstash Redis from the Vercel
50
+ // Marketplace) — in addition to, not instead of, this baseline. Do not remove
51
+ // it to make a test pass; raise `max` per route.
52
+ app.use(rateLimit({ windowMs: 60_000, max: 120 }));
47
53
 
48
54
  app.route({
49
55
  method: "GET",
@@ -57,13 +63,13 @@ app.route({
57
63
  },
58
64
  },
59
65
  handler: async () => ({
60
- status: 200,
66
+ status: 200 as const,
61
67
  body: { ok: true as const, runtime: "vercel" as const },
62
68
  }),
63
69
  });
64
70
 
65
71
  // daloy-minimal:strip-start books
66
- const Book = z.object({ id: z.string(), title: z.string() });
72
+ const Book = z.object({ id: z.string(), title: z.string() }).strict();
67
73
  const books = new Map<string, z.infer<typeof Book>>([
68
74
  ["1", { id: "1", title: "Noli Me Tangere" }],
69
75
  ]);
@@ -73,7 +79,7 @@ app.route({
73
79
  path: "/books/:id",
74
80
  operationId: "getBookById",
75
81
  tags: ["Books"],
76
- request: { params: z.object({ id: z.string() }) },
82
+ request: { params: z.object({ id: z.string().min(1) }).strict() },
77
83
  responses: {
78
84
  200: { description: "Found", body: Book },
79
85
  404: { description: "Not found" },
@@ -81,7 +87,7 @@ app.route({
81
87
  handler: async ({ params }) => {
82
88
  const book = books.get(params.id);
83
89
  if (!book) throw new NotFoundError(`Book ${params.id} not found`);
84
- return { status: 200, body: book };
90
+ return { status: 200 as const, body: book };
85
91
  },
86
92
  });
87
93
  // daloy-minimal:strip-end books
@@ -13,7 +13,7 @@
13
13
  "hooks:install": "git config core.hooksPath .githooks"
14
14
  },
15
15
  "dependencies": {
16
- "@daloyjs/core": "^1.0.0-beta.7",
16
+ "@daloyjs/core": "^1.0.0-rc.0",
17
17
  "zod": "^4.4.3"
18
18
  },
19
19
  "devDependencies": {