@stacksjs/router 0.70.37 → 0.70.43

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.
@@ -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
+ }
@@ -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,5 @@
1
+ import type { RouteRegistry } from '../../../../../app/Routes';
2
+ /**
3
+ * Load all routes from the registry
4
+ */
5
+ export declare function loadRoutes(registry: RouteRegistry): Promise<void>;
@@ -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.37",
4
+ "version": "0.70.43",
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.30",
56
- "@stacksjs/config": "0.70.30",
55
+ "@stacksjs/actions": "^0.70.43",
56
+ "@stacksjs/config": "^0.70.43",
57
57
  "better-dx": "^0.2.12",
58
- "@stacksjs/error-handling": "0.70.30",
59
- "@stacksjs/logging": "0.70.30",
60
- "@stacksjs/orm": "0.70.30",
61
- "@stacksjs/path": "0.70.30",
62
- "@stacksjs/storage": "0.70.30",
63
- "@stacksjs/types": "0.70.30",
64
- "@stacksjs/validation": "0.70.30"
58
+ "@stacksjs/error-handling": "^0.70.43",
59
+ "@stacksjs/logging": "^0.70.43",
60
+ "@stacksjs/orm": "^0.70.43",
61
+ "@stacksjs/path": "^0.70.43",
62
+ "@stacksjs/storage": "^0.70.43",
63
+ "@stacksjs/types": "^0.70.43",
64
+ "@stacksjs/validation": "^0.70.43"
65
65
  }
66
66
  }