@daloyjs/core 1.0.0-beta.3 → 1.0.0-beta.5

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/dist/docs.d.ts CHANGED
@@ -254,6 +254,42 @@ export interface DocsAssetOptions {
254
254
  */
255
255
  crossOrigin?: "anonymous" | "use-credentials";
256
256
  }
257
+ /**
258
+ * Provider-neutral login launcher rendered into generated docs pages.
259
+ *
260
+ * Use this when the OpenAPI docs should expose a visible authorization control
261
+ * that sends developers to a local login form or to an external identity
262
+ * provider such as Entra ID, Auth0, Better Auth, Clerk, Okta, Keycloak, or any
263
+ * other OAuth2/OIDC front end. The launcher only opens the configured URL; it
264
+ * never stores tokens or bypasses the OpenAPI UI's normal security-scheme
265
+ * handling.
266
+ *
267
+ * @since 0.43.0
268
+ */
269
+ export interface DocsAuthLauncherOptions {
270
+ /**
271
+ * Absolute `http(s)` URL or same-origin/relative URL for the login or
272
+ * authorization entry point. `javascript:`, `data:`, and other executable
273
+ * schemes are refused when the HTML is generated.
274
+ */
275
+ loginUrl: string;
276
+ /** Button text. Defaults to `"Authorize"`. */
277
+ label?: string;
278
+ /**
279
+ * Accessible helper text shown as the button title and screen-reader label.
280
+ * Defaults to `"Open login or identity provider"`.
281
+ */
282
+ description?: string;
283
+ /**
284
+ * How to open {@link DocsAuthLauncherOptions.loginUrl}. Defaults to
285
+ * `"popup"` so docs remain open while the provider flow runs.
286
+ */
287
+ target?: "popup" | "_blank" | "_self";
288
+ /** Popup width in CSS/device pixels. Defaults to `520`. */
289
+ popupWidth?: number;
290
+ /** Popup height in CSS/device pixels. Defaults to `720`. */
291
+ popupHeight?: number;
292
+ }
257
293
  /** Shared options for {@link scalarHtml}, {@link swaggerUiHtml}, and {@link redocHtml}. */
258
294
  export interface DocsOptions {
259
295
  /** Absolute or relative URL of the OpenAPI document to render. */
@@ -267,6 +303,14 @@ export interface DocsOptions {
267
303
  assets?: DocsAssetOptions;
268
304
  /** CSP `nonce` to apply to inline/script tags; must match the response CSP. */
269
305
  scriptNonce?: string;
306
+ /**
307
+ * Optional authorization launcher rendered into the docs page. It gives
308
+ * Scalar, Swagger UI, and Redoc a consistent visible button that opens a
309
+ * local login form or third-party identity-provider authorization URL.
310
+ *
311
+ * @since 0.43.0
312
+ */
313
+ auth?: DocsAuthLauncherOptions;
270
314
  }
271
315
  /** Options for {@link scalarHtml}; adds Scalar-specific UI configuration. */
272
316
  export interface ScalarHtmlOptions extends DocsOptions {
package/dist/docs.js CHANGED
@@ -49,8 +49,7 @@ function integrityAttr(integrity, crossOrigin) {
49
49
  export function scalarHtml(opts) {
50
50
  const title = escapeHtml(opts.title ?? "API Reference");
51
51
  const url = escapeHtml(opts.specUrl);
52
- const scriptUrl = escapeHtml(opts.assets?.scalarScriptUrl ??
53
- `${JSDELIVR_ORIGIN}/npm/@scalar/api-reference`);
52
+ const scriptUrl = escapeHtml(opts.assets?.scalarScriptUrl ?? `${JSDELIVR_ORIGIN}/npm/@scalar/api-reference`);
54
53
  const scriptSri = integrityAttr(opts.assets?.scalarScriptIntegrity, opts.assets?.crossOrigin);
55
54
  const nonce = nonceAttr(opts.scriptNonce);
56
55
  const configuration = scalarConfigurationAttr(opts.specUrl, opts.configuration);
@@ -62,6 +61,7 @@ export function scalarHtml(opts) {
62
61
  </head><body>
63
62
  <script id="api-reference" data-url="${url}"${configuration}${nonce}></script>
64
63
  <script src="${scriptUrl}"${scriptSri}${nonce}></script>
64
+ ${docsAuthLauncherHtml(opts.auth, opts.scriptNonce)}
65
65
  </body></html>`;
66
66
  }
67
67
  /**
@@ -74,10 +74,8 @@ export function scalarHtml(opts) {
74
74
  */
75
75
  export function swaggerUiHtml(opts) {
76
76
  const title = escapeHtml(opts.title ?? "API Docs");
77
- const cssUrl = escapeHtml(opts.assets?.swaggerUiCssUrl ??
78
- `${JSDELIVR_ORIGIN}/npm/swagger-ui-dist/swagger-ui.css`);
79
- const bundleUrl = escapeHtml(opts.assets?.swaggerUiBundleUrl ??
80
- `${JSDELIVR_ORIGIN}/npm/swagger-ui-dist/swagger-ui-bundle.js`);
77
+ const cssUrl = escapeHtml(opts.assets?.swaggerUiCssUrl ?? `${JSDELIVR_ORIGIN}/npm/swagger-ui-dist/swagger-ui.css`);
78
+ const bundleUrl = escapeHtml(opts.assets?.swaggerUiBundleUrl ?? `${JSDELIVR_ORIGIN}/npm/swagger-ui-dist/swagger-ui-bundle.js`);
81
79
  const cssSri = integrityAttr(opts.assets?.swaggerUiCssIntegrity, opts.assets?.crossOrigin);
82
80
  const bundleSri = integrityAttr(opts.assets?.swaggerUiBundleIntegrity, opts.assets?.crossOrigin);
83
81
  const nonce = nonceAttr(opts.scriptNonce);
@@ -97,6 +95,7 @@ export function swaggerUiHtml(opts) {
97
95
  <div id="swagger"></div>
98
96
  <script src="${bundleUrl}"${bundleSri}${nonce}></script>
99
97
  <script${nonce}>window.onload=()=>SwaggerUIBundle(${configuration});</script>
98
+ ${docsAuthLauncherHtml(opts.auth, opts.scriptNonce)}
100
99
  </body></html>`;
101
100
  }
102
101
  /**
@@ -115,8 +114,7 @@ export function swaggerUiHtml(opts) {
115
114
  */
116
115
  export function redocHtml(opts) {
117
116
  const title = escapeHtml(opts.title ?? "API Docs");
118
- const scriptUrl = escapeHtml(opts.assets?.redocScriptUrl ??
119
- `${JSDELIVR_ORIGIN}/npm/redoc/bundles/redoc.standalone.js`);
117
+ const scriptUrl = escapeHtml(opts.assets?.redocScriptUrl ?? `${JSDELIVR_ORIGIN}/npm/redoc/bundles/redoc.standalone.js`);
120
118
  const scriptSri = integrityAttr(opts.assets?.redocScriptIntegrity, opts.assets?.crossOrigin);
121
119
  const nonce = nonceAttr(opts.scriptNonce);
122
120
  const specArg = jsonForScript(opts.specUrl);
@@ -130,6 +128,7 @@ export function redocHtml(opts) {
130
128
  <div id="redoc"></div>
131
129
  <script src="${scriptUrl}"${scriptSri}${nonce}></script>
132
130
  <script${nonce}>Redoc.init(${specArg},${optionsArg},document.getElementById("redoc"));</script>
131
+ ${docsAuthLauncherHtml(opts.auth, opts.scriptNonce)}
133
132
  </body></html>`;
134
133
  }
135
134
  /**
@@ -238,6 +237,46 @@ function escapeHtml(s) {
238
237
  function jsonForScript(value) {
239
238
  return JSON.stringify(value).replace(/[<\u2028\u2029]/g, (c) => ({ "<": "\\u003c", "\u2028": "\\u2028", "\u2029": "\\u2029" })[c]);
240
239
  }
240
+ function docsAuthLauncherHtml(auth, scriptNonce) {
241
+ if (!auth)
242
+ return "";
243
+ const loginUrl = normalizeDocsAuthLoginUrl(auth.loginUrl);
244
+ const label = auth.label ?? "Authorize";
245
+ const description = auth.description ?? "Open login or identity provider";
246
+ const target = auth.target ?? "popup";
247
+ const popupWidth = positiveIntegerOrDefault(auth.popupWidth, 520);
248
+ const popupHeight = positiveIntegerOrDefault(auth.popupHeight, 720);
249
+ const payload = jsonForScript({
250
+ loginUrl,
251
+ target,
252
+ popupWidth,
253
+ popupHeight,
254
+ });
255
+ const nonce = nonceAttr(scriptNonce);
256
+ return `<style>
257
+ .daloy-docs-auth{position:fixed;right:16px;top:16px;z-index:2147483647;display:inline-flex;align-items:center;gap:8px;border:1px solid #1d4ed8;border-radius:6px;background:#2563eb;color:#fff;font:600 14px/1.2 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;padding:10px 14px;box-shadow:0 8px 24px rgba(15,23,42,.18);cursor:pointer}
258
+ .daloy-docs-auth:focus{outline:3px solid rgba(37,99,235,.35);outline-offset:2px}
259
+ </style>
260
+ <button type="button" class="daloy-docs-auth" data-daloy-docs-auth title="${escapeHtml(description)}" aria-label="${escapeHtml(description)}">${escapeHtml(label)}</button>
261
+ <script${nonce}>(()=>{const o=${payload};const b=document.querySelector("[data-daloy-docs-auth]");if(!b)return;b.addEventListener("click",()=>{if(o.target==="_self"){window.location.assign(o.loginUrl);return;}if(o.target==="_blank"){window.open(o.loginUrl,"_blank","noopener,noreferrer");return;}const left=Math.max(0,Math.round((window.screenX||0)+((window.outerWidth||o.popupWidth)-o.popupWidth)/2));const top=Math.max(0,Math.round((window.screenY||0)+((window.outerHeight||o.popupHeight)-o.popupHeight)/2));const features="popup=yes,width="+o.popupWidth+",height="+o.popupHeight+",left="+left+",top="+top+",noopener,noreferrer";window.open(o.loginUrl,"daloy_docs_auth",features);});})();</script>`;
262
+ }
263
+ function normalizeDocsAuthLoginUrl(loginUrl) {
264
+ if (typeof loginUrl !== "string" || loginUrl.trim() === "") {
265
+ throw new TypeError("docs auth loginUrl must be a non-empty string");
266
+ }
267
+ const trimmed = loginUrl.trim();
268
+ const parsed = new URL(trimmed, "https://daloyjs.local");
269
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
270
+ throw new TypeError(`docs auth loginUrl must be an http(s) or relative URL; got ${JSON.stringify(loginUrl)}`);
271
+ }
272
+ return trimmed;
273
+ }
274
+ function positiveIntegerOrDefault(value, fallback) {
275
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
276
+ return value;
277
+ }
278
+ return fallback;
279
+ }
241
280
  function scalarConfigurationAttr(specUrl, configuration) {
242
281
  if (!configuration)
243
282
  return "";
package/dist/index.d.ts CHANGED
@@ -11,28 +11,28 @@ export type { BehindProxyConfig, ConnInfo } from "./conn-info.js";
11
11
  export { subdomains, PSL_SNAPSHOT_DATE, PSL_PUBLIC_SUFFIXES, MAX_SNAPSHOT_AGE_DAYS, } from "./subdomains.js";
12
12
  export type { SubdomainsOptions, SubdomainsResult } from "./subdomains.js";
13
13
  export { defineDependency, DEPENDENCY_MARKER } from "./dependency.js";
14
- export type { DependencyHooks, DependencyOptions, } from "./dependency.js";
14
+ export type { DependencyHooks, DependencyOptions } from "./dependency.js";
15
15
  export type { RouteDefinition, HttpMethod, PathString, RequestSchemas, ResponsesMap, ResponseSpec, AuthSpec, Hooks, BaseContext, AppState, AuthScheme, AuthContext, HandlerReturn, InferRequest, ParamsOf, PathParams, CallbackDefinition, CallbackMap, CallbackOperation, RouteExample, RouteMeta, } from "./types.js";
16
16
  export { HttpError, BadRequestError, ValidationError, NotFoundError, ConflictError, UnauthorizedError, ForbiddenError, MethodNotAllowedError, PayloadTooLargeError, RequestHeaderFieldsTooLargeError, UnsupportedMediaTypeError, TooManyRequestsError, RequestTimeoutError, InternalError, MessageLeakError, httpError, SAFE_CUSTOM_ERROR_RESPONSE_HEADERS, checkCustomErrorResponseHeaders, } from "./errors.js";
17
17
  export type { ProblemDetails, ProblemRenderOptions, HttpErrorOptions } from "./errors.js";
18
18
  export type { StandardSchemaV1 } from "./schema.js";
19
19
  export { validate, isStandardSchema } from "./schema.js";
20
20
  export { diffOpenAPI, hasBreakingChanges } from "./openapi-diff.js";
21
- export type { ChangeSeverity, OpenAPIChange, OpenAPIDiffResult, } from "./openapi-diff.js";
21
+ export type { ChangeSeverity, OpenAPIChange, OpenAPIDiffResult } from "./openapi-diff.js";
22
22
  export { readBodyLimited, safeJsonParse, isForbiddenObjectKey, sanitizeHeaderName, sanitizeHeaderValue, timingSafeEqual, randomId, assertNoDuplicateSingletonHeaders, assertNoReservedInternalHeaders, assertHeaderCountWithinLimit, DEFAULT_MAX_HEADER_COUNT, RESERVED_INBOUND_HEADER_PREFIXES, SMUGGLING_SINGLETON_HEADERS, verifyWebhookSignature, signWebhookPayload, WEBHOOK_DEFAULT_TOLERANCE_SECONDS, assertStrongSecret, MIN_PROD_SECRET_BYTES, WEAK_SECRET_STRINGS, sanitizeFilename, assertSafeRelativePath, hasMongoOperatorKeys, assertNoMongoOperators, } from "./security.js";
23
23
  export type { WebhookHmacAlgorithm } from "./security.js";
24
24
  export { requestId, secureHeaders, SECURE_HEADERS_MARKER, cors, CORS_HOOK_MARKER, CORS_ORIGIN_ALLOW_MARKER, CORS_WILDCARD_ORIGIN_MARKER, rateLimit, loginThrottle, timing, bearerAuth, basicAuth, csrf, CSRF_HOOK_MARKER, fetchMetadata, requireScopes, REQUIRE_SCOPES_AGGREGATE_KEY, REQUIRE_SCOPES_HOOK_MARKER, _resetSharedRateLimitStoresForTests, } from "./middleware.js";
25
25
  export { etag } from "./etag.js";
26
26
  export type { ETagOptions } from "./etag.js";
27
27
  export { compression, COMPRESSION_HOOK_MARKER, _resetCompressionRuntimeProbeForTests, } from "./compression.js";
28
- export type { CompressionEncoding, CompressionOptions, } from "./compression.js";
28
+ export type { CompressionEncoding, CompressionOptions } from "./compression.js";
29
29
  export { createJwtSigner, createJwtVerifier, JwtError, DEFAULT_JWT_MAX_LIFETIME_SECONDS, } from "./jwt.js";
30
30
  export type { JwtAlgorithm, JwtKeyMaterial, JwtSignerOptions, JwtVerifierOptions, JwtVerified, } from "./jwt.js";
31
31
  export { jwk } from "./jwk.js";
32
- export type { JwkAlgorithm, JwkOptions, JwkSet, JwkSource, JwkVerifyHook, } from "./jwk.js";
32
+ export type { JwkAlgorithm, JwkOptions, JwkSet, JwkSource, JwkVerifyHook } from "./jwk.js";
33
33
  export { serializeCookie, serializeClearCookie, assertCookieAttributes, readRequestCookie, } from "./cookie.js";
34
34
  export type { CookieAttributes, CookieSameSite } from "./cookie.js";
35
- export { assertTemporalClaims, TemporalClaimError, } from "./time-claims.js";
35
+ export { assertTemporalClaims, TemporalClaimError } from "./time-claims.js";
36
36
  export type { TemporalClaims, TemporalClaimErrorCode, AssertTemporalClaimsOptions, } from "./time-claims.js";
37
37
  export { every, some, except } from "./combine.js";
38
38
  export type { ExceptPredicate } from "./combine.js";
@@ -42,24 +42,24 @@ export { fetchGuard, SsrfBlockedError } from "./fetch-guard.js";
42
42
  export type { FetchGuardOptions, SsrfBlockReason } from "./fetch-guard.js";
43
43
  export { resilientFetch, CircuitBreaker, CircuitOpenError, FetchTimeoutError, } from "./fetch-resilience.js";
44
44
  export type { ResilientFetchOptions, CircuitBreakerOptions, CircuitState, RetryContext, } from "./fetch-resilience.js";
45
- export { createWebhookSender, MemoryWebhookDeadLetterSink, } from "./webhook-delivery.js";
45
+ export { createWebhookSender, MemoryWebhookDeadLetterSink } from "./webhook-delivery.js";
46
46
  export type { WebhookEvent, WebhookSenderOptions, WebhookDeliveryResult, WebhookDeadLetter, WebhookDeadLetterSink, WebhookAttempt, } from "./webhook-delivery.js";
47
- export { Scheduler, CronParseError, parseCron, nextCronRun, } from "./scheduler.js";
47
+ export { Scheduler, CronParseError, parseCron, nextCronRun } from "./scheduler.js";
48
48
  export type { SchedulerOptions, SchedulerLogger, TimerFns, TaskDefinition, TaskHandler, TaskRunContext, TaskErrorInfo, TaskState, CronFields, } from "./scheduler.js";
49
49
  export { clientCertAuth, setClientCertificate, getClientCertificate, normalizePeerCertificate, parseForwardedClientCert, } from "./mtls.js";
50
50
  export type { ClientCertificate, ClientCertificateSource, ClientCertAuthOptions, ClientCertHeaderConfig, PeerCertificateLike, } from "./mtls.js";
51
51
  export { signMessage, signRequest, verifyMessage, verifyRequest, httpSignatureAuth, contentDigest, verifyContentDigest, DEFAULT_SIGNATURE_LABEL, DEFAULT_MAX_SIGNATURE_AGE_SECONDS, DEFAULT_SIGNATURE_CLOCK_SKEW_SECONDS, } from "./http-signatures.js";
52
52
  export type { HttpSignatureAlgorithm, HttpSignatureKeyMaterial, HttpSignatureKey, SignMessageOptions, SignRequestOptions, MessageSignature, VerifyMessageOptions, VerifyResult, VerifySuccess, VerifyFailure, KeyResolutionInfo, HttpSignatureAuthOptions, ContentDigestAlgorithm, } from "./http-signatures.js";
53
- export { autoBan, MemoryAutoBanStore, _resetAutoBanStoresForTests, } from "./auto-ban.js";
53
+ export { autoBan, MemoryAutoBanStore, _resetAutoBanStoresForTests } from "./auto-ban.js";
54
54
  export type { AutoBanOptions, AutoBanStore, AutoBanRecord, AutoBanEvent, AutoBanStrikeEvent, } from "./auto-ban.js";
55
55
  export { botGuard, GOOGLEBOT, BINGBOT, WELL_KNOWN_BOTS } from "./bot-guard.js";
56
- export type { BotGuardOptions, BotGuardEvent, BotResolver, VerifiedBotRule, } from "./bot-guard.js";
56
+ export type { BotGuardOptions, BotGuardEvent, BotResolver, VerifiedBotRule } from "./bot-guard.js";
57
57
  export { ipReputation, urlFeed } from "./ip-reputation.js";
58
58
  export type { IpReputationOptions, IpReputationFeed, IpReputationMatch, IpReputationController, UrlFeedOptions, } from "./ip-reputation.js";
59
59
  export { geoBlock } from "./geo-block.js";
60
60
  export type { GeoBlockOptions, GeoBlockDecision, GeoBlockReason, GeoState, CountryFromIp, CountryFromContext, } from "./geo-block.js";
61
61
  export { concurrencyLimit } from "./concurrency-limit.js";
62
- export type { ConcurrencyLimitOptions, ConcurrencyRejection, } from "./concurrency-limit.js";
62
+ export type { ConcurrencyLimitOptions, ConcurrencyRejection } from "./concurrency-limit.js";
63
63
  export { requestDecompression, decompressRequestBody, DecompressionBombError, UnsupportedContentEncodingError, MalformedCompressedBodyError, _resetRequestDecompressionProbeForTests, } from "./request-decompression.js";
64
64
  export type { RequestDecompressionOptions, RequestDecompressionEncoding, DecompressionBombInfo, } from "./request-decompression.js";
65
65
  export { waf } from "./waf.js";
@@ -67,17 +67,17 @@ export type { WafOptions, WafMode, WafRuleId, WafRuleConfig, WafInspectConfig, W
67
67
  export { safeRedirect, OpenRedirectBlockedError } from "./safe-redirect.js";
68
68
  export type { SafeRedirectOptions, SafeRedirectStatus, SafeRedirectBlockReason, } from "./safe-redirect.js";
69
69
  export { loadShedding, LOAD_SHEDDING_MARKER } from "./load-shedding.js";
70
- export type { LoadSheddingOptions, LoadSheddingSnapshot, } from "./load-shedding.js";
70
+ export type { LoadSheddingOptions, LoadSheddingSnapshot } from "./load-shedding.js";
71
71
  export { defineConfig, ConfigValidationError } from "./config.js";
72
- export type { ConfigSource, DefineConfigOptions, } from "./config.js";
72
+ export type { ConfigSource, DefineConfigOptions } from "./config.js";
73
73
  export type { RequestIdOptions, SecureHeadersOptions, CspDirectivesOptions, CorsOptions, CorsOriginAllow, RateLimitOptions, RateLimitStore, LoginThrottleOptions, CsrfOptions, CsrfCookieOptions, FetchMetadataOptions, BasicAuthOptions, } from "./middleware.js";
74
74
  export type { BearerAuthOptions, BearerAuthVerifyHook } from "./middleware.js";
75
75
  export { createLogger, noopLogger, DEFAULT_REDACT_KEYS } from "./logger.js";
76
- export type { Logger, LogLevel, ConsoleLoggerOptions, LoggerRedactionOptions, } from "./logger.js";
77
- export type { ScalarJsonPrimitive, ScalarJsonValue, ScalarReferenceConfiguration, ScalarTheme, RedocConfiguration, RedocHtmlOptions, SwaggerUiConfiguration, SwaggerUiHtmlOptions, AsyncApiHtmlOptions, DocsAssetOptions, } from "./docs.js";
76
+ export type { Logger, LogLevel, ConsoleLoggerOptions, LoggerRedactionOptions } from "./logger.js";
77
+ export type { ScalarJsonPrimitive, ScalarJsonValue, ScalarReferenceConfiguration, ScalarTheme, RedocConfiguration, RedocHtmlOptions, SwaggerUiConfiguration, SwaggerUiHtmlOptions, AsyncApiHtmlOptions, DocsAssetOptions, DocsAuthLauncherOptions, } from "./docs.js";
78
78
  export { formatStartupBanner, printStartupBanner } from "./banner.js";
79
79
  export type { StartupBannerLink, StartupBannerOptions } from "./banner.js";
80
- export { sseStream, sseResponse, ndjsonStream, ndjsonResponse, } from "./streaming.js";
80
+ export { sseStream, sseResponse, ndjsonStream, ndjsonResponse } from "./streaming.js";
81
81
  export type { SSEMessage, StreamOptions, SSEStreamOptions, SSEResponseOptions, NDJSONResponseOptions, } from "./streaming.js";
82
82
  export { httpBearerScheme, httpBasicScheme, apiKeyScheme, oauth2Scheme, openIdConnectScheme, REQUIRE_PAYLOAD_AUTH_EXTENSION, securitySchemeRequiresPayloadAuth, toOpenAPISecurityScheme, } from "./security-schemes.js";
83
83
  export type { ApiKeyLocation, ApiKeyScheme, ApiKeySchemeOptions, HttpBasicScheme, HttpBasicSchemeOptions, HttpBearerScheme, HttpBearerSchemeOptions, OAuth2AuthorizationCodeFlow, OAuth2ClientCredentialsFlow, OAuth2Flows, OAuth2ImplicitFlow, OAuth2PasswordFlow, OAuth2Scheme, OAuth2SchemeOptions, OpenIdConnectScheme, OpenIdConnectSchemeOptions, SecurityScheme, RequirePayloadAuthExtension, } from "./security-schemes.js";
@@ -88,11 +88,11 @@ export type { SessionOptions, SessionCookieOptions, SessionContext, SessionRecor
88
88
  export { idempotency, MemoryIdempotencyStore, _resetSharedIdempotencyStoresForTests, } from "./idempotency.js";
89
89
  export type { IdempotencyOptions, IdempotencyStore, IdempotencyRecord, StoredIdempotentResponse, } from "./idempotency.js";
90
90
  export { responseCache, MemoryResponseCacheStore, _resetSharedResponseCacheStoresForTests, } from "./response-cache.js";
91
- export type { ResponseCacheOptions, ResponseCacheStore, CachedResponse, } from "./response-cache.js";
91
+ export type { ResponseCacheOptions, ResponseCacheStore, CachedResponse } from "./response-cache.js";
92
92
  export { encodeCursor, decodeCursor, buildLinkHeader, buildPageLinks, paginationQuery, MAX_CURSOR_LENGTH, } from "./pagination.js";
93
93
  export type { PaginationLink, PageLinkOptions, PageLinks, PaginationQueryOptions, PaginationParams, PaginationQuerySchema, } from "./pagination.js";
94
94
  export { MetricsRegistry, Counter, Gauge, Histogram, httpMetrics, DEFAULT_DURATION_BUCKETS, PROMETHEUS_CONTENT_TYPE, } from "./metrics.js";
95
- export type { MetricLabels, MetricsRegistryOptions, HttpMetricsOptions, } from "./metrics.js";
95
+ export type { MetricLabels, MetricsRegistryOptions, HttpMetricsOptions } from "./metrics.js";
96
96
  export { fileField, multipartObject, isFileFieldSchema, isMultipartObjectSchema, } from "./multipart.js";
97
97
  export type { FileFieldSchema, FileFieldOptions, FileMagicBytesOption, FileMagicBytesSignature, MultipartObjectOptions, MultipartShape, UploadedFile, } from "./multipart.js";
98
98
  export { otelTracing, TRACING_SPAN_KIND_SERVER, TRACING_SPAN_STATUS_UNSET, TRACING_SPAN_STATUS_OK, TRACING_SPAN_STATUS_ERROR, } from "./tracing.js";
package/dist/index.js CHANGED
@@ -18,16 +18,16 @@ export { compression, COMPRESSION_HOOK_MARKER, _resetCompressionRuntimeProbeForT
18
18
  export { createJwtSigner, createJwtVerifier, JwtError, DEFAULT_JWT_MAX_LIFETIME_SECONDS, } from "./jwt.js";
19
19
  export { jwk } from "./jwk.js";
20
20
  export { serializeCookie, serializeClearCookie, assertCookieAttributes, readRequestCookie, } from "./cookie.js";
21
- export { assertTemporalClaims, TemporalClaimError, } from "./time-claims.js";
21
+ export { assertTemporalClaims, TemporalClaimError } from "./time-claims.js";
22
22
  export { every, some, except } from "./combine.js";
23
23
  export { ipRestriction } from "./ip-restriction.js";
24
24
  export { fetchGuard, SsrfBlockedError } from "./fetch-guard.js";
25
25
  export { resilientFetch, CircuitBreaker, CircuitOpenError, FetchTimeoutError, } from "./fetch-resilience.js";
26
- export { createWebhookSender, MemoryWebhookDeadLetterSink, } from "./webhook-delivery.js";
27
- export { Scheduler, CronParseError, parseCron, nextCronRun, } from "./scheduler.js";
26
+ export { createWebhookSender, MemoryWebhookDeadLetterSink } from "./webhook-delivery.js";
27
+ export { Scheduler, CronParseError, parseCron, nextCronRun } from "./scheduler.js";
28
28
  export { clientCertAuth, setClientCertificate, getClientCertificate, normalizePeerCertificate, parseForwardedClientCert, } from "./mtls.js";
29
29
  export { signMessage, signRequest, verifyMessage, verifyRequest, httpSignatureAuth, contentDigest, verifyContentDigest, DEFAULT_SIGNATURE_LABEL, DEFAULT_MAX_SIGNATURE_AGE_SECONDS, DEFAULT_SIGNATURE_CLOCK_SKEW_SECONDS, } from "./http-signatures.js";
30
- export { autoBan, MemoryAutoBanStore, _resetAutoBanStoresForTests, } from "./auto-ban.js";
30
+ export { autoBan, MemoryAutoBanStore, _resetAutoBanStoresForTests } from "./auto-ban.js";
31
31
  export { botGuard, GOOGLEBOT, BINGBOT, WELL_KNOWN_BOTS } from "./bot-guard.js";
32
32
  export { ipReputation, urlFeed } from "./ip-reputation.js";
33
33
  export { geoBlock } from "./geo-block.js";
@@ -39,7 +39,7 @@ export { loadShedding, LOAD_SHEDDING_MARKER } from "./load-shedding.js";
39
39
  export { defineConfig, ConfigValidationError } from "./config.js";
40
40
  export { createLogger, noopLogger, DEFAULT_REDACT_KEYS } from "./logger.js";
41
41
  export { formatStartupBanner, printStartupBanner } from "./banner.js";
42
- export { sseStream, sseResponse, ndjsonStream, ndjsonResponse, } from "./streaming.js";
42
+ export { sseStream, sseResponse, ndjsonStream, ndjsonResponse } from "./streaming.js";
43
43
  export { httpBearerScheme, httpBasicScheme, apiKeyScheme, oauth2Scheme, openIdConnectScheme, REQUIRE_PAYLOAD_AUTH_EXTENSION, securitySchemeRequiresPayloadAuth, toOpenAPISecurityScheme, } from "./security-schemes.js";
44
44
  export { discriminator, discriminatedUnion } from "./discriminator.js";
45
45
  export { session, rotateSession, signValue, verifySignedValue, MemorySessionStore, SESSION_HOOK_MARKER, SESSION_SECRETS_MARKER, } from "./session.js";
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.5",
4
- "serialNumber": "urn:uuid:6e9958fe-26f1-5084-94ea-5ff9bfb68412",
4
+ "serialNumber": "urn:uuid:448a4447-0b88-5512-8ce5-485b6688ff4c",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-06-24T05:34:04.875Z",
7
+ "timestamp": "2026-07-01T13:38:31.674Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "DaloyJS",
11
11
  "name": "daloy-generate-sbom",
12
- "version": "1.0.0-beta.3"
12
+ "version": "1.0.0-beta.5"
13
13
  }
14
14
  ],
15
15
  "authors": [
@@ -19,11 +19,11 @@
19
19
  ],
20
20
  "component": {
21
21
  "type": "library",
22
- "bom-ref": "pkg:npm/@daloyjs/core@1.0.0-beta.3",
22
+ "bom-ref": "pkg:npm/@daloyjs/core@1.0.0-beta.5",
23
23
  "name": "@daloyjs/core",
24
- "version": "1.0.0-beta.3",
24
+ "version": "1.0.0-beta.5",
25
25
  "description": "DaloyJS is a runtime-portable, contract-first TypeScript web framework with built-in OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-first defaults. Hono-grade portability, Elysia-grade DX, FastAPI-grade docs, Fastify-grade ops — distributed via pnpm.",
26
- "purl": "pkg:npm/@daloyjs/core@1.0.0-beta.3",
26
+ "purl": "pkg:npm/@daloyjs/core@1.0.0-beta.5",
27
27
  "licenses": [
28
28
  {
29
29
  "license": {
@@ -46,9 +46,9 @@
46
46
  }
47
47
  ],
48
48
  "swid": {
49
- "tagId": "swidtag--daloyjs-core-1.0.0-beta.3",
49
+ "tagId": "swidtag--daloyjs-core-1.0.0-beta.5",
50
50
  "name": "@daloyjs/core",
51
- "version": "1.0.0-beta.3",
51
+ "version": "1.0.0-beta.5",
52
52
  "tagVersion": 0,
53
53
  "patch": false
54
54
  }
@@ -57,7 +57,7 @@
57
57
  "components": [],
58
58
  "dependencies": [
59
59
  {
60
- "ref": "pkg:npm/@daloyjs/core@1.0.0-beta.3",
60
+ "ref": "pkg:npm/@daloyjs/core@1.0.0-beta.5",
61
61
  "dependsOn": []
62
62
  }
63
63
  ]
@@ -2,10 +2,10 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "@daloyjs/core-1.0.0-beta.3",
6
- "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-beta.3-6e9958fe-26f1-5084-94ea-5ff9bfb68412",
5
+ "name": "@daloyjs/core-1.0.0-beta.5",
6
+ "documentNamespace": "https://github.com/daloyjs/daloy/sbom/@daloyjs/core-1.0.0-beta.5-448a4447-0b88-5512-8ce5-485b6688ff4c",
7
7
  "creationInfo": {
8
- "created": "2026-06-24T05:34:04.875Z",
8
+ "created": "2026-07-01T13:38:31.674Z",
9
9
  "creators": [
10
10
  "Tool: daloy-generate-sbom",
11
11
  "Organization: DaloyJS"
@@ -16,7 +16,7 @@
16
16
  {
17
17
  "SPDXID": "SPDXRef-Package--daloyjs-core",
18
18
  "name": "@daloyjs/core",
19
- "versionInfo": "1.0.0-beta.3",
19
+ "versionInfo": "1.0.0-beta.5",
20
20
  "downloadLocation": "https://github.com/daloyjs/daloy",
21
21
  "filesAnalyzed": false,
22
22
  "licenseConcluded": "MIT",
@@ -27,7 +27,7 @@
27
27
  {
28
28
  "referenceCategory": "PACKAGE-MANAGER",
29
29
  "referenceType": "purl",
30
- "referenceLocator": "pkg:npm/@daloyjs/core@1.0.0-beta.3"
30
+ "referenceLocator": "pkg:npm/@daloyjs/core@1.0.0-beta.5"
31
31
  }
32
32
  ]
33
33
  }
package/dist/tenancy.d.ts CHANGED
@@ -5,9 +5,9 @@
5
5
  * once per request, validates and normalizes it, and exposes it on
6
6
  * `ctx.state.tenant` for handlers and downstream middleware. It is the
7
7
  * single source of truth for "who is this request for" so the per-tenant
8
- * isolation knobs on the rest of the framework (`rateLimit` `keyGenerator`,
9
- * `concurrencyLimit` / `idempotency` / `responseCache` `scope`) can all key
10
- * off the same resolved value via {@link tenantScope}.
8
+ * isolation knobs on the rest of the framework (`rateLimit` /
9
+ * `responseCache` `keyGenerator`, `concurrencyLimit` / `idempotency` `scope`)
10
+ * can all key off the same resolved value via {@link tenantScope}.
11
11
  *
12
12
  * Secure-by-default posture:
13
13
  *
@@ -227,10 +227,19 @@ export interface TenantScopeOptions {
227
227
  * or poison another tenant's:
228
228
  *
229
229
  * ```ts
230
- * rateLimit({ windowMs: 60_000, max: 100, keyGenerator: tenantScope() });
231
- * concurrencyLimit({ maxConcurrent: 20, scope: tenantScope() });
232
- * idempotency({ scope: tenantScope() }); // CWE-524 cross-tenant cache defense
233
- * responseCache({ ttlMs: 30_000, scope: tenantScope() });
230
+ * const scope = tenantScope();
231
+ * rateLimit({ windowMs: 60_000, max: 100, keyGenerator: scope });
232
+ * concurrencyLimit({ maxConcurrent: 20, scope });
233
+ * idempotency({ scope }); // CWE-524 cross-tenant cache defense
234
+ * // responseCache's keyGenerator REPLACES the whole key and it takes
235
+ * // ttlSeconds, so fold the tenant in alongside the path yourself:
236
+ * responseCache({
237
+ * ttlSeconds: 30,
238
+ * keyGenerator: (ctx) => {
239
+ * const u = new URL(ctx.request.url);
240
+ * return `${scope(ctx)}:${ctx.request.method} ${u.pathname}${u.search}`;
241
+ * },
242
+ * });
234
243
  * ```
235
244
  *
236
245
  * The `tenant:` prefix keeps these keys from colliding with other key spaces
package/dist/tenancy.js CHANGED
@@ -5,9 +5,9 @@
5
5
  * once per request, validates and normalizes it, and exposes it on
6
6
  * `ctx.state.tenant` for handlers and downstream middleware. It is the
7
7
  * single source of truth for "who is this request for" so the per-tenant
8
- * isolation knobs on the rest of the framework (`rateLimit` `keyGenerator`,
9
- * `concurrencyLimit` / `idempotency` / `responseCache` `scope`) can all key
10
- * off the same resolved value via {@link tenantScope}.
8
+ * isolation knobs on the rest of the framework (`rateLimit` /
9
+ * `responseCache` `keyGenerator`, `concurrencyLimit` / `idempotency` `scope`)
10
+ * can all key off the same resolved value via {@link tenantScope}.
11
11
  *
12
12
  * Secure-by-default posture:
13
13
  *
@@ -270,10 +270,19 @@ export function tenancy(opts) {
270
270
  * or poison another tenant's:
271
271
  *
272
272
  * ```ts
273
- * rateLimit({ windowMs: 60_000, max: 100, keyGenerator: tenantScope() });
274
- * concurrencyLimit({ maxConcurrent: 20, scope: tenantScope() });
275
- * idempotency({ scope: tenantScope() }); // CWE-524 cross-tenant cache defense
276
- * responseCache({ ttlMs: 30_000, scope: tenantScope() });
273
+ * const scope = tenantScope();
274
+ * rateLimit({ windowMs: 60_000, max: 100, keyGenerator: scope });
275
+ * concurrencyLimit({ maxConcurrent: 20, scope });
276
+ * idempotency({ scope }); // CWE-524 cross-tenant cache defense
277
+ * // responseCache's keyGenerator REPLACES the whole key and it takes
278
+ * // ttlSeconds, so fold the tenant in alongside the path yourself:
279
+ * responseCache({
280
+ * ttlSeconds: 30,
281
+ * keyGenerator: (ctx) => {
282
+ * const u = new URL(ctx.request.url);
283
+ * return `${scope(ctx)}:${ctx.request.method} ${u.pathname}${u.search}`;
284
+ * },
285
+ * });
277
286
  * ```
278
287
  *
279
288
  * The `tenant:` prefix keeps these keys from colliding with other key spaces
package/dist/types.d.ts CHANGED
@@ -422,7 +422,32 @@ export interface RouteDefinition<P extends PathString = PathString, M extends Ht
422
422
  */
423
423
  meta?: RouteMeta;
424
424
  hooks?: Hooks;
425
- handler: (ctx: BaseContext<P, Req>) => HandlerReturn<Res> | Promise<HandlerReturn<Res>>;
425
+ /**
426
+ * The route handler. Receives the typed, validated {@link BaseContext} and
427
+ * returns either:
428
+ *
429
+ * - a structured result `{ status, body, headers? }` whose `body` is
430
+ * validated against the route's response schema and typed end-to-end into
431
+ * the OpenAPI document and generated client (the common case), or
432
+ * - a raw web-standard {@link Response} as an escape hatch for streaming,
433
+ * proxying, or pre-built bodies (for example an AI SDK
434
+ * `result.toUIMessageStreamResponse()`, or an upstream `fetch()` response
435
+ * forwarded verbatim).
436
+ *
437
+ * A returned `Response` **bypasses response-schema validation and the
438
+ * typed-client body type by design** — there is no schema that can describe
439
+ * an opaque stream. It is still finalized exactly like every other response,
440
+ * so no security control is skipped: headers set via `ctx.set` (including
441
+ * `secureHeaders()` and CORS) are copied onto it, `x-request-id` is added
442
+ * when absent, any `onSend` / `onResponse` hooks run, server-fingerprint
443
+ * headers (`server`, `x-powered-by`) are stripped, and a `HEAD` request still
444
+ * yields an empty body. This mirrors the existing `beforeHandle` `Response`
445
+ * passthrough. Prefer the structured result whenever a schema can describe
446
+ * the payload; reach for `Response` only when it genuinely cannot.
447
+ *
448
+ * @since 0.1.0
449
+ */
450
+ handler: (ctx: BaseContext<P, Req>) => HandlerReturn<Res> | Response | Promise<HandlerReturn<Res> | Response>;
426
451
  }
427
452
  /**
428
453
  * One operation inside an OpenAPI Callback Object. Mirrors a route minus
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daloyjs/core",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.5",
4
4
  "description": "DaloyJS is a runtime-portable, contract-first TypeScript web framework with built-in OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-first defaults. Hono-grade portability, Elysia-grade DX, FastAPI-grade docs, Fastify-grade ops — distributed via pnpm.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -37,7 +37,7 @@
37
37
  "daloy": "bin/daloy.mjs"
38
38
  },
39
39
  "engines": {
40
- "node": ">=24.0.0",
40
+ "node": "^24.0.0 || >=26.0.0",
41
41
  "pnpm": ">=11.0.0"
42
42
  },
43
43
  "exports": {
@@ -227,9 +227,9 @@
227
227
  }
228
228
  },
229
229
  "devDependencies": {
230
- "@hey-api/openapi-ts": "^0.97.1",
231
- "@types/node": "^25.7.0",
232
- "fast-check": "^3.23.2",
230
+ "@hey-api/openapi-ts": "^0.99.0",
231
+ "@types/node": "^26.0.1",
232
+ "fast-check": "^4.8.0",
233
233
  "prettier": "^3.8.3",
234
234
  "tsx": "^4.22.3",
235
235
  "typescript": "^6.0.3",
@@ -240,6 +240,7 @@
240
240
  "dev": "tsc -w -p tsconfig.json",
241
241
  "example": "node --import tsx examples/basic.ts",
242
242
  "bench": "node --import tsx bench/router.bench.ts",
243
+ "bench:serverless": "node --import tsx bench/serverless-cold-path.bench.ts",
243
244
  "test": "node --import tsx --test tests/**/*.test.ts",
244
245
  "test:red-team": "node --import tsx --test tests/red-team-attacks.test.ts tests/red-team-attacks-2.test.ts tests/red-team-attacks-3.test.ts tests/red-team-attacks-4.test.ts tests/red-team-attacks-5.test.ts tests/red-team-attacks-6.test.ts tests/red-team-attacks-7.test.ts tests/red-team-attacks-8.test.ts tests/red-team-attacks-9.test.ts tests/red-team-attacks-10.test.ts",
245
246
  "red-team:live": "node --import tsx red-team-live/run.ts",