@stacksjs/router 0.70.37 → 0.70.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action-paths.d.ts +7 -0
- package/dist/error-handler.d.ts +62 -0
- package/dist/index.d.ts +35 -0
- package/dist/middleware.d.ts +36 -0
- package/dist/rate-limit.d.ts +35 -0
- package/dist/request-context.d.ts +59 -0
- package/dist/route-loader.d.ts +5 -0
- package/dist/stacks-router.d.ts +113 -0
- package/package.json +10 -10
|
@@ -0,0 +1,7 @@
|
|
|
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;
|
|
@@ -0,0 +1,62 @@
|
|
|
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. Useful for tests asserting that an
|
|
14
|
+
* action ran a single query for `posts` instead of one-per-user.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getQueryShapeCounts(): ReadonlyMap<string, number>;
|
|
17
|
+
/**
|
|
18
|
+
* Clear tracked queries (e.g., after successful response)
|
|
19
|
+
*/
|
|
20
|
+
export declare function clearTrackedQueries(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Create an Ignition-style error response for development
|
|
23
|
+
*/
|
|
24
|
+
export declare function createErrorResponse(error: Error, request: Request | EnhancedRequest, options?: {
|
|
25
|
+
status?: number
|
|
26
|
+
handlerPath?: string
|
|
27
|
+
routingContext?: {
|
|
28
|
+
controller?: string
|
|
29
|
+
routeName?: string
|
|
30
|
+
middleware?: string[]
|
|
31
|
+
}
|
|
32
|
+
}): Promise<Response>;
|
|
33
|
+
/**
|
|
34
|
+
* Create a middleware error response (401, 403, etc.)
|
|
35
|
+
*
|
|
36
|
+
* Reads `statusCode` OR `status` off the error so both shapes are honored:
|
|
37
|
+
* - middleware that throws `Object.assign(new Error('msg'), { statusCode: 401 })`
|
|
38
|
+
* - framework HttpError instances where the field is named `status`
|
|
39
|
+
*
|
|
40
|
+
* Without the `status` fallback, every `HttpError(401, …)` throw from auth or
|
|
41
|
+
* validation middleware leaks out as a 500 with an Ignition error page —
|
|
42
|
+
* which is what we used to ship for `GET /api/me` without a token.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createMiddlewareErrorResponse(error: Error & { statusCode?: number, status?: number }, request: Request | EnhancedRequest): Promise<Response>;
|
|
45
|
+
/**
|
|
46
|
+
* Create a validation error response
|
|
47
|
+
*/
|
|
48
|
+
export declare function createValidationErrorResponse(errors: Record<string, string[]>, _request: Request | EnhancedRequest): Response;
|
|
49
|
+
/**
|
|
50
|
+
* Create a 404 Not Found response
|
|
51
|
+
*/
|
|
52
|
+
export declare function createNotFoundResponse(path: string, request: Request | EnhancedRequest): Promise<Response>;
|
|
53
|
+
/**
|
|
54
|
+
* Standard error response structure used across all JSON error responses.
|
|
55
|
+
*/
|
|
56
|
+
export declare interface ErrorResponseBody {
|
|
57
|
+
error: string
|
|
58
|
+
message: string
|
|
59
|
+
status: number
|
|
60
|
+
timestamp: string
|
|
61
|
+
details?: Record<string, unknown>
|
|
62
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type { MiddlewareConfig, Request } from './middleware';
|
|
2
|
+
// Export route registry types
|
|
3
|
+
export type { RouteDefinition, RouteRegistry } from '../../../../../app/Routes';
|
|
4
|
+
/**
|
|
5
|
+
* @stacksjs/router - Stacks Router
|
|
6
|
+
*
|
|
7
|
+
* A thin wrapper around bun-router that adds Stacks-specific
|
|
8
|
+
* action/controller resolution for string-based route handlers.
|
|
9
|
+
*
|
|
10
|
+
* All routing functionality comes directly from bun-router.
|
|
11
|
+
*/
|
|
12
|
+
// Re-export everything from bun-router (includes response factory)
|
|
13
|
+
export * from '@stacksjs/bun-router';
|
|
14
|
+
// Export Stacks-specific action resolver and URL helper
|
|
15
|
+
export { clearMiddlewareCache, createStacksRouter, installMiddlewareHotReload, route, serve, serverResponse, url } from './stacks-router';
|
|
16
|
+
// Export request context helpers
|
|
17
|
+
export { cacheRequestQuery, getCurrentRequest, getTraceId, request, runWithRequest, setCurrentRequest, withTraceId } from './request-context';
|
|
18
|
+
// Export Middleware class for defining route middleware
|
|
19
|
+
export { Middleware } from './middleware';
|
|
20
|
+
// Export route loader
|
|
21
|
+
export { loadRoutes } from './route-loader';
|
|
22
|
+
// Export error handler utilities
|
|
23
|
+
export {
|
|
24
|
+
clearTrackedQueries,
|
|
25
|
+
createErrorResponse,
|
|
26
|
+
createMiddlewareErrorResponse,
|
|
27
|
+
createNotFoundResponse,
|
|
28
|
+
createValidationErrorResponse,
|
|
29
|
+
getQueryShapeCounts,
|
|
30
|
+
trackQuery,
|
|
31
|
+
} from './error-handler';
|
|
32
|
+
// Export route introspection helpers
|
|
33
|
+
export { listRegisteredRoutes, routeParams } from './stacks-router';
|
|
34
|
+
// Export action-level rate limiting helpers
|
|
35
|
+
export { rateLimit, rateLimitStatus, clearRateLimit } from './rate-limit';
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { EnhancedRequest } from '@stacksjs/bun-router';
|
|
2
|
+
/**
|
|
3
|
+
* Read the active trace id, or `undefined` outside any traced scope.
|
|
4
|
+
*
|
|
5
|
+
* Falls back to the request's `_requestId` if no explicit trace was
|
|
6
|
+
* set so the helper is always useful from an HTTP handler — the router
|
|
7
|
+
* sets `_requestId` per request, and that value is the implicit trace
|
|
8
|
+
* for downstream calls until something more specific is configured.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getTraceId(): string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Run `fn` under a fresh trace scope. Used by queue workers and cron
|
|
13
|
+
* triggers to associate background work with the originating request
|
|
14
|
+
* (or a synthetic id when there's no parent).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* await withTraceId(genId(), async () => {
|
|
19
|
+
* await job.handle()
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function withTraceId<T>(id: string, fn: () => T): T;
|
|
24
|
+
/**
|
|
25
|
+
* Run `fetcher()` once per `key` per request. Subsequent callers within
|
|
26
|
+
* the same request lifecycle await the cached Promise.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const user = await cacheRequestQuery(`User.find:${id}`, () => User.find(id))
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function cacheRequestQuery<T>(key: string, fetcher: () => T | Promise<T>): Promise<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Set the current request context
|
|
36
|
+
* Called by middleware/router when handling a request
|
|
37
|
+
*/
|
|
38
|
+
export declare function setCurrentRequest(req: EnhancedRequest): void;
|
|
39
|
+
/**
|
|
40
|
+
* Run a function with a request context
|
|
41
|
+
* All code executed within the callback will have access to the request
|
|
42
|
+
*/
|
|
43
|
+
export declare function runWithRequest<T>(req: EnhancedRequest, fn: () => T): T;
|
|
44
|
+
/**
|
|
45
|
+
* Get the current request from context
|
|
46
|
+
*/
|
|
47
|
+
export declare function getCurrentRequest(): EnhancedRequest | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Request proxy that provides access to the current request
|
|
50
|
+
* Similar to Laravel's request() helper
|
|
51
|
+
*
|
|
52
|
+
* Methods:
|
|
53
|
+
* - bearerToken() - Get the bearer token from Authorization header
|
|
54
|
+
* - user() - Get the authenticated user (async)
|
|
55
|
+
* - userToken() - Get the current access token (async)
|
|
56
|
+
* - tokenCan(ability) - Check if token has an ability (async)
|
|
57
|
+
* - tokenCant(ability) - Check if token doesn't have an ability (async)
|
|
58
|
+
*/
|
|
59
|
+
export declare const request: Proxy;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Server } from 'bun';
|
|
2
|
+
import { Router } from '@stacksjs/bun-router';
|
|
3
|
+
import type { ActionHandler, EnhancedRequest, Route, ServerOptions } from '@stacksjs/bun-router';
|
|
4
|
+
/**
|
|
5
|
+
* Generate a full URL for a named route, like Laravel's route() helper.
|
|
6
|
+
*
|
|
7
|
+
* Validates path parameters at call time so a typo'd argument
|
|
8
|
+
* (`url('user.post', { userId: 1 })` against `/users/{id}`) throws
|
|
9
|
+
* immediately with a list of expected names instead of silently
|
|
10
|
+
* producing a URL with `{id}` left literal in the path.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Define a named route
|
|
15
|
+
* route.get('/api/email/unsubscribe', 'Actions/UnsubscribeAction').name('email.unsubscribe')
|
|
16
|
+
*
|
|
17
|
+
* // Generate URL
|
|
18
|
+
* url('email.unsubscribe', { token: 'abc-123' })
|
|
19
|
+
* // → https://stacksjs.com/api/email/unsubscribe?token=abc-123
|
|
20
|
+
*
|
|
21
|
+
* // With path parameters
|
|
22
|
+
* route.get('/users/{id}/posts/{postId}', handler).name('user.post')
|
|
23
|
+
* url('user.post', { id: 42, postId: 7 })
|
|
24
|
+
* // → https://stacksjs.com/users/42/posts/7
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function url(routeName: string, params?: Record<string, string | number>): string;
|
|
28
|
+
/**
|
|
29
|
+
* List the placeholder names a named route expects — handy for
|
|
30
|
+
* codegen/test cases and for detecting typos before runtime.
|
|
31
|
+
*/
|
|
32
|
+
export declare function routeParams(routeName: string): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Snapshot of the registered routes — `{ method, path, name? }` per
|
|
35
|
+
* route. Used by `buddy route:list` and the dev-server startup banner.
|
|
36
|
+
*/
|
|
37
|
+
export declare function listRegisteredRoutes(): Array<{ method: string, path: string, name?: string }>;
|
|
38
|
+
/**
|
|
39
|
+
* Clear the middleware cache (useful for hot-reload in development).
|
|
40
|
+
*
|
|
41
|
+
* `installMiddlewareHotReload()` will wire this up automatically when
|
|
42
|
+
* called from the dev server — production should never invoke it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function clearMiddlewareCache(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Watch `app/Middleware/` and `app/Middleware.ts` and invalidate the
|
|
47
|
+
* cached middleware modules whenever a file changes. Intended for the
|
|
48
|
+
* dev server only — calling this in production is a no-op (the
|
|
49
|
+
* watcher handle is created but never fires anything user code cares
|
|
50
|
+
* about). Returns a `disposer()` to stop watching.
|
|
51
|
+
*
|
|
52
|
+
* Without this hook, editing a middleware file in dev requires a
|
|
53
|
+
* full server restart to see the change — the import map caches the
|
|
54
|
+
* old version forever.
|
|
55
|
+
*/
|
|
56
|
+
export declare function installMiddlewareHotReload(): () => void;
|
|
57
|
+
/**
|
|
58
|
+
* Create a Stacks-enhanced router
|
|
59
|
+
*/
|
|
60
|
+
export declare function createStacksRouter(config?: StacksRouterConfig): StacksRouterInstance;
|
|
61
|
+
/**
|
|
62
|
+
* Handle a server request through the router
|
|
63
|
+
* This is the main entry point for the Stacks server
|
|
64
|
+
*/
|
|
65
|
+
export declare function serverResponse(request: Request, _body?: string): Promise<Response>;
|
|
66
|
+
// Export serve function that uses the default router
|
|
67
|
+
export declare function serve(options?: ServerOptions): Promise<Server<unknown>>;
|
|
68
|
+
// Create and export a default router instance
|
|
69
|
+
export declare const route: unknown;
|
|
70
|
+
declare interface StacksRouterConfig {
|
|
71
|
+
verbose?: boolean
|
|
72
|
+
apiPrefix?: string
|
|
73
|
+
}
|
|
74
|
+
declare interface GroupOptions {
|
|
75
|
+
prefix?: string
|
|
76
|
+
middleware?: string | string[]
|
|
77
|
+
}
|
|
78
|
+
declare interface ResourceRouteOptions {
|
|
79
|
+
only?: ResourceAction[]
|
|
80
|
+
except?: ResourceAction[]
|
|
81
|
+
middleware?: string | string[]
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Chainable route interface for middleware and naming support
|
|
85
|
+
*/
|
|
86
|
+
declare interface ChainableRoute {
|
|
87
|
+
middleware: (name: string) => ChainableRoute
|
|
88
|
+
name: (routeName: string) => ChainableRoute
|
|
89
|
+
skipCsrf: () => ChainableRoute
|
|
90
|
+
}
|
|
91
|
+
export declare interface StacksRouterInstance {
|
|
92
|
+
bunRouter: Router
|
|
93
|
+
routes: Route[]
|
|
94
|
+
get: (path: string, handler: StacksHandler) => ChainableRoute
|
|
95
|
+
post: (path: string, handler: StacksHandler) => ChainableRoute
|
|
96
|
+
put: (path: string, handler: StacksHandler) => ChainableRoute
|
|
97
|
+
patch: (path: string, handler: StacksHandler) => ChainableRoute
|
|
98
|
+
delete: (path: string, handler: StacksHandler) => ChainableRoute
|
|
99
|
+
options: (path: string, handler: StacksHandler) => ChainableRoute
|
|
100
|
+
group: (options: GroupOptions, callback: () => void | Promise<void>) => StacksRouterInstance | Promise<StacksRouterInstance>
|
|
101
|
+
resource: (name: string, handler: string, options?: ResourceRouteOptions) => StacksRouterInstance
|
|
102
|
+
match: (methods: string[], path: string, handler: StacksHandler) => ChainableRoute
|
|
103
|
+
health: () => StacksRouterInstance
|
|
104
|
+
use: (middleware: ActionHandler) => StacksRouterInstance
|
|
105
|
+
register: (routePath: string, options?: { prefix?: string, middleware?: string | string[] }) => Promise<StacksRouterInstance>
|
|
106
|
+
serve: (options?: ServerOptions) => Promise<Server<unknown>>
|
|
107
|
+
handleRequest: (req: Request) => Promise<Response>
|
|
108
|
+
importRoutes: () => Promise<void>
|
|
109
|
+
loadDiscoveredRoutes: () => Promise<void>
|
|
110
|
+
}
|
|
111
|
+
declare type RouteHandlerFn = (_req: EnhancedRequest) => Response | Promise<Response>;
|
|
112
|
+
declare type StacksHandler = string | RouteHandlerFn;
|
|
113
|
+
declare type ResourceAction = 'index' | 'store' | 'show' | 'update' | 'destroy';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/router",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.42",
|
|
5
5
|
"description": "The Stacks framework router.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -52,15 +52,15 @@
|
|
|
52
52
|
"@stacksjs/bun-router": "^0.0.13"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@stacksjs/actions": "0.70.
|
|
56
|
-
"@stacksjs/config": "0.70.
|
|
55
|
+
"@stacksjs/actions": "^0.70.42",
|
|
56
|
+
"@stacksjs/config": "^0.70.42",
|
|
57
57
|
"better-dx": "^0.2.12",
|
|
58
|
-
"@stacksjs/error-handling": "0.70.
|
|
59
|
-
"@stacksjs/logging": "0.70.
|
|
60
|
-
"@stacksjs/orm": "0.70.
|
|
61
|
-
"@stacksjs/path": "0.70.
|
|
62
|
-
"@stacksjs/storage": "0.70.
|
|
63
|
-
"@stacksjs/types": "0.70.
|
|
64
|
-
"@stacksjs/validation": "0.70.
|
|
58
|
+
"@stacksjs/error-handling": "^0.70.42",
|
|
59
|
+
"@stacksjs/logging": "^0.70.42",
|
|
60
|
+
"@stacksjs/orm": "^0.70.42",
|
|
61
|
+
"@stacksjs/path": "^0.70.42",
|
|
62
|
+
"@stacksjs/storage": "^0.70.42",
|
|
63
|
+
"@stacksjs/types": "^0.70.42",
|
|
64
|
+
"@stacksjs/validation": "^0.70.42"
|
|
65
65
|
}
|
|
66
66
|
}
|