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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-daloy",
3
- "version": "1.0.0-rc.2",
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:e20f72cf-1e62-57e3-bb1f-3c578913560b",
4
+ "serialNumber": "urn:uuid:d3388094-26fe-5e1d-980d-c256c3d08aef",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-07-07T09:24:35.735Z",
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.2"
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.2",
18
+ "bom-ref": "pkg:npm/create-daloy@1.0.0-rc.4",
19
19
  "name": "create-daloy",
20
- "version": "1.0.0-rc.2",
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.2",
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.2",
45
+ "tagId": "swidtag-create-daloy-1.0.0-rc.4",
46
46
  "name": "create-daloy",
47
- "version": "1.0.0-rc.2",
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.2",
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.2",
6
- "documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-1.0.0-rc.2-e20f72cf-1e62-57e3-bb1f-3c578913560b",
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-07T09:24:35.735Z",
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.2",
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.2"
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 (`app.route({...})`). The OpenAPI
38
- spec, the typed client, and the runtime validation are all derived from
39
- it.
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.route({...})`** with `method`, `path`, `operationId`, `tags`,
122
- `responses`, `handler` (plus `request` when accepting input). Add `meta`
123
- examples / descriptions when the route is user-facing or consumed by
124
- agents.
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.route({
147
- method: "GET",
148
- path: "/books/:id",
149
- operationId: "getBookById",
150
- tags: ["Books"],
151
- request: { params: BookParams },
152
- responses: {
153
- 200: { description: "Found", body: Book },
154
- 404: { description: "Not found" },
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
- handler: async ({ params }) => {
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
@@ -19,7 +19,7 @@
19
19
  "hooks:install": "git config core.hooksPath .githooks"
20
20
  },
21
21
  "dependencies": {
22
- "@daloyjs/core": "^1.0.0-rc.2",
22
+ "@daloyjs/core": "^1.0.0-rc.4",
23
23
  "zod": "^4.4.3"
24
24
  },
25
25
  "devDependencies": {
@@ -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.route({
62
- method: "GET",
63
- path: "/healthz",
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") }),
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
- handler: async () => ({
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.route({
86
- method: "GET",
87
- path: "/books/:id",
88
- operationId: "getBookById",
89
- tags: ["Books"],
90
- request: { params: z.object({ id: z.string().min(1) }).strict() },
91
- responses: {
92
- 200: { description: "Found", body: Book },
93
- 404: { description: "Not found" },
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
- handler: async ({ params }) => {
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 (`app.route({...})`).
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.route({...})`** with `method`, `path`, `operationId`,
116
- `tags`, `responses`, `handler` (plus `request` when accepting input).
117
- Add `meta` examples / descriptions when the route is user-facing or
118
- consumed by agents.
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.route({
148
- method: "GET",
149
- path: "/books/:id",
150
- operationId: "getBookById",
151
- tags: ["Books"],
152
- request: { params: z.object({ id: z.string().min(1) }).strict() },
153
- responses: {
154
- 200: { description: "Found", body: Book },
155
- 404: { description: "Not found" },
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
- handler: async ({ params }) => {
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
  }
@@ -16,7 +16,7 @@
16
16
  "hooks:install": "git config core.hooksPath .githooks"
17
17
  },
18
18
  "dependencies": {
19
- "@daloyjs/core": "^1.0.0-rc.2",
19
+ "@daloyjs/core": "^1.0.0-rc.4",
20
20
  "zod": "^4.4.3"
21
21
  },
22
22
  "devDependencies": {
@@ -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.route({
36
- method: "GET",
37
- path: "/healthz",
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") }),
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
- handler: async () => ({
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.route({
59
- method: "GET",
60
- path: "/books/:id",
61
- operationId: "getBookById",
62
- tags: ["Books"],
63
- request: { params: z.object({ id: z.string().min(1) }).strict() },
64
- responses: {
65
- 200: { description: "Found", body: Book },
66
- 404: { description: "Not found" },
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
- handler: async ({ params }) => {
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 (`app.route({...})`).
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.route({...})`** with `method`, `path`, `operationId`,
124
- `tags`, `responses`, `handler` (plus `request` when accepting input).
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.route({
147
- method: "GET",
148
- path: "/books/:id",
149
- operationId: "getBookById",
150
- tags: ["Books"],
151
- request: { params: BookParams },
152
- responses: {
153
- 200: { description: "Found", body: Book },
154
- 404: { description: "Not found" },
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
- handler: async ({ params }) => {
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.2",
14
- "@daloyjs/core/banner": "jsr:@daloyjs/daloy@^1.0.0-rc.2/banner",
15
- "@daloyjs/core/contract": "jsr:@daloyjs/daloy@^1.0.0-rc.2/contract",
16
- "@daloyjs/core/deno": "jsr:@daloyjs/daloy@^1.0.0-rc.2/deno",
17
- "@daloyjs/core/openapi": "jsr:@daloyjs/daloy@^1.0.0-rc.2/openapi",
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.route({
63
- method: "GET",
64
- path: "/healthz",
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") }),
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
- handler: async () => ({
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.route({
87
- method: "GET",
88
- path: "/books/:id",
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" },
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
- handler: async ({ params }) => {
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. Method, path, request schemas, and response schemas live in one place — `app.route({...})`.
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 (`app.route({...})`). The OpenAPI
39
- spec, the typed client, and the runtime validation are all derived from
40
- it. Never duplicate that information by hand-writing fetch calls, types,
41
- or `openapi.json` entries.
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.route({...})`.** Required keys: `method`, `path`,
140
- `operationId`, `tags`, `responses`, `handler`. Add `request` when the
141
- route accepts input, and add `meta` examples / descriptions when the
142
- route is user-facing or consumed by agents.
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.route({
170
- method: "GET",
171
- path: "/books/:id",
172
- operationId: "getBookById",
173
- tags: ["Books"],
174
- request: { params: BookParams },
175
- responses: {
176
- 200: { description: "Found", body: Book },
177
- 404: { description: "Not found" },
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
- handler: async ({ params }) => {
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
@@ -20,7 +20,7 @@
20
20
  "hooks:install": "git config core.hooksPath .githooks"
21
21
  },
22
22
  "dependencies": {
23
- "@daloyjs/core": "^1.0.0-rc.2",
23
+ "@daloyjs/core": "^1.0.0-rc.4",
24
24
  "zod": "^4.4.3"
25
25
  },
26
26
  "devDependencies": {
@@ -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.route({
67
- method: "GET",
68
- path: "/healthz",
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() }),
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
- handler: async () => ({
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.route({
91
- method: "GET",
92
- path: "/books/:id",
93
- operationId: "getBookById",
94
- tags: ["Books"],
95
- request: { params: z.object({ id: z.string().min(1) }).strict() },
96
- responses: {
97
- 200: { description: "Found", body: Book },
98
- 404: { description: "Not found" },
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
- handler: async ({ params }) => {
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()`. 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.
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 so DaloyJS owns routing — do not split into per-path files that bypass the middleware chain, and do not remove the rewrite (the root domain would 404).
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 (`app.route({...})`).
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.route({...})`** with `method`, `path`, `operationId`,
124
- `tags`, `responses`, `handler` (plus `request` when accepting input).
125
- Add `meta` examples / descriptions when the route is user-facing or
126
- consumed by agents.
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.route({
144
- method: "GET",
145
- path: "/books/:id",
146
- operationId: "getBookById",
147
- tags: ["Books"],
148
- request: { params: BookParams },
149
- responses: {
150
- 200: { description: "Found", body: Book },
151
- 404: { description: "Not found" },
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
- handler: async ({ params }) => {
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.route({
55
- method: "GET",
56
- path: "/healthz",
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") }),
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
- handler: async () => ({
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.route({
78
- method: "GET",
79
- path: "/books/:id",
80
- operationId: "getBookById",
81
- tags: ["Books"],
82
- request: { params: z.object({ id: z.string().min(1) }).strict() },
83
- responses: {
84
- 200: { description: "Found", body: Book },
85
- 404: { description: "Not found" },
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
- handler: async ({ params }) => {
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);
@@ -16,7 +16,7 @@
16
16
  "hooks:install": "git config core.hooksPath .githooks"
17
17
  },
18
18
  "dependencies": {
19
- "@daloyjs/core": "^1.0.0-rc.2",
19
+ "@daloyjs/core": "^1.0.0-rc.4",
20
20
  "zod": "^4.4.3"
21
21
  },
22
22
  "devDependencies": {