@daloyjs/core 1.0.0-beta.5 → 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.
- package/README.md +2 -1
- package/dist/adapters/bun.d.ts +13 -1
- package/dist/adapters/bun.js +8 -1
- package/dist/adapters/cloudflare.d.ts +7 -1
- package/dist/adapters/cloudflare.js +6 -1
- package/dist/adapters/deno.d.ts +11 -1
- package/dist/adapters/deno.js +8 -1
- package/dist/adapters/fastly.d.ts +12 -2
- package/dist/adapters/fastly.js +12 -2
- package/dist/adapters/lambda.d.ts +37 -1
- package/dist/adapters/lambda.js +6 -1
- package/dist/adapters/node.d.ts +12 -1
- package/dist/adapters/node.js +7 -1
- package/dist/adapters/vercel.d.ts +13 -1
- package/dist/adapters/vercel.js +12 -1
- package/dist/app.d.ts +67 -17
- package/dist/app.js +97 -31
- package/dist/banner.d.ts +6 -0
- package/dist/banner.js +6 -0
- package/dist/cli.d.ts +35 -0
- package/dist/cli.js +23 -1
- package/dist/combine.d.ts +8 -0
- package/dist/combine.js +8 -0
- package/dist/compression.d.ts +3 -0
- package/dist/compression.js +3 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +4 -0
- package/dist/conn-info.d.ts +35 -2
- package/dist/conn-info.js +35 -2
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +2 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/cookie.js +12 -0
- package/dist/dependency.d.ts +4 -0
- package/dist/dependency.js +3 -0
- package/dist/discriminator.d.ts +13 -0
- package/dist/discriminator.js +23 -1
- package/dist/docs.d.ts +78 -0
- package/dist/docs.js +21 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +14 -0
- package/dist/etag.d.ts +2 -0
- package/dist/etag.js +2 -0
- package/dist/fetch-guard.d.ts +7 -0
- package/dist/fetch-guard.js +7 -0
- package/dist/fetch-resilience.d.ts +4 -0
- package/dist/fetch-resilience.js +4 -0
- package/dist/http-signatures.d.ts +32 -0
- package/dist/http-signatures.js +30 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/ip-restriction.d.ts +40 -3
- package/dist/ip-restriction.js +35 -3
- package/dist/jwk.d.ts +12 -1
- package/dist/jwk.js +6 -0
- package/dist/jwt.d.ts +14 -0
- package/dist/jwt.js +10 -0
- package/dist/load-shedding.d.ts +3 -0
- package/dist/load-shedding.js +3 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.js +3 -0
- package/dist/mcp.d.ts +574 -0
- package/dist/mcp.js +623 -0
- package/dist/middleware.d.ts +68 -0
- package/dist/middleware.js +17 -0
- package/dist/mtls.d.ts +19 -2
- package/dist/mtls.js +12 -2
- package/dist/multipart.d.ts +42 -5
- package/dist/multipart.js +41 -5
- package/dist/openapi.d.ts +15 -9
- package/dist/openapi.js +6 -9
- package/dist/rate-limit-redis.d.ts +21 -2
- package/dist/rate-limit-redis.js +17 -2
- package/dist/safe-redirect.d.ts +6 -0
- package/dist/safe-redirect.js +6 -0
- package/dist/sbom.cdx.json +9 -9
- package/dist/sbom.spdx.json +5 -5
- package/dist/scheduler.d.ts +4 -0
- package/dist/schema.d.ts +25 -0
- package/dist/security-schemes.d.ts +50 -0
- package/dist/security-schemes.js +6 -0
- package/dist/security.d.ts +33 -0
- package/dist/security.js +28 -10
- package/dist/session.d.ts +34 -5
- package/dist/session.js +31 -5
- package/dist/streaming.d.ts +19 -0
- package/dist/streaming.js +16 -0
- package/dist/subdomains.d.ts +4 -0
- package/dist/subdomains.js +4 -0
- package/dist/time-claims.d.ts +22 -2
- package/dist/time-claims.js +6 -0
- package/dist/tracing.d.ts +12 -0
- package/dist/tracing.js +6 -0
- package/dist/types.d.ts +70 -1
- package/dist/waf.js +21 -1
- package/dist/webhook-delivery.d.ts +6 -0
- package/dist/webhook-delivery.js +5 -0
- package/dist/websocket.d.ts +137 -4
- package/dist/websocket.js +105 -4
- package/package.json +6 -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
|
|
2473
|
-
"
|
|
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
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
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
|
|
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) {
|
package/dist/compression.d.ts
CHANGED
|
@@ -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;
|
package/dist/compression.js
CHANGED
|
@@ -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) {
|