@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/dist/app.js CHANGED
@@ -195,10 +195,28 @@ export const DALOY_REQUEST_RAW_BODY = Symbol.for("daloyjs.request.rawBody");
195
195
  */
196
196
  export const DALOY_RAW_STREAM = Symbol.for("daloyjs.response.rawStream");
197
197
  /**
198
+ * The DaloyJS application: a contract-first router plus a web-standard
199
+ * `fetch(Request): Promise<Response>` handler that runs unchanged on Node,
200
+ * Bun, Deno, Cloudflare Workers, and Vercel via the adapters.
201
+ *
202
+ * Routes registered with {@link App.route} are validated against their
203
+ * declared schemas on the way in and out, accumulate in the typed
204
+ * {@link App.routes} tuple (powering OpenAPI generation and the typed
205
+ * client), and are served with secure defaults: body limits, request
206
+ * timeouts, security headers, and prod-mode error redaction.
207
+ *
208
+ * @typeParam Routes - Compile-time tuple of registered route definitions,
209
+ * grown by each {@link App.route} call.
198
210
  * @since 0.1.0
199
211
  */
200
212
  export class App {
213
+ /**
214
+ * Resolved constructor options. The security-relevant trio is always
215
+ * populated: `validateResponses` (default `true`), `bodyLimitBytes`
216
+ * (default 1 MiB), and `requestTimeoutMs` (default 30_000 ms).
217
+ */
201
218
  options;
219
+ /** Structured logger for the app. Defaults to a JSON-lines console logger; override via `options.logger`. */
202
220
  log;
203
221
  /**
204
222
  * Public registry: enables OpenAPI gen, typed-client gen, dead-route detection.
@@ -932,6 +950,9 @@ export class App {
932
950
  operationId: "getOpenAPIDocument",
933
951
  ...(tags.length ? { tags } : {}),
934
952
  summary: "OpenAPI 3.1 document",
953
+ // Framework-owned body (the generated spec document); the missing
954
+ // schema is intentional, not an OWASP API3 leak risk.
955
+ acknowledgeNoResponseBodySchema: true,
935
956
  responses: {
936
957
  200: { description: "OpenAPI 3.1 document for this application." },
937
958
  },
@@ -947,6 +968,7 @@ export class App {
947
968
  operationId: "getOpenAPIDocumentYaml",
948
969
  ...(tags.length ? { tags } : {}),
949
970
  summary: "OpenAPI 3.1 document (YAML)",
971
+ acknowledgeNoResponseBodySchema: true,
950
972
  responses: {
951
973
  200: { description: "OpenAPI 3.1 document for this application, in YAML." },
952
974
  },
@@ -985,6 +1007,7 @@ export class App {
985
1007
  operationId: "getDocsUI",
986
1008
  ...(tags.length ? { tags } : {}),
987
1009
  summary: "Interactive API reference",
1010
+ acknowledgeNoResponseBodySchema: true,
988
1011
  responses: {
989
1012
  200: { description: "Interactive API documentation UI." },
990
1013
  },
@@ -1082,6 +1105,8 @@ export class App {
1082
1105
  operationId: "getAsyncAPIDocument",
1083
1106
  ...(tags.length ? { tags } : {}),
1084
1107
  summary: "AsyncAPI 3.0 document",
1108
+ // Framework-owned bodies on the AsyncAPI surface, like mountDocs above.
1109
+ acknowledgeNoResponseBodySchema: true,
1085
1110
  responses: {
1086
1111
  200: { description: "AsyncAPI 3.0 document for this application's WebSocket channels." },
1087
1112
  },
@@ -1094,6 +1119,7 @@ export class App {
1094
1119
  operationId: "getAsyncAPIDocumentYaml",
1095
1120
  ...(tags.length ? { tags } : {}),
1096
1121
  summary: "AsyncAPI 3.0 document (YAML)",
1122
+ acknowledgeNoResponseBodySchema: true,
1097
1123
  responses: {
1098
1124
  200: { description: "AsyncAPI 3.0 document for this application, in YAML." },
1099
1125
  },
@@ -1115,6 +1141,7 @@ export class App {
1115
1141
  operationId: "getAsyncAPIUI",
1116
1142
  ...(tags.length ? { tags } : {}),
1117
1143
  summary: "Interactive AsyncAPI reference",
1144
+ acknowledgeNoResponseBodySchema: true,
1118
1145
  responses: {
1119
1146
  200: { description: "Interactive AsyncAPI documentation UI." },
1120
1147
  },
@@ -1443,6 +1470,8 @@ export class App {
1443
1470
  operationId: "metrics",
1444
1471
  tags: ["Observability"],
1445
1472
  summary: "Prometheus metrics scrape endpoint",
1473
+ // Prometheus text exposition rendered by the framework registry.
1474
+ acknowledgeNoResponseBodySchema: true,
1446
1475
  handler: async ({ request }) => {
1447
1476
  if (buckets && rateLimitConfig) {
1448
1477
  const key = healthRouteKey(request);
@@ -1561,6 +1590,8 @@ export class App {
1561
1590
  operationId: isHealth ? "healthcheck" : "readinesscheck",
1562
1591
  tags: ["Health"],
1563
1592
  summary: isHealth ? "Liveness probe" : "Readiness probe",
1593
+ // Framework-serialized probe payload; missing schema is intentional.
1594
+ acknowledgeNoResponseBodySchema: true,
1564
1595
  handler: async ({ request }) => {
1565
1596
  if (buckets && rateLimitConfig) {
1566
1597
  const key = healthRouteKey(request);
@@ -1799,6 +1830,20 @@ export class App {
1799
1830
  this.resetBootGuardCache();
1800
1831
  return this;
1801
1832
  }
1833
+ /**
1834
+ * Apply an ordered list of plugin extensions to the
1835
+ * group-level hook chain. Each extension's `handler` is wrapped into a
1836
+ * single-event {@link Hooks} bundle so subsequent route registrations
1837
+ * pick it up via the normal hook composition path.
1838
+ * @internal
1839
+ */
1840
+ applyExtensions(ordered) {
1841
+ for (const ext of ordered) {
1842
+ const hooks = { [ext.event]: ext.handler };
1843
+ this.groupHooks.push(hooks);
1844
+ }
1845
+ this._coldPathHooksCache = undefined;
1846
+ }
1802
1847
  /**
1803
1848
  * Decorate `ctx.state` with a value available inside every handler and hook.
1804
1849
  *
@@ -1821,22 +1866,10 @@ export class App {
1821
1866
  *
1822
1867
  * @param key - Property name on `ctx.state`.
1823
1868
  * @param value - Value bound to that property on every request.
1869
+ * @param opts - Pass `{ override: true }` to replace an existing decoration (logged as a warning).
1824
1870
  * @returns This `App` instance for chaining.
1871
+ * @throws Error if `key` is already decorated and `opts.override` is not `true`.
1825
1872
  */
1826
- /**
1827
- * Apply an ordered list of plugin extensions to the
1828
- * group-level hook chain. Each extension's `handler` is wrapped into a
1829
- * single-event {@link Hooks} bundle so subsequent route registrations
1830
- * pick it up via the normal hook composition path.
1831
- * @internal
1832
- */
1833
- applyExtensions(ordered) {
1834
- for (const ext of ordered) {
1835
- const hooks = { [ext.event]: ext.handler };
1836
- this.groupHooks.push(hooks);
1837
- }
1838
- this._coldPathHooksCache = undefined;
1839
- }
1840
1873
  decorate(key, value, opts = {}) {
1841
1874
  if (Object.prototype.hasOwnProperty.call(this.decorations, key) && opts.override !== true) {
1842
1875
  // Namespace-protected decorators. Refuse to silently
@@ -2440,13 +2473,6 @@ export class App {
2440
2473
  const req = url instanceof Request ? url : new Request(url, init);
2441
2474
  return this.fetch(req);
2442
2475
  }
2443
- /**
2444
- * Return a JSON-serializable summary of every registered route. Useful for
2445
- * dead-route detection, dashboards, and tests that want to assert against
2446
- * the route table without parsing the OpenAPI document.
2447
- *
2448
- * @returns Array of one {@link IntrospectedRoute} per registered route.
2449
- */
2450
2476
  /**
2451
2477
  * Emit a one-time development warning when any route declares a `2xx`
2452
2478
  * response without a body schema, because response-field stripping
@@ -2469,9 +2495,18 @@ export class App {
2469
2495
  routes: offending.slice(0, 20),
2470
2496
  }, `${offending.length} route(s) declare a 2xx response with no body schema; ` +
2471
2497
  "response field-level stripping (OWASP API3) is not applied there, so a handler that " +
2472
- "returns undeclared fields will leak them. Declare a response body schema, or ignore if " +
2473
- "the route intentionally returns no body. Run `daloy doctor` to list them.");
2498
+ "returns undeclared fields will leak them. Declare a response body schema, or set " +
2499
+ "`acknowledgeNoResponseBodySchema: true` on routes that intentionally return an opaque " +
2500
+ "or body-less response. Run `daloy doctor` to list them. " +
2501
+ "See https://daloyjs.dev/docs/security/owasp-api-top-10#api3");
2474
2502
  }
2503
+ /**
2504
+ * Return a JSON-serializable summary of every registered route. Useful for
2505
+ * dead-route detection, dashboards, and tests that want to assert against
2506
+ * the route table without parsing the OpenAPI document.
2507
+ *
2508
+ * @returns Array of one {@link IntrospectedRoute} per registered route.
2509
+ */
2475
2510
  introspect() {
2476
2511
  return this.routes.map((r) => {
2477
2512
  const route = {
@@ -2639,6 +2674,12 @@ function detectHeaderMutatingMiddleware(layers) {
2639
2674
  /**
2640
2675
  * Topological sort of plugin extensions. Refuses-at-call
2641
2676
  * on cyclic ordering with a structured error naming the cycle.
2677
+ *
2678
+ * @param exts - Extensions to order by their `before` / `after` constraints (Kahn's algorithm).
2679
+ * @returns The extensions in a valid execution order.
2680
+ * @throws Error on duplicate extension names, on a `before`/`after` cycle
2681
+ * (naming the members), or when two extensions mutate the same response
2682
+ * header without declaring an ordering between each other.
2642
2683
  * @internal
2643
2684
  */
2644
2685
  export function topoSortExtensions(exts) {
@@ -3018,7 +3059,10 @@ function hasRequestSchema(request, key) {
3018
3059
  * It powers both the `daloy doctor` `audit.response.bodySchema` finding and
3019
3060
  * the development-mode boot warning emitted on the first request. The result
3020
3061
  * is advisory — a route may legitimately return no body — so callers treat
3021
- * it as a `warn`, never a hard error.
3062
+ * it as a `warn`, never a hard error. Routes that set
3063
+ * `acknowledgeNoResponseBodySchema: true` (including the framework-mounted
3064
+ * docs, AsyncAPI, health, and metrics routes, whose bodies DaloyJS itself
3065
+ * serializes) are skipped: the missing schema is declared intentional.
3022
3066
  *
3023
3067
  * @param routes - Route definitions to inspect (typically `app.routes`).
3024
3068
  * @returns One entry per offending route with the affected `2xx` status codes.
@@ -3027,6 +3071,8 @@ function hasRequestSchema(request, key) {
3027
3071
  export function findRoutesMissingResponseBodySchema(routes) {
3028
3072
  const offending = [];
3029
3073
  for (const route of routes) {
3074
+ if (route.acknowledgeNoResponseBodySchema === true)
3075
+ continue;
3030
3076
  const statuses = [];
3031
3077
  const responses = route.responses;
3032
3078
  for (const key of Object.keys(responses)) {
@@ -3238,15 +3284,33 @@ async function readBody(req, ct, limit, multipart) {
3238
3284
  // in memory on runtimes whose adapter does not cap at the socket layer
3239
3285
  // (Workers / Deno / Vercel). `readBodyLimited` streams the body and
3240
3286
  // throws `PayloadTooLargeError` the instant it exceeds `limit`; we then
3241
- // re-parse the bounded bytes with the standard `formData()` parser,
3242
- // preserving the multipart boundary via the original Content-Type. This is
3287
+ // re-parse the bounded bytes with the standard `formData()` parser. This is
3243
3288
  // Web-standard only (`Request` + `formData`), so it stays runtime-portable.
3244
3289
  const bytes = await readBodyLimited(req, limit);
3245
- const fd = await new Request(req.url, {
3246
- method: "POST",
3247
- headers: { "content-type": ct },
3248
- body: bytes,
3249
- }).formData();
3290
+ // The `ct` argument is lower-cased by the caller for case-insensitive
3291
+ // media-type matching, but multipart boundaries are case-SENSITIVE (RFC
3292
+ // 2046 §5.1.1). Reconstructing the Request with the lower-cased boundary
3293
+ // would stop it matching the delimiter in the raw body, so the platform
3294
+ // parser throws and every upload from a client that uses a mixed-case
3295
+ // boundary — Chromium/WebKit browsers (`----WebKitFormBoundary…`) and curl
3296
+ // (`------------------------…`) — fails. Re-read the ORIGINAL-case
3297
+ // Content-Type header to preserve the boundary verbatim.
3298
+ const rawContentType = req.headers.get("content-type") ?? ct;
3299
+ let fd;
3300
+ try {
3301
+ fd = await new Request(req.url, {
3302
+ method: "POST",
3303
+ headers: { "content-type": rawContentType },
3304
+ body: bytes,
3305
+ }).formData();
3306
+ }
3307
+ catch {
3308
+ // A body the platform parser cannot read is a malformed client request,
3309
+ // not a server fault: surface it as an RFC 9457 400 rather than letting
3310
+ // the raw parser TypeError bubble up as a generic 500 (which would also
3311
+ // pollute server-error monitoring with client-controlled input).
3312
+ throw new BadRequestError("Malformed multipart/form-data body");
3313
+ }
3250
3314
  const out = {};
3251
3315
  let fields = 0;
3252
3316
  let files = 0;
@@ -3449,6 +3513,8 @@ function serializeErr(err) {
3449
3513
  * for ergonomics and matches the factory pattern used by Express, Fastify,
3450
3514
  * and Hono adapters.
3451
3515
  *
3516
+ * @param options - Same {@link AppOptions} accepted by the `App` constructor. Default: `{}`.
3517
+ * @returns A new {@link App} instance.
3452
3518
  * @since 0.3.0
3453
3519
  */
3454
3520
  export function createApp(options = {}) {
package/dist/banner.d.ts CHANGED
@@ -30,10 +30,16 @@ export interface StartupBannerOptions {
30
30
  /**
31
31
  * Build the multi-line startup banner string without printing it. Useful for
32
32
  * tests, custom loggers, or wrapping the output in additional context.
33
+ *
34
+ * @param options Banner content and rendering flags ({@link StartupBannerOptions}).
35
+ * @returns The framed banner as a single string with `\n` line separators.
33
36
  */
34
37
  export declare function formatStartupBanner(options: StartupBannerOptions): string;
35
38
  /**
36
39
  * Print {@link formatStartupBanner} to stdout (or a custom writer). Designed to
37
40
  * replace ad-hoc `console.log("listening on …")` calls in starter templates.
41
+ *
42
+ * @param options Banner content and rendering flags ({@link StartupBannerOptions}).
43
+ * @param write Output sink for the banner text. Defaults to `process.stdout.write`.
38
44
  */
39
45
  export declare function printStartupBanner(options: StartupBannerOptions, write?: (s: string) => void): void;
package/dist/banner.js CHANGED
@@ -86,6 +86,9 @@ function visibleWidth(s) {
86
86
  /**
87
87
  * Build the multi-line startup banner string without printing it. Useful for
88
88
  * tests, custom loggers, or wrapping the output in additional context.
89
+ *
90
+ * @param options Banner content and rendering flags ({@link StartupBannerOptions}).
91
+ * @returns The framed banner as a single string with `\n` line separators.
89
92
  */
90
93
  export function formatStartupBanner(options) {
91
94
  const useColor = options.color ?? detectColor();
@@ -126,6 +129,9 @@ export function formatStartupBanner(options) {
126
129
  /**
127
130
  * Print {@link formatStartupBanner} to stdout (or a custom writer). Designed to
128
131
  * replace ad-hoc `console.log("listening on …")` calls in starter templates.
132
+ *
133
+ * @param options Banner content and rendering flags ({@link StartupBannerOptions}).
134
+ * @param write Output sink for the banner text. Defaults to `process.stdout.write`.
129
135
  */
130
136
  export function printStartupBanner(options, write = (s) => process.stdout.write(s)) {
131
137
  write(`\n${formatStartupBanner(options)}\n\n`);
package/dist/cli.d.ts CHANGED
@@ -12,7 +12,9 @@
12
12
  import type { App } from "./app.js";
13
13
  /** I/O hooks used by {@link runCli} to read modules, write output, and spawn child processes. */
14
14
  export interface CliIO {
15
+ /** Write a chunk to standard output (tables, JSON/YAML dumps). */
15
16
  stdout: (chunk: string) => void;
17
+ /** Write a chunk to standard error (usage errors, diagnostics). */
16
18
  stderr: (chunk: string) => void;
17
19
  /** Resolve a user-provided entry specifier to a module to import. */
18
20
  importEntry: (specifier: string) => Promise<unknown>;
@@ -37,15 +39,22 @@ export interface CliIO {
37
39
  }
38
40
  /** Return value of {@link runCli}. The caller wires `exitCode` into `process.exit(...)`. */
39
41
  export interface CliResult {
42
+ /** Process exit code: `0` on success, `1` on load failures or failed checks, `2` on usage errors. */
40
43
  exitCode: number;
41
44
  }
42
45
  /** Parsed CLI flags accepted by {@link runCli}. See {@link parseArgs}. */
43
46
  export interface CliOptions {
47
+ /** `--json`: print machine-readable JSON instead of a table. */
44
48
  json: boolean;
49
+ /** `--check`: run the contract test suite; exit 1 on errors. */
45
50
  check: boolean;
51
+ /** `--schemas`: include per-route schema presence (body/query/params/headers). */
46
52
  schemas: boolean;
53
+ /** `--openapi`: print the App's OpenAPI 3.1 document. */
47
54
  openapi: boolean;
55
+ /** `--asyncapi`: print the AsyncAPI 3.0 document for the App's WebSocket surfaces. */
48
56
  asyncapi: boolean;
57
+ /** `--ai`: print the AI/codegen-friendly route-catalog dump ({@link buildAiDump}). */
49
58
  ai: boolean;
50
59
  /**
51
60
  * Output format for `--ai` and `--openapi`. Defaults to `"json"`.
@@ -56,10 +65,15 @@ export interface CliOptions {
56
65
  * @since 0.14.2
57
66
  */
58
67
  format?: "json" | "yaml";
68
+ /** `--tag <tag>`: only show routes that declare this tag. */
59
69
  tag?: string;
70
+ /** `--method <method>`: only show routes for this HTTP method (uppercased by {@link parseArgs}). */
60
71
  method?: string;
72
+ /** Entry file to load the App from (last positional argument). */
61
73
  entry?: string;
74
+ /** `-h` / `--help`: print usage and exit 0. */
62
75
  help: boolean;
76
+ /** `-v` / `--version`: print the CLI version and exit 0. */
63
77
  version: boolean;
64
78
  /** Override runtime detection for `daloy dev`. */
65
79
  runtime?: DevRuntime;
@@ -76,6 +90,7 @@ export type DevRuntime = "node" | "bun" | "deno";
76
90
  * Detect which JS runtime is hosting the CLI. Inspects
77
91
  * `globalThis.process.versions` for Bun/Deno markers; falls back to Node.
78
92
  *
93
+ * @returns `"bun"`, `"deno"`, or `"node"` (the default when no marker is found).
79
94
  * @since 0.3.0
80
95
  */
81
96
  export declare function detectRuntime(): DevRuntime;
@@ -92,6 +107,9 @@ export declare function detectRuntime(): DevRuntime;
92
107
  * `runCli()` callers and tests so the invariant holds regardless of how the
93
108
  * entry value was produced.
94
109
  *
110
+ * @param entry - Entry path to validate.
111
+ * @param context - Label prefixed to error messages (e.g. `"daloy dev"`).
112
+ * @throws Error when the path is empty, contains NUL/CR/LF, or starts with `-`.
95
113
  * @internal
96
114
  */
97
115
  export declare function assertSafeEntryPath(entry: string, context: string): void;
@@ -102,6 +120,8 @@ export declare function assertSafeEntryPath(entry: string, context: string): voi
102
120
  * paths like `src/server.ts` are rewritten to `./src/server.ts`. This is
103
121
  * the second half of the {@link assertSafeEntryPath} defense.
104
122
  *
123
+ * @param entry - Entry path to anchor.
124
+ * @returns The path unchanged if already absolute or `./`/`../`-anchored (Windows drive paths included); otherwise `./` + path.
105
125
  * @internal
106
126
  */
107
127
  export declare function normalizeEntryArg(entry: string): string;
@@ -124,6 +144,10 @@ export declare function normalizeEntryArg(entry: string): string;
124
144
  * `SECURITY.md` § "CLI threat model" for the full rationale (and why we
125
145
  * are not vulnerable to the class of bug Snyk reported as CVE-2022-22984).
126
146
  *
147
+ * @param runtime - Host runtime to build the command for.
148
+ * @param entry - Entry file to run; validated and `./`-anchored before use.
149
+ * @returns The executable name and argv array to pass to `spawn({ shell: false })`.
150
+ * @throws Error via {@link assertSafeEntryPath} when the entry path is unsafe.
127
151
  * @since 0.3.0
128
152
  */
129
153
  export declare function buildDevCommand(runtime: DevRuntime, entry: string): {
@@ -133,6 +157,10 @@ export declare function buildDevCommand(runtime: DevRuntime, entry: string): {
133
157
  /**
134
158
  * Parse a process-style argv (without the `node`/`daloy` prefix) into a
135
159
  * `{ command, opts }` pair. Throws on unknown flags or invalid enum values.
160
+ *
161
+ * @param argv - Arguments after the binary name, e.g. `["inspect", "--json", "./src/server.ts"]`.
162
+ * @returns The resolved command (default `"inspect"`) and parsed {@link CliOptions}.
163
+ * @throws Error on unknown `-`-prefixed flags, missing flag values, or invalid `--format`/`--runtime` values.
136
164
  */
137
165
  export declare function parseArgs(argv: readonly string[]): {
138
166
  command: string;
@@ -142,6 +170,10 @@ export declare function parseArgs(argv: readonly string[]): {
142
170
  * Execute the CLI against the supplied argv and {@link CliIO}. Does not read
143
171
  * `process.argv`, write to process stdio, or call `process.exit()` directly,
144
172
  * so tests can drive `inspect`/`dev`/`doctor` with in-memory stdio.
173
+ *
174
+ * @param argv - Process-style arguments (without the `node`/`daloy` prefix); see {@link parseArgs}.
175
+ * @param io - I/O hooks for stdout/stderr, module import, spawning, and file reads.
176
+ * @returns A {@link CliResult} whose `exitCode` the caller feeds to `process.exit()`.
145
177
  */
146
178
  export declare function runCli(argv: readonly string[], io: CliIO): Promise<CliResult>;
147
179
  /**
@@ -151,6 +183,9 @@ export declare function runCli(argv: readonly string[], io: CliIO): Promise<CliR
151
183
  * stable and self-describing so LLMs and SDK builders can consume it
152
184
  * without round-tripping through OpenAPI.
153
185
  *
186
+ * @param app - Loaded App whose route registry is dumped.
187
+ * @param opts - Parsed CLI options; `--tag` / `--method` filter the catalog.
188
+ * @returns JSON-serializable dump with `daloy.ai` version marker, `generatedAt`, and the `routes` array.
154
189
  * @since 0.14.0
155
190
  */
156
191
  export declare function buildAiDump(app: App, opts: CliOptions): Record<string, unknown>;
package/dist/cli.js CHANGED
@@ -118,6 +118,7 @@ const DEFAULT_DEV_ENTRIES = [
118
118
  * Detect which JS runtime is hosting the CLI. Inspects
119
119
  * `globalThis.process.versions` for Bun/Deno markers; falls back to Node.
120
120
  *
121
+ * @returns `"bun"`, `"deno"`, or `"node"` (the default when no marker is found).
121
122
  * @since 0.3.0
122
123
  */
123
124
  export function detectRuntime() {
@@ -142,6 +143,9 @@ export function detectRuntime() {
142
143
  * `runCli()` callers and tests so the invariant holds regardless of how the
143
144
  * entry value was produced.
144
145
  *
146
+ * @param entry - Entry path to validate.
147
+ * @param context - Label prefixed to error messages (e.g. `"daloy dev"`).
148
+ * @throws Error when the path is empty, contains NUL/CR/LF, or starts with `-`.
145
149
  * @internal
146
150
  */
147
151
  export function assertSafeEntryPath(entry, context) {
@@ -163,6 +167,8 @@ export function assertSafeEntryPath(entry, context) {
163
167
  * paths like `src/server.ts` are rewritten to `./src/server.ts`. This is
164
168
  * the second half of the {@link assertSafeEntryPath} defense.
165
169
  *
170
+ * @param entry - Entry path to anchor.
171
+ * @returns The path unchanged if already absolute or `./`/`../`-anchored (Windows drive paths included); otherwise `./` + path.
166
172
  * @internal
167
173
  */
168
174
  export function normalizeEntryArg(entry) {
@@ -194,6 +200,10 @@ export function normalizeEntryArg(entry) {
194
200
  * `SECURITY.md` § "CLI threat model" for the full rationale (and why we
195
201
  * are not vulnerable to the class of bug Snyk reported as CVE-2022-22984).
196
202
  *
203
+ * @param runtime - Host runtime to build the command for.
204
+ * @param entry - Entry file to run; validated and `./`-anchored before use.
205
+ * @returns The executable name and argv array to pass to `spawn({ shell: false })`.
206
+ * @throws Error via {@link assertSafeEntryPath} when the entry path is unsafe.
197
207
  * @since 0.3.0
198
208
  */
199
209
  export function buildDevCommand(runtime, entry) {
@@ -242,6 +252,10 @@ async function resolveDevEntry(entry) {
242
252
  /**
243
253
  * Parse a process-style argv (without the `node`/`daloy` prefix) into a
244
254
  * `{ command, opts }` pair. Throws on unknown flags or invalid enum values.
255
+ *
256
+ * @param argv - Arguments after the binary name, e.g. `["inspect", "--json", "./src/server.ts"]`.
257
+ * @returns The resolved command (default `"inspect"`) and parsed {@link CliOptions}.
258
+ * @throws Error on unknown `-`-prefixed flags, missing flag values, or invalid `--format`/`--runtime` values.
245
259
  */
246
260
  export function parseArgs(argv) {
247
261
  const opts = {
@@ -343,6 +357,10 @@ function readFlagValue(argv, index, flag) {
343
357
  * Execute the CLI against the supplied argv and {@link CliIO}. Does not read
344
358
  * `process.argv`, write to process stdio, or call `process.exit()` directly,
345
359
  * so tests can drive `inspect`/`dev`/`doctor` with in-memory stdio.
360
+ *
361
+ * @param argv - Process-style arguments (without the `node`/`daloy` prefix); see {@link parseArgs}.
362
+ * @param io - I/O hooks for stdout/stderr, module import, spawning, and file reads.
363
+ * @returns A {@link CliResult} whose `exitCode` the caller feeds to `process.exit()`.
346
364
  */
347
365
  export async function runCli(argv, io) {
348
366
  let parsed;
@@ -750,7 +768,8 @@ async function runDoctor(opts, io) {
750
768
  message: `${missingBody.length} route(s) declare a 2xx response with no body schema, so ` +
751
769
  `response field-level stripping (OWASP API3) is not applied: ${sample}` +
752
770
  `${missingBody.length > 5 ? ", …" : ""}. Declare a response body schema so undeclared ` +
753
- "handler fields cannot leak, or ignore if the route intentionally returns no body.",
771
+ "handler fields cannot leak, or set `acknowledgeNoResponseBodySchema: true` on routes " +
772
+ "that intentionally return an opaque or body-less response.",
754
773
  });
755
774
  }
756
775
  }
@@ -826,6 +845,9 @@ async function runDev(opts, io) {
826
845
  * stable and self-describing so LLMs and SDK builders can consume it
827
846
  * without round-tripping through OpenAPI.
828
847
  *
848
+ * @param app - Loaded App whose route registry is dumped.
849
+ * @param opts - Parsed CLI options; `--tag` / `--method` filter the catalog.
850
+ * @returns JSON-serializable dump with `daloy.ai` version marker, `generatedAt`, and the `routes` array.
829
851
  * @since 0.14.0
830
852
  */
831
853
  export function buildAiDump(app, opts) {
package/dist/combine.d.ts CHANGED
@@ -29,6 +29,8 @@ import type { Hooks, BaseContext } from "./types.js";
29
29
  * app.use(adminStack);
30
30
  * ```
31
31
  *
32
+ * @param layers Hook bundles to compose, executed in the order given.
33
+ * @returns A single merged {@link Hooks} bundle preserving all lifecycle phases.
32
34
  * @since 0.19.0
33
35
  */
34
36
  export declare function every(...layers: Hooks[]): Hooks;
@@ -61,6 +63,8 @@ export declare function every(...layers: Hooks[]): Hooks;
61
63
  * ));
62
64
  * ```
63
65
  *
66
+ * @param layers Candidate hook bundles; the first `beforeHandle` that passes wins.
67
+ * @returns A merged {@link Hooks} bundle with the OR-style `beforeHandle` strategy.
64
68
  * @since 0.19.0
65
69
  */
66
70
  export declare function some(...layers: Hooks[]): Hooks;
@@ -91,6 +95,10 @@ export type ExceptPredicate = string | string[] | ((ctx: BaseContext<any, any>)
91
95
  * exempted. Wrap each bundle with {@link except} individually when you
92
96
  * need to gate other phases.
93
97
  *
98
+ * @param when Paths or predicate ({@link ExceptPredicate}) that exempt a request.
99
+ * @param hooks The hook bundle whose `beforeHandle` is skipped on a match.
100
+ * @returns A {@link Hooks} bundle whose `beforeHandle` is gated by `when`.
101
+ * @throws Error at composition time if a string pattern does not start with `/`.
94
102
  * @since 0.19.0
95
103
  */
96
104
  export declare function except(when: ExceptPredicate, hooks: Hooks): Hooks;
package/dist/combine.js CHANGED
@@ -28,6 +28,8 @@
28
28
  * app.use(adminStack);
29
29
  * ```
30
30
  *
31
+ * @param layers Hook bundles to compose, executed in the order given.
32
+ * @returns A single merged {@link Hooks} bundle preserving all lifecycle phases.
31
33
  * @since 0.19.0
32
34
  */
33
35
  export function every(...layers) {
@@ -62,6 +64,8 @@ export function every(...layers) {
62
64
  * ));
63
65
  * ```
64
66
  *
67
+ * @param layers Candidate hook bundles; the first `beforeHandle` that passes wins.
68
+ * @returns A merged {@link Hooks} bundle with the OR-style `beforeHandle` strategy.
65
69
  * @since 0.19.0
66
70
  */
67
71
  export function some(...layers) {
@@ -119,6 +123,10 @@ export function some(...layers) {
119
123
  * exempted. Wrap each bundle with {@link except} individually when you
120
124
  * need to gate other phases.
121
125
  *
126
+ * @param when Paths or predicate ({@link ExceptPredicate}) that exempt a request.
127
+ * @param hooks The hook bundle whose `beforeHandle` is skipped on a match.
128
+ * @returns A {@link Hooks} bundle whose `beforeHandle` is gated by `when`.
129
+ * @throws Error at composition time if a string pattern does not start with `/`.
122
130
  * @since 0.19.0
123
131
  */
124
132
  export function except(when, hooks) {
@@ -121,6 +121,9 @@ export declare function _resetCompressionRuntimeProbeForTests(): void;
121
121
  * app.use(compression());
122
122
  * ```
123
123
  *
124
+ * @param opts Compression tuning ({@link CompressionOptions}); `minimumSize` defaults to `1024` bytes.
125
+ * @returns A {@link Hooks} bundle whose `onSend` compresses eligible response bodies.
126
+ * @throws TypeError if `compressLevel` is passed or `minimumSize` is not a finite non-negative integer.
124
127
  * @since 0.25.0
125
128
  */
126
129
  export declare function compression(opts?: CompressionOptions): Hooks;
@@ -281,6 +281,9 @@ async function compressBytes(bytes, encoding) {
281
281
  * app.use(compression());
282
282
  * ```
283
283
  *
284
+ * @param opts Compression tuning ({@link CompressionOptions}); `minimumSize` defaults to `1024` bytes.
285
+ * @returns A {@link Hooks} bundle whose `onSend` compresses eligible response bodies.
286
+ * @throws TypeError if `compressLevel` is passed or `minimumSize` is not a finite non-negative integer.
284
287
  * @since 0.25.0
285
288
  */
286
289
  export function compression(opts = {}) {
package/dist/config.d.ts CHANGED
@@ -21,6 +21,7 @@ import { type StandardSchemaV1 } from "./schema.js";
21
21
  * dashboard, a startup probe, or a custom error renderer.
22
22
  */
23
23
  export declare class ConfigValidationError extends Error {
24
+ /** Every validation issue, as `{ key, message }` pairs (`key` is the dotted path, `"<root>"`/`"<source>"` for top-level failures). */
24
25
  readonly issues: ReadonlyArray<{
25
26
  key: string;
26
27
  message: string;
@@ -92,6 +93,9 @@ export interface DefineConfigOptions<S extends StandardSchemaV1> {
92
93
  * export const config = await defineConfig({ schema: Config });
93
94
  * ```
94
95
  *
96
+ * @param opts - Schema, source (default `"env"`), optional `transform`, and the stderr sink.
97
+ * @returns The validated, typed configuration object.
98
+ * @throws {ConfigValidationError} When the source cannot be read or the schema reports issues.
95
99
  * @since 0.20.0
96
100
  */
97
101
  export declare function defineConfig<S extends StandardSchemaV1>(opts: DefineConfigOptions<S>): Promise<StandardSchemaV1.InferOutput<S>>;
package/dist/config.js CHANGED
@@ -21,6 +21,7 @@ import { validate } from "./schema.js";
21
21
  * dashboard, a startup probe, or a custom error renderer.
22
22
  */
23
23
  export class ConfigValidationError extends Error {
24
+ /** Every validation issue, as `{ key, message }` pairs (`key` is the dotted path, `"<root>"`/`"<source>"` for top-level failures). */
24
25
  issues;
25
26
  constructor(issues) {
26
27
  const summary = issues
@@ -90,6 +91,9 @@ function pathToKey(path) {
90
91
  * export const config = await defineConfig({ schema: Config });
91
92
  * ```
92
93
  *
94
+ * @param opts - Schema, source (default `"env"`), optional `transform`, and the stderr sink.
95
+ * @returns The validated, typed configuration object.
96
+ * @throws {ConfigValidationError} When the source cannot be read or the schema reports issues.
93
97
  * @since 0.20.0
94
98
  */
95
99
  export async function defineConfig(opts) {