create-daloy 1.0.0-beta.6 → 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 +1 -1
- package/sbom.cdx.json +9 -9
- package/sbom.spdx.json +5 -5
- package/templates/bun-basic/_agents/skills/daloyjs-best-practices/SKILL.md +21 -4
- package/templates/bun-basic/package.json +1 -1
- package/templates/bun-basic/src/build-app.ts +4 -4
- package/templates/cloudflare-worker/_agents/skills/daloyjs-best-practices/SKILL.md +20 -3
- package/templates/cloudflare-worker/package.json +1 -1
- package/templates/cloudflare-worker/src/index.ts +10 -5
- package/templates/deno-basic/_agents/skills/daloyjs-best-practices/SKILL.md +20 -3
- package/templates/deno-basic/deno.json +5 -5
- package/templates/deno-basic/src/build-app.ts +4 -4
- package/templates/node-basic/_agents/skills/daloyjs-best-practices/SKILL.md +22 -4
- package/templates/node-basic/package.json +1 -1
- package/templates/node-basic/src/build-app.ts +4 -4
- package/templates/vercel/AGENTS.md +2 -2
- package/templates/vercel/_agents/skills/daloyjs-best-practices/SKILL.md +22 -5
- package/templates/vercel/api/index.ts +11 -5
- 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-
|
|
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:
|
|
4
|
+
"serialNumber": "urn:uuid:59eb0e74-bb0a-538a-b5e2-6c2d63f0c0ff",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-07-
|
|
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-
|
|
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-
|
|
18
|
+
"bom-ref": "pkg:npm/create-daloy@1.0.0-rc.0",
|
|
19
19
|
"name": "create-daloy",
|
|
20
|
-
"version": "1.0.0-
|
|
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-
|
|
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-
|
|
45
|
+
"tagId": "swidtag-create-daloy-1.0.0-rc.0",
|
|
46
46
|
"name": "create-daloy",
|
|
47
|
-
"version": "1.0.0-
|
|
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-
|
|
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-
|
|
6
|
-
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-1.0.0-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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>
|
|
@@ -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
|
|
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
|
|
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
|
|
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>
|
|
@@ -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
|
|
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
|
|
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
|
|
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-
|
|
14
|
-
"@daloyjs/core/banner": "jsr:@daloyjs/daloy@^1.0.0-
|
|
15
|
-
"@daloyjs/core/contract": "jsr:@daloyjs/daloy@^1.0.0-
|
|
16
|
-
"@daloyjs/core/deno": "jsr:@daloyjs/daloy@^1.0.0-
|
|
17
|
-
"@daloyjs/core/openapi": "jsr:@daloyjs/daloy@^1.0.0-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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>
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
219
|
-
|
|
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
|