create-daloy 0.1.23 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/bin/create-daloy.mjs +54 -13
- package/package.json +1 -1
- package/templates/bun-basic/package.json +1 -1
- package/templates/bun-basic/src/index.ts +6 -2
- package/templates/cloudflare-worker/AGENTS.md +2 -2
- package/templates/cloudflare-worker/_agents/skills/daloyjs-best-practices/SKILL.md +5 -6
- package/templates/cloudflare-worker/package.json +1 -1
- package/templates/cloudflare-worker/src/index.ts +1 -1
- package/templates/cloudflare-worker/wrangler.toml +1 -1
- package/templates/deno-basic/deno.json +2 -2
- package/templates/deno-basic/src/main.ts +11 -7
- package/templates/node-basic/package.json +1 -1
- package/templates/vercel-edge/AGENTS.md +1 -1
- package/templates/vercel-edge/README.md +13 -2
- package/templates/vercel-edge/_agents/skills/daloyjs-best-practices/SKILL.md +4 -3
- package/templates/vercel-edge/api/[...path].ts +5 -2
- package/templates/vercel-edge/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,6 +83,7 @@ A Vercel Edge API bootstrap using `@daloyjs/core/vercel` with:
|
|
|
83
83
|
|
|
84
84
|
- `api/[...path].ts` catch-all routing so DaloyJS owns the API surface.
|
|
85
85
|
- `export const config = { runtime: "edge" }` ready for Vercel Edge.
|
|
86
|
+
- Node.js migration notes using Vercel's default `{ fetch }` export shape.
|
|
86
87
|
- `vercel dev` / `vercel deploy` scripts.
|
|
87
88
|
- `secureHeaders` and `requestId` enabled by default, with smaller edge-friendly body and timeout limits.
|
|
88
89
|
- A health route and bookstore route mirroring the Node starter.
|
package/bin/create-daloy.mjs
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
// Zero runtime dependencies; uses only Node built-ins.
|
|
4
4
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { existsSync } from "node:fs";
|
|
6
|
+
import { existsSync, openSync } from "node:fs";
|
|
7
7
|
import { mkdir, readdir, readFile, writeFile, copyFile, rm } from "node:fs/promises";
|
|
8
8
|
import { createInterface } from "node:readline/promises";
|
|
9
9
|
import { stdin as input, stdout as output } from "node:process";
|
|
10
|
+
import { ReadStream as TtyReadStream } from "node:tty";
|
|
10
11
|
import path from "node:path";
|
|
11
12
|
import { fileURLToPath } from "node:url";
|
|
12
13
|
|
|
@@ -24,7 +25,7 @@ const TEMPLATE_OPTIONS = [
|
|
|
24
25
|
{
|
|
25
26
|
value: "vercel-edge",
|
|
26
27
|
title: "Vercel Edge",
|
|
27
|
-
description: "Catch-all Edge
|
|
28
|
+
description: "Catch-all Vercel Edge route with Node.js migration notes",
|
|
28
29
|
},
|
|
29
30
|
{
|
|
30
31
|
value: "cloudflare-worker",
|
|
@@ -794,9 +795,43 @@ function runQuiet(cmd, args, cwd) {
|
|
|
794
795
|
//
|
|
795
796
|
// `ask`/`askYesNo` use readline for resilience (paste, history, multi-line
|
|
796
797
|
// input). `askChoice` upgrades to raw-mode arrow-key navigation when stdin is
|
|
797
|
-
// a TTY
|
|
798
|
+
// a TTY. When package-manager wrappers hide raw mode on process.stdin (pnpm
|
|
799
|
+
// does this on some terminals), we reopen the controlling TTY directly before
|
|
800
|
+
// falling back to the numbered prompt used by the readline-driven tests.
|
|
798
801
|
// ----------------------------------------------------------------------------
|
|
799
802
|
|
|
803
|
+
function choiceInputMode({ stdinIsTTY, hasRawMode, platform }) {
|
|
804
|
+
if (stdinIsTTY && hasRawMode) return "stdin";
|
|
805
|
+
if (platform !== "win32") return "tty";
|
|
806
|
+
return "numbered";
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function openChoiceInputStream() {
|
|
810
|
+
const mode = choiceInputMode({
|
|
811
|
+
stdinIsTTY: process.stdin.isTTY,
|
|
812
|
+
hasRawMode: typeof process.stdin.setRawMode === "function",
|
|
813
|
+
platform: process.platform,
|
|
814
|
+
});
|
|
815
|
+
if (mode === "stdin") {
|
|
816
|
+
return { stream: process.stdin, dispose: () => {} };
|
|
817
|
+
}
|
|
818
|
+
if (mode !== "tty") return null;
|
|
819
|
+
try {
|
|
820
|
+
const fd = openSync("/dev/tty", "r");
|
|
821
|
+
const stream = new TtyReadStream(fd);
|
|
822
|
+
if (!stream.isTTY || typeof stream.setRawMode !== "function") {
|
|
823
|
+
stream.destroy();
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
return {
|
|
827
|
+
stream,
|
|
828
|
+
dispose: () => stream.destroy(),
|
|
829
|
+
};
|
|
830
|
+
} catch {
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
800
835
|
function printPromptHeader(question) {
|
|
801
836
|
console.log(`${color(COLORS.cyan, SYMBOLS.stepActive)} ${color(COLORS.bold, question)}`);
|
|
802
837
|
}
|
|
@@ -842,8 +877,9 @@ function optionDescription(option) {
|
|
|
842
877
|
// Arrow-key powered choice prompt. Falls back to numbered input whenever raw
|
|
843
878
|
// mode is unavailable (CI, piped stdin, integration tests).
|
|
844
879
|
async function askChoice(rl, question, choices, defaultChoice) {
|
|
845
|
-
const
|
|
846
|
-
if (!
|
|
880
|
+
const rawInputHandle = openChoiceInputStream();
|
|
881
|
+
if (!rawInputHandle) return askChoiceNumbered(rl, question, choices, defaultChoice);
|
|
882
|
+
const rawInput = rawInputHandle.stream;
|
|
847
883
|
|
|
848
884
|
printPromptHeader(question);
|
|
849
885
|
printRailLine(color(COLORS.dim, `Use \u2191 \u2193 to navigate, Enter to confirm, type a number to jump.`));
|
|
@@ -875,9 +911,9 @@ async function askChoice(rl, question, choices, defaultChoice) {
|
|
|
875
911
|
|
|
876
912
|
// Pause readline so it doesn't fight us for stdin while we're in raw mode.
|
|
877
913
|
rl.pause();
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
914
|
+
rawInput.setRawMode(true);
|
|
915
|
+
rawInput.resume();
|
|
916
|
+
rawInput.setEncoding("utf8");
|
|
881
917
|
|
|
882
918
|
// Initial render
|
|
883
919
|
process.stdout.write(render(index) + "\n");
|
|
@@ -891,9 +927,10 @@ async function askChoice(rl, question, choices, defaultChoice) {
|
|
|
891
927
|
process.stdout.write(render(newIndex) + "\n");
|
|
892
928
|
}
|
|
893
929
|
function cleanup() {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
930
|
+
rawInput.removeListener("data", onData);
|
|
931
|
+
rawInput.setRawMode(false);
|
|
932
|
+
rawInput.pause();
|
|
933
|
+
rawInputHandle.dispose();
|
|
897
934
|
}
|
|
898
935
|
function onData(chunk) {
|
|
899
936
|
const data = chunk.toString();
|
|
@@ -934,7 +971,7 @@ async function askChoice(rl, question, choices, defaultChoice) {
|
|
|
934
971
|
rerender(index);
|
|
935
972
|
}
|
|
936
973
|
}
|
|
937
|
-
|
|
974
|
+
rawInput.on("data", onData);
|
|
938
975
|
});
|
|
939
976
|
|
|
940
977
|
// Replace the rendered list with a single confirmation line.
|
|
@@ -1272,4 +1309,8 @@ async function main() {
|
|
|
1272
1309
|
}
|
|
1273
1310
|
}
|
|
1274
1311
|
|
|
1275
|
-
|
|
1312
|
+
if (process.env.DALOY_TEST_IMPORT !== "1") {
|
|
1313
|
+
await main();
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
export { choiceInputMode };
|
package/package.json
CHANGED
|
@@ -4,8 +4,12 @@ import { buildApp } from "./build-app.ts";
|
|
|
4
4
|
const app = buildApp();
|
|
5
5
|
const port = Number(process.env.PORT ?? 3000);
|
|
6
6
|
|
|
7
|
-
serve(app, {
|
|
8
|
-
|
|
7
|
+
const handle = serve(app, {
|
|
8
|
+
port,
|
|
9
|
+
// Bun closes idle keep-alive connections after this many seconds.
|
|
10
|
+
idleTimeout: 30,
|
|
11
|
+
});
|
|
12
|
+
console.log(`DaloyJS (Bun) listening on ${handle.url ?? `http://localhost:${port}`}`);
|
|
9
13
|
// daloy-minimal:strip-start docs
|
|
10
14
|
console.log(` Swagger UI: http://localhost:${port}/docs`);
|
|
11
15
|
console.log(` OpenAPI JSON: http://localhost:${port}/openapi.json`);
|
|
@@ -15,7 +15,7 @@ A [DaloyJS](https://daloyjs.dev) REST API deployed to **Cloudflare Workers**. **
|
|
|
15
15
|
|
|
16
16
|
## Project shape
|
|
17
17
|
|
|
18
|
-
- `src/index.ts` — Worker entrypoint. Builds the `App`, registers routes/middleware, and exports `default
|
|
18
|
+
- `src/index.ts` — Worker entrypoint. Builds the `App`, registers routes/middleware, and exports `default toFetchHandler(app)` from `@daloyjs/core/cloudflare`. Do NOT wrap the result in another `{ fetch }` object — `toFetchHandler` already returns the shape Workers expect.
|
|
19
19
|
- `wrangler.toml` — Worker configuration (name, compatibility date, bindings, routes).
|
|
20
20
|
- `tests/` — test files.
|
|
21
21
|
|
|
@@ -26,7 +26,7 @@ A [DaloyJS](https://daloyjs.dev) REST API deployed to **Cloudflare Workers**. **
|
|
|
26
26
|
3. Preserve literal types in responses: `status: 200 as const`, `z.literal(...)` on discriminator fields.
|
|
27
27
|
4. Throw typed errors (`NotFoundError`, `BadRequestError`, etc.) from `@daloyjs/core`.
|
|
28
28
|
5. Keep `requestId()`, `secureHeaders()`, and `rateLimit()` enabled. For high-traffic routes, attach Cloudflare's native rate-limit binding (the in-memory limiter resets per isolate).
|
|
29
|
-
6. Stay on the Workers runtime: only Web Standards APIs + Cloudflare bindings. No `node:` modules unless `nodejs_compat`
|
|
29
|
+
6. Stay on the Workers runtime: only Web Standards APIs + Cloudflare bindings. No `node:` modules unless you explicitly add `nodejs_compat` and require it.
|
|
30
30
|
7. Bindings flow through `env`. Read KV/D1/R2/secrets from the `env` argument; never read them via globals.
|
|
31
31
|
8. Long-running work belongs in `ctx.waitUntil(...)`, not blocking the response.
|
|
32
32
|
9. Every new route ships with a test that covers a happy path and at least one unhappy path.
|
|
@@ -23,9 +23,8 @@ Do **not** use this skill for tasks unrelated to the API itself.
|
|
|
23
23
|
DaloyJS is a **contract-first** framework. On Workers, additionally:
|
|
24
24
|
|
|
25
25
|
1. **Stay on the Workers runtime.** Only Web Standards APIs and
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
opts in.
|
|
26
|
+
Cloudflare-specific bindings. No `node:` modules unless the user
|
|
27
|
+
explicitly adds `nodejs_compat` to `wrangler.toml` and opts in.
|
|
29
28
|
2. **The route definition is the contract.** Method, path, request
|
|
30
29
|
schemas, and response schemas live in one place (`app.route({...})`).
|
|
31
30
|
3. **Zod schemas validate at every boundary.**
|
|
@@ -40,8 +39,8 @@ DaloyJS is a **contract-first** framework. On Workers, additionally:
|
|
|
40
39
|
## Project shape
|
|
41
40
|
|
|
42
41
|
- `src/index.ts` — the Worker entrypoint. Builds the `App`, registers
|
|
43
|
-
routes/middleware, and exports `default
|
|
44
|
-
|
|
42
|
+
routes/middleware, and exports `default toFetchHandler(app)` from
|
|
43
|
+
`@daloyjs/core/cloudflare`. Do not wrap the result in another `{ fetch }`.
|
|
45
44
|
- `wrangler.toml` — Worker config (name, compatibility date, bindings,
|
|
46
45
|
routes).
|
|
47
46
|
- `tests/` — test files using Workers-compatible test runners (e.g.
|
|
@@ -115,7 +114,7 @@ function buildApp(env: Env) {
|
|
|
115
114
|
|
|
116
115
|
export default {
|
|
117
116
|
fetch: (req: Request, env: Env, ctx: ExecutionContext) =>
|
|
118
|
-
toFetchHandler(buildApp(env))(req, env, ctx),
|
|
117
|
+
toFetchHandler<Env>(buildApp(env)).fetch(req, env, ctx),
|
|
119
118
|
};
|
|
120
119
|
```
|
|
121
120
|
|
|
@@ -8,8 +8,8 @@
|
|
|
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": "npm:@daloyjs/core@^0.
|
|
12
|
-
"@daloyjs/core/": "npm:@daloyjs/core@^0.
|
|
11
|
+
"@daloyjs/core": "npm:@daloyjs/core@^0.8.0",
|
|
12
|
+
"@daloyjs/core/": "npm:@daloyjs/core@^0.8.0/",
|
|
13
13
|
"zod": "npm:zod@^4.4.3"
|
|
14
14
|
},
|
|
15
15
|
"compilerOptions": {
|
|
@@ -4,10 +4,14 @@ import { buildApp } from "./build-app.ts";
|
|
|
4
4
|
const app = buildApp();
|
|
5
5
|
const port = Number(Deno.env.get("PORT") ?? 3000);
|
|
6
6
|
|
|
7
|
-
serve(app, {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
console.log(`
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
console.log(`
|
|
7
|
+
serve(app, {
|
|
8
|
+
port,
|
|
9
|
+
onListen: ({ hostname, port: actualPort }) => {
|
|
10
|
+
console.log(`DaloyJS (Deno) listening on http://${hostname}:${actualPort}`);
|
|
11
|
+
// daloy-minimal:strip-start docs
|
|
12
|
+
console.log(` Swagger UI: http://${hostname}:${actualPort}/docs`);
|
|
13
|
+
console.log(` OpenAPI JSON: http://${hostname}:${actualPort}/openapi.json`);
|
|
14
|
+
// daloy-minimal:strip-end docs
|
|
15
|
+
console.log(` Health: http://${hostname}:${actualPort}/healthz`);
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -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` — Edge entrypoint. Builds the `App`, registers routes/middleware, and exports `default
|
|
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`.
|
|
19
19
|
- `vercel.json` — Vercel build/runtime configuration.
|
|
20
20
|
- `tests/` — test files.
|
|
21
21
|
|
|
@@ -37,10 +37,21 @@ The API entry lives at `api/[...path].ts` and uses `@daloyjs/core/vercel`:
|
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
39
|
export const config = { runtime: "edge" };
|
|
40
|
-
export default
|
|
40
|
+
export default toWebHandler(app);
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
This starter defaults to Vercel's Edge runtime for compatibility with the
|
|
44
|
+
`vercel-edge` template name. Vercel now recommends Node.js for new projects;
|
|
45
|
+
for Node.js Functions, remove the `config` export and use the official default
|
|
46
|
+
`{ fetch }` shape instead:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { toFetchHandler } from "@daloyjs/core/vercel";
|
|
50
|
+
|
|
51
|
+
export default toFetchHandler(app);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
That catch-all API route lets DaloyJS own routing while Vercel handles the runtime.
|
|
44
55
|
|
|
45
56
|
## What's included
|
|
46
57
|
|
|
@@ -39,7 +39,7 @@ DaloyJS is a **contract-first** framework. On Vercel Edge, additionally:
|
|
|
39
39
|
## Project shape
|
|
40
40
|
|
|
41
41
|
- `api/[...path].ts` — the Edge entrypoint. Builds the `App`, registers
|
|
42
|
-
routes/middleware, and exports `default
|
|
42
|
+
routes/middleware, and exports `default toWebHandler(app)` plus
|
|
43
43
|
`export const config = { runtime: "edge" }`.
|
|
44
44
|
- `vercel.json` — Vercel build/runtime configuration.
|
|
45
45
|
- `tests/` — test files (`*.test.ts`).
|
|
@@ -179,8 +179,9 @@ Aim for **100% line and function coverage** on the routes you add.
|
|
|
179
179
|
handles routing. Do not split routes into multiple Vercel API files
|
|
180
180
|
unless the user explicitly asks (it disables shared middleware and a
|
|
181
181
|
unified OpenAPI).
|
|
182
|
-
- Use `
|
|
183
|
-
a `fetch(req)` adapter.
|
|
182
|
+
- Use `toWebHandler(app)` from `@daloyjs/core/vercel` for Edge — never
|
|
183
|
+
hand-roll a `fetch(req)` adapter. For Vercel's recommended Node.js
|
|
184
|
+
runtime, remove the Edge config and export `default toFetchHandler(app)`.
|
|
184
185
|
- Do not import `@daloyjs/core/node`, `@daloyjs/core/bun`, etc. — only
|
|
185
186
|
`@daloyjs/core` and `@daloyjs/core/vercel`.
|
|
186
187
|
- Avoid Node-only APIs (`Buffer`, `fs`, full `process` API). If a
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { App, NotFoundError, requestId, secureHeaders } from "@daloyjs/core";
|
|
3
|
-
import {
|
|
3
|
+
import { toWebHandler } from "@daloyjs/core/vercel";
|
|
4
4
|
// daloy-minimal:strip-start docs
|
|
5
5
|
import { generateOpenAPI } from "@daloyjs/core/openapi";
|
|
6
6
|
import { htmlResponse, swaggerUiHtml } from "@daloyjs/core/docs";
|
|
7
7
|
// daloy-minimal:strip-end docs
|
|
8
8
|
|
|
9
|
+
// This template defaults to Vercel's Edge runtime for compatibility with the
|
|
10
|
+
// existing `vercel-edge` starter. For Vercel's recommended Node.js runtime,
|
|
11
|
+
// remove this config and export `toFetchHandler(app)` from @daloyjs/core/vercel.
|
|
9
12
|
export const config = { runtime: "edge" };
|
|
10
13
|
|
|
11
14
|
const app = new App({
|
|
@@ -95,4 +98,4 @@ app.route({
|
|
|
95
98
|
});
|
|
96
99
|
// daloy-minimal:strip-end docs
|
|
97
100
|
|
|
98
|
-
export default
|
|
101
|
+
export default toWebHandler(app);
|