create-daloy 0.38.2 → 0.38.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/sbom.cdx.json +9 -9
- package/sbom.spdx.json +5 -5
- package/templates/bun-basic/package.json +1 -1
- package/templates/bun-basic/src/build-app.ts +9 -13
- package/templates/cloudflare-worker/AGENTS.md +1 -1
- package/templates/cloudflare-worker/_agents/skills/daloyjs-best-practices/SKILL.md +6 -5
- package/templates/cloudflare-worker/package.json +1 -1
- package/templates/cloudflare-worker/src/index.ts +16 -0
- package/templates/deno-basic/deno.json +4 -4
- package/templates/deno-basic/src/build-app.ts +9 -13
- package/templates/node-basic/package.json +1 -1
- package/templates/node-basic/src/build-app.ts +9 -13
- package/templates/vercel/AGENTS.md +7 -6
- package/templates/vercel/README.md +14 -4
- package/templates/vercel/_agents/skills/daloyjs-best-practices/SKILL.md +19 -12
- package/templates/vercel/api/{[...path].ts → index.ts} +18 -3
- package/templates/vercel/package.json +2 -2
- package/templates/vercel/src/dev.ts +16 -0
- package/templates/vercel/tests/app.test.ts +1 -1
- package/templates/vercel/tsconfig.json +1 -1
- package/templates/vercel/vercel.json +2 -2
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:2a24975c-f4c4-542d-80c9-be6056ddc994",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-06-
|
|
7
|
+
"timestamp": "2026-06-16T19:48:12.143Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "DaloyJS",
|
|
11
11
|
"name": "daloy-generate-sbom",
|
|
12
|
-
"version": "0.38.
|
|
12
|
+
"version": "0.38.3"
|
|
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.3",
|
|
19
19
|
"name": "create-daloy",
|
|
20
|
-
"version": "0.38.
|
|
20
|
+
"version": "0.38.3",
|
|
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.3",
|
|
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.3",
|
|
46
46
|
"name": "create-daloy",
|
|
47
|
-
"version": "0.38.
|
|
47
|
+
"version": "0.38.3",
|
|
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.3",
|
|
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.3",
|
|
6
|
+
"documentNamespace": "https://github.com/daloyjs/daloy/sbom/create-daloy-0.38.3-2a24975c-f4c4-542d-80c9-be6056ddc994",
|
|
7
7
|
"creationInfo": {
|
|
8
|
-
"created": "2026-06-
|
|
8
|
+
"created": "2026-06-16T19:48:12.143Z",
|
|
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.3",
|
|
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.3"
|
|
31
31
|
}
|
|
32
32
|
]
|
|
33
33
|
}
|
|
@@ -40,19 +40,15 @@ export function buildApp(): App {
|
|
|
40
40
|
// `info.title` / `info.version` are pulled from package.json by default;
|
|
41
41
|
// set `openapi.info` here to override them.
|
|
42
42
|
openapi: {
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
// the
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`
|
|
53
|
-
: `http://localhost:${process.env.PORT ?? 3000}`),
|
|
54
|
-
},
|
|
55
|
-
],
|
|
43
|
+
// Leave `servers` unset so the Scalar "Try it" panel and the generated
|
|
44
|
+
// client target the origin the docs are served from — the deployed
|
|
45
|
+
// domain in production, localhost in dev — which keeps "Try it" within
|
|
46
|
+
// the connect-src 'self' CSP on every platform. Never hardcode a URL
|
|
47
|
+
// here: a localhost default breaks "Try it" once deployed. Set PUBLIC_URL
|
|
48
|
+
// to pin an absolute base URL (e.g. for client codegen).
|
|
49
|
+
...(process.env.PUBLIC_URL
|
|
50
|
+
? { servers: [{ url: process.env.PUBLIC_URL }] }
|
|
51
|
+
: {}),
|
|
56
52
|
},
|
|
57
53
|
docs: true,
|
|
58
54
|
// daloy-minimal:strip-end docs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AGENTS.md
|
|
2
2
|
|
|
3
|
-
A [DaloyJS](https://daloyjs.dev) REST API deployed to **Cloudflare Workers**. **Contract-first**: routes are defined with Zod schemas and OpenAPI 3.1 is generated from them.
|
|
3
|
+
A [DaloyJS](https://daloyjs.dev) REST API deployed to **Cloudflare Workers**. **Contract-first**: routes are defined with Zod schemas and OpenAPI 3.1 is generated from them. `docs: true` is set in `new App({...})`, so `GET /openapi.json`, `GET /openapi.yaml`, and `GET /docs` (Scalar UI) are auto-mounted. DaloyJS is dependency-free and the Scalar UI loads from a CDN, so this adds negligible Worker bundle size; drop `docs` (and the `openapi` block) if you want the smallest possible bundle.
|
|
4
4
|
|
|
5
5
|
- Package manager: pnpm (use `pnpm` unless the project's `package.json` was rewritten for npm/yarn/bun).
|
|
6
6
|
- Runtime: Cloudflare Workers (Web Standard `Request`/`Response`).
|
|
@@ -70,12 +70,13 @@ pnpm audit # supply-chain audit
|
|
|
70
70
|
|
|
71
71
|
Always run `pnpm typecheck` and `pnpm test` before declaring a task done.
|
|
72
72
|
|
|
73
|
-
## OpenAPI & docs routes
|
|
73
|
+
## OpenAPI & docs routes
|
|
74
74
|
|
|
75
|
-
This Worker starter
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
This Worker starter sets `docs: true` in `new App({...})`, so three routes
|
|
76
|
+
are auto-mounted off the spec generated from your route definitions.
|
|
77
|
+
DaloyJS is dependency-free and the Scalar UI loads from a CDN, so the bundle
|
|
78
|
+
cost is negligible; drop `docs` (and the `openapi` block) if you need the
|
|
79
|
+
smallest possible Worker. The routes:
|
|
79
80
|
|
|
80
81
|
- `GET /openapi.json` — OpenAPI 3.1 spec as JSON.
|
|
81
82
|
- `GET /openapi.yaml` — OpenAPI 3.1 spec as YAML (served inline as
|
|
@@ -6,6 +6,22 @@ const app = new App({
|
|
|
6
6
|
bodyLimitBytes: 256 * 1024,
|
|
7
7
|
requestTimeoutMs: 5_000,
|
|
8
8
|
production: true,
|
|
9
|
+
// Cloudflare Workers always run behind Cloudflare's edge, which sets
|
|
10
|
+
// X-Forwarded-For. Declare that single trusted hop so DaloyJS reads the real
|
|
11
|
+
// client IP instead of refusing the (otherwise spoofable) header and
|
|
12
|
+
// returning 500 in production. Increase the hop count if you put an
|
|
13
|
+
// additional proxy in front of the Worker.
|
|
14
|
+
behindProxy: { hops: 1 },
|
|
15
|
+
// daloy-minimal:strip-start docs
|
|
16
|
+
// Auto-mounted docs (since `docs: true`): GET /openapi.json, /openapi.yaml,
|
|
17
|
+
// and /docs (Scalar UI). DaloyJS is dependency-free and the Scalar UI loads
|
|
18
|
+
// from a CDN, so this adds negligible Worker bundle size. Drop `docs` (and
|
|
19
|
+
// this `openapi` block) if you want the smallest possible bundle.
|
|
20
|
+
openapi: {
|
|
21
|
+
info: { title: "My Daloy Cloudflare API", version: "0.0.1" },
|
|
22
|
+
},
|
|
23
|
+
docs: true,
|
|
24
|
+
// daloy-minimal:strip-end docs
|
|
9
25
|
});
|
|
10
26
|
|
|
11
27
|
app.use(requestId());
|
|
@@ -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.3",
|
|
12
|
+
"@daloyjs/core/banner": "jsr:@daloyjs/daloy@^0.38.3/banner",
|
|
13
|
+
"@daloyjs/core/deno": "jsr:@daloyjs/daloy@^0.38.3/deno",
|
|
14
|
+
"@daloyjs/core/openapi": "jsr:@daloyjs/daloy@^0.38.3/openapi",
|
|
15
15
|
"zod": "npm:zod@^4.4.3"
|
|
16
16
|
},
|
|
17
17
|
"compilerOptions": {
|
|
@@ -41,19 +41,15 @@ export function buildApp(): App {
|
|
|
41
41
|
// `info.title` / `info.version` are pulled from deno.json by default;
|
|
42
42
|
// set `openapi.info` here to override them.
|
|
43
43
|
openapi: {
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// the
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
? `https://${Deno.env.get("RAILWAY_PUBLIC_DOMAIN")}`
|
|
54
|
-
: `http://localhost:${Deno.env.get("PORT") ?? "3000"}`),
|
|
55
|
-
},
|
|
56
|
-
],
|
|
44
|
+
// Leave `servers` unset so the Scalar "Try it" panel and the generated
|
|
45
|
+
// client target the origin the docs are served from — the deployed
|
|
46
|
+
// domain in production, localhost in dev — which keeps "Try it" within
|
|
47
|
+
// the connect-src 'self' CSP on every platform (Deno Deploy, etc.).
|
|
48
|
+
// Never hardcode a URL here: a localhost default breaks "Try it" once
|
|
49
|
+
// deployed. Set PUBLIC_URL to pin an absolute base URL (e.g. for codegen).
|
|
50
|
+
...(Deno.env.get("PUBLIC_URL")
|
|
51
|
+
? { servers: [{ url: Deno.env.get("PUBLIC_URL")! }] }
|
|
52
|
+
: {}),
|
|
57
53
|
},
|
|
58
54
|
docs: true,
|
|
59
55
|
// daloy-minimal:strip-end docs
|
|
@@ -45,19 +45,15 @@ export function buildApp(): App {
|
|
|
45
45
|
// `info.title` / `info.version` are pulled from package.json by default;
|
|
46
46
|
// set `openapi.info` here to override them.
|
|
47
47
|
openapi: {
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
// the
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`
|
|
58
|
-
: `http://localhost:${process.env.PORT ?? 3000}`),
|
|
59
|
-
},
|
|
60
|
-
],
|
|
48
|
+
// Leave `servers` unset so the Scalar "Try it" panel and the generated
|
|
49
|
+
// client target the origin the docs are served from — the deployed
|
|
50
|
+
// domain in production, localhost in dev — which keeps "Try it" within
|
|
51
|
+
// the connect-src 'self' CSP on every platform. Never hardcode a URL
|
|
52
|
+
// here: a localhost default breaks "Try it" once deployed. Set PUBLIC_URL
|
|
53
|
+
// to pin an absolute base URL (e.g. for client codegen).
|
|
54
|
+
...(process.env.PUBLIC_URL
|
|
55
|
+
? { servers: [{ url: process.env.PUBLIC_URL }] }
|
|
56
|
+
: {}),
|
|
61
57
|
},
|
|
62
58
|
docs: true,
|
|
63
59
|
// daloy-minimal:strip-end docs
|
|
@@ -7,7 +7,7 @@ A [DaloyJS](https://daloyjs.dev) REST API deployed to **Vercel** on the **Node.j
|
|
|
7
7
|
|
|
8
8
|
## Commands
|
|
9
9
|
|
|
10
|
-
- `pnpm dev` — local
|
|
10
|
+
- `pnpm dev` — local Node dev server (`src/dev.ts`) on http://localhost:3000 (no `vercel dev` / login needed; serves the same app the Vercel Function runs)
|
|
11
11
|
- `pnpm typecheck` — `tsc --noEmit`
|
|
12
12
|
- `pnpm test` — run test suite
|
|
13
13
|
- `pnpm deploy` — deploy to Vercel
|
|
@@ -15,8 +15,9 @@ A [DaloyJS](https://daloyjs.dev) REST API deployed to **Vercel** on the **Node.j
|
|
|
15
15
|
|
|
16
16
|
## Project shape
|
|
17
17
|
|
|
18
|
-
- `api/
|
|
19
|
-
- `vercel.json` — Vercel
|
|
18
|
+
- `api/index.ts` — the single 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). `vercel.json` rewrites every path (`/(.*)` → `/api`) to this one function, so DaloyJS owns all routing and the app's routes are served at the site root (`/healthz`, `/docs`, …), not under `/api/*`. If you specifically need the Edge runtime, add `export const runtime = "edge"` and switch to `default toWebHandler(app)`.
|
|
19
|
+
- `vercel.json` — Vercel config. The `rewrites` rule routing all paths to `/api` is **required** for root routing; do not remove it. (`functions` sets memory / maxDuration.)
|
|
20
|
+
- `src/dev.ts` — local Node dev server (`pnpm dev`). Imports the `app` exported from `api/index.ts` and serves it via `@daloyjs/core/node` at the root, so you get fast local iteration without `vercel dev`. Dev-only; it is not under `api/`, so Vercel never deploys it.
|
|
20
21
|
- `tests/` — test files.
|
|
21
22
|
|
|
22
23
|
## Imports
|
|
@@ -24,7 +25,7 @@ A [DaloyJS](https://daloyjs.dev) REST API deployed to **Vercel** on the **Node.j
|
|
|
24
25
|
This project uses TypeScript with `"allowImportingTsExtensions"`, so relative imports use the **`.ts` extension** — the actual file you see on disk:
|
|
25
26
|
|
|
26
27
|
```ts
|
|
27
|
-
import handler from "../api/
|
|
28
|
+
import handler from "../api/index.ts";
|
|
28
29
|
```
|
|
29
30
|
|
|
30
31
|
You import the file you see. Vercel bundles the `api/` functions at deploy time and resolves `.ts` directly, and the test runner (tsx) does too. Bare-specifier imports from packages (`@daloyjs/core`, `zod`, …) do not need an extension.
|
|
@@ -37,7 +38,7 @@ You import the file you see. Vercel bundles the `api/` functions at deploy time
|
|
|
37
38
|
4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core`.
|
|
38
39
|
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
40
|
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
|
-
7.
|
|
41
|
+
7. Keep a single `api/index.ts` entry and the `vercel.json` `/(.*)` → `/api` rewrite so DaloyJS handles all routing at the site root.
|
|
41
42
|
8. Every new route ships with a test that covers a happy path and at least one unhappy path.
|
|
42
43
|
|
|
43
44
|
## Secure-by-default (do not let an AI strip these)
|
|
@@ -49,7 +50,7 @@ Per Supabase + Aikido on [secure-by-default development](https://www.aikido.dev/
|
|
|
49
50
|
- 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
51
|
- JWT verifiers keep an explicit `algorithms` allowlist; never trust the token's `alg` header, never allow `none`, always check `exp` / `nbf`.
|
|
51
52
|
- 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
|
-
- Keep `api/
|
|
53
|
+
- Keep the single `api/index.ts` entry and the `vercel.json` rewrite so DaloyJS owns routing — do not split into per-path files that bypass the middleware chain, and do not remove the rewrite (the root domain would 404).
|
|
53
54
|
- `.env`, `.env.local`, secrets, private keys: never commit. Use `vercel env` for production secrets.
|
|
54
55
|
|
|
55
56
|
## Process expectations
|
|
@@ -27,7 +27,7 @@ curl http://localhost:3000/books/1
|
|
|
27
27
|
- OpenAPI 3.1 YAML: <http://localhost:3000/openapi.yaml>
|
|
28
28
|
|
|
29
29
|
After deploying, the same routes serve `/docs`, `/openapi.json`, and `/openapi.yaml` from your Vercel deployment URL.
|
|
30
|
-
To brand Scalar, change `docs: true` in `api/
|
|
30
|
+
To brand Scalar, change `docs: true` in `api/index.ts` to `docs: { scalar: { theme, customCss } }`.
|
|
31
31
|
|
|
32
32
|
<!-- daloy-minimal:strip-end docs -->
|
|
33
33
|
|
|
@@ -37,7 +37,7 @@ To brand Scalar, change `docs: true` in `api/[...path].ts` to `docs: { scalar: {
|
|
|
37
37
|
pnpm deploy
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
The API entry lives at `api/
|
|
40
|
+
The API entry lives at `api/index.ts` and uses `@daloyjs/core/vercel`:
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
43
|
import { toFetchHandler } from "@daloyjs/core/vercel";
|
|
@@ -59,14 +59,24 @@ export const runtime = "edge";
|
|
|
59
59
|
export default toWebHandler(app);
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
`vercel.json` rewrites every path to this single function:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{ "rewrites": [{ "source": "/(.*)", "destination": "/api" }] }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
So DaloyJS owns all routing and the app's routes are served at the **site root**
|
|
69
|
+
(`/healthz`, `/docs`, `/openapi.json`, …) rather than under `/api/*`. Without
|
|
70
|
+
this rewrite the function only answers `/api/*` and the root domain returns a
|
|
71
|
+
Vercel 404. (The demo defines no `/` route, so the bare root returns the app's
|
|
72
|
+
problem+json 404 — visit `/docs` or `/healthz`.)
|
|
63
73
|
|
|
64
74
|
## Imports
|
|
65
75
|
|
|
66
76
|
This project uses TypeScript with `"allowImportingTsExtensions"`. Relative imports use the `.ts` extension — the actual file on disk:
|
|
67
77
|
|
|
68
78
|
```ts
|
|
69
|
-
import handler from "../api/
|
|
79
|
+
import handler from "../api/index.ts";
|
|
70
80
|
```
|
|
71
81
|
|
|
72
82
|
Vercel bundles the `api/` functions at deploy time and resolves `.ts` directly, and the test runner (tsx) does too.
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Best practices for building, testing, and hardening this DaloyJS REST API on
|
|
5
5
|
Vercel (Node.js runtime). Use when adding or changing HTTP routes, Zod
|
|
6
6
|
schemas, middleware, or error handling; regenerating the OpenAPI spec or the
|
|
7
|
-
typed Hey API client; keeping the
|
|
7
|
+
typed Hey API client; keeping the single Vercel Functions entrypoint and
|
|
8
8
|
Web-Standard handler; or working on auth, rate limits, secrets, and the
|
|
9
9
|
project's quality gates.
|
|
10
10
|
license: MIT
|
|
@@ -49,22 +49,28 @@ DaloyJS is a **contract-first** framework. On Vercel, additionally:
|
|
|
49
49
|
in-memory rate limiter resets per instance — for high-traffic
|
|
50
50
|
deployments, back it with an external shared store (e.g. Upstash
|
|
51
51
|
Redis).
|
|
52
|
-
6. **One
|
|
53
|
-
|
|
52
|
+
6. **One entrypoint + a rewrite.** `api/index.ts` is the only function,
|
|
53
|
+
and `vercel.json` rewrites every path (`/(.*)` → `/api`) to it, so
|
|
54
|
+
DaloyJS owns all routing at the site root and generates a unified
|
|
55
|
+
OpenAPI spec. Removing the rewrite makes the root domain 404.
|
|
54
56
|
|
|
55
57
|
## Project shape
|
|
56
58
|
|
|
57
|
-
- `api/
|
|
59
|
+
- `api/index.ts` — the Vercel Functions entrypoint. Builds the `App`,
|
|
58
60
|
registers routes/middleware, and exports `default toFetchHandler(app)`
|
|
59
61
|
(Node.js Functions expect a default export with a `fetch` method; Node.js
|
|
60
62
|
is the default runtime, so no `runtime` export is needed).
|
|
61
|
-
- `vercel.json` — Vercel
|
|
63
|
+
- `vercel.json` — Vercel config. The `rewrites` rule (`/(.*)` → `/api`) is
|
|
64
|
+
required so DaloyJS routes at the site root; do not remove it.
|
|
65
|
+
- `src/dev.ts` — local Node dev server (`pnpm dev`). Serves the `app`
|
|
66
|
+
exported from `api/index.ts` via `@daloyjs/core/node`; dev-only, never
|
|
67
|
+
deployed (it lives outside `api/`).
|
|
62
68
|
- `tests/` — test files (`*.test.ts`).
|
|
63
69
|
|
|
64
70
|
## Commands cheat-sheet
|
|
65
71
|
|
|
66
72
|
```bash
|
|
67
|
-
pnpm dev # local
|
|
73
|
+
pnpm dev # local Node dev server (src/dev.ts) on http://localhost:3000
|
|
68
74
|
pnpm typecheck # tsc --noEmit
|
|
69
75
|
pnpm test # run test suite
|
|
70
76
|
pnpm deploy # deploy to Vercel
|
|
@@ -93,7 +99,7 @@ from `@daloyjs/core/openapi`.
|
|
|
93
99
|
|
|
94
100
|
## Workflow: add a new route
|
|
95
101
|
|
|
96
|
-
1. **Open `api/
|
|
102
|
+
1. **Open `api/index.ts`.**
|
|
97
103
|
2. **Design schemas first.** Use `z.object({...}).strict()` for inputs.
|
|
98
104
|
3. **Call `app.route({...})`** with `method`, `path`, `operationId`,
|
|
99
105
|
`tags`, `responses`, `handler` (plus `request` when accepting input).
|
|
@@ -162,7 +168,7 @@ needed for unit tests.
|
|
|
162
168
|
```ts
|
|
163
169
|
import { test } from "node:test";
|
|
164
170
|
import assert from "node:assert/strict";
|
|
165
|
-
import handler from "../api/
|
|
171
|
+
import handler from "../api/index.ts";
|
|
166
172
|
|
|
167
173
|
// Either import the underlying app, or test via the handler's fetch
|
|
168
174
|
// method (the default export is the Vercel `{ fetch }` object) by
|
|
@@ -213,10 +219,11 @@ Aim for **100% line and function coverage** on the routes you add.
|
|
|
213
219
|
|
|
214
220
|
## Pitfalls and guardrails
|
|
215
221
|
|
|
216
|
-
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
222
|
+
- Keep the single `api/index.ts` entry and the `vercel.json` `/(.*)` →
|
|
223
|
+
`/api` rewrite so DaloyJS handles routing at the site root. Do not
|
|
224
|
+
remove the rewrite (the root domain would 404) and do not split routes
|
|
225
|
+
into multiple Vercel API files unless the user explicitly asks (it
|
|
226
|
+
disables shared middleware and a unified OpenAPI).
|
|
220
227
|
- Use `toFetchHandler(app)` from `@daloyjs/core/vercel` for Node.js
|
|
221
228
|
Functions — never hand-roll a `fetch(req)` adapter. If you opt into the
|
|
222
229
|
Edge runtime, use `toWebHandler(app)` with `export const runtime = "edge"`.
|
|
@@ -6,15 +6,30 @@ import { toFetchHandler } from "@daloyjs/core/vercel";
|
|
|
6
6
|
// recommends for standalone functions (it runs on Fluid Compute, with the
|
|
7
7
|
// performance of the edge network but full Node API support). Node.js is the
|
|
8
8
|
// default runtime, so no `runtime` export is needed. Vercel Node.js Functions
|
|
9
|
-
//
|
|
10
|
-
//
|
|
9
|
+
// expect a default export with a `fetch` method, which is exactly what
|
|
10
|
+
// `toFetchHandler(app)` returns. If you specifically need the Edge runtime
|
|
11
11
|
// instead, add `export const runtime = "edge"` and switch the default export to
|
|
12
12
|
// `toWebHandler(app)` from "@daloyjs/core/vercel".
|
|
13
|
+
//
|
|
14
|
+
// This single function owns ALL routing: `vercel.json` rewrites every path to
|
|
15
|
+
// `/api`, and DaloyJS matches the original request path (`/healthz`, `/docs`,
|
|
16
|
+
// …). So the app's routes are served at the site root, not under `/api/*`.
|
|
17
|
+
// Do not split this into per-path files — keep one entry so the middleware
|
|
18
|
+
// chain and the generated OpenAPI spec stay unified.
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
// Exported so the local dev server (`src/dev.ts`, run by `pnpm dev`) can serve
|
|
21
|
+
// the same app over a plain Node listener. Vercel uses the default export below.
|
|
22
|
+
export const app = new App({
|
|
15
23
|
bodyLimitBytes: 256 * 1024,
|
|
16
24
|
requestTimeoutMs: 5_000,
|
|
17
25
|
production: process.env.NODE_ENV === "production",
|
|
26
|
+
// Reverse-proxy posture. On Vercel, every request reaches the function
|
|
27
|
+
// through Vercel's edge, which sets `x-forwarded-for`. The app must declare
|
|
28
|
+
// that trusted hop, otherwise DaloyJS refuses the spoofable header and
|
|
29
|
+
// returns 500 in production. Vercel is exactly one edge hop, so the default
|
|
30
|
+
// is 1; override TRUST_PROXY_HOPS only if you put another proxy in front
|
|
31
|
+
// (e.g. set it to 2 behind Cloudflare -> Vercel).
|
|
32
|
+
behindProxy: { hops: Number(process.env.TRUST_PROXY_HOPS ?? "1") },
|
|
18
33
|
// daloy-minimal:strip-start docs
|
|
19
34
|
// Auto-mounted docs (when `docs: true`):
|
|
20
35
|
// GET /openapi.json — OpenAPI 3.1 spec (JSON)
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"dev": "
|
|
7
|
+
"dev": "node --import tsx/esm src/dev.ts",
|
|
8
8
|
"deploy": "vercel deploy",
|
|
9
9
|
"typecheck": "tsc --noEmit",
|
|
10
10
|
"test": "node --import tsx/esm --test tests/**/*.test.ts",
|
|
11
11
|
"audit": "pnpm audit --prod"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@daloyjs/core": "^0.38.
|
|
14
|
+
"@daloyjs/core": "^0.38.3",
|
|
15
15
|
"zod": "^4.4.3"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Local development server.
|
|
2
|
+
//
|
|
3
|
+
// Vercel runs `api/index.ts` as a Function in production; this serves the very
|
|
4
|
+
// same app over a plain Node listener so you get fast local iteration without
|
|
5
|
+
// `vercel dev` (no Vercel login, no edge emulation). Routes are served at the
|
|
6
|
+
// root here (`/healthz`, `/docs`, …), exactly as on the deployed site — in
|
|
7
|
+
// production the `vercel.json` rewrite maps every path to the Function.
|
|
8
|
+
//
|
|
9
|
+
// Run with: `pnpm dev` (or `npm run dev`).
|
|
10
|
+
import { serve } from "@daloyjs/core/node";
|
|
11
|
+
import { app } from "../api/index.ts";
|
|
12
|
+
|
|
13
|
+
const port = Number(process.env.PORT ?? 3000);
|
|
14
|
+
serve(app, { port });
|
|
15
|
+
// eslint-disable-next-line no-console
|
|
16
|
+
console.log(`DaloyJS dev server listening on http://localhost:${port}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import handler from "../api/
|
|
3
|
+
import handler from "../api/index.ts";
|
|
4
4
|
|
|
5
5
|
test("Vercel Node.js handler responds through DaloyJS", async () => {
|
|
6
6
|
// Vercel Node.js Functions invoke the default export's `fetch` method.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://openapi.vercel.sh/vercel.json",
|
|
3
|
-
"
|
|
3
|
+
"rewrites": [{ "source": "/(.*)", "destination": "/api" }],
|
|
4
4
|
"functions": {
|
|
5
|
-
"api/
|
|
5
|
+
"api/index.ts": { "memory": 1024, "maxDuration": 30 }
|
|
6
6
|
}
|
|
7
7
|
}
|