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.
- package/README.md +27 -8
- package/bin/create-daloy.mjs +24 -4
- package/package.json +1 -1
- package/sbom.cdx.json +9 -9
- package/sbom.spdx.json +5 -5
- package/templates/_ci/deno/_github/workflows/codeql.yml +2 -2
- package/templates/_ci/deno/_github/workflows/container-scan.yml +3 -3
- package/templates/_ci/deno/_github/workflows/opengrep.yml +1 -1
- package/templates/_ci/deno/_github/workflows/scorecard.yml +1 -1
- package/templates/_ci/node/_github/workflows/codeql.yml +2 -2
- package/templates/_ci/node/_github/workflows/container-scan.yml +3 -3
- package/templates/_ci/node/_github/workflows/opengrep.yml +1 -1
- package/templates/_ci/node/_github/workflows/scorecard.yml +1 -1
- package/templates/bun-basic/README.md +14 -0
- package/templates/bun-basic/_vscode/mcp.json +8 -0
- package/templates/bun-basic/package.json +1 -1
- package/templates/bun-basic/src/build-app.ts +26 -1
- package/templates/bun-basic/src/index.ts +4 -1
- package/templates/cloudflare-worker/README.md +16 -0
- package/templates/cloudflare-worker/_vscode/mcp.json +8 -0
- package/templates/cloudflare-worker/package.json +1 -1
- package/templates/deno-basic/README.md +14 -0
- package/templates/deno-basic/_vscode/mcp.json +8 -0
- package/templates/deno-basic/deno.json +4 -4
- package/templates/deno-basic/src/build-app.ts +26 -1
- package/templates/node-basic/README.md +14 -0
- package/templates/node-basic/_vscode/mcp.json +8 -0
- package/templates/node-basic/package.json +1 -1
- package/templates/node-basic/src/build-app.ts +26 -1
- package/templates/node-basic/tsconfig.build.json +2 -1
- package/templates/{vercel-edge → vercel}/AGENTS.md +5 -5
- package/templates/{vercel-edge → vercel}/README.md +29 -11
- package/templates/{vercel-edge → vercel}/_Dockerfile +2 -2
- package/templates/{vercel-edge → vercel}/_agents/skills/daloyjs-best-practices/SKILL.md +48 -39
- package/templates/vercel/_vscode/mcp.json +8 -0
- package/templates/{vercel-edge → vercel}/api/[...path].ts +13 -9
- package/templates/{vercel-edge → vercel}/package.json +1 -1
- package/templates/vercel/tests/app.test.ts +10 -0
- package/templates/vercel/vercel.json +7 -0
- package/templates/vercel-edge/tests/app.test.ts +0 -9
- package/templates/vercel-edge/vercel.json +0 -4
- /package/templates/{vercel-edge → vercel}/CLAUDE.md +0 -0
- /package/templates/{vercel-edge → vercel}/_dockerignore +0 -0
- /package/templates/{vercel-edge → vercel}/_env.example +0 -0
- /package/templates/{vercel-edge → vercel}/_gitignore +0 -0
- /package/templates/{vercel-edge → vercel}/_npmrc +0 -0
- /package/templates/{vercel-edge → vercel}/pnpm-workspace.yaml +0 -0
- /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
|
|
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
|
|
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
|
|
91
|
+
### `vercel`
|
|
92
92
|
|
|
93
|
-
A Vercel
|
|
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
|
|
97
|
-
-
|
|
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
|
|
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.
|
package/bin/create-daloy.mjs
CHANGED
|
@@ -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
|
|
27
|
-
title: "Vercel
|
|
28
|
-
description: "Catch-all Vercel
|
|
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
|
|
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
package/sbom.cdx.json
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.5",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:2bbd5e7e-5035-5e09-92c8-3e1aec60f5a1",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-06-
|
|
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.
|
|
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.
|
|
18
|
+
"bom-ref": "pkg:npm/create-daloy@0.38.2",
|
|
19
19
|
"name": "create-daloy",
|
|
20
|
-
"version": "0.38.
|
|
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.
|
|
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.
|
|
45
|
+
"tagId": "swidtag-create-daloy-0.38.2",
|
|
46
46
|
"name": "create-daloy",
|
|
47
|
-
"version": "0.38.
|
|
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.
|
|
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.
|
|
6
|
-
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-0.38.
|
|
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-
|
|
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.
|
|
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.
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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>.
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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>.
|
|
@@ -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>.
|
|
@@ -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.
|
|
12
|
-
"@daloyjs/core/banner": "jsr:@daloyjs/daloy@^0.38.
|
|
13
|
-
"@daloyjs/core/deno": "jsr:@daloyjs/daloy@^0.38.
|
|
14
|
-
"@daloyjs/core/openapi": "jsr:@daloyjs/daloy@^0.38.
|
|
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
|
-
|
|
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>.
|
|
@@ -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
|
-
|
|
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
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
A [DaloyJS](https://daloyjs.dev) REST API deployed to **Vercel
|
|
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
|
|
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
|
|
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.
|
|
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 `
|
|
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
|
|
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
|
|
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
|
-
|
|
44
|
-
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 {
|
|
56
|
+
import { toWebHandler } from "@daloyjs/core/vercel";
|
|
54
57
|
|
|
55
|
-
export
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
|
6
|
-
middleware, or error handling; regenerating the OpenAPI spec or the
|
|
7
|
-
Hey API client; keeping the catch-all
|
|
8
|
-
|
|
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
|
|
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
|
|
17
|
-
truth** for how to add routes, write tests,
|
|
18
|
-
the quality gates. Read this in full before
|
|
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
|
|
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
|
|
35
|
+
DaloyJS is a **contract-first** framework. On Vercel, additionally:
|
|
35
36
|
|
|
36
|
-
1. **
|
|
37
|
-
|
|
38
|
-
|
|
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,
|
|
47
|
-
|
|
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
|
|
54
|
-
routes/middleware, and exports `default
|
|
55
|
-
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
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
|
|
163
|
-
//
|
|
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
|
|
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
|
|
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
|
-
-
|
|
191
|
-
adding heavy dependencies. Inspect bundle size during
|
|
192
|
-
|
|
193
|
-
|
|
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`
|
|
199
|
-
|
|
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 `
|
|
213
|
-
hand-roll a `fetch(req)` adapter.
|
|
214
|
-
runtime,
|
|
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
|
-
-
|
|
218
|
-
|
|
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).
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { App, NotFoundError, requestId, secureHeaders } from "@daloyjs/core";
|
|
3
|
-
import {
|
|
3
|
+
import { toFetchHandler } from "@daloyjs/core/vercel";
|
|
4
4
|
|
|
5
|
-
// This template
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
74
|
+
export default toFetchHandler(app);
|
|
@@ -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
|
+
});
|
|
@@ -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
|
-
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|