@daloyjs/core 0.1.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.
Files changed (74) hide show
  1. package/README.md +296 -0
  2. package/dist/adapters/bun.d.ts +16 -0
  3. package/dist/adapters/bun.d.ts.map +1 -0
  4. package/dist/adapters/bun.js +25 -0
  5. package/dist/adapters/bun.js.map +1 -0
  6. package/dist/adapters/cloudflare.d.ts +11 -0
  7. package/dist/adapters/cloudflare.d.ts.map +1 -0
  8. package/dist/adapters/cloudflare.js +6 -0
  9. package/dist/adapters/cloudflare.js.map +1 -0
  10. package/dist/adapters/deno.d.ts +12 -0
  11. package/dist/adapters/deno.d.ts.map +1 -0
  12. package/dist/adapters/deno.js +13 -0
  13. package/dist/adapters/deno.js.map +1 -0
  14. package/dist/adapters/node.d.ts +25 -0
  15. package/dist/adapters/node.d.ts.map +1 -0
  16. package/dist/adapters/node.js +90 -0
  17. package/dist/adapters/node.js.map +1 -0
  18. package/dist/adapters/vercel.d.ts +13 -0
  19. package/dist/adapters/vercel.d.ts.map +1 -0
  20. package/dist/adapters/vercel.js +4 -0
  21. package/dist/adapters/vercel.js.map +1 -0
  22. package/dist/app.d.ts +104 -0
  23. package/dist/app.d.ts.map +1 -0
  24. package/dist/app.js +487 -0
  25. package/dist/app.js.map +1 -0
  26. package/dist/client.d.ts +40 -0
  27. package/dist/client.d.ts.map +1 -0
  28. package/dist/client.js +61 -0
  29. package/dist/client.js.map +1 -0
  30. package/dist/contract.d.ts +31 -0
  31. package/dist/contract.d.ts.map +1 -0
  32. package/dist/contract.js +76 -0
  33. package/dist/contract.js.map +1 -0
  34. package/dist/docs.d.ts +16 -0
  35. package/dist/docs.d.ts.map +1 -0
  36. package/dist/docs.js +51 -0
  37. package/dist/docs.js.map +1 -0
  38. package/dist/errors.d.ts +75 -0
  39. package/dist/errors.d.ts.map +1 -0
  40. package/dist/errors.js +155 -0
  41. package/dist/errors.js.map +1 -0
  42. package/dist/index.d.ts +13 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +7 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/logger.d.ts +26 -0
  47. package/dist/logger.d.ts.map +1 -0
  48. package/dist/logger.js +74 -0
  49. package/dist/logger.js.map +1 -0
  50. package/dist/middleware.d.ts +62 -0
  51. package/dist/middleware.d.ts.map +1 -0
  52. package/dist/middleware.js +201 -0
  53. package/dist/middleware.js.map +1 -0
  54. package/dist/openapi.d.ts +28 -0
  55. package/dist/openapi.d.ts.map +1 -0
  56. package/dist/openapi.js +178 -0
  57. package/dist/openapi.js.map +1 -0
  58. package/dist/router.d.ts +30 -0
  59. package/dist/router.d.ts.map +1 -0
  60. package/dist/router.js +135 -0
  61. package/dist/router.js.map +1 -0
  62. package/dist/schema.d.ts +45 -0
  63. package/dist/schema.d.ts.map +1 -0
  64. package/dist/schema.js +13 -0
  65. package/dist/schema.js.map +1 -0
  66. package/dist/security.d.ts +21 -0
  67. package/dist/security.d.ts.map +1 -0
  68. package/dist/security.js +107 -0
  69. package/dist/security.js.map +1 -0
  70. package/dist/types.d.ts +98 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +2 -0
  73. package/dist/types.js.map +1 -0
  74. package/package.json +108 -0
package/README.md ADDED
@@ -0,0 +1,296 @@
1
+ # DaloyJS
2
+
3
+ > A **runtime-portable TypeScript web framework** with built-in **contract-first routing**, **validation**, **OpenAPI (Hey API)**, **typed client generation**, **large-scale maintainability**, and **highly secured by default (pnpm)**.
4
+
5
+ 📚 **Documentation site:** [`./daloyjs.dev`](./daloyjs.dev) — a Next.js 16 + shadcn/ui + Tailwind v4 site with the landing page, getting-started guide, tutorials, security docs, and full API reference. Run it with:
6
+
7
+ ```zsh
8
+ cd daloyjs.dev
9
+ pnpm install
10
+ pnpm dev # http://localhost:3000
11
+ pnpm build # static prerender of every docs route
12
+ ```
13
+
14
+ ---
15
+
16
+ DaloyJS exists to be the framework you'd build if you took the best ideas from each modern stack:
17
+
18
+ | You want | Today's best-of | What DaloyJS gives you |
19
+ |---|---|---|
20
+ | Best **OpenAPI ergonomics** | [FastAPI](https://fastapi.tiangolo.com) | First-class OpenAPI 3.1 generation from a single route definition. |
21
+ | Best **Vercel / serverless / edge fit** | [Hono](https://hono.dev/docs/) | Web-standard `Request → Response` core, multi-runtime adapters. |
22
+ | Mature **Swagger / docs / ops** in Node | [Fastify](https://fastify.dev/docs/latest/Reference/) | Encapsulated plugins, structured logger, graceful shutdown, request ids, hooks. |
23
+ | Modern **TS-first DX**, Bun acceptable | [Elysia](https://elysiajs.com/at-glance.html) | End-to-end typed handlers, typed context, typed client. |
24
+ | Best-in-class **typed client codegen** for any consumer | [Hey API](https://heyapi.dev/openapi-ts/get-started) | One command (`pnpm gen`) emits a fully-typed fetch SDK from your spec. |
25
+ | **Better supply-chain security** than npm | [pnpm](https://pnpm.io/motivation) | Strict, content-addressable installs; reproducible lockfile; per-project `.npmrc` hardening. |
26
+
27
+ ```
28
+ 56/56 tests passing · clean strict TypeScript 6 · runs on Node, Bun, Deno, Cloudflare, Vercel
29
+ ~12.3M static-route ops/sec · ~1.5M dynamic-route ops/sec on M-class CPU
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Why a new framework?
35
+
36
+ Each existing stack is excellent at one thing and forces tradeoffs everywhere else:
37
+
38
+ - Hono is small and portable but OpenAPI is a plugin afterthought.
39
+ - Elysia has gorgeous typing but pulls you toward Bun.
40
+ - Fastify has the best Node ops story but is Node-only and validation/types/docs are not unified.
41
+ - FastAPI has the best docs ergonomics — but it's Python.
42
+ - Hey API gives you the best typed client — but you still need a server that produces a clean spec.
43
+ - npm leaves supply-chain protection up to you.
44
+
45
+ DaloyJS combines the wins:
46
+
47
+ 1. **Explicit contracts, minimal ceremony.** One `app.route({...})` is the source of truth for validation, types, OpenAPI, the typed client, and contract tests.
48
+ 2. **One source of truth for validation, typing, and docs** via [Standard Schema](https://github.com/standard-schema/standard-schema) — Zod 4 / Valibot / ArkType / TypeBox all work, no lock-in.
49
+ 3. **Portable core, optional runtime optimizations** — the only thing the core knows is `Request → Response`. Adapters live at the edge.
50
+ 4. **Secure by default — bad defaults are bugs.** Body limits, prototype-pollution-safe JSON, path-traversal rejection, request timeouts, Helmet-grade headers, RFC 9457 problem+json errors with prod-mode redaction.
51
+ 5. **Tooling and inspectability over magic.** `app.introspect()` is a public API; contract-test runner is built in.
52
+ 6. **Optimize for large-team maintenance**, not only solo-dev speed. Encapsulated plugins, decorators, request ids, structured logger.
53
+
54
+ ---
55
+
56
+ ## Install
57
+
58
+ DaloyJS is distributed via **pnpm** for [supply-chain hygiene](https://pnpm.io/motivation) — strict isolation, content-addressable store, deterministic lockfile, no phantom dependencies.
59
+
60
+ ```bash
61
+ pnpm add daloy zod@^4
62
+ ```
63
+
64
+ Zod 4 is the recommended validator for new DaloyJS apps because it is modern, smaller, and Standard-Schema-compatible. DaloyJS still accepts any Standard Schema validator, so teams can use Valibot, ArkType, TypeBox, or another compatible schema library when that better fits their stack.
65
+
66
+ The repo ships an [`.npmrc`](.npmrc) with hardened defaults:
67
+
68
+ ```ini
69
+ auto-install-peers=true
70
+ strict-peer-dependencies=true
71
+ prefer-frozen-lockfile=true
72
+ verify-store-integrity=true
73
+ # Optional: pnpm 10+ supply-chain controls
74
+ # minimum-release-age=1440 # wait 24h before installing fresh releases
75
+ # ignore-scripts=true # whitelist install scripts via approve-builds
76
+ ```
77
+
78
+ Run `pnpm audit --prod` regularly (or `pnpm run audit` in this repo) — and `pnpm install --frozen-lockfile` in CI.
79
+
80
+ ---
81
+
82
+ ## Hello world
83
+
84
+ ```ts
85
+ import { z } from "zod";
86
+ import { App, NotFoundError, secureHeaders, rateLimit, requestId } from "daloy";
87
+ import { serve } from "daloy/node";
88
+
89
+ const app = new App({ bodyLimitBytes: 1024 * 1024, requestTimeoutMs: 5_000 });
90
+
91
+ // Security defaults — usually three plugins in other frameworks.
92
+ app.use(requestId());
93
+ app.use(secureHeaders());
94
+ app.use(rateLimit({ windowMs: 60_000, max: 120 }));
95
+
96
+ app.route({
97
+ method: "GET",
98
+ path: "/books/:id",
99
+ operationId: "getBookById",
100
+ tags: ["Books"],
101
+ request: { params: z.object({ id: z.string() }) },
102
+ responses: {
103
+ 200: { description: "Found", body: z.object({ id: z.string(), title: z.string() }) },
104
+ 404: { description: "Not found" },
105
+ },
106
+ handler: async ({ params }) => ({
107
+ status: 200,
108
+ body: { id: params.id, title: `Book ${params.id}` },
109
+ }),
110
+ });
111
+
112
+ serve(app, { port: 3000 });
113
+ ```
114
+
115
+ ---
116
+
117
+ ## OpenAPI + Hey API typed client
118
+
119
+ DaloyJS produces a clean OpenAPI 3.1 document with **zero plugins**, then [@hey-api/openapi-ts](https://heyapi.dev/openapi-ts/get-started) turns that into a fully typed TypeScript SDK that any consumer (your web app, mobile RN bundle, internal CLI) can drop in.
120
+
121
+ ```bash
122
+ pnpm gen # writes generated/openapi.json + generated/client/
123
+ ```
124
+
125
+ That single command runs the two scripts:
126
+
127
+ ```jsonc
128
+ // package.json
129
+ "scripts": {
130
+ "gen:openapi": "node --import tsx scripts/dump-openapi.ts",
131
+ "gen:client": "openapi-ts",
132
+ "gen": "pnpm gen:openapi && pnpm gen:client"
133
+ }
134
+ ```
135
+
136
+ `openapi-ts.config.ts`:
137
+
138
+ ```ts
139
+ import { defineConfig } from "@hey-api/openapi-ts";
140
+ export default defineConfig({
141
+ input: "./generated/openapi.json",
142
+ output: { path: "./generated/client", postProcess: ["prettier"] },
143
+ plugins: ["@hey-api/client-fetch", "@hey-api/typescript", "@hey-api/sdk"],
144
+ });
145
+ ```
146
+
147
+ For TypeScript consumers in the same monorepo you can skip codegen entirely and use the **in-process typed client**:
148
+
149
+ ```ts
150
+ import { createClient } from "daloy/client";
151
+ const client = createClient(app, { baseUrl: "http://localhost:3000" });
152
+ const r = await client.getBookById({ params: { id: "1" } });
153
+ // ^? { status: 200; body: { id: string; title: string } } | { status: 404; ... }
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Built-in docs UI (Scalar / Swagger UI)
159
+
160
+ ```ts
161
+ import { scalarHtml, htmlResponse } from "daloy/docs";
162
+ // returns a self-contained HTML page that loads /openapi.json
163
+ ```
164
+
165
+ Mount at `/docs` and the UI is always contract-accurate — never stale.
166
+
167
+ ---
168
+
169
+ ## Security defaults (no plugins required)
170
+
171
+ | Threat | Default behavior |
172
+ |---|---|
173
+ | **Body-size DoS** | Streamed read, hard cap (default 1 MiB), `Content-Length` checked first. |
174
+ | **Prototype pollution** | `safeJsonParse` strips `__proto__` / `constructor` / `prototype` via reviver. |
175
+ | **Header / response splitting** | `sanitizeHeaderName` / `sanitizeHeaderValue` reject CRLF + NUL. |
176
+ | **Path traversal** | Router rejects `..` segments and `//` before walking. |
177
+ | **Slow-loris / hung handlers** | `requestTimeoutMs` aborts handlers (default 30 s); Node adapter sets `requestTimeout` + `headersTimeout` + `maxHeaderSize`. |
178
+ | **MIME sniffing** | `secureHeaders()` sets `X-Content-Type-Options: nosniff`. |
179
+ | **Clickjacking** | `X-Frame-Options: DENY` + CSP `frame-ancestors 'none'`. |
180
+ | **XSS via injected scripts** | Strict CSP `default-src 'self'` baseline. |
181
+ | **Cross-origin leakage** | `cross-origin-opener-policy` + `cross-origin-resource-policy` set to `same-origin`. |
182
+ | **Information disclosure (5xx)** | Production mode strips `detail` from 5xx problem+json automatically. |
183
+ | **Credential timing attacks** | `timingSafeEqual()` for tokens & signatures. |
184
+ | **Brute-force / scraping** | `rateLimit()` with token-bucket + `Retry-After`. |
185
+ | **Method confusion** | Real **405** with `Allow` header, not a misleading 404. |
186
+ | **CORS misconfig** | Explicit allowlist; never `*` with credentials. |
187
+ | **Request correlation** | Cryptographic `randomId()` request ids on every response. |
188
+ | **Supply chain** | Distributed via pnpm with hardening `.npmrc`; reproducible lockfile; opt-in `ignore-scripts` + `minimum-release-age`. |
189
+
190
+ ---
191
+
192
+ ## Performance
193
+
194
+ ```text
195
+ $ pnpm bench
196
+ static route lookup 12,363,799 ops/sec
197
+ dynamic 4-segment lookup 1,513,983 ops/sec
198
+ miss 4,763,878 ops/sec
199
+ ```
200
+
201
+ - Static (no-param) routes resolve via a single `Map.get` — **~12M ops/sec**.
202
+ - Dynamic routes walk a trie, **O(path-segments)** regardless of route count.
203
+ - Body parsing is lazy and only runs when a route declares a body schema.
204
+ - No regex on the hot path.
205
+
206
+ ---
207
+
208
+ ## Test client + contract tests
209
+
210
+ ```ts
211
+ const res = await app.request("/books/1");
212
+
213
+ import { runContractTests } from "daloy/contract";
214
+ const report = await runContractTests(app);
215
+ if (!report.ok) process.exit(1);
216
+ ```
217
+
218
+ The contract runner verifies that declared examples actually match their schemas, flags duplicate/missing operationIds, dead routes, and accidental body schemas on safe methods.
219
+
220
+ ---
221
+
222
+ ## Plugin encapsulation (Fastify-style)
223
+
224
+ ```ts
225
+ const usersPlugin = {
226
+ name: "users",
227
+ register(app) {
228
+ app.route({ method: "GET", path: "/me", operationId: "me",
229
+ responses: { 200: { description: "ok" } },
230
+ handler: async () => ({ status: 200, body: { user: "alice" } }) });
231
+ },
232
+ };
233
+ app.register(usersPlugin, { prefix: "/users", tags: ["Users"] });
234
+ await app.ready();
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Multi-runtime
240
+
241
+ ```ts
242
+ import { serve } from "daloy/node"; // Node
243
+ import { serve } from "daloy/bun"; // Bun
244
+ import { serve } from "daloy/deno"; // Deno
245
+ import { toFetchHandler } from "daloy/cloudflare"; // Cloudflare Workers
246
+ import { toEdgeHandler } from "daloy/vercel"; // Vercel Edge / Next.js
247
+ ```
248
+
249
+ The core only ever sees `Request → Response`. Adapters live at the edge.
250
+
251
+ ---
252
+
253
+ ## References
254
+
255
+ - Hey API — typed OpenAPI client codegen: <https://heyapi.dev/openapi-ts/get-started>
256
+ - Hono — portable web-standard router: <https://hono.dev/docs/>
257
+ - Elysia — TS-first DX & typed context: <https://elysiajs.com/at-glance.html>
258
+ - Fastify — production Node web framework: <https://fastify.dev/docs/latest/Reference/>
259
+ - pnpm — strict, secure, content-addressable package manager: <https://pnpm.io/motivation>
260
+ - Standard Schema — universal validator interface: <https://github.com/standard-schema/standard-schema>
261
+ - RFC 9457 — Problem Details for HTTP APIs: <https://www.rfc-editor.org/rfc/rfc9457>
262
+
263
+ ---
264
+
265
+ ## Status & roadmap
266
+
267
+ **Implemented (v0.1):**
268
+
269
+ - [x] Trie router with static fast path + 405 with `Allow` + traversal guard
270
+ - [x] Contract-first `app.route()`, groups, encapsulated plugins, decorators
271
+ - [x] Standard Schema validation (Zod 4 / Valibot / ArkType / TypeBox)
272
+ - [x] Problem+json error model with prod-mode redaction
273
+ - [x] OpenAPI 3.1 generator (built-in)
274
+ - [x] In-process test client + contract-test runner
275
+ - [x] In-process typed client factory + Hey API codegen integration (`pnpm gen`)
276
+ - [x] Node / Bun / Deno / Cloudflare / Vercel adapters
277
+ - [x] Security: body limits, content-type allowlist, prototype-pollution-safe JSON, path-traversal rejection, request timeout, header injection guards
278
+ - [x] Security middleware: `secureHeaders` / `cors` / `rateLimit` / `requestId` / `bearerAuth` / `timing` / `timingSafeEqual`
279
+ - [x] Pluggable structured logger + request id propagation
280
+ - [x] Graceful shutdown
281
+ - [x] Mock mode
282
+ - [x] Scalar + Swagger UI handlers
283
+ - [x] **pnpm-first distribution with hardened `.npmrc`**
284
+
285
+ **Next:**
286
+
287
+ - [ ] WebSocket primitives + adapter coverage
288
+ - [ ] Streaming response helpers (SSE, NDJSON)
289
+ - [ ] CSRF helper
290
+ - [ ] CLI: route inspector, schema inspector, dead-route detector
291
+ - [ ] OpenTelemetry tracing hook
292
+ - [ ] HTTP/2 + HTTP/3 adapters
293
+
294
+ ## License
295
+
296
+ MIT
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Bun adapter — `Bun.serve` already speaks web-standard fetch,
3
+ * so this is the smallest possible wrapper.
4
+ */
5
+ import type { App } from "../app.js";
6
+ export interface BunServeOptions {
7
+ port?: number;
8
+ hostname?: string;
9
+ /** Maximum request body bytes (Bun-level cap). Default: 16 MiB. */
10
+ maxRequestBodySize?: number;
11
+ }
12
+ export declare function serve(app: App, opts?: BunServeOptions): {
13
+ stop: () => Promise<void>;
14
+ port: number;
15
+ };
16
+ //# sourceMappingURL=bun.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../../src/adapters/bun.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,GAAE,eAAoB,GAAG;IAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA0BvG"}
@@ -0,0 +1,25 @@
1
+ export function serve(app, opts = {}) {
2
+ const Bun = globalThis.Bun;
3
+ if (!Bun?.serve)
4
+ throw new Error("Bun runtime not detected");
5
+ const server = Bun.serve({
6
+ port: opts.port ?? 3000,
7
+ hostname: opts.hostname ?? "0.0.0.0",
8
+ maxRequestBodySize: opts.maxRequestBodySize ?? 16 * 1024 * 1024,
9
+ fetch: (req) => app.fetch(req),
10
+ error: (err) => new Response(JSON.stringify({
11
+ type: "https://daloyjs.dev/errors/internal",
12
+ title: "Internal Server Error",
13
+ status: 500,
14
+ detail: err.message,
15
+ }), { status: 500, headers: { "content-type": "application/problem+json" } }),
16
+ });
17
+ return {
18
+ port: server.port,
19
+ stop: async () => {
20
+ await app.shutdown();
21
+ server.stop(true);
22
+ },
23
+ };
24
+ }
25
+ //# sourceMappingURL=bun.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun.js","sourceRoot":"","sources":["../../src/adapters/bun.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,KAAK,CAAC,GAAQ,EAAE,OAAwB,EAAE;IACxD,MAAM,GAAG,GAAI,UAAkB,CAAC,GAAG,CAAC;IACpC,IAAI,CAAC,GAAG,EAAE,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC;QACvB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;QACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,SAAS;QACpC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,IAAI,EAAE,GAAG,IAAI,GAAG,IAAI;QAC/D,KAAK,EAAE,CAAC,GAAY,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC;QACvC,KAAK,EAAE,CAAC,GAAU,EAAE,EAAE,CACpB,IAAI,QAAQ,CACV,IAAI,CAAC,SAAS,CAAC;YACb,IAAI,EAAE,qCAAqC;YAC3C,KAAK,EAAE,uBAAuB;YAC9B,MAAM,EAAE,GAAG;YACX,MAAM,EAAE,GAAG,CAAC,OAAO;SACpB,CAAC,EACF,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,EAAE,CACzE;KACJ,CAAC,CAAC;IACH,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,KAAK,IAAI,EAAE;YACf,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Cloudflare Workers / generic fetch handler adapter.
3
+ *
4
+ * Usage:
5
+ * export default toFetchHandler(app);
6
+ */
7
+ import type { App } from "../app.js";
8
+ export declare function toFetchHandler(app: App): {
9
+ fetch: (req: Request, env?: unknown, ctx?: unknown) => Promise<Response>;
10
+ };
11
+ //# sourceMappingURL=cloudflare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErC,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG;IAAE,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAAE,CAIrH"}
@@ -0,0 +1,6 @@
1
+ export function toFetchHandler(app) {
2
+ return {
3
+ fetch: (req) => app.fetch(req),
4
+ };
5
+ }
6
+ //# sourceMappingURL=cloudflare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,cAAc,CAAC,GAAQ;IACrC,OAAO;QACL,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC;KAC/B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Deno adapter — `Deno.serve` is web-standard fetch.
3
+ */
4
+ import type { App } from "../app.js";
5
+ export interface DenoServeOptions {
6
+ port?: number;
7
+ hostname?: string;
8
+ }
9
+ export declare function serve(app: App, opts?: DenoServeOptions): {
10
+ shutdown: () => Promise<void>;
11
+ };
12
+ //# sourceMappingURL=deno.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deno.d.ts","sourceRoot":"","sources":["../../src/adapters/deno.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,GAAE,gBAAqB,GAAG;IAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAa9F"}
@@ -0,0 +1,13 @@
1
+ export function serve(app, opts = {}) {
2
+ const D = globalThis.Deno;
3
+ if (!D?.serve)
4
+ throw new Error("Deno runtime not detected");
5
+ const server = D.serve({ port: opts.port ?? 3000, hostname: opts.hostname ?? "0.0.0.0" }, (req) => app.fetch(req));
6
+ return {
7
+ shutdown: async () => {
8
+ await app.shutdown();
9
+ await server.shutdown?.();
10
+ },
11
+ };
12
+ }
13
+ //# sourceMappingURL=deno.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deno.js","sourceRoot":"","sources":["../../src/adapters/deno.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,KAAK,CAAC,GAAQ,EAAE,OAAyB,EAAE;IACzD,MAAM,CAAC,GAAI,UAAkB,CAAC,IAAI,CAAC;IACnC,IAAI,CAAC,CAAC,EAAE,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CACpB,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,SAAS,EAAE,EACjE,CAAC,GAAY,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CACjC,CAAC;IACF,OAAO;QACL,QAAQ,EAAE,KAAK,IAAI,EAAE;YACnB,MAAM,GAAG,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;QAC5B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Node adapter: translates IncomingMessage/ServerResponse to web-standard
3
+ * Request/Response. Includes graceful shutdown wired to SIGTERM/SIGINT.
4
+ */
5
+ import { type Server } from "node:http";
6
+ import type { App } from "../app.js";
7
+ export interface NodeServerOptions {
8
+ port?: number;
9
+ hostname?: string;
10
+ /** Connection-level timeout in ms. Default: 30000. */
11
+ connectionTimeoutMs?: number;
12
+ /** Drain timeout for graceful shutdown. Default: 10000. */
13
+ shutdownTimeoutMs?: number;
14
+ /** Listen for SIGTERM/SIGINT and shut down. Default: true. */
15
+ handleSignals?: boolean;
16
+ /** Maximum HTTP header size bytes (DoS protection). Default: 16 KiB. */
17
+ maxHeaderBytes?: number;
18
+ }
19
+ export interface NodeServerHandle {
20
+ server: Server;
21
+ port: number;
22
+ close(): Promise<void>;
23
+ }
24
+ export declare function serve(app: App, opts?: NodeServerOptions): NodeServerHandle;
25
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/adapters/node.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAEjG,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,GAAE,iBAAsB,GAAG,gBAAgB,CAmD9E"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Node adapter: translates IncomingMessage/ServerResponse to web-standard
3
+ * Request/Response. Includes graceful shutdown wired to SIGTERM/SIGINT.
4
+ */
5
+ import { createServer } from "node:http";
6
+ import { Readable } from "node:stream";
7
+ export function serve(app, opts = {}) {
8
+ const server = createServer({ maxHeaderSize: opts.maxHeaderBytes ?? 16 * 1024 }, async (req, res) => {
9
+ try {
10
+ const request = await toWebRequest(req);
11
+ const response = await app.fetch(request);
12
+ await sendWebResponse(response, res);
13
+ }
14
+ catch (e) {
15
+ if (!res.headersSent) {
16
+ res.statusCode = 500;
17
+ res.setHeader("content-type", "application/problem+json");
18
+ res.end(JSON.stringify({
19
+ type: "https://daloyjs.dev/errors/internal",
20
+ title: "Internal Server Error",
21
+ status: 500,
22
+ }));
23
+ }
24
+ else {
25
+ res.destroy(e);
26
+ }
27
+ }
28
+ });
29
+ server.requestTimeout = opts.connectionTimeoutMs ?? 30_000;
30
+ server.headersTimeout = opts.connectionTimeoutMs ?? 30_000;
31
+ server.keepAliveTimeout = 5_000;
32
+ const port = opts.port ?? 3000;
33
+ const hostname = opts.hostname ?? "0.0.0.0";
34
+ server.listen(port, hostname);
35
+ let closed = false;
36
+ const close = async () => {
37
+ if (closed)
38
+ return;
39
+ closed = true;
40
+ await app.shutdown(opts.shutdownTimeoutMs ?? 10_000);
41
+ await new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())));
42
+ };
43
+ if (opts.handleSignals !== false) {
44
+ const onSignal = (sig) => {
45
+ app.log.info({ sig }, "DaloyJS received signal, shutting down");
46
+ void close().then(() => process.exit(0));
47
+ };
48
+ process.once("SIGTERM", () => onSignal("SIGTERM"));
49
+ process.once("SIGINT", () => onSignal("SIGINT"));
50
+ }
51
+ return { server, port, close };
52
+ }
53
+ async function toWebRequest(req) {
54
+ const host = req.headers.host ?? "localhost";
55
+ const url = `http://${host}${req.url ?? "/"}`;
56
+ const headers = new Headers();
57
+ for (const [k, v] of Object.entries(req.headers)) {
58
+ if (v === undefined)
59
+ continue;
60
+ if (Array.isArray(v))
61
+ headers.set(k, v.join(", "));
62
+ else
63
+ headers.set(k, String(v));
64
+ }
65
+ const method = req.method ?? "GET";
66
+ const init = { method, headers };
67
+ if (method !== "GET" && method !== "HEAD") {
68
+ init.body = Readable.toWeb(req);
69
+ init.duplex = "half";
70
+ }
71
+ return new Request(url, init);
72
+ }
73
+ async function sendWebResponse(res, out) {
74
+ out.statusCode = res.status;
75
+ res.headers.forEach((v, k) => out.setHeader(k, v));
76
+ if (!res.body) {
77
+ out.end();
78
+ return;
79
+ }
80
+ const reader = res.body.getReader();
81
+ while (true) {
82
+ const { done, value } = await reader.read();
83
+ if (done)
84
+ break;
85
+ if (value)
86
+ out.write(value);
87
+ }
88
+ out.end();
89
+ }
90
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/adapters/node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Vercel Edge / generic Web-standard handler.
3
+ *
4
+ * Usage in Next.js Route Handlers:
5
+ * export const GET = (req) => app.fetch(req);
6
+ * export const POST = (req) => app.fetch(req);
7
+ *
8
+ * Or just:
9
+ * export default toEdgeHandler(app);
10
+ */
11
+ import type { App } from "../app.js";
12
+ export declare function toEdgeHandler(app: App): (req: Request) => Promise<Response>;
13
+ //# sourceMappingURL=vercel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel.d.ts","sourceRoot":"","sources":["../../src/adapters/vercel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErC,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,IAC5B,KAAK,OAAO,uBACrB"}
@@ -0,0 +1,4 @@
1
+ export function toEdgeHandler(app) {
2
+ return (req) => app.fetch(req);
3
+ }
4
+ //# sourceMappingURL=vercel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel.js","sourceRoot":"","sources":["../../src/adapters/vercel.ts"],"names":[],"mappings":"AAYA,MAAM,UAAU,aAAa,CAAC,GAAQ;IACpC,OAAO,CAAC,GAAY,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC1C,CAAC"}