@stacksjs/router 0.70.86 → 0.70.88

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/router",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.86",
5
+ "version": "0.70.88",
6
6
  "description": "The Stacks framework router.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -54,18 +54,18 @@
54
54
  "prepublishOnly": "bun run build"
55
55
  },
56
56
  "dependencies": {
57
- "@stacksjs/bun-router": "0.0.17"
57
+ "@stacksjs/bun-router": "^0.0.19"
58
58
  },
59
59
  "devDependencies": {
60
- "@stacksjs/actions": "0.70.86",
61
- "@stacksjs/config": "0.70.86",
60
+ "@stacksjs/actions": "0.70.88",
61
+ "@stacksjs/config": "0.70.88",
62
62
  "better-dx": "^0.2.16",
63
- "@stacksjs/error-handling": "0.70.86",
64
- "@stacksjs/logging": "0.70.86",
65
- "@stacksjs/orm": "0.70.86",
66
- "@stacksjs/path": "0.70.86",
67
- "@stacksjs/storage": "0.70.86",
68
- "@stacksjs/types": "0.70.86",
69
- "@stacksjs/validation": "0.70.86"
63
+ "@stacksjs/error-handling": "0.70.88",
64
+ "@stacksjs/logging": "0.70.88",
65
+ "@stacksjs/orm": "0.70.88",
66
+ "@stacksjs/path": "0.70.88",
67
+ "@stacksjs/storage": "0.70.88",
68
+ "@stacksjs/types": "0.70.88",
69
+ "@stacksjs/validation": "0.70.88"
70
70
  }
71
71
  }
@@ -1,7 +0,0 @@
1
- /**
2
- * Action path types for the Stacks router.
3
- * This file is auto-generated or manually maintained to provide
4
- * type-safe string paths for routing to actions and controllers.
5
- */
6
- // Base type for all action paths - will be narrowed as actions are added
7
- export type StacksActionPath = string;
@@ -1,24 +0,0 @@
1
- /*`,
2
- * not `application/json`). This predicate fixes that asymmetry by making
3
- * JSON the default unless the client explicitly opts into HTML.
4
- */
5
- export declare function isApiRequest(req: Request | { headers: Headers }): boolean;
6
- /**
7
- * "Is this request JSON-shaped?" — the single source of truth used across
8
- * the framework to decide JSON vs HTML for responses (errors, primitives,
9
- * empty results) and to widen request-body parsing to every JSON variant.
10
- *
11
- * The historical bug was scattered ad-hoc checks: `error-handler` looked at
12
- * `Accept`, `formatResult` ignored the request entirely, `parseRequestBody`
13
- * did `contentType.includes('application/json')` so `application/vnd.api+json`
14
- * went unparsed. Centralizing the decision here means a future tweak (e.g.,
15
- * treating an `apiResponse: true` route group as always-JSON) lands in one
16
- * place and every response path picks it up.
17
- */
18
- /**
19
- * Matches `application/json` plus any RFC-6838 structured-suffix subtype:
20
- * `application/vnd.api+json`, `application/ld+json`, `application/hal+json`,
21
- * `application/problem+json`, etc. Case-insensitive; tolerates a trailing
22
- * `;` (for `;charset=utf-8`) or end-of-string.
23
- */
24
- export declare const JSON_CONTENT_TYPE: unknown;
@@ -1,19 +0,0 @@
1
- import type { SessionData, SessionStore } from '@stacksjs/bun-router';
2
- export declare interface EncryptedSessionStoreOptions {
3
- appKey?: string
4
- }
5
- /**
6
- * Wrap a bun-router `SessionStore<SessionData>` so all writes are
7
- * encrypted on the way in and decrypted on the way out. Drop-in
8
- * replacement for any of the built-in stores.
9
- */
10
- export declare class EncryptedSessionStore implements SessionStore<SessionData> {
11
- constructor(inner: SessionStore<SessionData>, opts?: EncryptedSessionStoreOptions);
12
- set(sid: string, session: SessionData, ttl?: number): Promise<void>;
13
- touch(sid: string, session: SessionData, ttl?: number): Promise<void>;
14
- get(sid: string): Promise<SessionData | null>;
15
- destroy(sid: string): Promise<void>;
16
- all(): Promise<Record<string, SessionData>>;
17
- length(): Promise<number>;
18
- clear(): Promise<void>;
19
- }
@@ -1,68 +0,0 @@
1
- import type { EnhancedRequest } from '@stacksjs/bun-router';
2
- /**
3
- * Add a query to the recent queries list for error context.
4
- * Uses a circular buffer for O(1) insert instead of array.shift().
5
- *
6
- * Also runs N+1 detection: when the same query *shape* (with bound
7
- * values normalized away) repeats more than `N1_THRESHOLD` times within
8
- * a single request lifecycle, we warn once via `log.warn`. The signal
9
- * is highly correlated with missing eager loading.
10
- */
11
- export declare function trackQuery(query: string, time?: number, connection?: string): void;
12
- /**
13
- * Snapshot of query shape counts for the active request. Useful for
14
- * tests asserting that an action ran a single query for `posts`
15
- * instead of one-per-user.
16
- */
17
- export declare function getQueryShapeCounts(): ReadonlyMap<string, number>;
18
- /**
19
- * Reset query tracking for the active scope.
20
- *
21
- * Inside a request, this clears the per-request tracking object — but
22
- * the object is also auto-collected when the request goes out of scope,
23
- * so the explicit call is mainly useful for tests that re-use a single
24
- * request. Outside a request, this clears the process-wide fallback.
25
- */
26
- export declare function clearTrackedQueries(): void;
27
- /**
28
- * Create an Ignition-style error response for development
29
- */
30
- export declare function createErrorResponse(error: Error, request: Request | EnhancedRequest, options?: {
31
- status?: number
32
- handlerPath?: string
33
- routingContext?: {
34
- controller?: string
35
- routeName?: string
36
- middleware?: string[]
37
- }
38
- }): Promise<Response>;
39
- /**
40
- * Create a middleware error response (401, 403, etc.)
41
- *
42
- * Reads `statusCode` OR `status` off the error so both shapes are honored:
43
- * - middleware that throws `Object.assign(new Error('msg'), { statusCode: 401 })`
44
- * - framework HttpError instances where the field is named `status`
45
- *
46
- * Without the `status` fallback, every `HttpError(401, …)` throw from auth or
47
- * validation middleware leaks out as a 500 with an Ignition error page —
48
- * which is what we used to ship for `GET /api/me` without a token.
49
- */
50
- export declare function createMiddlewareErrorResponse(error: Error & { statusCode?: number, status?: number, headers?: Record<string, string> }, request: Request | EnhancedRequest): Promise<Response>;
51
- /**
52
- * Create a validation error response
53
- */
54
- export declare function createValidationErrorResponse(errors: Record<string, string[]>, _request: Request | EnhancedRequest): Response;
55
- /**
56
- * Create a 404 Not Found response
57
- */
58
- export declare function createNotFoundResponse(path: string, request: Request | EnhancedRequest): Promise<Response>;
59
- /**
60
- * Standard error response structure used across all JSON error responses.
61
- */
62
- export declare interface ErrorResponseBody {
63
- error: string
64
- message: string
65
- status: number
66
- timestamp: string
67
- details?: Record<string, unknown>
68
- }
package/dist/index.d.ts DELETED
@@ -1,81 +0,0 @@
1
- import './request-augmentation';
2
- // Re-export the augmentation types so userland can refer to the marker
3
- // surface explicitly when needed.
4
- export type { StacksRequestExtensions, StacksRequestMacros, StacksRequestMarkers } from './request-augmentation';
5
- export type { MiddlewareConfig, Request } from './middleware';
6
- // Export route registry types — owned here rather than in app/Routes.ts
7
- // so the path doesn't depend on a 5-level relative reach across the
8
- // framework defaults tree (stacksjs/stacks#1863, T-10).
9
- export type { RouteDefinition, RouteRegistry } from './route-types';
10
- export type { PathParamRejection, SanitizePathParamOptions } from './path-sanitize';
11
- export type { StreamOptions } from './stacks-router';
12
- export type { SignedUrlOptions, SignedUrlVerifyResult } from './signed-url';
13
- export type { EncryptedSessionStoreOptions } from './encrypted-session-store';
14
- export type {
15
- RedisClient,
16
- SessionConfig,
17
- SessionData,
18
- SessionStore,
19
- StacksSessionConfig,
20
- } from './session-factory';
21
- // Re-export everything from bun-router (includes response factory)
22
- export * from '@stacksjs/bun-router';
23
- // Export Stacks-specific action resolver and URL helper
24
- export { assertRouteMiddlewareResolvable, clearMiddlewareCache, createStacksRouter, findUnresolvableRouteMiddleware, installMiddlewareHotReload, route, serve, serverResponse, url, warnOnMultipleRouterInstances } from './stacks-router';
25
- // Export request context helpers
26
- export { cacheRequestQuery, getCurrentRequest, getTraceId, request, runWithRequest, setCurrentRequest, withTraceId } from './request-context';
27
- // Export Middleware class for defining route middleware
28
- export { Middleware } from './middleware';
29
- // Export route loader
30
- export { loadRoutes } from './route-loader';
31
- // Export error handler utilities
32
- export {
33
- clearTrackedQueries,
34
- createErrorResponse,
35
- createMiddlewareErrorResponse,
36
- createNotFoundResponse,
37
- createValidationErrorResponse,
38
- getQueryShapeCounts,
39
- trackQuery,
40
- } from './error-handler';
41
- // Export route introspection helpers
42
- export { listRegisteredRoutes, routeParams } from './stacks-router';
43
- // Export JSON-vs-HTML negotiation predicate so userland can short-circuit
44
- // the same decision the framework makes in formatResult / error-handler.
45
- export { isApiRequest, JSON_CONTENT_TYPE } from './api-shape';
46
- // Export action-level rate limiting helpers
47
- export { rateLimit, rateLimitStatus, clearRateLimit } from './rate-limit';
48
- // Export path-param sanitization helper (stacksjs/stacks#1870 R-12).
49
- // Defense-in-depth for actions that interpolate route params into
50
- // filesystem paths; the helper enforces no-traversal / no-absolute /
51
- // no-null-byte / length ceiling at a single chokepoint.
52
- export { PathParamError, safePathParam, sanitizePathParam } from './path-sanitize';
53
- // Export the streaming-response helper for SSE / NDJSON / chunked
54
- // binary returns (stacksjs/stacks#1870 R-4). Actions can return
55
- // `stream(asyncGen, { type: 'sse' })` and the router pipes it back
56
- // with the right Content-Type + no-cache headers.
57
- export { stream } from './stacks-router';
58
- // Signed-URL helpers — HMAC over the URL + optional expiry so single-
59
- // use links (email verify, password reset, unsubscribe) can be handed
60
- // out without long-lived bearer tokens. Pair `signedUrl(...)` with the
61
- // `signed` middleware (or call `verifySignedUrl(req.url)` directly).
62
- // See stacksjs/stacks#1870 R-7.
63
- export { signedUrl, signUrl, verifySignedUrl, verifySignedUrlMiddleware } from './signed-url';
64
- // Encryption-at-rest wrapper for any bun-router SessionStore
65
- // (stacksjs/stacks#1878 Se-4). Opt-in: wrap your existing store
66
- // instance so session payloads are AES-GCM encrypted via APP_KEY
67
- // before being persisted.
68
- export { EncryptedSessionStore } from './encrypted-session-store';
69
- // Session driver factory (stacksjs/stacks#1889, F-2 from #1874).
70
- // Builds a SessionStore from the Stacks config — picks the right
71
- // driver from `config.session.driver`, optionally wraps with
72
- // EncryptedSessionStore. Re-exports all four bun-router store
73
- // classes so callers can assemble custom stacks manually too.
74
- export {
75
- createSessionStore,
76
- createStacksSessionStore,
77
- DatabaseSessionStore,
78
- FileSessionStore,
79
- MemorySessionStore,
80
- RedisSessionStore,
81
- } from './session-factory';
package/dist/index.js DELETED
@@ -1,14 +0,0 @@
1
- // @bun
2
- var{defineProperty:K,getOwnPropertyNames:R,getOwnPropertyDescriptor:k}=Object,g=Object.prototype.hasOwnProperty;function h($){return this[$]}var E=($)=>{var F=(Y??=new WeakMap).get($),W;if(F)return F;if(F=K({},"__esModule",{value:!0}),$&&typeof $==="object"||typeof $==="function"){for(var V of R($))if(!g.call(F,V))K(F,V,{get:h.bind($,V),enumerable:!(W=k($,V))||W.enumerable})}return Y.set($,F),F},Y;var l=($)=>$;function q($,F){this[$]=l.bind(null,F)}var d=($,F)=>{for(var W in F)K($,W,{get:F[W],enumerable:!0,configurable:!0,set:q.bind(F,W)})};var f=($,F)=>()=>($&&(F=$($=0)),F);var D=import.meta.require;function z($){let F=$.headers,W=F.get("content-type")||"";if(i.test(W))return!0;if(F.get("sec-fetch-dest")==="document")return!1;if((F.get("accept")||"").includes("text/html"))return!1;return!0}var i;var b=f(()=>{i=/^application\/(?:json|.+\+json)(?:;|$)/i});import c from"process";import{AsyncLocalStorage as U}from"async_hooks";import{log as v0}from"@stacksjs/logging";function I(){return n.getStore()}var p,n,u,w0,x0;var H=f(()=>{p=Symbol.for("stacks.router.requestStorage"),n=globalThis[p]??=new U,u=Symbol.for("stacks.router.traceStorage"),w0=globalThis[u]??=new U;x0=new Proxy({},{get($,F){let W=I();if(!W){if(c.env.NODE_ENV!=="production")console.warn(`[RequestContext] Accessing request.${String(F)} outside of request context`);if(F==="bearerToken")return()=>null;if(F==="user"||F==="userToken")return async()=>{return};if(F==="tokenCan"||F==="tokenCant")return async()=>!1;if(F==="headers")return new Headers;if(F==="url")return"";if(F==="method")return"GET";return}let V=W[F];if(typeof V==="function")return V.bind(W);return V}})});var Q={};d(Q,{trackQuery:()=>e,getQueryShapeCounts:()=>$0,createValidationErrorResponse:()=>O0,createNotFoundResponse:()=>L0,createMiddlewareErrorResponse:()=>N0,createErrorResponse:()=>J,clearTrackedQueries:()=>F0});import N from"process";import{log as o}from"@stacksjs/logging";import{createErrorHandler as a,renderProductionErrorPage as A}from"@stacksjs/error-handling";function P($){let F={error:$.error,message:$.message,status:$.status,timestamp:new Date().toISOString()};if($.details)F.details=$.details;return JSON.stringify(F)}function M(){let $=(N.env.APP_ENV??"").toLowerCase();if($==="development")return!0;if(!$&&N.env.NODE_ENV==="development")return!0;return!1}function O(){return{"Content-Type":"application/json"}}function s(){return O()}function v(){return{buffer:Array(X).fill(null),writeIndex:0,count:0,shapeCounts:new Map,n1Warned:new Set}}function m(){let $=I();if(!$)return S;let F=$[w];if(!F)F=v(),$[w]=F;return F}function r($){return $.replace(/'(?:[^']|'')*'/g,"?").replace(/"(?:[^"]|"")*"/g,"?").replace(/\b\d+(?:\.\d+)?\b/g,"?").replace(/IN\s*\([^)]*\)/gi,"IN (?)").replace(/\s+/g," ").trim().toUpperCase()}function e($,F,W){let V=m();if(V.buffer[V.writeIndex]={query:$,time:F,connection:W},V.writeIndex=(V.writeIndex+1)%X,V.count<X)V.count++;if(!M())return;let G=r($);if(G.startsWith("INSERT INTO QUERY_LOGS")||G.startsWith("EXPLAIN"))return;let Z=(V.shapeCounts.get(G)??0)+1;if(V.shapeCounts.set(G,Z),Z===t+1&&!V.n1Warned.has(G))V.n1Warned.add(G),import("@stacksjs/logging").then(({log:B})=>{B.warn(`[orm] Possible N+1 \u2014 query shape ran ${Z}\xD7 in this request:
3
- ${G}
4
- Hint: load related rows with .with('relation') or eager-load via includes() before iterating.`)}).catch(()=>{})}function C(){let $=m();if($.count===0)return[];let F=[],W=$.count<X?0:$.writeIndex;for(let V=0;V<$.count;V++){let G=$.buffer[(W+V)%X];if(G)F.push(G)}return F}function $0(){return new Map(m().shapeCounts)}function F0(){let $=I();if($&&$[w]){$[w]=v();return}S=v()}function V0(){return{appName:"Stacks",theme:"auto",showEnvironment:!0,showQueries:!0,showRequest:!0,enableCopyMarkdown:!0,snippetLines:8,basePaths:[N.cwd()]}}function x($,F=0,W=new WeakSet){if(!$||typeof $!=="object"||F>=Z0)return $;if(W.has($))return G0;if(W.add($),Array.isArray($))return $.map((G)=>x(G,F+1,W));let V={};for(let[G,Z]of Object.entries($)){let B=G.toLowerCase();if(W0.some((L)=>B.includes(L)))V[G]="********";else if(typeof Z==="object"&&Z!==null)V[G]=x(Z,F+1,W);else V[G]=Z}return V}function B0($){let F=$;if(F.jsonBody)return x(F.jsonBody);if(F.formBody)return x(F.formBody);return}async function j0($){let W=$._authenticatedUser;if(W)return{id:W.id,email:W.email,name:W.name||W.username};return}async function J($,F,W){let V=W?.status||500;if(o.debug(`[error] ${V} ${$.message}`),!M()){if(z(F)){let Z=V>=400&&V<500,B=$.details;return new Response(P({error:Z?$.name||"Client Error":"Internal Server Error",message:Z?$.message:"An unexpected error occurred.",status:V,details:Z&&B&&typeof B==="object"?B:void 0}),{status:V,headers:O()})}return new Response(A(V),{status:V,headers:{"Content-Type":"text/html; charset=utf-8"}})}try{let Z=a(V0());Z.setFramework("Stacks","0.70.0");let B=B0(F);if(B){let j=new URL(F.url);Z.setRequest({method:F.method,url:F.url,headers:Object.fromEntries(F.headers.entries()),queryParams:Object.fromEntries(j.searchParams.entries()),body:B})}else Z.setRequest(F);let L=await j0(F);if(L)Z.setUser(L);if(W?.routingContext)Z.setRouting(W.routingContext);else if(W?.handlerPath)Z.setRouting({controller:W.handlerPath});for(let j of C())Z.addQuery(j.query,j.time,j.connection);if(z(F)){let j={handler:W?.handlerPath};if(M())j.stack=$.stack?.split(`
5
- `).slice(0,10),j.queries=C().slice(-10);return new Response(P({error:$.name||"Error",message:$.message,status:V,details:j}),{status:V,headers:s()})}let T=N.env.APP_URL?N.env.APP_URL.startsWith("http")?N.env.APP_URL:`https://${N.env.APP_URL}`:M()?"*":F.headers.get("origin")??"null",y=await Z.render($,V);return new Response(y,{status:V,headers:{"Content-Type":"text/html; charset=utf-8","Access-Control-Allow-Origin":T}})}catch(Z){console.error("[Error Handler] Failed to render error page:",Z);let B=(L)=>L.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");return new Response(`
6
- <html>
7
- <head><title>Error</title></head>
8
- <body>
9
- <h1>Error</h1>
10
- <p>${B($.message)}</p>
11
- <pre>${B($.stack||"")}</pre>
12
- </body>
13
- </html>
14
- `,{status:V,headers:{"Content-Type":"text/html; charset=utf-8"}})}}async function N0($,F){let W=$.statusCode??$.status??500,V=M();if(W>=400&&W<500){let G=$.headers?{...$.headers,...O()}:O();return new Response(P({error:$.name||"ClientError",message:$.message,status:W}),{status:W,headers:G})}if(V)return await J($,F,{status:W});return new Response(P({error:"Internal Server Error",message:"An unexpected error occurred.",status:W}),{status:W,headers:O()})}function O0($,F){return new Response(P({error:"ValidationError",message:"Validation failed",status:422,details:{errors:$}}),{status:422,headers:O()})}async function L0($,F){if(M()){let V=Error(`Route not found: ${$}`);return V.name="NotFoundError",await J(V,F,{status:404})}if(z(F))return new Response(P({error:"NotFound",message:`Route not found: ${$}`,status:404}),{status:404,headers:O()});return new Response(A(404),{status:404,headers:{"Content-Type":"text/html; charset=utf-8"}})}var X=50,t=5,w,S,W0,Z0=10,G0="[Circular]";var _=f(()=>{b();H();w=Symbol.for("stacks.queryTracking"),S=v();W0=["password","secret","token","api_key","apikey","access_key","accesskey","private_key","privatekey","credit_card","creditcard","card_number","cardnumber","cvv","ssn","authorization","credential","aws_secret","aws_access","database_password","db_password","encryption_key","signing_key","bearer","session_id","sessionid","cookie"]});export*from"@stacksjs/bun-router";import("@stacksjs/database").then(({setQueryTracker:$})=>{if(typeof $==="function"){let{trackQuery:F}=(_(),E(Q));$(F)}}).catch(()=>{});export{p0 as withTraceId,g0 as warnOnMultipleRouterInstances,D1 as verifySignedUrlMiddleware,Y1 as verifySignedUrl,k0 as url,W1 as trackQuery,K1 as stream,m1 as signedUrl,J1 as signUrl,c0 as setCurrentRequest,R0 as serverResponse,y0 as serve,w1 as sanitizePathParam,v1 as safePathParam,i0 as runWithRequest,B1 as routeParams,T0 as route,d0 as request,P1 as rateLimitStatus,M1 as rateLimit,a0 as loadRoutes,G1 as listRegisteredRoutes,N1 as isApiRequest,_0 as installMiddlewareHotReload,q0 as getTraceId,V1 as getQueryShapeCounts,l0 as getCurrentRequest,Q0 as findUnresolvableRouteMiddleware,F1 as createValidationErrorResponse,A1 as createStacksSessionStore,S0 as createStacksRouter,C1 as createSessionStore,$1 as createNotFoundResponse,e0 as createMiddlewareErrorResponse,r0 as createErrorResponse,t0 as clearTrackedQueries,X1 as clearRateLimit,A0 as clearMiddlewareCache,E0 as cacheRequestQuery,C0 as assertRouteMiddlewareResolvable,T1 as RedisSessionStore,I1 as PathParamError,u0 as Middleware,_1 as MemorySessionStore,O1 as JSON_CONTENT_TYPE,Q1 as FileSessionStore,U1 as EncryptedSessionStore,S1 as DatabaseSessionStore};
@@ -1,37 +0,0 @@
1
- import type { EnhancedRequest } from '@stacksjs/bun-router';
2
- export declare interface MiddlewareConfig {
3
- name: string
4
- priority?: number
5
- handle: (request: EnhancedRequest) => void | Promise<void>
6
- }
7
- /**
8
- * Middleware class for defining route middleware
9
- *
10
- * Provides a simple, structured way to define middleware handlers
11
- * that can be attached to routes and route groups.
12
- *
13
- * The request object is an EnhancedRequest with helper methods like
14
- * `bearerToken()`, `get()`, `input()`, `has()`, etc.
15
- *
16
- * @example
17
- * ```ts
18
- * import { Middleware } from '@stacksjs/router'
19
- *
20
- * export default new Middleware({
21
- * name: 'Auth',
22
- * priority: 1,
23
- * async handle(request) {
24
- * const token = request.bearerToken()
25
- * if (!token) throw new HttpError(401, 'Unauthorized')
26
- * },
27
- * })
28
- * ```
29
- */
30
- export type Request = EnhancedRequest;
31
- export declare class Middleware {
32
- readonly name: string;
33
- readonly priority: number;
34
- readonly handle: (request: EnhancedRequest) => void | Promise<void>;
35
- constructor(config: MiddlewareConfig);
36
- toRouterHandler(): (req: EnhancedRequest, next: () => Promise<Response>) => Promise<Response>;
37
- }
@@ -1,64 +0,0 @@
1
- /**
2
- * Validate and return a path parameter, throwing if it's unsafe to use
3
- * in filesystem interpolation.
4
- *
5
- * The default contract is single-segment: no `/`, no `\`, no `..`, no
6
- * absolute path, no null bytes, no control characters, length ≤ 255.
7
- * Pass `allowSlashes: true` for a multi-segment path (still rejects
8
- * the rest).
9
- *
10
- * @throws {PathParamError} when the value fails any check.
11
- */
12
- export declare function sanitizePathParam(value: unknown, options?: SanitizePathParamOptions): string;
13
- /**
14
- * Non-throwing variant. Returns the sanitized value or `null` if any
15
- * check failed. Use when you want a fast yes/no in a conditional
16
- * without a try/catch around the throw site.
17
- */
18
- export declare function safePathParam(value: unknown, options?: SanitizePathParamOptions): string | null;
19
- export declare interface SanitizePathParamOptions {
20
- context?: string
21
- maxLength?: number
22
- allowSlashes?: boolean
23
- }
24
- /**
25
- * Path-parameter sanitization helpers.
26
- *
27
- * Route params arrive from the URL as untyped strings and are merged
28
- * directly into `req.params`. Actions that interpolate those values
29
- * into filesystem paths or shell commands without first scrubbing
30
- * them are vulnerable to `..`-traversal, absolute-path takeovers, and
31
- * null-byte truncation attacks.
32
- *
33
- * The router itself can't auto-sanitize every param (some are
34
- * deliberately path-shaped — file servers, asset proxies, etc.). What
35
- * we ship instead is a single canonical helper that callers reach for
36
- * at the boundary where the param meets the filesystem.
37
- *
38
- * See stacksjs/stacks#1870 R-12.
39
- *
40
- * @example
41
- * ```ts
42
- * import { sanitizePathParam } from '@stacksjs/router'
43
- *
44
- * const filename = sanitizePathParam(req.params.filename, {
45
- * context: 'avatar download',
46
- * })
47
- * return new Response(Bun.file(path.appPath(`avatars/${filename}`)))
48
- * ```
49
- */
50
- /**
51
- * Reasons {@link sanitizePathParam} rejects a value. Surfaced via the
52
- * thrown error so callers can log or branch.
53
- */
54
- export type PathParamRejection = | 'empty'
55
- | 'not-string'
56
- | 'absolute-path'
57
- | 'traversal'
58
- | 'null-byte'
59
- | 'control-char'
60
- | 'too-long';
61
- export declare class PathParamError extends Error {
62
- readonly reason: PathParamRejection;
63
- constructor(reason: PathParamRejection, value: unknown, context?: string);
64
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Check + consume a rate-limit slot for the current scope.
3
- *
4
- * @example
5
- * ```ts
6
- * await rateLimit('create-post', 10).per('hour')
7
- * await rateLimit('login-attempts', 5, { identity: email }).per('minute')
8
- * await rateLimit('expensive-job', 3).over(900) // custom 15-minute ttl
9
- * ```
10
- */
11
- export declare function rateLimit(key: string, max: number, options?: { identity?: string }): {
12
- /** Run with a string period name (`'minute'`, `'hour'`, …). */
13
- per: (period: Period) => Promise<void>
14
- /** Run with a numeric ttl in seconds. */
15
- over: (ttlSeconds: number) => Promise<void>
16
- };
17
- /**
18
- * Read the current bucket state without consuming a slot. Useful for
19
- * "you have N attempts remaining" hints in dashboards and pre-flight
20
- * checks. Returns `null` if the limiter's storage doesn't expose
21
- * `getCount` (the default memory storage does; redis storage may not).
22
- */
23
- export declare function rateLimitStatus(key: string, max: number, windowSeconds: number, options?: { identity?: string }): Promise<{ count: number, limit: number, remaining: number } | null>;
24
- /**
25
- * Drop the bucket for the given key (e.g. after a successful login,
26
- * the failed-attempt counter should reset).
27
- */
28
- export declare function clearRateLimit(key: string, max: number, windowSeconds: number, options?: { identity?: string }): Promise<void>;
29
- declare const PERIOD_SECONDS: {
30
- second: 1;
31
- minute: 60;
32
- hour: 3600;
33
- day: unknown
34
- };
35
- declare type Period = keyof typeof PERIOD_SECONDS;
@@ -1,71 +0,0 @@
1
- import type { FileInfo } from '@stacksjs/bun-router';
2
- /**
3
- * Stacks-specific marker fields attached to the request by the
4
- * router itself and the framework's default middleware.
5
- *
6
- * Markers are deliberately prefixed with `_` so they can't collide
7
- * with userland keys on the request, and their lifetimes are bounded
8
- * by the request's lifetime — they're never persisted.
9
- */
10
- export declare interface StacksRequestMarkers {
11
- _corsConfig?: unknown
12
- _forceJson?: boolean
13
- _skipCsrf?: boolean
14
- _compress?: boolean
15
- _middlewareParams?: Record<string, string>
16
- _requestId?: string
17
- _startNs?: bigint
18
- _authenticatedUser?: unknown
19
- _currentAccessToken?: unknown
20
- _bodyParsed?: boolean
21
- }
22
- /**
23
- * Laravel-style request-input macros that Stacks attaches in
24
- * `enhanceRequest` (router/src/stacks-router.ts). These shadow some
25
- * of bun-router's `RequestMacroMethods` with Stacks-specific
26
- * implementations (more permissive `T = any` generics so action
27
- * callers don't have to specify the return type for every read).
28
- *
29
- * Listed here as part of the augmentation so call sites like
30
- * `request.input(key)` type-check without `as any`.
31
- */
32
- export declare interface StacksRequestMacros {
33
- input?: <T = unknown>(key: string, defaultValue?: T) => T
34
- get?: <T = unknown>(key: string, defaultValue?: T) => T
35
- all?: () => Record<string, unknown>
36
- only?: <T extends Record<string, unknown>>(keys: string[]) => T
37
- except?: <T extends Record<string, unknown>>(keys: string[]) => T
38
- has?: (key: string | string[]) => boolean
39
- hasAny?: (keys: string[]) => boolean
40
- missing?: (key: string) => boolean
41
- filled?: (key: string) => boolean
42
- integer?: (key: string, defaultValue?: number) => number
43
- float?: (key: string, defaultValue?: number) => number
44
- boolean?: (key: string, defaultValue?: boolean) => boolean
45
- string?: (key: string, defaultValue?: string) => string
46
- array?: <T = unknown>(key: string, defaultValue?: T[]) => T[]
47
- file?: (key: string) => FileInfo | null
48
- files?: (key: string) => FileInfo[]
49
- hasFile?: (key: string) => boolean
50
- allFiles?: () => Record<string, FileInfo | FileInfo[]>
51
- getFiles?: () => Record<string, FileInfo | FileInfo[]>
52
- user?: () => Promise<unknown>
53
- userToken?: () => Promise<unknown>
54
- tokenCan?: (ability: string) => Promise<boolean>
55
- tokenCant?: (ability: string) => Promise<boolean>
56
- can?: (ability: string, ...args: unknown[]) => Promise<boolean>
57
- cannot?: (ability: string, ...args: unknown[]) => Promise<boolean>
58
- authorize?: (ability: string, ...args: unknown[]) => Promise<void>
59
- }
60
- /**
61
- * Union of Stacks markers + macros — useful as a single type alias for
62
- * places that previously cast to `any`.
63
- */
64
- export type StacksRequestExtensions = StacksRequestMarkers & StacksRequestMacros;
65
- declare module '@stacksjs/bun-router' {
66
- interface EnhancedRequestextends StacksRequestMarkers {
67
- allFiles?: StacksRequestMacros['allFiles']
68
- tokenCan?: StacksRequestMacros['tokenCan']
69
- can?: StacksRequestMacros['can']
70
- }
71
- }
@@ -1,82 +0,0 @@
1
- import type { EnhancedRequest } from '@stacksjs/bun-router';
2
- import type { RequestInstance } from '@stacksjs/types';
3
- /**
4
- * Read the active trace id, or `undefined` outside any traced scope.
5
- *
6
- * Falls back to the request's `_requestId` if no explicit trace was
7
- * set so the helper is always useful from an HTTP handler — the router
8
- * sets `_requestId` per request, and that value is the implicit trace
9
- * for downstream calls until something more specific is configured.
10
- */
11
- export declare function getTraceId(): string | undefined;
12
- /**
13
- * Run `fn` under a fresh trace scope. Used by queue workers and cron
14
- * triggers to associate background work with the originating request
15
- * (or a synthetic id when there's no parent).
16
- *
17
- * @example
18
- * ```ts
19
- * await withTraceId(genId(), async () => {
20
- * await job.handle()
21
- * })
22
- * ```
23
- */
24
- export declare function withTraceId<T>(id: string, fn: () => T): T;
25
- /**
26
- * Run `fetcher()` once per `key` per request. Subsequent callers within
27
- * the same request lifecycle await the cached Promise.
28
- *
29
- * @example
30
- * ```ts
31
- * const user = await cacheRequestQuery(`User.find:${id}`, () => User.find(id))
32
- * ```
33
- */
34
- export declare function cacheRequestQuery<T>(key: string, fetcher: () => T | Promise<T>): Promise<T>;
35
- /**
36
- * Set the current request context
37
- * Called by middleware/router when handling a request
38
- */
39
- export declare function setCurrentRequest(req: EnhancedRequest): void;
40
- /**
41
- * Clear the current request context.
42
- *
43
- * `setCurrentRequest` uses `AsyncLocalStorage.enterWith`, which mutates the
44
- * caller's async scope and never restores it. Call this in test teardown
45
- * (`afterEach`) whenever a test body calls `setCurrentRequest`, so the leaked
46
- * frame doesn't poison subsequently-collected test files (bun's runner
47
- * mis-registers tests when collected on a foreign async frame).
48
- */
49
- export declare function clearCurrentRequest(): void;
50
- /**
51
- * Run a function with a request context
52
- * All code executed within the callback will have access to the request
53
- */
54
- export declare function runWithRequest<T>(req: EnhancedRequest, fn: () => T): T;
55
- /**
56
- * Get the current request from context
57
- */
58
- export declare function getCurrentRequest(): EnhancedRequest | undefined;
59
- /**
60
- * Request proxy that provides access to the current request
61
- * (Laravel's `request()` helper, but typed).
62
- *
63
- * The proxy is statically typed as {@link RequestInstance} —
64
- * the canonical Stacks-side action-request surface
65
- * (stacksjs/stacks#1851 Phase 1). All the macros action handlers
66
- * reach for (`all`, `get`, `input`, `cookies`, `param`, `validate`,
67
- * `user`, `bearerToken`, …) resolve to their declared types instead
68
- * of `any`, eliminating most `(request as any)` casts in action code.
69
- *
70
- * Runtime is unchanged — the proxy still delegates to whichever
71
- * `EnhancedRequest` is in the AsyncLocalStorage slot. The type swap
72
- * is API-compatible: every method action code uses on `request`
73
- * existed on either type already, but only `RequestInstance` carries
74
- * the model-aware / path-aware narrowing.
75
- *
76
- * Methods worth knowing about:
77
- * - `bearerToken()` — Authorization header
78
- * - `user()` — authenticated user (async)
79
- * - `userToken()` — current access token (async)
80
- * - `tokenCan(ability)` / `tokenCant(ability)` — async ability checks
81
- */
82
- export declare const request: RequestInstance;
@@ -1,5 +0,0 @@
1
- import type { RouteRegistry } from './route-types';
2
- /**
3
- * Load all routes from the registry
4
- */
5
- export declare function loadRoutes(registry: RouteRegistry): Promise<void>;
@@ -1,12 +0,0 @@
1
- /**
2
- * Route registry types — owned by `@stacksjs/router` because the router
3
- * consumes them. `app/Routes.ts` (the project-level route map) imports
4
- * these via the public package name rather than a relative reach into
5
- * the framework defaults tree (stacksjs/stacks#1863, T-10).
6
- */
7
- export declare interface RouteDefinition {
8
- path: string
9
- prefix?: string
10
- middleware?: string | string[]
11
- }
12
- export type RouteRegistry = Record<string, string | RouteDefinition>;
@@ -1,20 +0,0 @@
1
- /**
2
- * Apply the default security headers to a Headers instance in-place.
3
- *
4
- * Headers applied unconditionally (cheap, no compat risk):
5
- * - `X-Content-Type-Options: nosniff` — blocks MIME-sniff XSS
6
- * - `X-Frame-Options: SAMEORIGIN` — clickjacking protection (CSP
7
- * `frame-ancestors` is the modern equivalent but XFO still ships)
8
- * - `Referrer-Policy: strict-origin-when-cross-origin` — modern default
9
- *
10
- * Production-only:
11
- * - `Strict-Transport-Security: max-age=31536000; includeSubDomains` —
12
- * tells browsers to commit to HTTPS for a year. Omits `preload` since
13
- * that's an irreversible commitment to the browser preload list.
14
- *
15
- * Skips overwriting any header that's already set — explicit userland
16
- * config wins. Skips entirely when `STACKS_SECURITY_HEADERS_DISABLE=true`.
17
- */
18
- export declare function applySecurityHeaders(headers: Headers): void;
19
- /** Test helper — reset the cached env-derived flags. */
20
- export declare function __resetSecurityHeadersCache(): void;
@@ -1,57 +0,0 @@
1
- import { createSessionStore } from '@stacksjs/bun-router';
2
- import type { RedisClient, SessionConfig, SessionData, SessionStore } from '@stacksjs/bun-router';
3
- // Re-export bun-router's session types so app code only has to
4
- // import from `@stacksjs/router` (one less package boundary to
5
- // learn). Drivers stay accessible by name for callers that want
6
- // to assemble a custom store manually.
7
- export type {
8
- RedisClient,
9
- SessionConfig,
10
- SessionData,
11
- SessionStore,
12
- };
13
- /**
14
- * Build a session store from the Stacks config. Mirrors what
15
- * `mail` / `Jobs` do for their driver registries — call once at
16
- * boot, pass the result into the session middleware.
17
- *
18
- * @example
19
- * ```ts
20
- * // config/session.ts (typical)
21
- * export const session = {
22
- * driver: 'redis',
23
- * ttl: 60 * 60 * 24, // 24h
24
- * cookie: { name: 'sid', httpOnly: true, sameSite: 'lax' },
25
- * redis: { client: useRedis() },
26
- * } satisfies StacksSessionConfig
27
- *
28
- * // Then at boot:
29
- * const store = createStacksSessionStore(session)
30
- * app.use(sessionMiddleware({ store }))
31
- * ```
32
- */
33
- export declare function createStacksSessionStore(config: StacksSessionConfig): SessionStore<SessionData>;
34
- /**
35
- * Stacks-level session configuration. Extends bun-router's
36
- * {@link SessionConfig} with:
37
- *
38
- * - `encrypt`: opt-in/out of {@link EncryptedSessionStore}
39
- * wrapping (default: `'auto'` — on in production, off in
40
- * dev/test where readability matters more than encryption-
41
- * at-rest)
42
- * - `appKey`: override key for the encryption (defaults to
43
- * `process.env.APP_KEY`)
44
- *
45
- * All other fields pass through to bun-router unchanged.
46
- */
47
- export declare interface StacksSessionConfig extends SessionConfig {
48
- encrypt?: boolean | 'auto'
49
- appKey?: string
50
- }
51
- export {
52
- DatabaseSessionStore,
53
- FileSessionStore,
54
- MemorySessionStore,
55
- RedisSessionStore,
56
- createSessionStore,
57
- } from '@stacksjs/bun-router';
@@ -1,50 +0,0 @@
1
- import type { EnhancedRequest } from '@stacksjs/bun-router';
2
- /**
3
- * Sign an existing URL (full or path-only). Returns a new URL string
4
- * with `expires` (optional) and `signature` query params appended.
5
- *
6
- * Path-only inputs (`/api/email/verify?user=42`) inherit `APP_URL` as
7
- * the origin — same convention as {@link buildUrl}.
8
- */
9
- export declare function signUrl(input: string, options?: SignedUrlOptions): string;
10
- /**
11
- * Convenience wrapper that resolves a named route to a URL via {@link buildUrl}
12
- * and then signs it. Mirrors Laravel's `URL::signedRoute()`.
13
- *
14
- * @example
15
- * ```ts
16
- * route.get('/api/email/verify', VerifyEmailAction).name('email.verify')
17
- * const link = signedUrl('email.verify', { user: 42 }, { ttl: 60 * 60 * 24 })
18
- * // → https://app.example.com/api/email/verify?user=42&expires=1716470400&signature=…
19
- * ```
20
- */
21
- export declare function signedUrl(routeName: string, params?: Record<string, string | number>, options?: SignedUrlOptions): string;
22
- /**
23
- * Verify the `signature` (and optional `expires`) on an incoming URL.
24
- * Returns a discriminated result so callers can pick their own status
25
- * code per failure mode.
26
- */
27
- export declare function verifySignedUrl(input: string | URL): SignedUrlVerifyResult;
28
- /**
29
- * Middleware shape for `route.middleware('signed')`. Verifies the
30
- * incoming URL's signature and throws a `Response` (the router's
31
- * short-circuit contract) when it fails. Drop in as a route-level
32
- * middleware on any URL minted by {@link signedUrl}.
33
- *
34
- * @example
35
- * ```ts
36
- * route.get('/email/verify', 'Actions/VerifyEmail').middleware('signed')
37
- * ```
38
- */
39
- export declare function verifySignedUrlMiddleware(req: EnhancedRequest): Promise<void>;
40
- export declare interface SignedUrlOptions {
41
- expiresAt?: number
42
- ttl?: number
43
- }
44
- /**
45
- * Result of {@link verifySignedUrl}. The `reason` only fires when `valid`
46
- * is `false` — gives callers (and middleware) enough to decide whether
47
- * to log, return 401, or return 410 (expired vs. tampered).
48
- */
49
- export type SignedUrlVerifyResult = | { valid: true }
50
- | { valid: false, reason: 'missing-signature' | 'expired' | 'invalid-signature' }
@@ -1,213 +0,0 @@
1
- import type { Server } from 'bun';
2
- import './request-augmentation';
3
- import { Router } from '@stacksjs/bun-router';
4
- import type { ActionHandler, EnhancedRequest, Route, ServerOptions } from '@stacksjs/bun-router';
5
- import type { ActionValidations, ValidationResult } from '@stacksjs/actions';
6
- /**
7
- * Warn (once per process) when more than one @stacksjs/router module has loaded
8
- * (stacksjs/stacks#1975 / #1982). Routing still works — the route table and
9
- * request context are process-global singletons — but a duplicated install is
10
- * worth surfacing. Called at serve() boot. Returns whether a split was detected
11
- * so callers/tests can assert on it without capturing logs.
12
- */
13
- export declare function warnOnMultipleRouterInstances(): boolean;
14
- /**
15
- * Generate a full URL for a named route, like Laravel's route() helper.
16
- *
17
- * Validates path parameters at call time so a typo'd argument
18
- * (`url('user.post', { userId: 1 })` against `/users/{id}`) throws
19
- * immediately with a list of expected names instead of silently
20
- * producing a URL with `{id}` left literal in the path.
21
- *
22
- * @example
23
- * ```typescript
24
- * // Define a named route
25
- * route.get('/api/email/unsubscribe', 'Actions/UnsubscribeAction').name('email.unsubscribe')
26
- *
27
- * // Generate URL
28
- * url('email.unsubscribe', { token: 'abc-123' })
29
- * // → https://stacksjs.com/api/email/unsubscribe?token=abc-123
30
- *
31
- * // With path parameters
32
- * route.get('/users/{id}/posts/{postId}', handler).name('user.post')
33
- * url('user.post', { id: 42, postId: 7 })
34
- * // → https://stacksjs.com/users/42/posts/7
35
- * ```
36
- */
37
- export declare function url(routeName: string, params?: Record<string, string | number>): string;
38
- /**
39
- * List the placeholder names a named route expects — handy for
40
- * codegen/test cases and for detecting typos before runtime.
41
- */
42
- export declare function routeParams(routeName: string): string[];
43
- /**
44
- * Snapshot of the registered routes — `{ method, path, name? }` per
45
- * route. Used by `buddy route:list` and the dev-server startup banner.
46
- */
47
- export declare function listRegisteredRoutes(): Array<{ method: string, path: string, name?: string }>;
48
- /**
49
- * Clear the middleware cache (useful for hot-reload in development).
50
- *
51
- * `installMiddlewareHotReload()` will wire this up automatically when
52
- * called from the dev server — production should never invoke it.
53
- */
54
- export declare function clearMiddlewareCache(): void;
55
- /**
56
- * Watch `app/Middleware/` and `app/Middleware.ts` and invalidate the
57
- * cached middleware modules whenever a file changes. Intended for the
58
- * dev server only — calling this in production is a no-op (the
59
- * watcher handle is created but never fires anything user code cares
60
- * about). Returns a `disposer()` to stop watching.
61
- *
62
- * Without this hook, editing a middleware file in dev requires a
63
- * full server restart to see the change — the import map caches the
64
- * old version forever.
65
- */
66
- export declare function installMiddlewareHotReload(): () => void;
67
- /**
68
- * Resolve every middleware alias referenced by a registered route and
69
- * report the ones that don't load. `csrf` is always checked too — it's
70
- * auto-injected on unsafe methods even when no route lists it.
71
- *
72
- * Resolution is inherently lazy (`.middleware(name)` is a sync chainable
73
- * that just records a string; the alias map and middleware modules load
74
- * via async dynamic import), so a throw at literal registration time is
75
- * impossible. Calling this after all routes are registered — the end of
76
- * `importRoutes()` and the compiled-binary boot in core/server — IS
77
- * effectively registration-time validation. See stacksjs/stacks#1957.
78
- */
79
- export declare function findUnresolvableRouteMiddleware(): Promise<Array<{ alias: string, routes: string[] }>>;
80
- /**
81
- * Throw when any registered route references a middleware alias that
82
- * cannot be resolved. Fail-closed boot validation: a typo'd `auth` alias
83
- * must abort startup loudly, not serve the route unprotected (the
84
- * request-time guard in createMiddlewareHandler 500s as a backstop).
85
- */
86
- export declare function assertRouteMiddlewareResolvable(): Promise<void>;
87
- /**
88
- * Run an action's declarative `validations:` against the request.
89
- *
90
- * @internal Exported for regression coverage of path-param coercion
91
- * (stacksjs/stacks#1865). Production callers should rely on the
92
- * router's action-resolution path, which invokes this for you.
93
- */
94
- export declare function validateActionInput(req: EnhancedRequest, validations: ActionValidations): Promise<ValidationResult>;
95
- export declare function stream(source: ReadableStream | AsyncIterable<string | Uint8Array>, options?: StreamOptions): Response;
96
- // Decorate the incoming request with the helpers the framework's middleware
97
- // and actions assume are always available. Names follow Laravel's convention
98
- // because that's the API surface Stacks userland expects.
99
- export declare function enhanceRequest(req: EnhancedRequest): EnhancedRequest;
100
- /**
101
- * Create a Stacks-enhanced router
102
- */
103
- export declare function createStacksRouter(config?: StacksRouterConfig): StacksRouterInstance;
104
- /**
105
- * Handle a server request through the router
106
- * This is the main entry point for the Stacks server
107
- */
108
- export declare function serverResponse(request: Request, _body?: string): Promise<Response>;
109
- // Export serve function that uses the default router
110
- export declare function serve(options?: ServerOptions): Promise<Server<unknown>>;
111
- export declare const route: StacksRouterInstance;
112
- declare interface StacksRouterConfig {
113
- verbose?: boolean
114
- apiPrefix?: string
115
- }
116
- declare interface GroupOptions {
117
- prefix?: string
118
- middleware?: string | string[]
119
- apiResponse?: boolean
120
- }
121
- declare interface ResourceRouteOptions {
122
- only?: ResourceAction[]
123
- except?: ResourceAction[]
124
- middleware?: string | string[]
125
- }
126
- /**
127
- * Chainable route interface for middleware and naming support
128
- */
129
- declare interface ChainableRoute {
130
- middleware: (name: string) => ChainableRoute
131
- name: (routeName: string) => ChainableRoute
132
- skipCsrf: () => ChainableRoute
133
- requireCsrf: () => ChainableRoute
134
- rateLimit: (max: number, window: 'second' | 'minute' | 'hour' | 'day' | number) => ChainableRoute
135
- }
136
- /**
137
- * Helper for streaming responses — wraps a `ReadableStream` or async
138
- * generator with the right headers for the chosen content type.
139
- *
140
- * Common shapes:
141
- *
142
- * ```ts
143
- * // Server-Sent Events
144
- * return stream(async function* () {
145
- * for await (const evt of source) yield `data: ${JSON.stringify(evt)}\n\n`
146
- * }, { type: 'sse' })
147
- *
148
- * // Chunked JSON (NDJSON) — one JSON object per line
149
- * return stream(async function* () {
150
- * for await (const row of rows) yield `${JSON.stringify(row)}\n`
151
- * }, { type: 'ndjson' })
152
- *
153
- * // Raw bytes — caller supplies a ReadableStream of Uint8Array chunks
154
- * return stream(myReadable, { contentType: 'application/octet-stream' })
155
- * ```
156
- *
157
- * The wrapper sets `Cache-Control: no-cache` and `Connection: keep-alive`
158
- * for SSE — the two headers a sane proxy / browser pair won't ignore — and
159
- * leaves backpressure / cancellation to the underlying stream.
160
- *
161
- * See stacksjs/stacks#1870 R-4.
162
- */
163
- export declare interface StreamOptions {
164
- type?: 'sse' | 'ndjson'
165
- contentType?: string
166
- headers?: HeadersInit
167
- status?: number
168
- }
169
- export declare interface StacksRouterInstance {
170
- bunRouter: Router
171
- routes: Route[]
172
- get: (path: string, handler: StacksHandler) => ChainableRoute
173
- post: (path: string, handler: StacksHandler) => ChainableRoute
174
- put: (path: string, handler: StacksHandler) => ChainableRoute
175
- patch: (path: string, handler: StacksHandler) => ChainableRoute
176
- delete: (path: string, handler: StacksHandler) => ChainableRoute
177
- options: (path: string, handler: StacksHandler) => ChainableRoute
178
- group: (options: GroupOptions, callback: () => void | Promise<void>) => StacksRouterInstance | Promise<StacksRouterInstance>
179
- resource: (name: string, handler: string, options?: ResourceRouteOptions) => StacksRouterInstance
180
- match: (methods: string[], path: string, handler: StacksHandler) => ChainableRoute
181
- health: () => StacksRouterInstance
182
- use: (middleware: ActionHandler | ((req: EnhancedRequest, next: () => Promise<Response>) => Response | Promise<Response>)) => StacksRouterInstance
183
- register: (routePath: string, options?: { prefix?: string, middleware?: string | string[] }) => Promise<StacksRouterInstance>
184
- serve: (options?: ServerOptions) => Promise<Server<unknown>>
185
- handleRequest: (req: Request) => Promise<Response>
186
- getAllowedMethods: (pathname: string, domain?: string) => string[]
187
- importRoutes: () => Promise<void>
188
- loadDiscoveredRoutes: () => Promise<void>
189
- }
190
- declare type RouteHandlerFn = (_req: EnhancedRequest) => Response | Promise<Response>;
191
- declare type StacksHandler = string | RouteHandlerFn;
192
- declare type ResourceAction = 'index' | 'store' | 'show' | 'update' | 'destroy';
193
- /**
194
- * FIFO-bounded Map. Wraps `Map` with a hard size cap; on overflow,
195
- * the oldest entry (Map insertion order) is evicted. Used for the
196
- * router's small framework-internal caches whose size is normally
197
- * bounded by action count, but which had no upper limit before —
198
- * tests that instantiate many short-lived routers would leak entries
199
- * across `createStacksRouter()` calls (stacksjs/stacks#1863 T-8).
200
- *
201
- * Insertion-order LRU is appropriate here because the access pattern
202
- * is "set once at action-load time, then many reads" — refreshing on
203
- * get would buy nothing since reads dominate.
204
- */
205
- declare class BoundedMap<K, V> {
206
- constructor(max: number);
207
- get(key: K): V | undefined;
208
- has(key: K): boolean;
209
- set(key: K, value: V): this;
210
- delete(key: K): boolean;
211
- clear(): void;
212
- get size(): number;
213
- }