create-daloy 0.1.13 → 0.1.15

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
@@ -13,8 +13,9 @@ bun create daloy my-api
13
13
  The CLI is interactive when arguments are missing. It will ask you for:
14
14
 
15
15
  - A project directory name (defaults to `my-daloy-app`)
16
- - A template (`node-basic`, `vercel-edge`, or `cloudflare-worker`)
17
- - A package manager (`pnpm`, `npm`, `yarn`, or `bun`)
16
+ - A template (`node-basic`, `vercel-edge`, `cloudflare-worker`, `bun-basic`, or `deno-basic`)
17
+ - A package manager (`pnpm`, `npm`, `yarn`, or `bun`) — not asked for the
18
+ `deno-basic` runtime template
18
19
  - Whether to install dependencies
19
20
  - Whether to initialize a git repository
20
21
 
@@ -32,11 +33,12 @@ pnpm create daloy@latest my-api \
32
33
 
33
34
  | Flag | Description |
34
35
  | --- | --- |
35
- | `--template <name>` | `node-basic` (default), `vercel-edge`, or `cloudflare-worker`. |
36
- | `--package-manager <pm>` | `pnpm` (default), `npm`, `yarn`, or `bun`. |
36
+ | `--template <name>` | `node-basic` (default), `vercel-edge`, `cloudflare-worker`, `bun-basic`, or `deno-basic`. |
37
+ | `--package-manager <pm>` | `pnpm` (default), `npm`, `yarn`, or `bun`. Ignored for `deno-basic`. |
37
38
  | `--list-templates` | Print available templates with descriptions. |
38
39
  | `--install` / `--no-install` | Install dependencies after scaffolding. Defaults to interactive. |
39
40
  | `--git` / `--no-git` | Initialize a git repository. Defaults to interactive. |
41
+ | `--minimal` | Strip the bookstore demo route and the built-in `/docs` + `/openapi.json` routes so only the framework bootstrap and `/healthz` ship. |
40
42
  | `--force` | Overwrite an existing non-empty directory. |
41
43
  | `--yes` | Accept all defaults; never prompt. |
42
44
  | `--help` | Print usage and exit. |
@@ -73,6 +75,41 @@ A Vercel Edge API bootstrap using `@daloyjs/core/vercel` with:
73
75
  - `vercel dev` / `vercel deploy` scripts.
74
76
  - A health route and bookstore route mirroring the Node starter.
75
77
 
78
+ ### `bun-basic`
79
+
80
+ A [Bun](https://bun.sh) runtime starter using `@daloyjs/core/bun` with:
81
+
82
+ - `bun --hot src/index.ts` for instant reloads.
83
+ - `bun test` wired to in-process `app.request(...)` checks.
84
+ - The same secure defaults (`secureHeaders`, `requestId`, `rateLimit`).
85
+ - A health route and contract-first `/books/:id` route with Zod validation.
86
+ - Hey API codegen wired to `bun run gen:openapi` + `bun run gen:client`.
87
+
88
+ ### `deno-basic`
89
+
90
+ A [Deno](https://deno.com) runtime starter using `@daloyjs/core/deno` with:
91
+
92
+ - A `deno.json` with `deno task dev`, `test`, and `gen:openapi` tasks.
93
+ - `@daloyjs/core` and Zod loaded via `npm:` import-map specifiers.
94
+ - Minimum-permissions dev script (`--allow-net --allow-env --allow-read`).
95
+ - A health route and contract-first `/books/:id` route with Zod validation.
96
+ - The CLI skips Node-style installs for this template (no `package.json`).
97
+
98
+ ## Minimal scaffolds
99
+
100
+ Pass `--minimal` to drop the bookstore demo route and the built-in
101
+ `/docs` + `/openapi.json` Swagger UI routes from any template that supports
102
+ them. The scaffolded app is left with the framework bootstrap and a single
103
+ `/healthz` route — the smallest realistic starting point:
104
+
105
+ ```bash
106
+ pnpm create daloy@latest my-api --template node-basic --minimal --yes
107
+ ```
108
+
109
+ Sentinel comments (`// daloy-minimal:strip-start <tag>` /
110
+ `// daloy-minimal:strip-end <tag>`) survive a default scaffold so you can
111
+ re-run with `--minimal`, or delete the marked blocks by hand later.
112
+
76
113
  ## What the CLI guarantees
77
114
 
78
115
  - Zero runtime dependencies (uses only Node built-ins) for a clean supply-chain footprint.
@@ -30,6 +30,16 @@ const TEMPLATE_OPTIONS = [
30
30
  title: "Cloudflare Workers",
31
31
  description: "Worker entrypoint with wrangler dev/deploy scripts",
32
32
  },
33
+ {
34
+ value: "bun-basic",
35
+ title: "Bun API",
36
+ description: "Bun-native server with `bun --hot`, `bun test`, and Hey API codegen",
37
+ },
38
+ {
39
+ value: "deno-basic",
40
+ title: "Deno API",
41
+ description: "Deno-native server with `deno task dev`, `deno test`, and `npm:` imports",
42
+ },
33
43
  ];
34
44
 
35
45
  const PACKAGE_MANAGER_OPTIONS = [
@@ -48,6 +58,17 @@ const RENAME_ON_COPY = new Map([
48
58
  ["_env.example", ".env.example"],
49
59
  ]);
50
60
 
61
+ // Templates that target a runtime instead of an npm package manager.
62
+ // For these templates we skip Node-style `<pm> install`, do not patch
63
+ // `package.json`, and let the user drive the runtime directly
64
+ // (e.g. `deno task dev`).
65
+ const NO_PACKAGE_JSON_TEMPLATES = new Set(["deno-basic"]);
66
+
67
+ // Text-file extensions that the `--minimal` post-processor scans for
68
+ // `daloy-minimal:strip-start <tag>` / `daloy-minimal:strip-end <tag>`
69
+ // sentinels.
70
+ const MINIMAL_STRIP_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".mjs", ".cjs", ".md"]);
71
+
51
72
  const COLORS = process.stdout.isTTY
52
73
  ? {
53
74
  reset: "\x1b[0m",
@@ -77,6 +98,7 @@ Options:
77
98
  --list-templates Print available templates and exit.
78
99
  --install / --no-install Install dependencies after scaffolding.
79
100
  --git / --no-git Initialize a git repository.
101
+ --minimal Strip the bookstore + Swagger/OpenAPI demo routes.
80
102
  --force Overwrite an existing non-empty directory.
81
103
  --yes, -y Accept all defaults; never prompt.
82
104
  --help, -h Print this help.
@@ -113,6 +135,7 @@ function parseArgs(argv) {
113
135
  help: false,
114
136
  version: false,
115
137
  listTemplates: false,
138
+ minimal: false,
116
139
  };
117
140
  const args = [...argv];
118
141
  while (args.length) {
@@ -122,6 +145,7 @@ function parseArgs(argv) {
122
145
  else if (a === "--list-templates") out.listTemplates = true;
123
146
  else if (a === "--yes" || a === "-y") out.yes = true;
124
147
  else if (a === "--force") out.force = true;
148
+ else if (a === "--minimal") out.minimal = true;
125
149
  else if (a === "--install") out.install = true;
126
150
  else if (a === "--no-install") out.install = false;
127
151
  else if (a === "--git") out.git = true;
@@ -256,6 +280,58 @@ async function normalizePackageManagerFiles(dir, packageManager) {
256
280
  await rm(npmrcPath, { force: true });
257
281
  }
258
282
 
283
+ /**
284
+ * `--minimal` post-processor.
285
+ *
286
+ * Templates ship with optional sections fenced by line comments:
287
+ *
288
+ * // daloy-minimal:strip-start <tag>
289
+ * ...
290
+ * // daloy-minimal:strip-end <tag>
291
+ *
292
+ * Markdown files can use equivalent HTML comments:
293
+ *
294
+ * <!-- daloy-minimal:strip-start <tag> -->
295
+ * ...
296
+ * <!-- daloy-minimal:strip-end <tag> -->
297
+ *
298
+ * When the user passes `--minimal`, this walks the scaffolded source files
299
+ * and deletes those blocks (including the sentinel lines themselves) so
300
+ * the resulting project ships only the health route plus the bare
301
+ * framework bootstrap. We deliberately keep matching to text files with
302
+ * known source extensions so the stripper never touches binary assets.
303
+ */
304
+ async function stripMinimalSections(dir) {
305
+ const stripPatterns = [
306
+ /^[ \t]*\/\/[ \t]*daloy-minimal:strip-start\b[\s\S]*?^[ \t]*\/\/[ \t]*daloy-minimal:strip-end\b.*\n?/gm,
307
+ /^[ \t]*<!--[ \t]*daloy-minimal:strip-start\b[\s\S]*?^[ \t]*<!--[ \t]*daloy-minimal:strip-end\b.*?-->[ \t]*\n?/gm,
308
+ ];
309
+ let stripped = 0;
310
+ await walk(dir);
311
+ return stripped;
312
+
313
+ async function walk(current) {
314
+ const entries = await readdir(current, { withFileTypes: true });
315
+ for (const entry of entries) {
316
+ const full = path.join(current, entry.name);
317
+ if (entry.isDirectory()) {
318
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
319
+ await walk(full);
320
+ continue;
321
+ }
322
+ if (!entry.isFile()) continue;
323
+ if (!MINIMAL_STRIP_EXTENSIONS.has(path.extname(entry.name))) continue;
324
+ const raw = await readFile(full, "utf8");
325
+ if (!raw.includes("daloy-minimal:strip-start")) continue;
326
+ const next = stripPatterns.reduce((current, pattern) => current.replace(pattern, ""), raw);
327
+ if (next !== raw) {
328
+ await writeFile(full, next, "utf8");
329
+ stripped += 1;
330
+ }
331
+ }
332
+ }
333
+ }
334
+
259
335
  function run(cmd, args, cwd) {
260
336
  return new Promise((resolve) => {
261
337
  const proc = spawn(cmd, args, { cwd, stdio: "inherit", shell: process.platform === "win32" });
@@ -319,19 +395,30 @@ function logWarn(message) {
319
395
  console.warn(`${color(COLORS.yellow, " [warn]")} ${message}`);
320
396
  }
321
397
 
322
- function printSummary({ projectName, template, packageManager, installDeps }) {
398
+ function printSummary({ projectName, template, packageManager, installDeps, skipPackageManager }) {
323
399
  console.log(color(COLORS.green, "\nCreated a new DaloyJS project."));
324
400
  console.log(`\n ${color(COLORS.bold, "Project")} ${projectName}`);
325
401
  console.log(` ${color(COLORS.bold, "Template")} ${template}`);
326
- console.log(` ${color(COLORS.bold, "Manager")} ${packageManager}`);
327
- console.log(`\n ${color(COLORS.bold, "Next steps")}`);
328
- console.log(` cd ${projectName}`);
329
- if (!installDeps) console.log(` ${packageManager} install`);
330
- console.log(` ${packageManager} run dev`);
331
- console.log(`\n ${color(COLORS.bold, "Useful commands")}`);
332
- console.log(` ${packageManager} run typecheck`);
333
- console.log(` ${packageManager} test`);
334
- if (template === "node-basic") console.log(` ${packageManager} run gen`);
402
+ if (skipPackageManager) {
403
+ console.log(` ${color(COLORS.bold, "Runtime")} ${template === "deno-basic" ? "Deno" : "runtime"}`);
404
+ console.log(`\n ${color(COLORS.bold, "Next steps")}`);
405
+ console.log(` cd ${projectName}`);
406
+ console.log(` deno task dev`);
407
+ console.log(`\n ${color(COLORS.bold, "Useful commands")}`);
408
+ console.log(` deno task typecheck`);
409
+ console.log(` deno task test`);
410
+ console.log(` deno task gen:openapi`);
411
+ } else {
412
+ console.log(` ${color(COLORS.bold, "Manager")} ${packageManager}`);
413
+ console.log(`\n ${color(COLORS.bold, "Next steps")}`);
414
+ console.log(` cd ${projectName}`);
415
+ if (!installDeps) console.log(` ${packageManager} install`);
416
+ console.log(` ${packageManager} run dev`);
417
+ console.log(`\n ${color(COLORS.bold, "Useful commands")}`);
418
+ console.log(` ${packageManager} run typecheck`);
419
+ console.log(` ${packageManager} test`);
420
+ if (template === "node-basic" || template === "bun-basic") console.log(` ${packageManager} run gen`);
421
+ }
335
422
  console.log(`\n ${color(COLORS.dim, "Docs: https://daloyjs.dev/docs")}`);
336
423
  console.log(color(COLORS.dim, " Issues: https://github.com/daloyjs/daloy/issues\n"));
337
424
  }
@@ -413,10 +500,15 @@ async function main() {
413
500
  }
414
501
 
415
502
  let packageManager = opts.packageManager;
503
+ const skipPackageManager = NO_PACKAGE_JSON_TEMPLATES.has(template);
416
504
  if (!packageManager) {
417
- packageManager = rl
418
- ? await askChoice(rl, "Choose a package manager:", PACKAGE_MANAGER_OPTIONS, detectedPm)
419
- : detectedPm;
505
+ if (skipPackageManager) {
506
+ packageManager = "pnpm"; // ignored for runtime-only templates
507
+ } else {
508
+ packageManager = rl
509
+ ? await askChoice(rl, "Choose a package manager:", PACKAGE_MANAGER_OPTIONS, detectedPm)
510
+ : detectedPm;
511
+ }
420
512
  }
421
513
  if (!PACKAGE_MANAGERS.includes(packageManager)) {
422
514
  console.error(
@@ -427,7 +519,11 @@ async function main() {
427
519
 
428
520
  let installDeps = opts.install;
429
521
  if (installDeps === undefined) {
430
- installDeps = rl ? await askYesNo(rl, `Install dependencies with ${packageManager}?`, true) : false;
522
+ if (skipPackageManager) {
523
+ installDeps = false;
524
+ } else {
525
+ installDeps = rl ? await askYesNo(rl, `Install dependencies with ${packageManager}?`, true) : false;
526
+ }
431
527
  }
432
528
 
433
529
  let initGit = opts.git;
@@ -442,12 +538,18 @@ async function main() {
442
538
  await mkdir(targetDir, { recursive: true });
443
539
  await copyTemplate(templateDir, targetDir);
444
540
  logStep("Template copied", template);
445
- await patchPackageJson(targetDir, projectName, packageManager);
446
- logStep("Package metadata written", projectName);
447
- await patchReadme(targetDir, packageManager);
448
- await normalizePackageManagerFiles(targetDir, packageManager);
449
- if (packageManager !== "pnpm") {
450
- logStep("Package-manager config normalized", packageManager);
541
+ if (opts.minimal) {
542
+ const count = await stripMinimalSections(targetDir);
543
+ logStep("Minimal mode applied", `${count} file${count === 1 ? "" : "s"} trimmed`);
544
+ }
545
+ if (!skipPackageManager) {
546
+ await patchPackageJson(targetDir, projectName, packageManager);
547
+ logStep("Package metadata written", projectName);
548
+ await patchReadme(targetDir, packageManager);
549
+ await normalizePackageManagerFiles(targetDir, packageManager);
550
+ if (packageManager !== "pnpm") {
551
+ logStep("Package-manager config normalized", packageManager);
552
+ }
451
553
  }
452
554
 
453
555
  if (initGit) {
@@ -469,7 +571,7 @@ async function main() {
469
571
  }
470
572
  }
471
573
 
472
- printSummary({ projectName, template, packageManager, installDeps });
574
+ printSummary({ projectName, template, packageManager, installDeps, skipPackageManager });
473
575
  } catch (err) {
474
576
  rl?.close();
475
577
  console.error(color(COLORS.red, `\n Failed: ${(err && err.message) || err}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-daloy",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
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",
@@ -0,0 +1,50 @@
1
+ # my-daloy-bun-app
2
+
3
+ A [DaloyJS](https://daloyjs.dev) starter for the [Bun](https://bun.sh) runtime.
4
+
5
+ ## Develop
6
+
7
+ ```bash
8
+ bun install
9
+ bun run dev # http://localhost:3000
10
+ ```
11
+
12
+ Try it:
13
+
14
+ ```bash
15
+ curl http://localhost:3000/healthz
16
+ <!-- daloy-minimal:strip-start books -->
17
+ curl http://localhost:3000/books/1
18
+ <!-- daloy-minimal:strip-end books -->
19
+ ```
20
+
21
+ <!-- daloy-minimal:strip-start docs -->
22
+ ## API documentation
23
+
24
+ - Swagger UI: <http://localhost:3000/docs>
25
+ - OpenAPI 3.1 JSON: <http://localhost:3000/openapi.json>
26
+ <!-- daloy-minimal:strip-end docs -->
27
+
28
+ ## Generate OpenAPI + typed client
29
+
30
+ ```bash
31
+ bun run gen:openapi
32
+ bun run gen:client
33
+ ```
34
+
35
+ ## Test
36
+
37
+ ```bash
38
+ bun test
39
+ ```
40
+
41
+ ## What's included
42
+
43
+ - `@daloyjs/core` with `secureHeaders`, `requestId`, and `rateLimit` enabled.
44
+ <!-- daloy-minimal:strip-start books -->
45
+ - A health route and contract-first `/books/:id` route with Zod validation.
46
+ <!-- daloy-minimal:strip-end books -->
47
+ - Hot reload via `bun --hot`.
48
+ - Hey API codegen wired to `bun run gen:openapi` + `bun run gen:client`.
49
+
50
+ Read the docs at <https://daloyjs.dev/docs>.
@@ -0,0 +1,10 @@
1
+ node_modules/
2
+ dist/
3
+ coverage/
4
+ .DS_Store
5
+ *.log
6
+ .env
7
+ .env.*
8
+ !.env.example
9
+ generated/
10
+ bun.lockb
@@ -0,0 +1,13 @@
1
+ # DaloyJS supply-chain hardening defaults.
2
+ #
3
+ # These pnpm-specific defaults are kept here so users who switch this Bun
4
+ # starter back to pnpm inherit the same secure-by-default install posture.
5
+ # When you scaffold with `--package-manager bun` the CLI removes this file
6
+ # so `bun install` does not warn about unsupported keys.
7
+
8
+ auto-install-peers=true
9
+ strict-peer-dependencies=true
10
+ prefer-frozen-lockfile=true
11
+ verify-store-integrity=true
12
+ minimum-release-age=1440
13
+ ignore-scripts=true
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "@hey-api/openapi-ts";
2
+
3
+ export default defineConfig({
4
+ input: "./generated/openapi.json",
5
+ output: { path: "./generated/client" },
6
+ plugins: ["@hey-api/client-fetch", "@hey-api/typescript", "@hey-api/sdk"],
7
+ });
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "my-daloy-bun-app",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "engines": {
7
+ "bun": ">=1.1.0"
8
+ },
9
+ "scripts": {
10
+ "dev": "bun --hot src/index.ts",
11
+ "start": "bun src/index.ts",
12
+ "typecheck": "tsc --noEmit",
13
+ "test": "bun test",
14
+ "gen:openapi": "bun run scripts/dump-openapi.ts",
15
+ "gen:client": "openapi-ts",
16
+ "gen": "pnpm gen:openapi && pnpm gen:client"
17
+ },
18
+ "dependencies": {
19
+ "@daloyjs/core": "^0.4.0",
20
+ "zod": "^4.4.3"
21
+ },
22
+ "devDependencies": {
23
+ "@hey-api/openapi-ts": "^0.97.1",
24
+ "@types/bun": "^1.1.0",
25
+ "typescript": "^6.0.3"
26
+ }
27
+ }
@@ -0,0 +1,19 @@
1
+ import { writeFile, mkdir } from "node:fs/promises";
2
+ import { generateOpenAPI } from "@daloyjs/core/openapi";
3
+ import { buildApp } from "../src/build-app.ts";
4
+
5
+ async function main() {
6
+ const app = buildApp();
7
+ const doc = generateOpenAPI(app, {
8
+ info: { title: "My Daloy Bun API", version: "0.0.1" },
9
+ servers: [{ url: "http://localhost:3000" }],
10
+ });
11
+ await mkdir("generated", { recursive: true });
12
+ await writeFile("generated/openapi.json", JSON.stringify(doc, null, 2));
13
+ console.log("wrote generated/openapi.json");
14
+ }
15
+
16
+ main().catch((err) => {
17
+ console.error(err);
18
+ process.exit(1);
19
+ });
@@ -0,0 +1,110 @@
1
+ import { z } from "zod";
2
+ import {
3
+ App,
4
+ NotFoundError,
5
+ rateLimit,
6
+ requestId,
7
+ secureHeaders,
8
+ } from "@daloyjs/core";
9
+ // daloy-minimal:strip-start docs
10
+ import { generateOpenAPI } from "@daloyjs/core/openapi";
11
+ import { htmlResponse, swaggerUiHtml } from "@daloyjs/core/docs";
12
+ // daloy-minimal:strip-end docs
13
+
14
+ /**
15
+ * Build the application as a pure factory so the same `App` is reused by
16
+ * `bun --hot src/index.ts`, `scripts/dump-openapi.ts`, and `bun test`
17
+ * without importing the `serve()` adapter (and accidentally booting the
18
+ * HTTP listener as a side effect).
19
+ */
20
+ export function buildApp(): App {
21
+ const app = new App({
22
+ bodyLimitBytes: 1024 * 1024,
23
+ requestTimeoutMs: 5_000,
24
+ production: process.env.NODE_ENV === "production",
25
+ });
26
+
27
+ app.use(requestId());
28
+ app.use(secureHeaders());
29
+ app.use(rateLimit({ windowMs: 60_000, max: 120 }));
30
+
31
+ app.route({
32
+ method: "GET",
33
+ path: "/healthz",
34
+ operationId: "healthz",
35
+ tags: ["Ops"],
36
+ responses: {
37
+ 200: {
38
+ description: "Service is healthy",
39
+ body: z.object({ ok: z.literal(true), runtime: z.literal("bun") }),
40
+ },
41
+ },
42
+ handler: async () => ({
43
+ status: 200,
44
+ body: { ok: true as const, runtime: "bun" as const },
45
+ }),
46
+ });
47
+
48
+ // daloy-minimal:strip-start books
49
+ const Book = z.object({ id: z.string(), title: z.string() });
50
+ const books = new Map<string, z.infer<typeof Book>>([
51
+ ["1", { id: "1", title: "Noli Me Tangere" }],
52
+ ["2", { id: "2", title: "El Filibusterismo" }],
53
+ ]);
54
+
55
+ app.route({
56
+ method: "GET",
57
+ path: "/books/:id",
58
+ operationId: "getBookById",
59
+ tags: ["Books"],
60
+ request: { params: z.object({ id: z.string() }) },
61
+ responses: {
62
+ 200: { description: "Found", body: Book },
63
+ 404: { description: "Not found" },
64
+ },
65
+ handler: async ({ params }) => {
66
+ const book = books.get(params.id);
67
+ if (!book) throw new NotFoundError(`Book ${params.id} not found`);
68
+ return { status: 200, body: book };
69
+ },
70
+ });
71
+ // daloy-minimal:strip-end books
72
+
73
+ // daloy-minimal:strip-start docs
74
+ app.route({
75
+ method: "GET",
76
+ path: "/openapi.json",
77
+ operationId: "getOpenAPI",
78
+ tags: ["Docs"],
79
+ responses: { 200: { description: "OpenAPI 3.1 document" } },
80
+ handler: async () => ({
81
+ status: 200 as const,
82
+ body: generateOpenAPI(app, {
83
+ info: { title: "My Daloy Bun API", version: "0.0.1" },
84
+ servers: [{ url: `http://localhost:${process.env.PORT ?? 3000}` }],
85
+ }),
86
+ }),
87
+ });
88
+
89
+ app.route({
90
+ method: "GET",
91
+ path: "/docs",
92
+ operationId: "docs",
93
+ tags: ["Docs"],
94
+ responses: { 200: { description: "API reference UI" } },
95
+ handler: async () => {
96
+ const html = swaggerUiHtml({ specUrl: "/openapi.json", title: "My Daloy Bun API" });
97
+ const res = htmlResponse(html);
98
+ return {
99
+ status: 200 as const,
100
+ body: html,
101
+ headers: Object.fromEntries(res.headers),
102
+ };
103
+ },
104
+ });
105
+ // daloy-minimal:strip-end docs
106
+
107
+ return app;
108
+ }
109
+
110
+ export default buildApp;
@@ -0,0 +1,15 @@
1
+ import { serve } from "@daloyjs/core/bun";
2
+ import { buildApp } from "./build-app.ts";
3
+
4
+ const app = buildApp();
5
+ const port = Number(process.env.PORT ?? 3000);
6
+
7
+ serve(app, { port });
8
+ console.log(`DaloyJS (Bun) 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`);
14
+
15
+ export default app;
@@ -0,0 +1,13 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { buildApp } from "../src/build-app.ts";
3
+
4
+ describe("buildApp", () => {
5
+ test("GET /healthz returns 200", async () => {
6
+ const app = buildApp();
7
+ const res = await app.request("/healthz");
8
+ expect(res.status).toBe(200);
9
+ const body = (await res.json()) as { ok: boolean; runtime: string };
10
+ expect(body.ok).toBe(true);
11
+ expect(body.runtime).toBe("bun");
12
+ });
13
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2022"],
7
+ "types": ["bun-types"],
8
+ "strict": true,
9
+ "noUncheckedIndexedAccess": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "noEmit": true,
16
+ "allowImportingTsExtensions": true
17
+ },
18
+ "include": ["src/**/*", "tests/**/*", "scripts/**/*"]
19
+ }
@@ -10,7 +10,7 @@
10
10
  "test": "node --import tsx --test tests/**/*.test.ts"
11
11
  },
12
12
  "dependencies": {
13
- "@daloyjs/core": "^0.3.3",
13
+ "@daloyjs/core": "^0.4.0",
14
14
  "zod": "^4.4.3"
15
15
  },
16
16
  "devDependencies": {
@@ -11,6 +11,7 @@ const app = new App({
11
11
  app.use(requestId());
12
12
  app.use(secureHeaders());
13
13
 
14
+ // daloy-minimal:strip-start books
14
15
  const Book = z.object({ id: z.string(), title: z.string() });
15
16
  const books = new Map<string, z.infer<typeof Book>>([
16
17
  ["1", { id: "1", title: "Noli Me Tangere" }],
@@ -32,5 +33,6 @@ app.route({
32
33
  return { status: 200, body: book };
33
34
  },
34
35
  });
36
+ // daloy-minimal:strip-end books
35
37
 
36
38
  export default { fetch: toFetchHandler(app) };
@@ -0,0 +1,53 @@
1
+ # my-daloy-deno-app
2
+
3
+ A [DaloyJS](https://daloyjs.dev) starter for the [Deno](https://deno.com) runtime.
4
+
5
+ ## Develop
6
+
7
+ ```bash
8
+ deno task dev # http://localhost:3000
9
+ ```
10
+
11
+ Try it:
12
+
13
+ ```bash
14
+ curl http://localhost:3000/healthz
15
+ <!-- daloy-minimal:strip-start books -->
16
+ curl http://localhost:3000/books/1
17
+ <!-- daloy-minimal:strip-end books -->
18
+ ```
19
+
20
+ <!-- daloy-minimal:strip-start docs -->
21
+ ## API documentation
22
+
23
+ - Swagger UI: <http://localhost:3000/docs>
24
+ - OpenAPI 3.1 JSON: <http://localhost:3000/openapi.json>
25
+ <!-- daloy-minimal:strip-end docs -->
26
+
27
+ ## Generate the OpenAPI spec
28
+
29
+ ```bash
30
+ deno task gen:openapi
31
+ # → generated/openapi.json
32
+ ```
33
+
34
+ To produce a typed SDK from that spec, run [@hey-api/openapi-ts](https://heyapi.dev)
35
+ through `npx` or your favorite Node package manager — it does not yet ship a
36
+ first-class Deno entry point.
37
+
38
+ ## Test
39
+
40
+ ```bash
41
+ deno task test
42
+ ```
43
+
44
+ ## What's included
45
+
46
+ - `@daloyjs/core` (loaded via `npm:` specifiers in `deno.json`).
47
+ - `secureHeaders`, `requestId`, and `rateLimit` enabled by default.
48
+ <!-- daloy-minimal:strip-start books -->
49
+ - A health route and contract-first `/books/:id` route with Zod validation.
50
+ <!-- daloy-minimal:strip-end books -->
51
+ - Minimal permissions: `--allow-net --allow-env --allow-read` for `dev`.
52
+
53
+ Read the docs at <https://daloyjs.dev/docs>.
@@ -0,0 +1,7 @@
1
+ node_modules/
2
+ .DS_Store
3
+ *.log
4
+ .env
5
+ .env.*
6
+ !.env.example
7
+ generated/
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "my-daloy-deno-app",
3
+ "tasks": {
4
+ "dev": "deno run --allow-net --allow-env --allow-read --watch src/main.ts",
5
+ "start": "deno run --allow-net --allow-env --allow-read src/main.ts",
6
+ "typecheck": "deno check src/main.ts",
7
+ "test": "deno test --allow-net --allow-env tests/",
8
+ "gen:openapi": "deno run --allow-net --allow-env --allow-read --allow-write scripts/dump-openapi.ts"
9
+ },
10
+ "imports": {
11
+ "@daloyjs/core": "npm:@daloyjs/core@^0.4.0",
12
+ "@daloyjs/core/": "npm:@daloyjs/core@^0.4.0/",
13
+ "zod": "npm:zod@^4.4.3"
14
+ },
15
+ "compilerOptions": {
16
+ "strict": true,
17
+ "lib": ["deno.window", "deno.unstable"]
18
+ },
19
+ "fmt": {
20
+ "lineWidth": 100
21
+ }
22
+ }
@@ -0,0 +1,12 @@
1
+ import { generateOpenAPI } from "@daloyjs/core/openapi";
2
+ import { buildApp } from "../src/build-app.ts";
3
+
4
+ const app = buildApp();
5
+ const doc = generateOpenAPI(app, {
6
+ info: { title: "My Daloy Deno API", version: "0.0.1" },
7
+ servers: [{ url: "http://localhost:3000" }],
8
+ });
9
+
10
+ await Deno.mkdir("generated", { recursive: true });
11
+ await Deno.writeTextFile("generated/openapi.json", JSON.stringify(doc, null, 2));
12
+ console.log("wrote generated/openapi.json");
@@ -0,0 +1,111 @@
1
+ import { z } from "zod";
2
+ import {
3
+ App,
4
+ NotFoundError,
5
+ rateLimit,
6
+ requestId,
7
+ secureHeaders,
8
+ } from "@daloyjs/core";
9
+ // daloy-minimal:strip-start docs
10
+ import { generateOpenAPI } from "@daloyjs/core/openapi";
11
+ import { htmlResponse, swaggerUiHtml } from "@daloyjs/core/docs";
12
+ // daloy-minimal:strip-end docs
13
+
14
+ /**
15
+ * Build the application as a pure factory.
16
+ *
17
+ * Keeping this separate from `serve(app, ...)` lets `deno test` and the
18
+ * OpenAPI dump script reuse the same `App` without booting an HTTP listener
19
+ * as a side effect.
20
+ */
21
+ export function buildApp(): App {
22
+ const app = new App({
23
+ bodyLimitBytes: 1024 * 1024,
24
+ requestTimeoutMs: 5_000,
25
+ production: Deno.env.get("DENO_ENV") === "production",
26
+ });
27
+
28
+ app.use(requestId());
29
+ app.use(secureHeaders());
30
+ app.use(rateLimit({ windowMs: 60_000, max: 120 }));
31
+
32
+ app.route({
33
+ method: "GET",
34
+ path: "/healthz",
35
+ operationId: "healthz",
36
+ tags: ["Ops"],
37
+ responses: {
38
+ 200: {
39
+ description: "Service is healthy",
40
+ body: z.object({ ok: z.literal(true), runtime: z.literal("deno") }),
41
+ },
42
+ },
43
+ handler: async () => ({
44
+ status: 200,
45
+ body: { ok: true as const, runtime: "deno" as const },
46
+ }),
47
+ });
48
+
49
+ // daloy-minimal:strip-start books
50
+ const Book = z.object({ id: z.string(), title: z.string() });
51
+ const books = new Map<string, z.infer<typeof Book>>([
52
+ ["1", { id: "1", title: "Noli Me Tangere" }],
53
+ ["2", { id: "2", title: "El Filibusterismo" }],
54
+ ]);
55
+
56
+ app.route({
57
+ method: "GET",
58
+ path: "/books/:id",
59
+ operationId: "getBookById",
60
+ tags: ["Books"],
61
+ request: { params: z.object({ id: z.string() }) },
62
+ responses: {
63
+ 200: { description: "Found", body: Book },
64
+ 404: { description: "Not found" },
65
+ },
66
+ handler: async ({ params }) => {
67
+ const book = books.get(params.id);
68
+ if (!book) throw new NotFoundError(`Book ${params.id} not found`);
69
+ return { status: 200, body: book };
70
+ },
71
+ });
72
+ // daloy-minimal:strip-end books
73
+
74
+ // daloy-minimal:strip-start docs
75
+ app.route({
76
+ method: "GET",
77
+ path: "/openapi.json",
78
+ operationId: "getOpenAPI",
79
+ tags: ["Docs"],
80
+ responses: { 200: { description: "OpenAPI 3.1 document" } },
81
+ handler: async () => ({
82
+ status: 200 as const,
83
+ body: generateOpenAPI(app, {
84
+ info: { title: "My Daloy Deno API", version: "0.0.1" },
85
+ servers: [{ url: `http://localhost:${Deno.env.get("PORT") ?? "3000"}` }],
86
+ }),
87
+ }),
88
+ });
89
+
90
+ app.route({
91
+ method: "GET",
92
+ path: "/docs",
93
+ operationId: "docs",
94
+ tags: ["Docs"],
95
+ responses: { 200: { description: "API reference UI" } },
96
+ handler: async () => {
97
+ const html = swaggerUiHtml({ specUrl: "/openapi.json", title: "My Daloy Deno API" });
98
+ const res = htmlResponse(html);
99
+ return {
100
+ status: 200 as const,
101
+ body: html,
102
+ headers: Object.fromEntries(res.headers),
103
+ };
104
+ },
105
+ });
106
+ // daloy-minimal:strip-end docs
107
+
108
+ return app;
109
+ }
110
+
111
+ export default buildApp;
@@ -0,0 +1,13 @@
1
+ import { serve } from "@daloyjs/core/deno";
2
+ import { buildApp } from "./build-app.ts";
3
+
4
+ const app = buildApp();
5
+ const port = Number(Deno.env.get("PORT") ?? 3000);
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`);
@@ -0,0 +1,11 @@
1
+ import { assertEquals } from "jsr:@std/assert@^1.0.0";
2
+ import { buildApp } from "../src/build-app.ts";
3
+
4
+ Deno.test("GET /healthz returns 200", async () => {
5
+ const app = buildApp();
6
+ const res = await app.request("/healthz");
7
+ assertEquals(res.status, 200);
8
+ const body = (await res.json()) as { ok: boolean; runtime: string };
9
+ assertEquals(body.ok, true);
10
+ assertEquals(body.runtime, "deno");
11
+ });
@@ -13,15 +13,19 @@ Try it:
13
13
 
14
14
  ```bash
15
15
  curl http://localhost:3000/healthz
16
+ <!-- daloy-minimal:strip-start books -->
16
17
  curl http://localhost:3000/books/1
18
+ <!-- daloy-minimal:strip-end books -->
17
19
  ```
18
20
 
21
+ <!-- daloy-minimal:strip-start docs -->
19
22
  ## API documentation
20
23
 
21
24
  - Swagger UI: <http://localhost:3000/docs>
22
25
  - OpenAPI 3.1 JSON: <http://localhost:3000/openapi.json>
23
26
 
24
27
  The spec is generated live from your routes, so it stays in sync with what is actually deployed.
28
+ <!-- daloy-minimal:strip-end docs -->
25
29
 
26
30
  ## Generate OpenAPI + typed client
27
31
 
@@ -41,7 +45,9 @@ node dist/index.js
41
45
  ## What's included
42
46
 
43
47
  - `@daloyjs/core` with `secureHeaders`, `requestId`, and `rateLimit` enabled.
48
+ <!-- daloy-minimal:strip-start books -->
44
49
  - A health route and a contract-first `/books/:id` route with Zod validation.
50
+ <!-- daloy-minimal:strip-end books -->
45
51
  - Hardened `.npmrc` for safer installs.
46
52
  - Hey API codegen wired to `pnpm gen`.
47
53
 
@@ -18,7 +18,7 @@
18
18
  "audit": "pnpm audit --prod"
19
19
  },
20
20
  "dependencies": {
21
- "@daloyjs/core": "^0.3.3",
21
+ "@daloyjs/core": "^0.4.0",
22
22
  "zod": "^4.4.3"
23
23
  },
24
24
  "devDependencies": {
@@ -6,8 +6,10 @@ import {
6
6
  requestId,
7
7
  secureHeaders,
8
8
  } from "@daloyjs/core";
9
+ // daloy-minimal:strip-start docs
9
10
  import { generateOpenAPI } from "@daloyjs/core/openapi";
10
11
  import { htmlResponse, swaggerUiHtml } from "@daloyjs/core/docs";
12
+ // daloy-minimal:strip-end docs
11
13
 
12
14
  /**
13
15
  * Build the application as a pure factory.
@@ -45,6 +47,7 @@ export function buildApp(): App {
45
47
  }),
46
48
  });
47
49
 
50
+ // daloy-minimal:strip-start books
48
51
  const Book = z.object({ id: z.string(), title: z.string() });
49
52
  const books = new Map<string, z.infer<typeof Book>>([
50
53
  ["1", { id: "1", title: "Noli Me Tangere" }],
@@ -67,7 +70,9 @@ export function buildApp(): App {
67
70
  return { status: 200, body: book };
68
71
  },
69
72
  });
73
+ // daloy-minimal:strip-end books
70
74
 
75
+ // daloy-minimal:strip-start docs
71
76
  // --- API documentation ---------------------------------------------------
72
77
  // `/openapi.json` returns the live OpenAPI 3.1 spec generated from the
73
78
  // routes defined above. `/docs` serves a Swagger UI page that loads it.
@@ -103,6 +108,7 @@ export function buildApp(): App {
103
108
  };
104
109
  },
105
110
  });
111
+ // daloy-minimal:strip-end docs
106
112
 
107
113
  return app;
108
114
  }
@@ -6,8 +6,10 @@ const port = Number(process.env.PORT ?? 3000);
6
6
 
7
7
  serve(app, { port });
8
8
  console.log(`DaloyJS listening on http://localhost:${port}`);
9
+ // daloy-minimal:strip-start docs
9
10
  console.log(` Swagger UI: http://localhost:${port}/docs`);
10
11
  console.log(` OpenAPI JSON: http://localhost:${port}/openapi.json`);
12
+ // daloy-minimal:strip-end docs
11
13
  console.log(` Health: http://localhost:${port}/healthz`);
12
14
 
13
15
  export default app;
@@ -13,15 +13,19 @@ Try it:
13
13
 
14
14
  ```bash
15
15
  curl http://localhost:3000/healthz
16
+ <!-- daloy-minimal:strip-start books -->
16
17
  curl http://localhost:3000/books/1
18
+ <!-- daloy-minimal:strip-end books -->
17
19
  ```
18
20
 
21
+ <!-- daloy-minimal:strip-start docs -->
19
22
  ## API documentation
20
23
 
21
24
  - Swagger UI: <http://localhost:3000/docs>
22
25
  - OpenAPI 3.1 JSON: <http://localhost:3000/openapi.json>
23
26
 
24
27
  After deploying, the same routes serve `/docs` and `/openapi.json` from your Vercel Edge URL.
28
+ <!-- daloy-minimal:strip-end docs -->
25
29
 
26
30
  ## Deploy
27
31
 
@@ -1,8 +1,10 @@
1
1
  import { z } from "zod";
2
2
  import { App, NotFoundError, requestId, secureHeaders } from "@daloyjs/core";
3
3
  import { toEdgeHandler } from "@daloyjs/core/vercel";
4
+ // daloy-minimal:strip-start docs
4
5
  import { generateOpenAPI } from "@daloyjs/core/openapi";
5
6
  import { htmlResponse, swaggerUiHtml } from "@daloyjs/core/docs";
7
+ // daloy-minimal:strip-end docs
6
8
 
7
9
  export const config = { runtime: "edge" };
8
10
 
@@ -32,6 +34,7 @@ app.route({
32
34
  }),
33
35
  });
34
36
 
37
+ // daloy-minimal:strip-start books
35
38
  const Book = z.object({ id: z.string(), title: z.string() });
36
39
  const books = new Map<string, z.infer<typeof Book>>([
37
40
  ["1", { id: "1", title: "Noli Me Tangere" }],
@@ -53,7 +56,9 @@ app.route({
53
56
  return { status: 200, body: book };
54
57
  },
55
58
  });
59
+ // daloy-minimal:strip-end books
56
60
 
61
+ // daloy-minimal:strip-start docs
57
62
  // --- API documentation -----------------------------------------------------
58
63
  // `/openapi.json` returns the OpenAPI 3.1 spec generated from the routes above.
59
64
  // `/docs` serves a Swagger UI page that loads that spec.
@@ -88,5 +93,6 @@ app.route({
88
93
  };
89
94
  },
90
95
  });
96
+ // daloy-minimal:strip-end docs
91
97
 
92
98
  export default toEdgeHandler(app);
@@ -10,7 +10,7 @@
10
10
  "test": "node --import tsx --test tests/**/*.test.ts"
11
11
  },
12
12
  "dependencies": {
13
- "@daloyjs/core": "^0.3.3",
13
+ "@daloyjs/core": "^0.4.0",
14
14
  "zod": "^4.4.3"
15
15
  },
16
16
  "devDependencies": {