@daloyjs/core 1.0.0-beta.6 → 1.0.0-beta.7

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 (99) hide show
  1. package/README.md +2 -2
  2. package/dist/adapters/bun.d.ts +13 -1
  3. package/dist/adapters/bun.js +8 -1
  4. package/dist/adapters/cloudflare.d.ts +7 -1
  5. package/dist/adapters/cloudflare.js +6 -1
  6. package/dist/adapters/deno.d.ts +11 -1
  7. package/dist/adapters/deno.js +8 -1
  8. package/dist/adapters/fastly.d.ts +12 -2
  9. package/dist/adapters/fastly.js +12 -2
  10. package/dist/adapters/lambda.d.ts +37 -1
  11. package/dist/adapters/lambda.js +6 -1
  12. package/dist/adapters/node.d.ts +12 -1
  13. package/dist/adapters/node.js +7 -1
  14. package/dist/adapters/vercel.d.ts +13 -1
  15. package/dist/adapters/vercel.js +12 -1
  16. package/dist/app.d.ts +67 -17
  17. package/dist/app.js +97 -31
  18. package/dist/banner.d.ts +6 -0
  19. package/dist/banner.js +6 -0
  20. package/dist/cli.d.ts +35 -0
  21. package/dist/cli.js +23 -1
  22. package/dist/combine.d.ts +8 -0
  23. package/dist/combine.js +8 -0
  24. package/dist/compression.d.ts +3 -0
  25. package/dist/compression.js +3 -0
  26. package/dist/config.d.ts +4 -0
  27. package/dist/config.js +4 -0
  28. package/dist/conn-info.d.ts +35 -2
  29. package/dist/conn-info.js +35 -2
  30. package/dist/contract.d.ts +2 -0
  31. package/dist/contract.js +2 -0
  32. package/dist/cookie.d.ts +12 -0
  33. package/dist/cookie.js +12 -0
  34. package/dist/dependency.d.ts +4 -0
  35. package/dist/dependency.js +3 -0
  36. package/dist/discriminator.d.ts +13 -0
  37. package/dist/discriminator.js +23 -1
  38. package/dist/docs.d.ts +78 -0
  39. package/dist/docs.js +21 -0
  40. package/dist/errors.d.ts +16 -0
  41. package/dist/errors.js +14 -0
  42. package/dist/etag.d.ts +2 -0
  43. package/dist/etag.js +2 -0
  44. package/dist/fetch-guard.d.ts +7 -0
  45. package/dist/fetch-guard.js +7 -0
  46. package/dist/fetch-resilience.d.ts +4 -0
  47. package/dist/fetch-resilience.js +4 -0
  48. package/dist/http-signatures.d.ts +32 -0
  49. package/dist/http-signatures.js +30 -0
  50. package/dist/index.d.ts +1 -1
  51. package/dist/ip-restriction.d.ts +40 -3
  52. package/dist/ip-restriction.js +35 -3
  53. package/dist/jwk.d.ts +12 -1
  54. package/dist/jwk.js +6 -0
  55. package/dist/jwt.d.ts +14 -0
  56. package/dist/jwt.js +10 -0
  57. package/dist/load-shedding.d.ts +3 -0
  58. package/dist/load-shedding.js +3 -0
  59. package/dist/logger.d.ts +13 -0
  60. package/dist/logger.js +3 -0
  61. package/dist/mcp.d.ts +152 -10
  62. package/dist/mcp.js +223 -19
  63. package/dist/middleware.d.ts +68 -0
  64. package/dist/middleware.js +17 -0
  65. package/dist/mtls.d.ts +19 -2
  66. package/dist/mtls.js +12 -2
  67. package/dist/multipart.d.ts +42 -5
  68. package/dist/multipart.js +41 -5
  69. package/dist/openapi.d.ts +15 -9
  70. package/dist/openapi.js +6 -9
  71. package/dist/rate-limit-redis.d.ts +21 -2
  72. package/dist/rate-limit-redis.js +17 -2
  73. package/dist/safe-redirect.d.ts +6 -0
  74. package/dist/safe-redirect.js +6 -0
  75. package/dist/sbom.cdx.json +9 -9
  76. package/dist/sbom.spdx.json +5 -5
  77. package/dist/scheduler.d.ts +4 -0
  78. package/dist/schema.d.ts +25 -0
  79. package/dist/security-schemes.d.ts +50 -0
  80. package/dist/security-schemes.js +6 -0
  81. package/dist/security.d.ts +33 -0
  82. package/dist/security.js +28 -10
  83. package/dist/session.d.ts +34 -5
  84. package/dist/session.js +31 -5
  85. package/dist/streaming.d.ts +19 -0
  86. package/dist/streaming.js +16 -0
  87. package/dist/subdomains.d.ts +4 -0
  88. package/dist/subdomains.js +4 -0
  89. package/dist/time-claims.d.ts +22 -2
  90. package/dist/time-claims.js +6 -0
  91. package/dist/tracing.d.ts +12 -0
  92. package/dist/tracing.js +6 -0
  93. package/dist/types.d.ts +70 -1
  94. package/dist/waf.js +21 -1
  95. package/dist/webhook-delivery.d.ts +6 -0
  96. package/dist/webhook-delivery.js +5 -0
  97. package/dist/websocket.d.ts +137 -4
  98. package/dist/websocket.js +105 -4
  99. package/package.json +1 -1
package/README.md CHANGED
@@ -509,7 +509,7 @@ The core only ever sees `Request → Response`. Adapters live at the edge.
509
509
 
510
510
  ## Status
511
511
 
512
- DaloyJS is now in the **`1.0.0` beta** (`1.0.0-beta.6`). The public API is feature-complete and stable for the 1.0 line; from `1.0.0` onward, breaking changes follow SemVer and deprecations get at least one minor cycle. Small adjustments are still possible before the `1.0.0` GA if beta feedback surfaces something. The framework is already in use for production trials.
512
+ DaloyJS is now in the **`1.0.0` beta** (`1.0.0-beta.7`). The public API is feature-complete and stable for the 1.0 line; from `1.0.0` onward, breaking changes follow SemVer and deprecations get at least one minor cycle. Small adjustments are still possible before the `1.0.0` GA if beta feedback surfaces something. The framework is already in use for production trials.
513
513
 
514
514
  **Release quality bar.** Every release ships with **≥90% line + function coverage and ≥90% branch coverage**, strict TypeScript, OpenSSF Scorecard, CodeQL + Opengrep dual SAST, zizmor workflow linting, and npm provenance. Coverage was relaxed from a former 100% gate so complex security work isn't blocked chasing throwaway tests for unreachable defensive branches or tsx source-map phantoms; see [AGENTS.md](AGENTS.md) for the policy.
515
515
 
@@ -520,7 +520,7 @@ DaloyJS is now in the **`1.0.0` beta** (`1.0.0-beta.6`). The public API is featu
520
520
  - Zero-config OpenAPI `info` autofill from `package.json` (Node / Bun) or `deno.json` / `deno.jsonc` (Deno); explicit `openapi.info` values always win.
521
521
  - RFC 7231 + RFC 5789 HTTP-method allowlist enforced inside `app.route()` (WebDAV, `TRACE`, `CONNECT` rejected at the framework boundary).
522
522
  - AI-friendly route metadata via optional `meta: { examples, extensions, summary, description, tags }`; examples are validated against your schemas at build time, surfaced as OpenAPI `examples` + `x-daloy-*` extensions, and dumped as `routes.json` / `routes.yaml` via `daloy inspect --ai`.
523
- - Dependency-free MCP Streamable HTTP server helpers at `@daloyjs/core/mcp`: `createMcpHandler()` exposes tools, resources, and prompts over JSON-RPC 2.0, while `mcpRoutes("/mcp", handler)` mounts the POST / GET / OPTIONS Daloy routes for a dedicated MCP service with the same auth, rate-limit, body-limit, and timeout middleware as any other app.
523
+ - Dependency-free MCP Streamable HTTP server helpers at `@daloyjs/core/mcp`: `createMcpHandler()` exposes tools (with `outputSchema`, `annotations`, and icons), resources, RFC 6570 resource templates, and prompts (with required-argument enforcement) over JSON-RPC 2.0 and validates `Origin` against DNS rebinding (with an `allowedOrigins` allowlist), while `mcpRoutes("/mcp", handler)` mounts the POST / GET / OPTIONS Daloy routes — with the JSON-RPC envelope schema surfaced in OpenAPI — for a dedicated MCP service with the same auth, rate-limit, body-limit, and timeout middleware as any other app.
524
524
  - API lifecycle and breaking-change detection: mark routes `deprecated` or give them a `sunset` date to emit RFC 8594 `Deprecation` / `Sunset` headers and an `x-sunset` OpenAPI extension, then gate CI with `diffOpenAPI()` / the `daloy diff` command, which fail on a breaking change versus the last published spec.
525
525
  - In-process test client (`app.request()`), contract-test runner (gated in CI via `daloy inspect --check` and shipped as a default test in every `create-daloy` template), in-process typed client, and Hey API codegen via `pnpm gen`.
526
526
 
@@ -18,7 +18,9 @@ export interface BunTLSOptions {
18
18
  }
19
19
  /** Options forwarded to `Bun.serve` by {@link serve}. */
20
20
  export interface BunServeOptions {
21
+ /** TCP port to listen on. Ignored when `unix` is set. Defaults to `3000`. */
21
22
  port?: number;
23
+ /** Interface to bind. Ignored when `unix` is set. Defaults to `"0.0.0.0"`. */
22
24
  hostname?: string;
23
25
  /** Maximum request body bytes (Bun-level cap). Default: 16 MiB. */
24
26
  maxRequestBodySize?: number;
@@ -33,9 +35,19 @@ export interface BunServeOptions {
33
35
  }
34
36
  /** Handle returned by {@link serve} for shutdown and listener introspection. */
35
37
  export interface BunServerHandle {
38
+ /** Port the server is actually listening on (as reported by Bun). */
36
39
  port: number;
40
+ /** Server URL as reported by `Bun.serve` (e.g. for startup logging), if available. */
37
41
  url: URL | undefined;
42
+ /** Graceful stop: drains {@link App.shutdown} hooks first, then force-stops the Bun server. */
38
43
  stop: () => Promise<void>;
39
44
  }
40
- /** Start `Bun.serve` bound to the given {@link App}, wiring HTTP and WebSocket routes. */
45
+ /**
46
+ * Start `Bun.serve` bound to the given {@link App}, wiring HTTP and WebSocket routes.
47
+ *
48
+ * @param app - The DaloyJS {@link App} whose `fetch` (and WebSocket routes) serve requests.
49
+ * @param opts - Listener options forwarded to `Bun.serve`; see {@link BunServeOptions}.
50
+ * @returns A {@link BunServerHandle} exposing the bound `port`, `url`, and a graceful `stop()`.
51
+ * @throws Error when the Bun runtime (`globalThis.Bun.serve`) is not detected.
52
+ */
41
53
  export declare function serve(app: App, opts?: BunServeOptions): BunServerHandle;
@@ -1,5 +1,12 @@
1
1
  import { WS_READY_STATE, WS_CLOSE_CODE, WS_MAX_CONTROL_PAYLOAD, encodeSendPayload, parseSubprotocols, validateSelectedSubprotocol, checkWebSocketOrigin, WebSocketProtocolError, } from "../websocket.js";
2
- /** Start `Bun.serve` bound to the given {@link App}, wiring HTTP and WebSocket routes. */
2
+ /**
3
+ * Start `Bun.serve` bound to the given {@link App}, wiring HTTP and WebSocket routes.
4
+ *
5
+ * @param app - The DaloyJS {@link App} whose `fetch` (and WebSocket routes) serve requests.
6
+ * @param opts - Listener options forwarded to `Bun.serve`; see {@link BunServeOptions}.
7
+ * @returns A {@link BunServerHandle} exposing the bound `port`, `url`, and a graceful `stop()`.
8
+ * @throws Error when the Bun runtime (`globalThis.Bun.serve`) is not detected.
9
+ */
3
10
  export function serve(app, opts = {}) {
4
11
  const Bun = globalThis.Bun;
5
12
  if (!Bun?.serve)
@@ -18,12 +18,18 @@
18
18
  import type { App } from "../app.js";
19
19
  /** Module shape expected by the Cloudflare Workers runtime as `export default`. */
20
20
  export interface ExportedFetchHandler<Env = unknown> {
21
+ /** Worker entry point: forwards the request to {@link App.fetch}. `env`/`ctx` are accepted but unused by the adapter. */
21
22
  fetch: (request: Request, env?: Env, ctx?: ExecutionContextLike) => Promise<Response>;
22
23
  }
23
24
  interface ExecutionContextLike {
24
25
  waitUntil?: (promise: Promise<unknown>) => void;
25
26
  passThroughOnException?: () => void;
26
27
  }
27
- /** Wrap an {@link App} in the `{ fetch }` object expected by Cloudflare Workers and other web-standard hosts. */
28
+ /**
29
+ * Wrap an {@link App} in the `{ fetch }` object expected by Cloudflare Workers and other web-standard hosts.
30
+ *
31
+ * @param app - The DaloyJS {@link App} that serves each incoming request.
32
+ * @returns An {@link ExportedFetchHandler} suitable as the module's `export default`.
33
+ */
28
34
  export declare function toFetchHandler<Env = unknown>(app: App): ExportedFetchHandler<Env>;
29
35
  export {};
@@ -1,4 +1,9 @@
1
- /** Wrap an {@link App} in the `{ fetch }` object expected by Cloudflare Workers and other web-standard hosts. */
1
+ /**
2
+ * Wrap an {@link App} in the `{ fetch }` object expected by Cloudflare Workers and other web-standard hosts.
3
+ *
4
+ * @param app - The DaloyJS {@link App} that serves each incoming request.
5
+ * @returns An {@link ExportedFetchHandler} suitable as the module's `export default`.
6
+ */
2
7
  export function toFetchHandler(app) {
3
8
  return {
4
9
  fetch: (req) => app.fetch(req),
@@ -9,7 +9,9 @@
9
9
  import type { App } from "../app.js";
10
10
  /** Options forwarded to `Deno.serve` by {@link serve}. */
11
11
  export interface DenoServeOptions {
12
+ /** TCP port to listen on. Defaults to `3000`. */
12
13
  port?: number;
14
+ /** Interface to bind. Defaults to `"0.0.0.0"`. */
13
15
  hostname?: string;
14
16
  /** Optional external signal that triggers graceful shutdown. */
15
17
  signal?: AbortSignal;
@@ -31,7 +33,15 @@ export interface DenoServeOptions {
31
33
  }
32
34
  /** Handle returned by {@link serve}; call `shutdown()` to drain. */
33
35
  export interface DenoServerHandle {
36
+ /** Graceful shutdown: drains {@link App.shutdown} hooks, then stops the Deno server. Idempotent. */
34
37
  shutdown: () => Promise<void>;
35
38
  }
36
- /** Start `Deno.serve` bound to the given {@link App} with graceful-shutdown wiring. */
39
+ /**
40
+ * Start `Deno.serve` bound to the given {@link App} with graceful-shutdown wiring.
41
+ *
42
+ * @param app - The DaloyJS {@link App} whose `fetch` serves each request.
43
+ * @param opts - Listener/TLS/shutdown options; see {@link DenoServeOptions}.
44
+ * @returns A {@link DenoServerHandle} whose `shutdown()` drains in-flight requests.
45
+ * @throws Error when the Deno runtime (`globalThis.Deno.serve`) is not detected.
46
+ */
37
47
  export declare function serve(app: App, opts?: DenoServeOptions): DenoServerHandle;
@@ -1,4 +1,11 @@
1
- /** Start `Deno.serve` bound to the given {@link App} with graceful-shutdown wiring. */
1
+ /**
2
+ * Start `Deno.serve` bound to the given {@link App} with graceful-shutdown wiring.
3
+ *
4
+ * @param app - The DaloyJS {@link App} whose `fetch` serves each request.
5
+ * @param opts - Listener/TLS/shutdown options; see {@link DenoServeOptions}.
6
+ * @returns A {@link DenoServerHandle} whose `shutdown()` drains in-flight requests.
7
+ * @throws Error when the Deno runtime (`globalThis.Deno.serve`) is not detected.
8
+ */
2
9
  export function serve(app, opts = {}) {
3
10
  const D = globalThis.Deno;
4
11
  const denoServe = D?.serve;
@@ -18,7 +18,17 @@
18
18
  * multipart helpers that rely on `node:stream`).
19
19
  */
20
20
  import type { App } from "../app.js";
21
- /** Wrap an {@link App} in a `(req) => Promise<Response>` function suitable for Fastly Compute. */
21
+ /**
22
+ * Wrap an {@link App} in a `(req) => Promise<Response>` function suitable for Fastly Compute.
23
+ *
24
+ * @param app - The DaloyJS {@link App} that serves each incoming request.
25
+ * @returns A web-standard handler delegating to {@link App.fetch}.
26
+ */
22
27
  export declare function toFastlyHandler(app: App): (req: Request) => Promise<Response>;
23
- /** Register a Fastly Compute `fetch` event listener that delegates to the given {@link App}. */
28
+ /**
29
+ * Register a Fastly Compute `fetch` event listener that delegates to the given {@link App}.
30
+ *
31
+ * @param app - The DaloyJS {@link App} that serves each incoming request.
32
+ * @throws Error when `globalThis.addEventListener` is missing (not a Fastly Compute runtime).
33
+ */
24
34
  export declare function installFastlyListener(app: App): void;
@@ -1,8 +1,18 @@
1
- /** Wrap an {@link App} in a `(req) => Promise<Response>` function suitable for Fastly Compute. */
1
+ /**
2
+ * Wrap an {@link App} in a `(req) => Promise<Response>` function suitable for Fastly Compute.
3
+ *
4
+ * @param app - The DaloyJS {@link App} that serves each incoming request.
5
+ * @returns A web-standard handler delegating to {@link App.fetch}.
6
+ */
2
7
  export function toFastlyHandler(app) {
3
8
  return (req) => app.fetch(req);
4
9
  }
5
- /** Register a Fastly Compute `fetch` event listener that delegates to the given {@link App}. */
10
+ /**
11
+ * Register a Fastly Compute `fetch` event listener that delegates to the given {@link App}.
12
+ *
13
+ * @param app - The DaloyJS {@link App} that serves each incoming request.
14
+ * @throws Error when `globalThis.addEventListener` is missing (not a Fastly Compute runtime).
15
+ */
6
16
  export function installFastlyListener(app) {
7
17
  const g = globalThis;
8
18
  if (typeof g.addEventListener !== "function") {
@@ -13,28 +13,45 @@ import type { App } from "../app.js";
13
13
  * The adapter performs no Node-only I/O, so it is safe in any runtime that
14
14
  * provides the standard `Request`/`Response`/`atob`/`btoa` globals.
15
15
  */
16
+ /** API Gateway REST API event (payload format v1.0). */
16
17
  export interface LambdaEventV1 {
18
+ /** Payload format version; `"1.0"` or absent for REST API events. */
17
19
  version?: "1.0" | string;
20
+ /** URL path of the request (e.g. `/users/42`). */
18
21
  path?: string;
22
+ /** HTTP method (e.g. `GET`). */
19
23
  httpMethod?: string;
24
+ /** Request headers, one value per name (last value wins in v1.0). */
20
25
  headers?: Record<string, string | undefined>;
26
+ /** Request headers with every value per name; `cookie` values are re-joined with `; `. */
21
27
  multiValueHeaders?: Record<string, string[] | undefined>;
28
+ /** Query parameters, one value per name. Used only when the multi-value map is empty. */
22
29
  queryStringParameters?: Record<string, string | undefined> | null;
30
+ /** Query parameters with every value per name; preferred over the single-value map. */
23
31
  multiValueQueryStringParameters?: Record<string, string[] | undefined> | null;
32
+ /** Request context; `domainName` is the host fallback and `path` the path fallback. */
24
33
  requestContext?: {
25
34
  domainName?: string;
26
35
  path?: string;
27
36
  };
37
+ /** Raw request body; base64-encoded when {@link LambdaEventV1.isBase64Encoded} is true. */
28
38
  body?: string;
39
+ /** True when `body` is base64-encoded (binary payloads). */
29
40
  isBase64Encoded?: boolean;
30
41
  }
31
42
  /** API Gateway HTTP API or Lambda Function URL event (payload format v2.0). */
32
43
  export interface LambdaEventV2 {
44
+ /** Payload format version; `"2.0"` for HTTP API / Function URL events. */
33
45
  version?: string;
46
+ /** URL path of the request, without the query string. */
34
47
  rawPath?: string;
48
+ /** Raw query string without the leading `?` (empty string when none). */
35
49
  rawQueryString?: string;
50
+ /** Request headers; multi-value headers arrive comma-joined in v2.0. */
36
51
  headers?: Record<string, string | undefined>;
52
+ /** Request cookies as individual strings; re-joined with `; ` into a `cookie` header. */
37
53
  cookies?: string[];
54
+ /** Request context; `http.method`/`http.path` carry the method and path, `domainName` the host fallback. */
38
55
  requestContext?: {
39
56
  http?: {
40
57
  method?: string;
@@ -42,32 +59,51 @@ export interface LambdaEventV2 {
42
59
  };
43
60
  domainName?: string;
44
61
  };
62
+ /** Raw request body; base64-encoded when {@link LambdaEventV2.isBase64Encoded} is true. */
45
63
  body?: string;
64
+ /** True when `body` is base64-encoded (binary payloads). */
46
65
  isBase64Encoded?: boolean;
47
66
  }
48
67
  /** Either payload format accepted by {@link toLambdaHandler}. */
49
68
  export type LambdaEvent = LambdaEventV1 | LambdaEventV2;
50
69
  /** Lambda response shape required by API Gateway REST API (payload format v1.0). */
51
70
  export interface LambdaResponseV1 {
71
+ /** HTTP status code of the response. */
52
72
  statusCode: number;
73
+ /** Response headers, one value per name (`set-cookie` excluded; see `multiValueHeaders`). */
53
74
  headers: Record<string, string>;
75
+ /** Multi-value headers; used to carry each `set-cookie` value separately in v1.0. */
54
76
  multiValueHeaders?: Record<string, string[]>;
77
+ /** Never present in v1.0 responses; cookies travel via `multiValueHeaders`. */
55
78
  cookies?: never;
79
+ /** Response body; base64-encoded when {@link LambdaResponseV1.isBase64Encoded} is true. */
56
80
  body: string;
81
+ /** True when `body` is base64-encoded (non-text content types). */
57
82
  isBase64Encoded: boolean;
58
83
  }
59
84
  /** Lambda response shape required by API Gateway HTTP API and Function URLs (payload format v2.0). */
60
85
  export interface LambdaResponseV2 {
86
+ /** HTTP status code of the response. */
61
87
  statusCode: number;
88
+ /** Response headers, one value per name (`set-cookie` excluded; see `cookies`). */
62
89
  headers: Record<string, string>;
90
+ /** Response cookies, one `set-cookie` value per entry (v2.0's cookie channel). */
63
91
  cookies?: string[];
92
+ /** Never present in v2.0 responses; cookies travel via `cookies`. */
64
93
  multiValueHeaders?: never;
94
+ /** Response body; base64-encoded when {@link LambdaResponseV2.isBase64Encoded} is true. */
65
95
  body: string;
96
+ /** True when `body` is base64-encoded (non-text content types). */
66
97
  isBase64Encoded: boolean;
67
98
  }
68
99
  /** Either response shape produced by {@link toLambdaHandler}, chosen automatically per event. */
69
100
  export type LambdaResponse = LambdaResponseV1 | LambdaResponseV2;
70
101
  /** Async handler shape consumed by AWS Lambda / Netlify Functions runtimes. */
71
102
  export type LambdaHandler = (event: LambdaEvent) => Promise<LambdaResponse>;
72
- /** Wrap an {@link App} as a Lambda/Netlify handler accepting either v1.0 or v2.0 event payloads. */
103
+ /**
104
+ * Wrap an {@link App} as a Lambda/Netlify handler accepting either v1.0 or v2.0 event payloads.
105
+ *
106
+ * @param app - The DaloyJS {@link App} that serves each translated request.
107
+ * @returns A {@link LambdaHandler} that converts the event to a `Request`, calls {@link App.fetch}, and emits the matching v1.0/v2.0 response shape.
108
+ */
73
109
  export declare function toLambdaHandler(app: App): LambdaHandler;
@@ -1,5 +1,10 @@
1
1
  const TEXT_TYPE_RE = /^(text\/|application\/(json|xml|javascript|x-www-form-urlencoded|.*\+json|.*\+xml))/i;
2
- /** Wrap an {@link App} as a Lambda/Netlify handler accepting either v1.0 or v2.0 event payloads. */
2
+ /**
3
+ * Wrap an {@link App} as a Lambda/Netlify handler accepting either v1.0 or v2.0 event payloads.
4
+ *
5
+ * @param app - The DaloyJS {@link App} that serves each translated request.
6
+ * @returns A {@link LambdaHandler} that converts the event to a `Request`, calls {@link App.fetch}, and emits the matching v1.0/v2.0 response shape.
7
+ */
3
8
  export function toLambdaHandler(app) {
4
9
  return async (event) => {
5
10
  const request = eventToRequest(event);
@@ -6,7 +6,9 @@ import { type Server } from "node:http";
6
6
  import type { App } from "../app.js";
7
7
  /** Options for the Node.js {@link serve} entry point. */
8
8
  export interface NodeServerOptions {
9
+ /** TCP port to listen on. Defaults to `3000`. */
9
10
  port?: number;
11
+ /** Interface to bind. Defaults to `"0.0.0.0"`. */
10
12
  hostname?: string;
11
13
  /**
12
14
  * Connection-level timeout in ms, applied to BOTH `headersTimeout` and
@@ -72,9 +74,18 @@ export interface NodeServerOptions {
72
74
  }
73
75
  /** Handle returned by {@link serve} exposing the underlying Node `Server` plus a `close()` for graceful shutdown. */
74
76
  export interface NodeServerHandle {
77
+ /** The underlying `node:http` `Server` instance, for advanced wiring (extra listeners, address introspection). */
75
78
  server: Server;
79
+ /** Port the server was asked to listen on ({@link NodeServerOptions.port}, default `3000`). */
76
80
  port: number;
81
+ /** Graceful shutdown: drains {@link App.shutdown} hooks, destroys WebSocket sockets, then closes the server. Idempotent. */
77
82
  close(): Promise<void>;
78
83
  }
79
- /** Start a Node.js HTTP (and optional WebSocket) server bound to the given {@link App}. */
84
+ /**
85
+ * Start a Node.js HTTP (and optional WebSocket) server bound to the given {@link App}.
86
+ *
87
+ * @param app - The DaloyJS {@link App} whose `fetch` (and WebSocket routes) serve requests.
88
+ * @param opts - Listener, timeout, proxy-trust, and hardening options; see {@link NodeServerOptions}.
89
+ * @returns A {@link NodeServerHandle} exposing the Node `Server`, the `port`, and a graceful `close()`.
90
+ */
80
91
  export declare function serve(app: App, opts?: NodeServerOptions): NodeServerHandle;
@@ -7,7 +7,13 @@ import { Readable } from "node:stream";
7
7
  import { DALOY_RAW_BODY, DALOY_RAW_STREAM, DALOY_REQUEST_RAW_BODY } from "../app.js";
8
8
  import { setClientCertificate, normalizePeerCertificate, } from "../mtls.js";
9
9
  import { FrameSink, encodeFrame, encodeClosePayload, encodeSendPayload, validateUpgrade, validateSelectedSubprotocol, checkWebSocketOrigin, WS_OPCODE, WS_CLOSE_CODE, WS_READY_STATE, WS_MAX_CONTROL_PAYLOAD, WebSocketProtocolError, WebSocketPayloadTooLargeError, } from "../websocket.js";
10
- /** Start a Node.js HTTP (and optional WebSocket) server bound to the given {@link App}. */
10
+ /**
11
+ * Start a Node.js HTTP (and optional WebSocket) server bound to the given {@link App}.
12
+ *
13
+ * @param app - The DaloyJS {@link App} whose `fetch` (and WebSocket routes) serve requests.
14
+ * @param opts - Listener, timeout, proxy-trust, and hardening options; see {@link NodeServerOptions}.
15
+ * @returns A {@link NodeServerHandle} exposing the Node `Server`, the `port`, and a graceful `close()`.
16
+ */
11
17
  export function serve(app, opts = {}) {
12
18
  const trustProxy = opts.trustProxy === true;
13
19
  const bufferedBodyMaxBytes = typeof opts.bufferedBodyMaxBytes === "number" && opts.bufferedBodyMaxBytes >= 0
@@ -23,16 +23,25 @@ import type { App } from "../app.js";
23
23
  export type WebHandler = (req: Request) => Promise<Response>;
24
24
  /** Default export shape for Vercel's web-standard `{ fetch }` runtime. */
25
25
  export interface FetchHandler {
26
+ /** Request entry point: forwards the request to {@link App.fetch}. */
26
27
  fetch: WebHandler;
27
28
  }
28
29
  declare const NEXT_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
29
30
  /** Record of per-method handlers expected by a Next.js App Router `route.ts` file. */
30
31
  export type RouteHandlers = Record<(typeof NEXT_METHODS)[number], WebHandler>;
31
- /** Wrap an {@link App} as a single web-standard fetch handler. */
32
+ /**
33
+ * Wrap an {@link App} as a single web-standard fetch handler.
34
+ *
35
+ * @param app - The DaloyJS {@link App} that serves each incoming request.
36
+ * @returns A {@link WebHandler} delegating to {@link App.fetch}.
37
+ */
32
38
  export declare function toWebHandler(app: App): WebHandler;
33
39
  /**
34
40
  * Build the default `{ fetch }` export expected by Vercel Node.js Functions
35
41
  * in the `/api` directory.
42
+ *
43
+ * @param app - The DaloyJS {@link App} that serves each incoming request.
44
+ * @returns A {@link FetchHandler} object suitable as the module's `export default`.
36
45
  */
37
46
  export declare function toFetchHandler(app: App): FetchHandler;
38
47
  /** Backward-compatible alias for {@link toWebHandler}. */
@@ -40,6 +49,9 @@ export declare const toEdgeHandler: typeof toWebHandler;
40
49
  /**
41
50
  * Build the `{ GET, POST, ... }` object expected by Next.js App Router
42
51
  * `route.ts` files when a DaloyJS app is mounted inside an existing Next app.
52
+ *
53
+ * @param app - The DaloyJS {@link App} that serves each incoming request.
54
+ * @returns A {@link RouteHandlers} record mapping every supported HTTP method to the same {@link WebHandler}.
43
55
  */
44
56
  export declare function toRouteHandlers(app: App): RouteHandlers;
45
57
  export {};
@@ -1,11 +1,19 @@
1
1
  const NEXT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
2
- /** Wrap an {@link App} as a single web-standard fetch handler. */
2
+ /**
3
+ * Wrap an {@link App} as a single web-standard fetch handler.
4
+ *
5
+ * @param app - The DaloyJS {@link App} that serves each incoming request.
6
+ * @returns A {@link WebHandler} delegating to {@link App.fetch}.
7
+ */
3
8
  export function toWebHandler(app) {
4
9
  return (req) => app.fetch(req);
5
10
  }
6
11
  /**
7
12
  * Build the default `{ fetch }` export expected by Vercel Node.js Functions
8
13
  * in the `/api` directory.
14
+ *
15
+ * @param app - The DaloyJS {@link App} that serves each incoming request.
16
+ * @returns A {@link FetchHandler} object suitable as the module's `export default`.
9
17
  */
10
18
  export function toFetchHandler(app) {
11
19
  return { fetch: toWebHandler(app) };
@@ -15,6 +23,9 @@ export const toEdgeHandler = toWebHandler;
15
23
  /**
16
24
  * Build the `{ GET, POST, ... }` object expected by Next.js App Router
17
25
  * `route.ts` files when a DaloyJS app is mounted inside an existing Next app.
26
+ *
27
+ * @param app - The DaloyJS {@link App} that serves each incoming request.
28
+ * @returns A {@link RouteHandlers} record mapping every supported HTTP method to the same {@link WebHandler}.
18
29
  */
19
30
  export function toRouteHandlers(app) {
20
31
  const handler = toWebHandler(app);
package/dist/app.d.ts CHANGED
@@ -51,7 +51,9 @@ export type SecurityPreset = "internal-service";
51
51
  export interface AppOptions {
52
52
  /** OpenAPI document metadata */
53
53
  title?: string;
54
+ /** OpenAPI `info.version`. Falls back to the host package manifest version, then `"0.0.0"`. */
54
55
  version?: string;
56
+ /** OpenAPI `info.description`. Falls back to the host package manifest description. */
55
57
  description?: string;
56
58
  /**
57
59
  * Topology-aware security posture preset. See {@link SecurityPreset}.
@@ -350,9 +352,13 @@ export interface AppOptions {
350
352
  * @since 0.3.0
351
353
  */
352
354
  export interface AppOpenAPIOptions {
355
+ /** OpenAPI `info` object. `title`/`version` fall back to {@link AppOptions.title} / {@link AppOptions.version}, then the host package manifest. */
353
356
  info?: Partial<OpenAPIInfo>;
357
+ /** `servers` array published in the generated document. */
354
358
  servers?: OpenAPIOptions["servers"];
359
+ /** Security schemes stitched into `components.securitySchemes`. */
355
360
  securitySchemes?: OpenAPIOptions["securitySchemes"];
361
+ /** OpenAPI 3.1 top-level `webhooks` operations, keyed by webhook name. */
356
362
  webhooks?: OpenAPIOptions["webhooks"];
357
363
  }
358
364
  /**
@@ -684,18 +690,31 @@ export interface CspReportRouteOptions {
684
690
  * @since 0.1.0
685
691
  */
686
692
  export interface IntrospectedRoute {
693
+ /** HTTP method the route responds to (e.g. `"GET"`). */
687
694
  method: HttpMethod;
695
+ /** Route path pattern as registered, including any group prefix (e.g. `/users/:id`). */
688
696
  path: string;
697
+ /** OpenAPI operationId, when declared on the route. */
689
698
  operationId?: string;
699
+ /** OpenAPI tags, when declared on the route or inherited from a group. */
690
700
  tags?: string[];
701
+ /** One-line OpenAPI summary, when declared. */
691
702
  summary?: string;
703
+ /** Longer OpenAPI description, when declared. */
692
704
  description?: string;
705
+ /** `true` when the route is marked deprecated in OpenAPI. */
693
706
  deprecated?: boolean;
707
+ /** Whether the route declares a request body schema. */
694
708
  hasBody: boolean;
709
+ /** Whether the route declares a query-string schema. */
695
710
  hasQuery: boolean;
711
+ /** Whether the route declares a path-params schema. */
696
712
  hasParams: boolean;
713
+ /** Whether the route declares a request-headers schema. */
697
714
  hasHeaders: boolean;
715
+ /** Declared response status codes (e.g. `[200, 404]`). */
698
716
  responses: number[];
717
+ /** Security scheme name and required scopes, when the route declares `auth`. */
699
718
  auth?: {
700
719
  scheme: string;
701
720
  scopes?: string[];
@@ -799,10 +818,28 @@ export declare const DALOY_RAW_STREAM: unique symbol;
799
818
  */
800
819
  type AppendRoute<Routes extends readonly RouteDefinition<any, any, any, any>[], R extends RouteDefinition<any, any, any, any>> = readonly RouteDefinition<any, any, any, any>[] extends Routes ? readonly [R] : readonly [...Routes, R];
801
820
  /**
821
+ * The DaloyJS application: a contract-first router plus a web-standard
822
+ * `fetch(Request): Promise<Response>` handler that runs unchanged on Node,
823
+ * Bun, Deno, Cloudflare Workers, and Vercel via the adapters.
824
+ *
825
+ * Routes registered with {@link App.route} are validated against their
826
+ * declared schemas on the way in and out, accumulate in the typed
827
+ * {@link App.routes} tuple (powering OpenAPI generation and the typed
828
+ * client), and are served with secure defaults: body limits, request
829
+ * timeouts, security headers, and prod-mode error redaction.
830
+ *
831
+ * @typeParam Routes - Compile-time tuple of registered route definitions,
832
+ * grown by each {@link App.route} call.
802
833
  * @since 0.1.0
803
834
  */
804
835
  export declare class App<Routes extends readonly RouteDefinition<any, any, any, any>[] = readonly RouteDefinition<any, any, any, any>[]> {
836
+ /**
837
+ * Resolved constructor options. The security-relevant trio is always
838
+ * populated: `validateResponses` (default `true`), `bodyLimitBytes`
839
+ * (default 1 MiB), and `requestTimeoutMs` (default 30_000 ms).
840
+ */
805
841
  readonly options: Required<Pick<AppOptions, "validateResponses" | "bodyLimitBytes" | "requestTimeoutMs">> & AppOptions;
842
+ /** Structured logger for the app. Defaults to a JSON-lines console logger; override via `options.logger`. */
806
843
  readonly log: Logger;
807
844
  /**
808
845
  * Public registry: enables OpenAPI gen, typed-client gen, dead-route detection.
@@ -1312,6 +1349,14 @@ export declare class App<Routes extends readonly RouteDefinition<any, any, any,
1312
1349
  * @returns This `App` instance for chaining.
1313
1350
  */
1314
1351
  use(hooks: Hooks): this;
1352
+ /**
1353
+ * Apply an ordered list of plugin extensions to the
1354
+ * group-level hook chain. Each extension's `handler` is wrapped into a
1355
+ * single-event {@link Hooks} bundle so subsequent route registrations
1356
+ * pick it up via the normal hook composition path.
1357
+ * @internal
1358
+ */
1359
+ private applyExtensions;
1315
1360
  /**
1316
1361
  * Decorate `ctx.state` with a value available inside every handler and hook.
1317
1362
  *
@@ -1334,16 +1379,10 @@ export declare class App<Routes extends readonly RouteDefinition<any, any, any,
1334
1379
  *
1335
1380
  * @param key - Property name on `ctx.state`.
1336
1381
  * @param value - Value bound to that property on every request.
1382
+ * @param opts - Pass `{ override: true }` to replace an existing decoration (logged as a warning).
1337
1383
  * @returns This `App` instance for chaining.
1384
+ * @throws Error if `key` is already decorated and `opts.override` is not `true`.
1338
1385
  */
1339
- /**
1340
- * Apply an ordered list of plugin extensions to the
1341
- * group-level hook chain. Each extension's `handler` is wrapped into a
1342
- * single-event {@link Hooks} bundle so subsequent route registrations
1343
- * pick it up via the normal hook composition path.
1344
- * @internal
1345
- */
1346
- private applyExtensions;
1347
1386
  decorate<K extends string, V>(key: K, value: V, opts?: {
1348
1387
  override?: boolean;
1349
1388
  }): this;
@@ -1474,13 +1513,6 @@ export declare class App<Routes extends readonly RouteDefinition<any, any, any,
1474
1513
  * @returns Fulfills with the `Response` produced by the matching handler.
1475
1514
  */
1476
1515
  request(input: string | URL | Request, init?: RequestInit): Promise<Response>;
1477
- /**
1478
- * Return a JSON-serializable summary of every registered route. Useful for
1479
- * dead-route detection, dashboards, and tests that want to assert against
1480
- * the route table without parsing the OpenAPI document.
1481
- *
1482
- * @returns Array of one {@link IntrospectedRoute} per registered route.
1483
- */
1484
1516
  /**
1485
1517
  * Emit a one-time development warning when any route declares a `2xx`
1486
1518
  * response without a body schema, because response-field stripping
@@ -1490,6 +1522,13 @@ export declare class App<Routes extends readonly RouteDefinition<any, any, any,
1490
1522
  * `secureDefaults: false`. See {@link findRoutesMissingResponseBodySchema}.
1491
1523
  */
1492
1524
  private warnMissingResponseBodySchemas;
1525
+ /**
1526
+ * Return a JSON-serializable summary of every registered route. Useful for
1527
+ * dead-route detection, dashboards, and tests that want to assert against
1528
+ * the route table without parsing the OpenAPI document.
1529
+ *
1530
+ * @returns Array of one {@link IntrospectedRoute} per registered route.
1531
+ */
1493
1532
  introspect(): IntrospectedRoute[];
1494
1533
  /**
1495
1534
  * Begin graceful shutdown.
@@ -1532,6 +1571,12 @@ export declare class App<Routes extends readonly RouteDefinition<any, any, any,
1532
1571
  /**
1533
1572
  * Topological sort of plugin extensions. Refuses-at-call
1534
1573
  * on cyclic ordering with a structured error naming the cycle.
1574
+ *
1575
+ * @param exts - Extensions to order by their `before` / `after` constraints (Kahn's algorithm).
1576
+ * @returns The extensions in a valid execution order.
1577
+ * @throws Error on duplicate extension names, on a `before`/`after` cycle
1578
+ * (naming the members), or when two extensions mutate the same response
1579
+ * header without declaring an ordering between each other.
1535
1580
  * @internal
1536
1581
  */
1537
1582
  export declare function topoSortExtensions(exts: ReadonlyArray<PluginExtension>): PluginExtension[];
@@ -1550,13 +1595,16 @@ export declare function topoSortExtensions(exts: ReadonlyArray<PluginExtension>)
1550
1595
  * It powers both the `daloy doctor` `audit.response.bodySchema` finding and
1551
1596
  * the development-mode boot warning emitted on the first request. The result
1552
1597
  * is advisory — a route may legitimately return no body — so callers treat
1553
- * it as a `warn`, never a hard error.
1598
+ * it as a `warn`, never a hard error. Routes that set
1599
+ * `acknowledgeNoResponseBodySchema: true` (including the framework-mounted
1600
+ * docs, AsyncAPI, health, and metrics routes, whose bodies DaloyJS itself
1601
+ * serializes) are skipped: the missing schema is declared intentional.
1554
1602
  *
1555
1603
  * @param routes - Route definitions to inspect (typically `app.routes`).
1556
1604
  * @returns One entry per offending route with the affected `2xx` status codes.
1557
1605
  * @since 0.40.0
1558
1606
  */
1559
- export declare function findRoutesMissingResponseBodySchema(routes: readonly Pick<RouteDefinition<any, any, any, any>, "method" | "path" | "responses">[]): Array<{
1607
+ export declare function findRoutesMissingResponseBodySchema(routes: readonly Pick<RouteDefinition<any, any, any, any>, "method" | "path" | "responses" | "acknowledgeNoResponseBodySchema">[]): Array<{
1560
1608
  method: string;
1561
1609
  path: string;
1562
1610
  statuses: number[];
@@ -1575,6 +1623,8 @@ export declare function findRoutesMissingResponseBodySchema(routes: readonly Pic
1575
1623
  * for ergonomics and matches the factory pattern used by Express, Fastify,
1576
1624
  * and Hono adapters.
1577
1625
  *
1626
+ * @param options - Same {@link AppOptions} accepted by the `App` constructor. Default: `{}`.
1627
+ * @returns A new {@link App} instance.
1578
1628
  * @since 0.3.0
1579
1629
  */
1580
1630
  export declare function createApp(options?: AppOptions): App;