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 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.
@@ -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 API route using @daloyjs/core/vercel",
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, with a numbered fallback used by the readline-driven tests.
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 canRawMode = process.stdin.isTTY && typeof process.stdin.setRawMode === "function";
846
- if (!canRawMode) return askChoiceNumbered(rl, question, choices, defaultChoice);
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
- process.stdin.setRawMode(true);
879
- process.stdin.resume();
880
- process.stdin.setEncoding("utf8");
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
- process.stdin.removeListener("data", onData);
895
- process.stdin.setRawMode(false);
896
- process.stdin.pause();
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
- process.stdin.on("data", onData);
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
- await main();
1312
+ if (process.env.DALOY_TEST_IMPORT !== "1") {
1313
+ await main();
1314
+ }
1315
+
1316
+ export { choiceInputMode };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-daloy",
3
- "version": "0.1.23",
3
+ "version": "0.2.0",
4
4
  "description": "Scaffold a new DaloyJS project. Run with `pnpm create daloy`, `npm create daloy@latest`, `yarn create daloy`, or `bun create daloy`.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -16,7 +16,7 @@
16
16
  "gen": "pnpm gen:openapi && pnpm gen:client"
17
17
  },
18
18
  "dependencies": {
19
- "@daloyjs/core": "^0.7.5",
19
+ "@daloyjs/core": "^0.8.0",
20
20
  "zod": "^4.4.3"
21
21
  },
22
22
  "devDependencies": {
@@ -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, { port });
8
- console.log(`DaloyJS (Bun) listening on http://localhost:${port}`);
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 { fetch: toFetchHandler(app) }` from `@daloyjs/core/cloudflare`.
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` is enabled and required.
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
- Cloudflare-specific bindings. No `node:` modules unless
27
- `nodejs_compat` is enabled in `wrangler.toml` and the user explicitly
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 { fetch: toFetchHandler(app) }`
44
- from `@daloyjs/core/cloudflare`.
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
 
@@ -10,7 +10,7 @@
10
10
  "test": "node --import tsx/esm --test tests/**/*.test.ts"
11
11
  },
12
12
  "dependencies": {
13
- "@daloyjs/core": "^0.7.5",
13
+ "@daloyjs/core": "^0.8.0",
14
14
  "zod": "^4.4.3"
15
15
  },
16
16
  "devDependencies": {
@@ -35,4 +35,4 @@ app.route({
35
35
  });
36
36
  // daloy-minimal:strip-end books
37
37
 
38
- export default { fetch: toFetchHandler(app) };
38
+ export default toFetchHandler(app);
@@ -1,3 +1,3 @@
1
1
  name = "my-daloy-worker"
2
2
  main = "src/index.ts"
3
- compatibility_date = "2025-01-01"
3
+ compatibility_date = "2026-05-01"
@@ -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.7.5",
12
- "@daloyjs/core/": "npm:@daloyjs/core@^0.7.5/",
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, { port });
8
- console.log(`DaloyJS (Deno) listening on http://localhost:${port}`);
9
- // daloy-minimal:strip-start docs
10
- console.log(` Swagger UI: http://localhost:${port}/docs`);
11
- console.log(` OpenAPI JSON: http://localhost:${port}/openapi.json`);
12
- // daloy-minimal:strip-end docs
13
- console.log(` Health: http://localhost:${port}/healthz`);
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
+ });
@@ -18,7 +18,7 @@
18
18
  "audit": "pnpm audit --prod"
19
19
  },
20
20
  "dependencies": {
21
- "@daloyjs/core": "^0.7.5",
21
+ "@daloyjs/core": "^0.8.0",
22
22
  "zod": "^4.4.3"
23
23
  },
24
24
  "devDependencies": {
@@ -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 toEdgeHandler(app)` plus `export const config = { runtime: "edge" }`. **Keep it a catch-all** so DaloyJS owns routing.
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 toEdgeHandler(app);
40
+ export default toWebHandler(app);
41
41
  ```
42
42
 
43
- That catch-all API route lets DaloyJS own routing while Vercel handles the Edge runtime.
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 toEdgeHandler(app)` plus
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 `toEdgeHandler(app)` from `@daloyjs/core/vercel` — never hand-roll
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 { toEdgeHandler } from "@daloyjs/core/vercel";
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 toEdgeHandler(app);
101
+ export default toWebHandler(app);
@@ -10,7 +10,7 @@
10
10
  "test": "node --import tsx/esm --test tests/**/*.test.ts"
11
11
  },
12
12
  "dependencies": {
13
- "@daloyjs/core": "^0.7.5",
13
+ "@daloyjs/core": "^0.8.0",
14
14
  "zod": "^4.4.3"
15
15
  },
16
16
  "devDependencies": {