create-daloy 0.1.14 → 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 +41 -4
- package/bin/create-daloy.mjs +123 -21
- package/package.json +1 -1
- package/templates/bun-basic/README.md +50 -0
- package/templates/bun-basic/_gitignore +10 -0
- package/templates/bun-basic/_npmrc +13 -0
- package/templates/bun-basic/openapi-ts.config.ts +7 -0
- package/templates/bun-basic/package.json +27 -0
- package/templates/bun-basic/scripts/dump-openapi.ts +19 -0
- package/templates/bun-basic/src/build-app.ts +110 -0
- package/templates/bun-basic/src/index.ts +15 -0
- package/templates/bun-basic/tests/healthz.test.ts +13 -0
- package/templates/bun-basic/tsconfig.json +19 -0
- package/templates/cloudflare-worker/src/index.ts +2 -0
- package/templates/deno-basic/README.md +53 -0
- package/templates/deno-basic/_gitignore +7 -0
- package/templates/deno-basic/deno.json +22 -0
- package/templates/deno-basic/scripts/dump-openapi.ts +12 -0
- package/templates/deno-basic/src/build-app.ts +111 -0
- package/templates/deno-basic/src/main.ts +13 -0
- package/templates/deno-basic/tests/healthz_test.ts +11 -0
- package/templates/node-basic/README.md +6 -0
- package/templates/node-basic/src/build-app.ts +6 -0
- package/templates/node-basic/src/index.ts +2 -0
- package/templates/vercel-edge/README.md +4 -0
- package/templates/vercel-edge/api/[...path].ts +6 -0
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`,
|
|
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 `
|
|
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.
|
package/bin/create-daloy.mjs
CHANGED
|
@@ -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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
if (
|
|
450
|
-
|
|
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
|
@@ -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,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,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
|
+
}
|
|
@@ -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,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
|
|
|
@@ -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);
|