@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 +11 -11
- package/dist/action-paths.d.ts +0 -7
- package/dist/api-shape.d.ts +0 -24
- package/dist/encrypted-session-store.d.ts +0 -19
- package/dist/error-handler.d.ts +0 -68
- package/dist/index.d.ts +0 -81
- package/dist/index.js +0 -14
- package/dist/middleware.d.ts +0 -37
- package/dist/path-sanitize.d.ts +0 -64
- package/dist/rate-limit.d.ts +0 -35
- package/dist/request-augmentation.d.ts +0 -71
- package/dist/request-context.d.ts +0 -82
- package/dist/route-loader.d.ts +0 -5
- package/dist/route-types.d.ts +0 -12
- package/dist/security-headers.d.ts +0 -20
- package/dist/session-factory.d.ts +0 -57
- package/dist/signed-url.d.ts +0 -50
- package/dist/stacks-router.d.ts +0 -213
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.
|
|
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.
|
|
57
|
+
"@stacksjs/bun-router": "^0.0.19"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@stacksjs/actions": "0.70.
|
|
61
|
-
"@stacksjs/config": "0.70.
|
|
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.
|
|
64
|
-
"@stacksjs/logging": "0.70.
|
|
65
|
-
"@stacksjs/orm": "0.70.
|
|
66
|
-
"@stacksjs/path": "0.70.
|
|
67
|
-
"@stacksjs/storage": "0.70.
|
|
68
|
-
"@stacksjs/types": "0.70.
|
|
69
|
-
"@stacksjs/validation": "0.70.
|
|
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
|
}
|
package/dist/action-paths.d.ts
DELETED
|
@@ -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;
|
package/dist/api-shape.d.ts
DELETED
|
@@ -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
|
-
}
|
package/dist/error-handler.d.ts
DELETED
|
@@ -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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");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};
|
package/dist/middleware.d.ts
DELETED
|
@@ -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
|
-
}
|
package/dist/path-sanitize.d.ts
DELETED
|
@@ -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
|
-
}
|
package/dist/rate-limit.d.ts
DELETED
|
@@ -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;
|
package/dist/route-loader.d.ts
DELETED
package/dist/route-types.d.ts
DELETED
|
@@ -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';
|
package/dist/signed-url.d.ts
DELETED
|
@@ -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' }
|
package/dist/stacks-router.d.ts
DELETED
|
@@ -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
|
-
}
|