create-daloy 0.38.0 → 0.38.2

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.
Files changed (48) hide show
  1. package/README.md +27 -8
  2. package/bin/create-daloy.mjs +24 -4
  3. package/package.json +1 -1
  4. package/sbom.cdx.json +9 -9
  5. package/sbom.spdx.json +5 -5
  6. package/templates/_ci/deno/_github/workflows/codeql.yml +2 -2
  7. package/templates/_ci/deno/_github/workflows/container-scan.yml +3 -3
  8. package/templates/_ci/deno/_github/workflows/opengrep.yml +1 -1
  9. package/templates/_ci/deno/_github/workflows/scorecard.yml +1 -1
  10. package/templates/_ci/node/_github/workflows/codeql.yml +2 -2
  11. package/templates/_ci/node/_github/workflows/container-scan.yml +3 -3
  12. package/templates/_ci/node/_github/workflows/opengrep.yml +1 -1
  13. package/templates/_ci/node/_github/workflows/scorecard.yml +1 -1
  14. package/templates/bun-basic/README.md +14 -0
  15. package/templates/bun-basic/_vscode/mcp.json +8 -0
  16. package/templates/bun-basic/package.json +1 -1
  17. package/templates/bun-basic/src/build-app.ts +26 -1
  18. package/templates/bun-basic/src/index.ts +4 -1
  19. package/templates/cloudflare-worker/README.md +16 -0
  20. package/templates/cloudflare-worker/_vscode/mcp.json +8 -0
  21. package/templates/cloudflare-worker/package.json +1 -1
  22. package/templates/deno-basic/README.md +14 -0
  23. package/templates/deno-basic/_vscode/mcp.json +8 -0
  24. package/templates/deno-basic/deno.json +4 -4
  25. package/templates/deno-basic/src/build-app.ts +26 -1
  26. package/templates/node-basic/README.md +14 -0
  27. package/templates/node-basic/_vscode/mcp.json +8 -0
  28. package/templates/node-basic/package.json +1 -1
  29. package/templates/node-basic/src/build-app.ts +26 -1
  30. package/templates/node-basic/tsconfig.build.json +2 -1
  31. package/templates/{vercel-edge → vercel}/AGENTS.md +5 -5
  32. package/templates/{vercel-edge → vercel}/README.md +29 -11
  33. package/templates/{vercel-edge → vercel}/_Dockerfile +2 -2
  34. package/templates/{vercel-edge → vercel}/_agents/skills/daloyjs-best-practices/SKILL.md +48 -39
  35. package/templates/vercel/_vscode/mcp.json +8 -0
  36. package/templates/{vercel-edge → vercel}/api/[...path].ts +13 -9
  37. package/templates/{vercel-edge → vercel}/package.json +1 -1
  38. package/templates/vercel/tests/app.test.ts +10 -0
  39. package/templates/vercel/vercel.json +7 -0
  40. package/templates/vercel-edge/tests/app.test.ts +0 -9
  41. package/templates/vercel-edge/vercel.json +0 -4
  42. /package/templates/{vercel-edge → vercel}/CLAUDE.md +0 -0
  43. /package/templates/{vercel-edge → vercel}/_dockerignore +0 -0
  44. /package/templates/{vercel-edge → vercel}/_env.example +0 -0
  45. /package/templates/{vercel-edge → vercel}/_gitignore +0 -0
  46. /package/templates/{vercel-edge → vercel}/_npmrc +0 -0
  47. /package/templates/{vercel-edge → vercel}/pnpm-workspace.yaml +0 -0
  48. /package/templates/{vercel-edge → vercel}/tsconfig.json +0 -0
package/README.md CHANGED
@@ -22,7 +22,7 @@ bun create daloy my-api
22
22
  The CLI is interactive when arguments are missing. It will ask you for:
23
23
 
24
24
  - A project directory name (defaults to `my-daloy-app`)
25
- - A template (`node-basic`, `vercel-edge`, `cloudflare-worker`, `bun-basic`, or `deno-basic`)
25
+ - A template (`node-basic`, `vercel`, `cloudflare-worker`, `bun-basic`, or `deno-basic`)
26
26
  - A package manager (`pnpm`, `npm`, `yarn`, or `bun`) — not asked for the
27
27
  `deno-basic` runtime template
28
28
  - Whether to install dependencies
@@ -50,7 +50,7 @@ pnpm create daloy@latest my-api \
50
50
 
51
51
  | Flag | Description |
52
52
  | --- | --- |
53
- | `--template <name>` | `node-basic` (default), `vercel-edge`, `cloudflare-worker`, `bun-basic`, or `deno-basic`. |
53
+ | `--template <name>` | `node-basic` (default), `vercel`, `cloudflare-worker`, `bun-basic`, or `deno-basic`. (`vercel-edge` is a deprecated alias for `vercel`.) |
54
54
  | `--package-manager <pm>` | `pnpm` (default), `npm`, `yarn`, or `bun`. Ignored for `deno-basic`. |
55
55
  | `--list-templates` | Print available templates with descriptions. |
56
56
  | `--install` / `--no-install` | Install dependencies after scaffolding. Defaults to **Y** for npm/yarn/bun and **N** for pnpm so you can review the hardened `.npmrc` / `pnpm-workspace.yaml` and aren't blocked by the 24h `minimumReleaseAge` embargo on the first run. |
@@ -88,17 +88,20 @@ A minimal Cloudflare Worker bootstrap using `@daloyjs/core/cloudflare` with:
88
88
  - `secureHeaders` and `requestId` enabled by default, with smaller edge-friendly body and timeout limits.
89
89
  - A Zod-validated `/healthz` route and contract-first `/books/:id` route exposed via `toFetchHandler(app)`.
90
90
 
91
- ### `vercel-edge`
91
+ ### `vercel`
92
92
 
93
- A Vercel Edge API bootstrap using `@daloyjs/core/vercel` with:
93
+ A Vercel API bootstrap on the **Node.js runtime** (Vercel's recommended runtime
94
+ for standalone functions, on Fluid Compute) using `@daloyjs/core/vercel` with:
94
95
 
95
96
  - `api/[...path].ts` catch-all routing so DaloyJS owns the API surface.
96
- - `export const config = { runtime: "edge" }` ready for Vercel Edge.
97
- - Node.js migration notes using Vercel's default `{ fetch }` export shape.
97
+ - `export default toFetchHandler(app)` the `{ fetch }` shape Vercel Node.js Functions expect (no `runtime` export needed; Node.js is the default).
98
+ - Notes for opting into the Edge runtime (`export const runtime = "edge"` + `toWebHandler(app)`) if you specifically need it.
98
99
  - `vercel dev` / `vercel deploy` scripts.
99
- - `secureHeaders` and `requestId` enabled by default, with smaller edge-friendly body and timeout limits.
100
+ - `secureHeaders` and `requestId` enabled by default, with smaller serverless-friendly body and timeout limits.
100
101
  - A health route and bookstore route mirroring the Node starter.
101
102
 
103
+ > The previous template name `vercel-edge` still works as a deprecated alias for `vercel`.
104
+
102
105
  ### `bun-basic`
103
106
 
104
107
  A [Bun](https://bun.sh) runtime starter using `@daloyjs/core/bun` with:
@@ -120,6 +123,20 @@ A [Deno](https://deno.com) runtime starter using `@daloyjs/core/deno` with:
120
123
  - A health route and contract-first `/books/:id` route with Zod validation.
121
124
  - The CLI skips Node-style installs for this template (no `package.json`).
122
125
 
126
+ ## Authentication (OAuth2 / OpenID Connect)
127
+
128
+ Scaffolded apps are **resource servers**: DaloyJS verifies and enforces access
129
+ tokens, it does **not** issue them. There is no built-in login UI, user
130
+ database, or OAuth2 authorization server (it is not an identity provider like
131
+ Keycloak, Auth0, or Duende IdentityServer). To add login, bring an OpenID
132
+ Connect provider — managed (Auth0, Okta, Clerk, Microsoft Entra ID, AWS
133
+ Cognito) or self-hosted open source (Keycloak, Zitadel, Ory, Logto) — and have
134
+ DaloyJS verify its JWTs with the first-party `jwk()`, `bearerAuth()`, and
135
+ `requireScopes()` helpers. Don't build your own authorization server.
136
+
137
+ See [Auth architecture](https://daloyjs.dev/docs/auth/architecture) for the full
138
+ picture and the two recommended designs (API resource server and browser BFF).
139
+
123
140
  ## Minimal scaffolds
124
141
 
125
142
  Pass `--minimal` to drop the bookstore demo route and the built-in
@@ -246,7 +263,7 @@ not need.
246
263
  - Zero runtime dependencies (uses only Node built-ins) for a clean supply-chain footprint.
247
264
  - A modern terminal experience with Unicode/color capability detection and ASCII fallbacks.
248
265
  - Templates are copied verbatim from this package's `templates/` directory.
249
- - Files and folders prefixed with `_` are renamed on copy (`_gitignore` → `.gitignore`, `_npmrc` → `.npmrc`, `_github/` → `.github`, `_agents/` → `.agents/`, `_Dockerfile` → `Dockerfile`, `_dockerignore` → `.dockerignore`, `_env.example` → `.env.example`) to survive npm packing.
266
+ - Files and folders prefixed with `_` are renamed on copy (`_gitignore` → `.gitignore`, `_npmrc` → `.npmrc`, `_github/` → `.github`, `_agents/` → `.agents/`, `_vscode/` → `.vscode/`, `_Dockerfile` → `Dockerfile`, `_dockerignore` → `.dockerignore`, `_env.example` → `.env.example`) to survive npm packing.
250
267
  - pnpm-specific `.npmrc` hardening is kept only when you choose `pnpm`; other package managers get a clean project without unsupported config warnings.
251
268
  - pnpm projects ship with `ignore-scripts=true`, `minimum-release-age=1440`, `verify-store-integrity=true`, `prefer-frozen-lockfile=true`, and `strict-peer-dependencies=true` by default.
252
269
  - `--with-ci` projects ship with pinned GitHub Actions workflows, CODEOWNERS, Dependabot, SECURITY.md, and lockfile-source verification.
@@ -260,3 +277,5 @@ Every scaffolded project ships with two files that help AI coding agents (Copilo
260
277
  - `.agents/skills/daloyjs-best-practices/SKILL.md` — comprehensive operational guidance following the open `agents/skills/<skill-name>/SKILL.md` convention: when to use the skill, project structure, core workflows (adding routes, regenerating the OpenAPI spec and client), schema and validation conventions, error-handling patterns, middleware order, testing best practices (happy and unhappy paths), security best practices, logging and observability notes, configuration and secrets handling, deployment notes, pitfalls and guardrails, and process expectations.
261
278
 
262
279
  Both files are tailored to the chosen template (Node, Bun, Deno, Vercel Edge, or Cloudflare Workers), and Node-style templates rewrite their commands to match your selected package manager. They follow the "instruction budget" advice — small root file, progressive disclosure for the rest — so they don't waste agent tokens. Edit or delete them freely; the framework does not depend on them at runtime.
280
+
281
+ Every scaffold also ships a `.vscode/mcp.json` (authored as `_vscode/mcp.json` in the template) that wires VS Code and other MCP-aware editors to the DaloyJS documentation MCP server (`https://daloyjs.dev/mcp`) over HTTP. AI assistants in your editor can then pull current DaloyJS docs with no manual setup. Delete the file or remove the server entry to opt out; it is editor configuration only and the framework does not depend on it at runtime.
@@ -23,9 +23,9 @@ const TEMPLATE_OPTIONS = [
23
23
  description: "Traditional REST API with secure defaults and Hey API codegen",
24
24
  },
25
25
  {
26
- value: "vercel-edge",
27
- title: "Vercel Edge",
28
- description: "Catch-all Vercel Edge route with Node.js migration notes",
26
+ value: "vercel",
27
+ title: "Vercel",
28
+ description: "Catch-all Vercel Functions REST API on the Node.js runtime (Fluid Compute)",
29
29
  },
30
30
  {
31
31
  value: "cloudflare-worker",
@@ -54,6 +54,15 @@ const PACKAGE_MANAGER_OPTIONS = [
54
54
  const TEMPLATES = TEMPLATE_OPTIONS.map((option) => option.value);
55
55
  const PACKAGE_MANAGERS = PACKAGE_MANAGER_OPTIONS.map((option) => option.value);
56
56
 
57
+ // Deprecated template names kept as aliases so existing
58
+ // `--template <old>` commands (and published docs/blog posts) keep working.
59
+ // Each maps to its current canonical template value.
60
+ const TEMPLATE_ALIASES = new Map([
61
+ // `vercel-edge` shipped before Vercel deprecated standalone Edge Functions;
62
+ // the template now targets the recommended Node.js runtime as `vercel`.
63
+ ["vercel-edge", "vercel"],
64
+ ]);
65
+
57
66
  const RENAME_ON_COPY = new Map([
58
67
  ["_gitignore", ".gitignore"],
59
68
  ["_npmrc", ".npmrc"],
@@ -65,6 +74,11 @@ const RENAME_ON_COPY = new Map([
65
74
  // `.agents/skills/<skill-name>/SKILL.md`. Templates author this as
66
75
  // `_agents/` so npm pack does not drop the dotfolder during publish.
67
76
  ["_agents", ".agents"],
77
+ // Directory: holds editor configuration such as `mcp.json`, which wires
78
+ // VS Code (and compatible editors) to the DaloyJS docs MCP server.
79
+ // Templates author this as `_vscode/` so npm pack does not drop the
80
+ // dotfolder during publish.
81
+ ["_vscode", ".vscode"],
68
82
  ]);
69
83
 
70
84
  // Templates that target a runtime instead of an npm package manager.
@@ -972,7 +986,7 @@ function cloudflareDeploySteps(packageManager) {
972
986
  }
973
987
 
974
988
  function renderDeployConfig({ template, packageManager, needsBunRuntime }) {
975
- if (template === "vercel-edge") {
989
+ if (template === "vercel") {
976
990
  return {
977
991
  header: vercelDeployHeader(),
978
992
  jobName: "Deploy to Vercel",
@@ -1669,6 +1683,12 @@ async function main() {
1669
1683
  if (!template) {
1670
1684
  template = rl ? await askChoice(rl, "Choose a starter template:", TEMPLATE_OPTIONS, "node-basic") : "node-basic";
1671
1685
  }
1686
+ // Resolve deprecated template aliases (e.g. `vercel-edge` -> `vercel`).
1687
+ if (TEMPLATE_ALIASES.has(template)) {
1688
+ const canonical = TEMPLATE_ALIASES.get(template);
1689
+ logWarn(`Template "${template}" is deprecated; using "${canonical}" instead.`);
1690
+ template = canonical;
1691
+ }
1672
1692
  if (!TEMPLATES.includes(template)) {
1673
1693
  logError(`Unknown template "${template}". Available: ${TEMPLATES.join(", ")}`);
1674
1694
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-daloy",
3
- "version": "0.38.0",
3
+ "version": "0.38.2",
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:b1a2eb69-121b-5cae-87a9-5d036a3e1a7a",
4
+ "serialNumber": "urn:uuid:2bbd5e7e-5035-5e09-92c8-3e1aec60f5a1",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-06-10T11:58:38.391Z",
7
+ "timestamp": "2026-06-16T14:42:42.888Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "DaloyJS",
11
11
  "name": "daloy-generate-sbom",
12
- "version": "0.38.0"
12
+ "version": "0.38.2"
13
13
  }
14
14
  ],
15
15
  "authors": [],
16
16
  "component": {
17
17
  "type": "library",
18
- "bom-ref": "pkg:npm/create-daloy@0.38.0",
18
+ "bom-ref": "pkg:npm/create-daloy@0.38.2",
19
19
  "name": "create-daloy",
20
- "version": "0.38.0",
20
+ "version": "0.38.2",
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@0.38.0",
22
+ "purl": "pkg:npm/create-daloy@0.38.2",
23
23
  "licenses": [
24
24
  {
25
25
  "license": {
@@ -42,9 +42,9 @@
42
42
  }
43
43
  ],
44
44
  "swid": {
45
- "tagId": "swidtag-create-daloy-0.38.0",
45
+ "tagId": "swidtag-create-daloy-0.38.2",
46
46
  "name": "create-daloy",
47
- "version": "0.38.0",
47
+ "version": "0.38.2",
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@0.38.0",
56
+ "ref": "pkg:npm/create-daloy@0.38.2",
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-0.38.0",
6
- "documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-0.38.0-b1a2eb69-121b-5cae-87a9-5d036a3e1a7a",
5
+ "name": "create-daloy-0.38.2",
6
+ "documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-0.38.2-2bbd5e7e-5035-5e09-92c8-3e1aec60f5a1",
7
7
  "creationInfo": {
8
- "created": "2026-06-10T11:58:38.391Z",
8
+ "created": "2026-06-16T14:42:42.888Z",
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": "0.38.0",
19
+ "versionInfo": "0.38.2",
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@0.38.0"
30
+ "referenceLocator": "pkg:npm/create-daloy@0.38.2"
31
31
  }
32
32
  ]
33
33
  }
@@ -44,13 +44,13 @@ jobs:
44
44
  show-progress: false
45
45
 
46
46
  - name: Initialize CodeQL
47
- uses: github/codeql-action/init@52485aec7be33610227643b0fe83936b8b5f061a # v3
47
+ uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
48
48
  with:
49
49
  languages: ${{ matrix.language }}
50
50
  build-mode: ${{ matrix.build-mode }}
51
51
  queries: security-extended,security-and-quality
52
52
 
53
53
  - name: Perform CodeQL Analysis
54
- uses: github/codeql-action/analyze@52485aec7be33610227643b0fe83936b8b5f061a # v3
54
+ uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
55
55
  with:
56
56
  category: "/language:${{ matrix.language }}"
@@ -139,7 +139,7 @@ jobs:
139
139
 
140
140
  - name: Upload hadolint SARIF
141
141
  if: steps.detect.outputs.present == 'true'
142
- uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
142
+ uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
143
143
  with:
144
144
  sarif_file: hadolint.sarif
145
145
  category: hadolint
@@ -162,7 +162,7 @@ jobs:
162
162
  exit-code: "0"
163
163
 
164
164
  - name: Upload Trivy filesystem SARIF
165
- uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
165
+ uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
166
166
  with:
167
167
  sarif_file: trivy-fs.sarif
168
168
  category: trivy-fs
@@ -186,7 +186,7 @@ jobs:
186
186
 
187
187
  - name: Upload Trivy image SARIF
188
188
  if: always() && steps.detect.outputs.present == 'true'
189
- uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
189
+ uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
190
190
  with:
191
191
  sarif_file: trivy-image.sarif
192
192
  category: trivy-image
@@ -131,7 +131,7 @@ jobs:
131
131
 
132
132
  - name: Upload SARIF to GitHub code scanning
133
133
  if: always()
134
- uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
134
+ uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
135
135
  with:
136
136
  sarif_file: opengrep.sarif
137
137
  category: opengrep
@@ -41,6 +41,6 @@ jobs:
41
41
  publish_results: true
42
42
 
43
43
  - name: Upload to code-scanning
44
- uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
44
+ uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
45
45
  with:
46
46
  sarif_file: results.sarif
@@ -44,13 +44,13 @@ jobs:
44
44
  show-progress: false
45
45
 
46
46
  - name: Initialize CodeQL
47
- uses: github/codeql-action/init@52485aec7be33610227643b0fe83936b8b5f061a # v3
47
+ uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
48
48
  with:
49
49
  languages: ${{ matrix.language }}
50
50
  build-mode: ${{ matrix.build-mode }}
51
51
  queries: security-extended,security-and-quality
52
52
 
53
53
  - name: Perform CodeQL Analysis
54
- uses: github/codeql-action/analyze@52485aec7be33610227643b0fe83936b8b5f061a # v3
54
+ uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
55
55
  with:
56
56
  category: "/language:${{ matrix.language }}"
@@ -157,7 +157,7 @@ jobs:
157
157
 
158
158
  - name: Upload hadolint SARIF
159
159
  if: steps.detect.outputs.present == 'true'
160
- uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
160
+ uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
161
161
  with:
162
162
  sarif_file: hadolint.sarif
163
163
  category: hadolint
@@ -180,7 +180,7 @@ jobs:
180
180
  exit-code: "0"
181
181
 
182
182
  - name: Upload Trivy filesystem SARIF
183
- uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
183
+ uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
184
184
  with:
185
185
  sarif_file: trivy-fs.sarif
186
186
  category: trivy-fs
@@ -207,7 +207,7 @@ jobs:
207
207
 
208
208
  - name: Upload Trivy image SARIF
209
209
  if: always() && steps.detect.outputs.present == 'true'
210
- uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
210
+ uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
211
211
  with:
212
212
  sarif_file: trivy-image.sarif
213
213
  category: trivy-image
@@ -163,7 +163,7 @@ jobs:
163
163
 
164
164
  - name: Upload SARIF to GitHub code scanning
165
165
  if: always()
166
- uses: github/codeql-action/upload-sarif@52485aec7be33610227643b0fe83936b8b5f061a # v3
166
+ uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
167
167
  with:
168
168
  sarif_file: opengrep.sarif
169
169
  category: opengrep
@@ -41,6 +41,6 @@ jobs:
41
41
  publish_results: true
42
42
 
43
43
  - name: Upload to code-scanning
44
- uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
44
+ uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
45
45
  with:
46
46
  sarif_file: results.sarif
@@ -63,4 +63,18 @@ Do not use `.js` here — that's the Node NodeNext convention and will not resol
63
63
  - Hot reload via `bun --hot`.
64
64
  - Hey API codegen wired to `bun run gen:openapi` + `bun run gen:client`.
65
65
 
66
+ ## Authentication (OAuth2 / OpenID Connect)
67
+
68
+ This app is a **resource server**: DaloyJS verifies and enforces access tokens,
69
+ it does **not** issue them. There is no built-in login UI, user database, or
70
+ OAuth2 authorization server (it is not an identity provider like Keycloak,
71
+ Auth0, or Duende IdentityServer). To add login, bring an OpenID Connect provider
72
+ — managed (Auth0, Okta, Clerk, Microsoft Entra ID, AWS Cognito) or self-hosted
73
+ open source (Keycloak, Zitadel, Ory, Logto) — and verify its JWTs with the
74
+ first-party `jwk()`, `bearerAuth()`, and `requireScopes()` helpers. Don't build
75
+ your own authorization server.
76
+
77
+ See [Auth architecture](https://daloyjs.dev/docs/auth/architecture) for the
78
+ recommended designs (API resource server and browser BFF).
79
+
66
80
  Read the docs at <https://daloyjs.dev/docs>.
@@ -0,0 +1,8 @@
1
+ {
2
+ "servers": {
3
+ "daloyjs-docs": {
4
+ "type": "http",
5
+ "url": "https://daloyjs.dev/mcp"
6
+ }
7
+ }
8
+ }
@@ -17,7 +17,7 @@
17
17
  "audit": "pnpm audit --prod"
18
18
  },
19
19
  "dependencies": {
20
- "@daloyjs/core": "^0.38.0",
20
+ "@daloyjs/core": "^0.38.2",
21
21
  "zod": "^4.4.3"
22
22
  },
23
23
  "devDependencies": {
@@ -18,6 +18,19 @@ export function buildApp(): App {
18
18
  bodyLimitBytes: 1024 * 1024,
19
19
  requestTimeoutMs: 5_000,
20
20
  production: process.env.NODE_ENV === "production",
21
+ // Reverse-proxy posture. When the app runs behind a trusted edge proxy
22
+ // (Railway, Render, Fly, Heroku, a single nginx / load balancer), set the
23
+ // TRUST_PROXY_HOPS env var to the number of proxy hops in front of it — a
24
+ // single PaaS edge is 1. DaloyJS then reads the real client IP from the
25
+ // matching X-Forwarded-For slot (used by rateLimit, requestId, and audit
26
+ // logs). Leave it unset when the app is exposed directly to the public
27
+ // internet: DaloyJS refuses to honor spoofable X-Forwarded-* headers
28
+ // (returning 500 on the first forwarded request) rather than trust a
29
+ // header an attacker can set. See the DaloyJS deployment guide for the
30
+ // per-platform hop counts.
31
+ ...(process.env.TRUST_PROXY_HOPS
32
+ ? { behindProxy: { hops: Number(process.env.TRUST_PROXY_HOPS) } }
33
+ : {}),
21
34
  // daloy-minimal:strip-start docs
22
35
  // Auto-mounted docs (when `docs: true`):
23
36
  // GET /openapi.json — OpenAPI 3.1 spec (JSON)
@@ -27,7 +40,19 @@ export function buildApp(): App {
27
40
  // `info.title` / `info.version` are pulled from package.json by default;
28
41
  // set `openapi.info` here to override them.
29
42
  openapi: {
30
- servers: [{ url: `http://localhost:${process.env.PORT ?? 3000}` }],
43
+ // Advertise the public origin so the Scalar "Try it" panel calls the
44
+ // deployed URL (and stays within the connect-src 'self' CSP) instead of
45
+ // localhost. Resolves PUBLIC_URL, then Railway's injected domain, then
46
+ // the local dev port.
47
+ servers: [
48
+ {
49
+ url:
50
+ process.env.PUBLIC_URL ??
51
+ (process.env.RAILWAY_PUBLIC_DOMAIN
52
+ ? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`
53
+ : `http://localhost:${process.env.PORT ?? 3000}`),
54
+ },
55
+ ],
31
56
  },
32
57
  docs: true,
33
58
  // daloy-minimal:strip-end docs
@@ -23,4 +23,7 @@ const links: StartupBannerLink[] = [
23
23
 
24
24
  printStartupBanner({ name: "DaloyJS API", url, runtime: "Bun", links });
25
25
 
26
- export default app;
26
+ // NOTE: This module intentionally has no default export. Bun auto-starts a
27
+ // server from any module whose default-exported object has a `fetch` method,
28
+ // which would collide with the explicit `serve()` call above on the same port
29
+ // (EADDRINUSE) and crash on startup (Railway's "Uncaught exception" loop).
@@ -22,3 +22,19 @@ pnpm deploy
22
22
  - `@daloyjs/core/cloudflare` with starter security middleware: `secureHeaders` and `requestId`.
23
23
  - Smaller edge-friendly body and timeout limits in the generated app.
24
24
  - `wrangler.toml` ready for local development and deploys.
25
+
26
+ ## Authentication (OAuth2 / OpenID Connect)
27
+
28
+ This app is a **resource server**: DaloyJS verifies and enforces access tokens,
29
+ it does **not** issue them. There is no built-in login UI, user database, or
30
+ OAuth2 authorization server (it is not an identity provider like Keycloak,
31
+ Auth0, or Duende IdentityServer). To add login, bring an OpenID Connect provider
32
+ — managed (Auth0, Okta, Clerk, Microsoft Entra ID, AWS Cognito) or self-hosted
33
+ open source (Keycloak, Zitadel, Ory, Logto) — and verify its JWTs with the
34
+ first-party `jwk()`, `bearerAuth()`, and `requireScopes()` helpers. Don't build
35
+ your own authorization server.
36
+
37
+ See [Auth architecture](https://daloyjs.dev/docs/auth/architecture) for the
38
+ recommended designs (API resource server and browser BFF).
39
+
40
+ Read the docs at <https://daloyjs.dev/docs>.
@@ -0,0 +1,8 @@
1
+ {
2
+ "servers": {
3
+ "daloyjs-docs": {
4
+ "type": "http",
5
+ "url": "https://daloyjs.dev/mcp"
6
+ }
7
+ }
8
+ }
@@ -11,7 +11,7 @@
11
11
  "audit": "pnpm audit --prod"
12
12
  },
13
13
  "dependencies": {
14
- "@daloyjs/core": "^0.38.0",
14
+ "@daloyjs/core": "^0.38.2",
15
15
  "zod": "^4.4.3"
16
16
  },
17
17
  "devDependencies": {
@@ -56,4 +56,18 @@ deno task test
56
56
  <!-- daloy-minimal:strip-end books -->
57
57
  - Minimal permissions: `--allow-net --allow-env --allow-read` for `dev`.
58
58
 
59
+ ## Authentication (OAuth2 / OpenID Connect)
60
+
61
+ This app is a **resource server**: DaloyJS verifies and enforces access tokens,
62
+ it does **not** issue them. There is no built-in login UI, user database, or
63
+ OAuth2 authorization server (it is not an identity provider like Keycloak,
64
+ Auth0, or Duende IdentityServer). To add login, bring an OpenID Connect provider
65
+ — managed (Auth0, Okta, Clerk, Microsoft Entra ID, AWS Cognito) or self-hosted
66
+ open source (Keycloak, Zitadel, Ory, Logto) — and verify its JWTs with the
67
+ first-party `jwk()`, `bearerAuth()`, and `requireScopes()` helpers. Don't build
68
+ your own authorization server.
69
+
70
+ See [Auth architecture](https://daloyjs.dev/docs/auth/architecture) for the
71
+ recommended designs (API resource server and browser BFF).
72
+
59
73
  Read the docs at <https://daloyjs.dev/docs>.
@@ -0,0 +1,8 @@
1
+ {
2
+ "servers": {
3
+ "daloyjs-docs": {
4
+ "type": "http",
5
+ "url": "https://daloyjs.dev/mcp"
6
+ }
7
+ }
8
+ }
@@ -8,10 +8,10 @@
8
8
  "gen:openapi": "deno run --allow-net --allow-env --allow-read --allow-write scripts/dump-openapi.ts"
9
9
  },
10
10
  "imports": {
11
- "@daloyjs/core": "jsr:@daloyjs/daloy@^0.38.0",
12
- "@daloyjs/core/banner": "jsr:@daloyjs/daloy@^0.38.0/banner",
13
- "@daloyjs/core/deno": "jsr:@daloyjs/daloy@^0.38.0/deno",
14
- "@daloyjs/core/openapi": "jsr:@daloyjs/daloy@^0.38.0/openapi",
11
+ "@daloyjs/core": "jsr:@daloyjs/daloy@^0.38.2",
12
+ "@daloyjs/core/banner": "jsr:@daloyjs/daloy@^0.38.2/banner",
13
+ "@daloyjs/core/deno": "jsr:@daloyjs/daloy@^0.38.2/deno",
14
+ "@daloyjs/core/openapi": "jsr:@daloyjs/daloy@^0.38.2/openapi",
15
15
  "zod": "npm:zod@^4.4.3"
16
16
  },
17
17
  "compilerOptions": {
@@ -19,6 +19,19 @@ export function buildApp(): App {
19
19
  bodyLimitBytes: 1024 * 1024,
20
20
  requestTimeoutMs: 5_000,
21
21
  production: Deno.env.get("DENO_ENV") === "production",
22
+ // Reverse-proxy posture. When the app runs behind a trusted edge proxy
23
+ // (Railway, Render, Fly, Heroku, a single nginx / load balancer), set the
24
+ // TRUST_PROXY_HOPS env var to the number of proxy hops in front of it — a
25
+ // single PaaS edge is 1. DaloyJS then reads the real client IP from the
26
+ // matching X-Forwarded-For slot (used by rateLimit, requestId, and audit
27
+ // logs). Leave it unset when the app is exposed directly to the public
28
+ // internet: DaloyJS refuses to honor spoofable X-Forwarded-* headers
29
+ // (returning 500 on the first forwarded request) rather than trust a
30
+ // header an attacker can set. See the DaloyJS deployment guide for the
31
+ // per-platform hop counts.
32
+ ...(Deno.env.get("TRUST_PROXY_HOPS")
33
+ ? { behindProxy: { hops: Number(Deno.env.get("TRUST_PROXY_HOPS")) } }
34
+ : {}),
22
35
  // daloy-minimal:strip-start docs
23
36
  // Auto-mounted docs (when `docs: true`):
24
37
  // GET /openapi.json — OpenAPI 3.1 spec (JSON)
@@ -28,7 +41,19 @@ export function buildApp(): App {
28
41
  // `info.title` / `info.version` are pulled from deno.json by default;
29
42
  // set `openapi.info` here to override them.
30
43
  openapi: {
31
- servers: [{ url: `http://localhost:${Deno.env.get("PORT") ?? "3000"}` }],
44
+ // Advertise the public origin so the Scalar "Try it" panel calls the
45
+ // deployed URL (and stays within the connect-src 'self' CSP) instead of
46
+ // localhost. Resolves PUBLIC_URL, then Railway's injected domain, then
47
+ // the local dev port.
48
+ servers: [
49
+ {
50
+ url:
51
+ Deno.env.get("PUBLIC_URL") ??
52
+ (Deno.env.get("RAILWAY_PUBLIC_DOMAIN")
53
+ ? `https://${Deno.env.get("RAILWAY_PUBLIC_DOMAIN")}`
54
+ : `http://localhost:${Deno.env.get("PORT") ?? "3000"}`),
55
+ },
56
+ ],
32
57
  },
33
58
  docs: true,
34
59
  // daloy-minimal:strip-end docs
@@ -65,4 +65,18 @@ On `pnpm build`, TypeScript rewrites the `.ts` specifier to `.js` in the compile
65
65
  - Hardened `.npmrc` for safer installs.
66
66
  - Hey API codegen wired to `pnpm gen`.
67
67
 
68
+ ## Authentication (OAuth2 / OpenID Connect)
69
+
70
+ This app is a **resource server**: DaloyJS verifies and enforces access tokens,
71
+ it does **not** issue them. There is no built-in login UI, user database, or
72
+ OAuth2 authorization server (it is not an identity provider like Keycloak,
73
+ Auth0, or Duende IdentityServer). To add login, bring an OpenID Connect provider
74
+ — managed (Auth0, Okta, Clerk, Microsoft Entra ID, AWS Cognito) or self-hosted
75
+ open source (Keycloak, Zitadel, Ory, Logto) — and verify its JWTs with the
76
+ first-party `jwk()`, `bearerAuth()`, and `requireScopes()` helpers. Don't build
77
+ your own authorization server.
78
+
79
+ See [Auth architecture](https://daloyjs.dev/docs/auth/architecture) for the
80
+ recommended designs (API resource server and browser BFF).
81
+
68
82
  Read the docs at <https://daloyjs.dev/docs>.
@@ -0,0 +1,8 @@
1
+ {
2
+ "servers": {
3
+ "daloyjs-docs": {
4
+ "type": "http",
5
+ "url": "https://daloyjs.dev/mcp"
6
+ }
7
+ }
8
+ }
@@ -18,7 +18,7 @@
18
18
  "audit": "pnpm audit --prod"
19
19
  },
20
20
  "dependencies": {
21
- "@daloyjs/core": "^0.38.0",
21
+ "@daloyjs/core": "^0.38.2",
22
22
  "zod": "^4.4.3"
23
23
  },
24
24
  "devDependencies": {
@@ -20,6 +20,19 @@ export function buildApp(): App {
20
20
  bodyLimitBytes: 1024 * 1024,
21
21
  requestTimeoutMs: 5_000,
22
22
  production: process.env.NODE_ENV === "production",
23
+ // Reverse-proxy posture. When the app runs behind a trusted edge proxy
24
+ // (Railway, Render, Fly, Heroku, a single nginx / load balancer), set the
25
+ // TRUST_PROXY_HOPS env var to the number of proxy hops in front of it — a
26
+ // single PaaS edge is 1. DaloyJS then reads the real client IP from the
27
+ // matching X-Forwarded-For slot (used by rateLimit, requestId, and audit
28
+ // logs). Leave it unset when the app is exposed directly to the public
29
+ // internet: DaloyJS refuses to honor spoofable X-Forwarded-* headers
30
+ // (returning 500 on the first forwarded request) rather than trust a
31
+ // header an attacker can set. See the DaloyJS deployment guide for the
32
+ // per-platform hop counts.
33
+ ...(process.env.TRUST_PROXY_HOPS
34
+ ? { behindProxy: { hops: Number(process.env.TRUST_PROXY_HOPS) } }
35
+ : {}),
23
36
  // daloy-minimal:strip-start docs
24
37
  // Auto-mounted docs (when `docs: true`):
25
38
  // GET /openapi.json — OpenAPI 3.1 spec (JSON)
@@ -32,7 +45,19 @@ export function buildApp(): App {
32
45
  // `info.title` / `info.version` are pulled from package.json by default;
33
46
  // set `openapi.info` here to override them.
34
47
  openapi: {
35
- servers: [{ url: `http://localhost:${process.env.PORT ?? 3000}` }],
48
+ // Advertise the public origin so the Scalar "Try it" panel calls the
49
+ // deployed URL (and stays within the connect-src 'self' CSP) instead of
50
+ // localhost. Resolves PUBLIC_URL, then Railway's injected domain, then
51
+ // the local dev port.
52
+ servers: [
53
+ {
54
+ url:
55
+ process.env.PUBLIC_URL ??
56
+ (process.env.RAILWAY_PUBLIC_DOMAIN
57
+ ? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`
58
+ : `http://localhost:${process.env.PORT ?? 3000}`),
59
+ },
60
+ ],
36
61
  },
37
62
  docs: true,
38
63
  // daloy-minimal:strip-end docs
@@ -3,8 +3,9 @@
3
3
  "compilerOptions": {
4
4
  "declaration": true,
5
5
  "outDir": "dist",
6
+ "rootDir": "src",
6
7
  "sourceMap": true
7
8
  },
8
- "include": ["src/**/*", "scripts/**/*"],
9
+ "include": ["src/**/*"],
9
10
  "exclude": ["node_modules", "dist", "tests"]
10
11
  }
@@ -1,9 +1,9 @@
1
1
  # AGENTS.md
2
2
 
3
- A [DaloyJS](https://daloyjs.dev) REST API deployed to **Vercel Edge**. **Contract-first**: routes are defined with Zod schemas and OpenAPI 3.1 is generated from them. When `docs: true` is set in `new App({...})`, three routes are auto-mounted: `GET /openapi.json`, `GET /openapi.yaml`, and `GET /docs` (Scalar UI).
3
+ A [DaloyJS](https://daloyjs.dev) REST API deployed to **Vercel** on the **Node.js runtime** (Vercel's recommended runtime for standalone functions, running on Fluid Compute). **Contract-first**: routes are defined with Zod schemas and OpenAPI 3.1 is generated from them. When `docs: true` is set in `new App({...})`, three routes are auto-mounted: `GET /openapi.json`, `GET /openapi.yaml`, and `GET /docs` (Scalar UI).
4
4
 
5
5
  - Package manager: pnpm (use `pnpm` unless the project's `package.json` was rewritten for npm/yarn/bun).
6
- - Runtime: Vercel Edge (Web Standard `Request`/`Response`).
6
+ - Runtime: Vercel Node.js Functions on Fluid Compute (Web Standard `Request`/`Response`).
7
7
 
8
8
  ## Commands
9
9
 
@@ -15,7 +15,7 @@ A [DaloyJS](https://daloyjs.dev) REST API deployed to **Vercel Edge**. **Contrac
15
15
 
16
16
  ## Project shape
17
17
 
18
- - `api/[...path].ts` — Vercel Edge entrypoint. Builds the `App`, registers routes/middleware, and exports `default toWebHandler(app)` plus `export const config = { runtime: "edge" }`. **Keep it a catch-all** so DaloyJS owns routing. For Vercel's recommended Node.js runtime, remove the Edge config and export `default toFetchHandler(app)` from `@daloyjs/core/vercel`.
18
+ - `api/[...path].ts` — Vercel Node.js Functions entrypoint. Builds the `App`, registers routes/middleware, and exports `default toFetchHandler(app)` from `@daloyjs/core/vercel` (Node.js Functions expect a default export with a `fetch` method; Node.js is the default runtime, so no `runtime` export is needed). **Keep it a catch-all** so DaloyJS owns routing. If you specifically need the Edge runtime, add `export const runtime = "edge"` and switch to `default toWebHandler(app)`.
19
19
  - `vercel.json` — Vercel build/runtime configuration.
20
20
  - `tests/` — test files.
21
21
 
@@ -36,7 +36,7 @@ You import the file you see. Vercel bundles the `api/` functions at deploy time
36
36
  3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields.
37
37
  4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core`.
38
38
  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).
39
- 6. Stay on the Edge runtime: only Web Standards APIs. No `node:` modules, no `fs`, no `Buffer`. If a feature requires Node, switch to a Node-runtime template.
39
+ 6. On the Node.js runtime the full Node API is available (`node:*`, `Buffer`, `fs`), but prefer Web Standards (`Request`/`Response`, `fetch`, Web Crypto) so the same app can also run on the Edge runtime or another adapter unchanged. If you opt into the Edge runtime, drop `node:` modules entirely.
40
40
  7. The catch-all `api/[...path].ts` must remain a catch-all so DaloyJS handles routing.
41
41
  8. Every new route ships with a test that covers a happy path and at least one unhappy path.
42
42
 
@@ -48,7 +48,7 @@ Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/
48
48
  - 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.
49
49
  - Every protected route attaches an auth `beforeHandle` and ships an unhappy-path test proving an unauthenticated request returns `401` (and wrong scope returns `403`) — the HTTP-boundary equivalent of Supabase's pgTAP policy tests.
50
50
  - JWT verifiers keep an explicit `algorithms` allowlist; never trust the token's `alg` header, never allow `none`, always check `exp` / `nbf`.
51
- - Credential / HMAC comparisons use `crypto.subtle.timingSafeEqual`, never `===`. Throw typed errors from `@daloyjs/core` so problem+json redacts in prod; never return raw stack traces.
51
+ - Credential / HMAC comparisons use a constant-time comparison (the framework's `timingSafeEqual`), never `===`. Throw typed errors from `@daloyjs/core` so problem+json redacts in prod; never return raw stack traces.
52
52
  - Keep `api/[...path].ts` a catch-all so DaloyJS owns routing — do not split into per-path files that bypass the middleware chain.
53
53
  - `.env`, `.env.local`, secrets, private keys: never commit. Use `vercel env` for production secrets.
54
54
 
@@ -1,6 +1,6 @@
1
1
  # my-daloy-vercel-api
2
2
 
3
- A [DaloyJS](https://daloyjs.dev) Vercel Edge API starter.
3
+ A [DaloyJS](https://daloyjs.dev) Vercel API starter on the Node.js runtime.
4
4
 
5
5
  ## Develop
6
6
 
@@ -26,7 +26,7 @@ curl http://localhost:3000/books/1
26
26
  - OpenAPI 3.1 JSON: <http://localhost:3000/openapi.json>
27
27
  - OpenAPI 3.1 YAML: <http://localhost:3000/openapi.yaml>
28
28
 
29
- After deploying, the same routes serve `/docs`, `/openapi.json`, and `/openapi.yaml` from your Vercel Edge URL.
29
+ After deploying, the same routes serve `/docs`, `/openapi.json`, and `/openapi.yaml` from your Vercel deployment URL.
30
30
  To brand Scalar, change `docs: true` in `api/[...path].ts` to `docs: { scalar: { theme, customCss } }`.
31
31
 
32
32
  <!-- daloy-minimal:strip-end docs -->
@@ -40,19 +40,23 @@ pnpm deploy
40
40
  The API entry lives at `api/[...path].ts` and uses `@daloyjs/core/vercel`:
41
41
 
42
42
  ```ts
43
- export const config = { runtime: "edge" };
44
- export default toWebHandler(app);
43
+ import { toFetchHandler } from "@daloyjs/core/vercel";
44
+
45
+ // Node.js is the default runtime — no `runtime` export needed.
46
+ export default toFetchHandler(app);
45
47
  ```
46
48
 
47
- This starter defaults to Vercel's Edge runtime for compatibility with the
48
- `vercel-edge` template name. Vercel now recommends Node.js for new projects;
49
- for Node.js Functions, remove the `config` export and use the official default
50
- `{ fetch }` shape instead:
49
+ This starter targets Vercel's Node.js runtime (on Fluid Compute), which Vercel
50
+ now recommends for standalone functions. Node.js Functions expect a default
51
+ export with a `fetch` method, which is exactly what `toFetchHandler(app)`
52
+ returns. If you specifically need the Edge runtime, add the `runtime` export and
53
+ switch to the bare web handler:
51
54
 
52
55
  ```ts
53
- import { toFetchHandler } from "@daloyjs/core/vercel";
56
+ import { toWebHandler } from "@daloyjs/core/vercel";
54
57
 
55
- export default toFetchHandler(app);
58
+ export const runtime = "edge";
59
+ export default toWebHandler(app);
56
60
  ```
57
61
 
58
62
  That catch-all API route lets DaloyJS own routing while Vercel handles the runtime.
@@ -70,10 +74,24 @@ Vercel bundles the `api/` functions at deploy time and resolves `.ts` directly,
70
74
  ## What's included
71
75
 
72
76
  - `@daloyjs/core/vercel` with starter security middleware: `secureHeaders` and `requestId`.
73
- - Smaller edge-friendly body and timeout limits in the generated app.
77
+ - Smaller serverless-friendly body and timeout limits in the generated app.
74
78
  <!-- daloy-minimal:strip-start books -->
75
79
  - A health route and a contract-first `/books/:id` route with Zod validation.
76
80
  <!-- daloy-minimal:strip-end books -->
77
81
  <!-- daloy-minimal:strip-start docs -->
78
82
  - A Scalar API reference UI at `/docs`, plus live OpenAPI 3.1 specs at `/openapi.json` and `/openapi.yaml`.
79
83
  <!-- daloy-minimal:strip-end docs -->
84
+
85
+ ## Authentication (OAuth2 / OpenID Connect)
86
+
87
+ This app is a **resource server**: DaloyJS verifies and enforces access tokens,
88
+ it does **not** issue them. There is no built-in login UI, user database, or
89
+ OAuth2 authorization server (it is not an identity provider like Keycloak,
90
+ Auth0, or Duende IdentityServer). To add login, bring an OpenID Connect provider
91
+ — managed (Auth0, Okta, Clerk, Microsoft Entra ID, AWS Cognito) or self-hosted
92
+ open source (Keycloak, Zitadel, Ory, Logto) — and verify its JWTs with the
93
+ first-party `jwk()`, `bearerAuth()`, and `requireScopes()` helpers. Don't build
94
+ your own authorization server.
95
+
96
+ See [Auth architecture](https://daloyjs.dev/docs/auth/architecture) for the
97
+ recommended designs (API resource server and browser BFF).
@@ -1,8 +1,8 @@
1
1
  # syntax=docker/dockerfile:1.7
2
- # Containerized local-dev / CI environment for a DaloyJS Vercel Edge API.
2
+ # Containerized local-dev / CI environment for a DaloyJS Vercel API.
3
3
  #
4
4
  # Production deploys go through `vercel deploy` to Vercel's managed
5
- # Edge runtime — **this Dockerfile is not a production runtime**. Ship
5
+ # Node.js runtime — **this Dockerfile is not a production runtime**. Ship
6
6
  # the function to Vercel; use this image for:
7
7
  # - Reproducible local dev (devcontainers, GitHub Codespaces).
8
8
  # - CI smoke tests that exercise the handler against `vercel dev`
@@ -2,20 +2,21 @@
2
2
  name: daloyjs-best-practices
3
3
  description: >-
4
4
  Best practices for building, testing, and hardening this DaloyJS REST API on
5
- Vercel Edge. Use when adding or changing HTTP routes, Zod schemas,
6
- middleware, or error handling; regenerating the OpenAPI spec or the typed
7
- Hey API client; keeping the catch-all Edge entrypoint and Web-Standard
8
- runtime constraints; or working on auth, rate limits, secrets, and the
5
+ Vercel (Node.js runtime). Use when adding or changing HTTP routes, Zod
6
+ schemas, middleware, or error handling; regenerating the OpenAPI spec or the
7
+ typed Hey API client; keeping the catch-all Vercel Functions entrypoint and
8
+ Web-Standard handler; or working on auth, rate limits, secrets, and the
9
9
  project's quality gates.
10
10
  license: MIT
11
11
  ---
12
12
 
13
- # SKILL.md — DaloyJS best practices (Vercel Edge)
13
+ # SKILL.md — DaloyJS best practices (Vercel, Node.js runtime)
14
14
 
15
15
  Operational guidance and best practices for AI coding agents working in this
16
- DaloyJS **Vercel Edge** project. This is the project's **single source of
17
- truth** for how to add routes, write tests, ship secure defaults, and run
18
- the quality gates. Read this in full before making non-trivial changes.
16
+ DaloyJS **Vercel** project on the **Node.js runtime** (Fluid Compute). This is
17
+ the project's **single source of truth** for how to add routes, write tests,
18
+ ship secure defaults, and run the quality gates. Read this in full before
19
+ making non-trivial changes.
19
20
 
20
21
  ## When to use this skill
21
22
 
@@ -24,18 +25,21 @@ Use this skill when you need to:
24
25
  - Add, modify, or remove HTTP routes in this project.
25
26
  - Adjust middleware, validation, or error handling.
26
27
  - Run tests or typecheck the project.
27
- - Deploy or troubleshoot the Edge runtime entrypoint.
28
+ - Deploy or troubleshoot the Vercel Functions entrypoint.
28
29
  - Harden the API (auth, CORS, rate limits, secrets, dependency hygiene).
29
30
 
30
31
  Do **not** use this skill for tasks unrelated to the API itself.
31
32
 
32
33
  ## Core principles
33
34
 
34
- DaloyJS is a **contract-first** framework. On Vercel Edge, additionally:
35
+ DaloyJS is a **contract-first** framework. On Vercel, additionally:
35
36
 
36
- 1. **Stay on the Edge runtime.** Only Web Standards APIs (no `node:`
37
- modules, no `fs`, no `Buffer`). If a feature requires Node APIs, the
38
- user must switch to a Node template.
37
+ 1. **Node.js runtime by default.** The full Node API is available
38
+ (`node:*`, `Buffer`, `fs`), but prefer Web Standards (`Request` /
39
+ `Response`, `fetch`, Web Crypto) so the same app can also run on the
40
+ Edge runtime or another adapter unchanged. Opt into Edge only when you
41
+ need it (`export const runtime = "edge"` + `toWebHandler(app)`), and
42
+ then drop `node:` modules.
39
43
  2. **The route definition is the contract.** Method, path, request
40
44
  schemas, and response schemas live in one place (`app.route({...})`).
41
45
  3. **Zod schemas validate at every boundary.**
@@ -43,16 +47,17 @@ DaloyJS is a **contract-first** framework. On Vercel Edge, additionally:
43
47
  5. **Secure by default.** `requestId()`, `secureHeaders()`, and
44
48
  `rateLimit()` are registered before route definitions. Note the
45
49
  in-memory rate limiter resets per instance — for high-traffic
46
- deployments, prefer Vercel's native rate-limiting (e.g.
47
- `@vercel/edge` + KV) or an external store.
50
+ deployments, back it with an external shared store (e.g. Upstash
51
+ Redis).
48
52
  6. **One catch-all entrypoint.** `api/[...path].ts` owns all routing so
49
53
  DaloyJS can generate a unified OpenAPI spec.
50
54
 
51
55
  ## Project shape
52
56
 
53
- - `api/[...path].ts` — the Edge entrypoint. Builds the `App`, registers
54
- routes/middleware, and exports `default toWebHandler(app)` plus
55
- `export const config = { runtime: "edge" }`.
57
+ - `api/[...path].ts` — the Vercel Functions entrypoint. Builds the `App`,
58
+ registers routes/middleware, and exports `default toFetchHandler(app)`
59
+ (Node.js Functions expect a default export with a `fetch` method; Node.js
60
+ is the default runtime, so no `runtime` export is needed).
56
61
  - `vercel.json` — Vercel build/runtime configuration.
57
62
  - `tests/` — test files (`*.test.ts`).
58
63
 
@@ -82,9 +87,9 @@ definitions:
82
87
  Customize via `docs: { openapiPath, openapiYamlPath, path, ui }`. Set
83
88
  `openapiYamlPath: false` to disable just the YAML route, `docs: "auto"` to
84
89
  mount only outside production, or `docs: false` to disable all three.
85
- On Vercel Edge the YAML serializer is pure-string (no Node deps) and
86
- adds <1KB to the bundle. For hand-rolled mounting, `openapiToYAML` is
87
- exported from `@daloyjs/core/openapi`.
90
+ On Vercel the YAML serializer is pure-string (no extra deps) and adds
91
+ <1KB to the bundle. For hand-rolled mounting, `openapiToYAML` is exported
92
+ from `@daloyjs/core/openapi`.
88
93
 
89
94
  ## Workflow: add a new route
90
95
 
@@ -151,7 +156,7 @@ Add CORS only when needed, with an explicit `origin` allowlist.
151
156
 
152
157
  ## Testing best practices
153
158
 
154
- Tests use in-process `app.request(...)` — no port, no Edge runtime
159
+ Tests use in-process `app.request(...)` — no port, no Vercel runtime
155
160
  needed for unit tests.
156
161
 
157
162
  ```ts
@@ -159,10 +164,11 @@ import { test } from "node:test";
159
164
  import assert from "node:assert/strict";
160
165
  import handler from "../api/[...path].ts";
161
166
 
162
- // Either import the underlying app, or test via the Edge handler's
163
- // fetch interface by passing a Web Request.
167
+ // Either import the underlying app, or test via the handler's fetch
168
+ // method (the default export is the Vercel `{ fetch }` object) by
169
+ // passing a Web Request.
164
170
  test("GET /healthz returns ok", async () => {
165
- const res = await handler(new Request("http://local/healthz"));
171
+ const res = await handler.fetch(new Request("http://local/healthz"));
166
172
  assert.equal(res.status, 200);
167
173
  });
168
174
  ```
@@ -180,23 +186,25 @@ Aim for **100% line and function coverage** on the routes you add.
180
186
  production traffic, back rate-limiting with Vercel KV or another
181
187
  shared store so limits apply across instances.
182
188
  - Never log secrets — filter `authorization`, `cookie`, etc.
183
- - Read secrets from `process.env` (available on Edge). Validate via Zod
184
- at module load.
189
+ - Read secrets from `process.env` (available on Node.js Functions).
190
+ Validate via Zod at module load.
185
191
  - For auth, verify JWT signatures with the Web Crypto API
186
- (`crypto.subtle`). Never trust the `alg` header from the token.
192
+ (`crypto.subtle`, available on both Node.js and Edge). Never trust the
193
+ `alg` header from the token.
187
194
  - Validate redirects against an allowlist.
188
195
  - Set `bodyLimitBytes` and `requestTimeoutMs` on `new App({...})` to
189
196
  mitigate DoS.
190
- - Edge functions have small bundle and CPU limits; be cautious about
191
- adding heavy dependencies. Inspect bundle size during deploy.
192
- - Pin Vercel project settings (regions, runtime version) explicitly in
193
- `vercel.json` rather than relying on dashboard defaults.
197
+ - Serverless functions still have bundle-size and cold-start costs; be
198
+ cautious about adding heavy dependencies. Inspect bundle size during
199
+ deploy.
200
+ - Pin Vercel project settings (regions, memory, maxDuration) explicitly
201
+ in `vercel.json` rather than relying on dashboard defaults.
194
202
 
195
203
  ## Logging & observability
196
204
 
197
205
  - Use `ctx.log` — it carries the request id.
198
- - `console.log` on Edge shows up in Vercel's runtime logs; the framework
199
- logger emits structured JSON for log aggregators.
206
+ - `console.log` shows up in Vercel's runtime logs; the framework logger
207
+ emits structured JSON for log aggregators.
200
208
 
201
209
  ## Configuration & secrets
202
210
 
@@ -209,13 +217,14 @@ Aim for **100% line and function coverage** on the routes you add.
209
217
  handles routing. Do not split routes into multiple Vercel API files
210
218
  unless the user explicitly asks (it disables shared middleware and a
211
219
  unified OpenAPI).
212
- - Use `toWebHandler(app)` from `@daloyjs/core/vercel` for Edge — never
213
- hand-roll a `fetch(req)` adapter. For Vercel's recommended Node.js
214
- runtime, remove the Edge config and export `default toFetchHandler(app)`.
220
+ - Use `toFetchHandler(app)` from `@daloyjs/core/vercel` for Node.js
221
+ Functions — never hand-roll a `fetch(req)` adapter. If you opt into the
222
+ Edge runtime, use `toWebHandler(app)` with `export const runtime = "edge"`.
215
223
  - Do not import `@daloyjs/core/node`, `@daloyjs/core/bun`, etc. — only
216
224
  `@daloyjs/core` and `@daloyjs/core/vercel`.
217
- - Avoid Node-only APIs (`Buffer`, `fs`, full `process` API). If a
218
- feature needs Node, switch to a Node-runtime template.
225
+ - Node APIs (`Buffer`, `fs`, full `process`) are available on the Node.js
226
+ runtime, but keep handlers Web-Standard where practical so the app can
227
+ also run on the Edge runtime unchanged.
219
228
  - Do not weaken response literal types (`as const`).
220
229
  - Do not return errors as `{ status: 4xx, body }`. Throw a typed error.
221
230
  - Do not add runtime dependencies without checking the hardened `.npmrc` (installs wait 24h after publish by default).
@@ -0,0 +1,8 @@
1
+ {
2
+ "servers": {
3
+ "daloyjs-docs": {
4
+ "type": "http",
5
+ "url": "https://daloyjs.dev/mcp"
6
+ }
7
+ }
8
+ }
@@ -1,11 +1,15 @@
1
1
  import { z } from "zod";
2
2
  import { App, NotFoundError, requestId, secureHeaders } from "@daloyjs/core";
3
- import { toWebHandler } from "@daloyjs/core/vercel";
3
+ import { toFetchHandler } from "@daloyjs/core/vercel";
4
4
 
5
- // This template defaults to Vercel's Edge runtime for compatibility with the
6
- // existing `vercel-edge` starter. For Vercel's recommended Node.js runtime,
7
- // remove this config and export `toFetchHandler(app)` from @daloyjs/core/vercel.
8
- export const config = { runtime: "edge" };
5
+ // This template targets Vercel's Node.js runtime the runtime Vercel now
6
+ // recommends for standalone functions (it runs on Fluid Compute, with the
7
+ // performance of the edge network but full Node API support). Node.js is the
8
+ // default runtime, so no `runtime` export is needed. Vercel Node.js Functions
9
+ // in `/api` expect a default export with a `fetch` method, which is exactly
10
+ // what `toFetchHandler(app)` returns. If you specifically need the Edge runtime
11
+ // instead, add `export const runtime = "edge"` and switch the default export to
12
+ // `toWebHandler(app)` from "@daloyjs/core/vercel".
9
13
 
10
14
  const app = new App({
11
15
  bodyLimitBytes: 256 * 1024,
@@ -17,7 +21,7 @@ const app = new App({
17
21
  // GET /openapi.yaml — OpenAPI 3.1 spec (YAML, served inline as text/yaml)
18
22
  // GET /docs — Scalar API reference UI that loads the spec
19
23
  openapi: {
20
- info: { title: "My Daloy Edge API", version: "0.0.1" },
24
+ info: { title: "My Daloy Vercel API", version: "0.0.1" },
21
25
  },
22
26
  docs: true,
23
27
  // daloy-minimal:strip-end docs
@@ -34,12 +38,12 @@ app.route({
34
38
  responses: {
35
39
  200: {
36
40
  description: "Service is healthy",
37
- body: z.object({ ok: z.literal(true), runtime: z.literal("vercel-edge") }),
41
+ body: z.object({ ok: z.literal(true), runtime: z.literal("vercel") }),
38
42
  },
39
43
  },
40
44
  handler: async () => ({
41
45
  status: 200,
42
- body: { ok: true as const, runtime: "vercel-edge" as const },
46
+ body: { ok: true as const, runtime: "vercel" as const },
43
47
  }),
44
48
  });
45
49
 
@@ -67,4 +71,4 @@ app.route({
67
71
  });
68
72
  // daloy-minimal:strip-end books
69
73
 
70
- export default toWebHandler(app);
74
+ export default toFetchHandler(app);
@@ -11,7 +11,7 @@
11
11
  "audit": "pnpm audit --prod"
12
12
  },
13
13
  "dependencies": {
14
- "@daloyjs/core": "^0.38.0",
14
+ "@daloyjs/core": "^0.38.2",
15
15
  "zod": "^4.4.3"
16
16
  },
17
17
  "devDependencies": {
@@ -0,0 +1,10 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import handler from "../api/[...path].ts";
4
+
5
+ test("Vercel Node.js handler responds through DaloyJS", async () => {
6
+ // Vercel Node.js Functions invoke the default export's `fetch` method.
7
+ const response = await handler.fetch(new Request("https://example.test/healthz"));
8
+ assert.equal(response.status, 200);
9
+ assert.equal((await response.json()).runtime, "vercel");
10
+ });
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://openapi.vercel.sh/vercel.json",
3
+ "cleanUrls": true,
4
+ "functions": {
5
+ "api/[...path].ts": { "memory": 1024, "maxDuration": 30 }
6
+ }
7
+ }
@@ -1,9 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import handler from "../api/[...path].ts";
4
-
5
- test("Vercel Edge handler responds through DaloyJS", async () => {
6
- const response = await handler(new Request("https://example.test/healthz"));
7
- assert.equal(response.status, 200);
8
- assert.equal((await response.json()).runtime, "vercel-edge");
9
- });
@@ -1,4 +0,0 @@
1
- {
2
- "version": 2,
3
- "cleanUrls": true
4
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes