@aura-stack/router 0.6.0-rc.2 → 0.6.0

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/types.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { ZodObject, z } from 'zod';
2
2
  import { RouterError } from './error.js';
3
3
  import { HeadersBuilder } from './headers.js';
4
- import { IncomingHttpHeaders } from 'http';
5
4
  import 'cookie';
6
5
 
7
6
  /**
@@ -65,7 +64,7 @@ interface GlobalContext {
65
64
  */
66
65
  type EndpointConfig<RouteParams extends RoutePattern = RoutePattern, Schemas extends EndpointSchemas = EndpointSchemas> = Prettify<{
67
66
  schemas?: Schemas;
68
- middlewares?: MiddlewareFunction<GetRouteParams<RouteParams>, {
67
+ use?: MiddlewareFunction<GetRouteParams<RouteParams>, {
69
68
  schemas: Schemas;
70
69
  }>[];
71
70
  }>;
@@ -137,7 +136,7 @@ type RouteHandler<Route extends RoutePattern, Config extends EndpointConfig> = (
137
136
  * Represents a route endpoint definition, specifying the HTTP method, route pattern,
138
137
  * handler function with inferred context types, and associated configuration.
139
138
  */
140
- interface RouteEndpoint<Method extends HTTPMethod = HTTPMethod, Route extends RoutePattern = RoutePattern, Config extends EndpointConfig = EndpointConfig> {
139
+ interface RouteEndpoint<Method extends HTTPMethod | HTTPMethod[] = HTTPMethod | HTTPMethod[], Route extends RoutePattern = RoutePattern, Config extends EndpointConfig = EndpointConfig> {
141
140
  method: Method;
142
141
  route: Route;
143
142
  handler: RouteHandler<Route, Config>;
@@ -146,7 +145,7 @@ interface RouteEndpoint<Method extends HTTPMethod = HTTPMethod, Route extends Ro
146
145
  /**
147
146
  * Infer the HTTP methods defined in the provided array of route endpoints.
148
147
  */
149
- type InferMethod<Endpoints extends RouteEndpoint[]> = Endpoints extends unknown[] ? Endpoints[number]["method"] : "unknown";
148
+ type InferMethod<Endpoints extends RouteEndpoint[]> = Endpoints extends (infer Endpoint)[] ? Endpoint extends RouteEndpoint<infer Method, infer _, infer __> ? Method extends HTTPMethod[] ? Method[number] : Method : never : never;
150
149
  /**
151
150
  * Generates an object with HTTP methods available by the router from `createRouter` function.
152
151
  * Each method is a function that takes a request and context, returning a promise of a response.
@@ -181,7 +180,7 @@ interface RouterConfig extends GlobalCtx {
181
180
  * You can use this to modify the request or return a response early.
182
181
  *
183
182
  * @example
184
- * middlewares: [
183
+ * use: [
185
184
  * async (request) => {
186
185
  * if(request.headers.get("Authorization")?.startsWith("Bearer ")) {
187
186
  * return Response.json({ message: "Unauthorized" }, { status: 401 })
@@ -190,7 +189,7 @@ interface RouterConfig extends GlobalCtx {
190
189
  * }
191
190
  * ]
192
191
  */
193
- middlewares?: GlobalMiddleware[];
192
+ use?: GlobalMiddleware[];
194
193
  /**
195
194
  * Error handler function that runs when an error is thrown in a router handler or middleware.
196
195
  * It can be used to customize the default error response provided by the router. If is an internal
@@ -219,14 +218,15 @@ type ExtractEndpoint<T> = T extends RouteEndpoint<infer M, infer P, infer C> ? {
219
218
  path: P;
220
219
  config: C;
221
220
  } : never;
221
+ type MethodIncludes<Method, Met extends HTTPMethod> = Method extends readonly HTTPMethod[] ? Met extends Method[number] ? true : false : Method extends HTTPMethod ? Method extends Met ? true : false : false;
222
222
  /**
223
223
  * @experimental
224
224
  */
225
- type RoutesByMethod<Defs extends readonly RouteEndpoint[], Met extends HTTPMethod> = ExtractEndpoint<Defs[number]> extends infer E ? (E extends {
226
- method: Met;
225
+ type RoutesByMethod<Defs extends readonly RouteEndpoint[], Met extends HTTPMethod> = ExtractEndpoint<Defs[number]> extends infer E ? E extends {
226
+ method: infer M;
227
227
  path: infer P;
228
- } ? P : never) : never;
229
- type ExtractRoutesByMethod<Defs extends RouteEndpoint[], Met extends HTTPMethod> = Defs extends unknown[] ? Defs extends [infer First, ...infer Rest] ? First extends RouteEndpoint<infer M, infer R> ? M extends Met ? R | ExtractRoutesByMethod<Rest extends RouteEndpoint[] ? Rest : [], Met> : ExtractRoutesByMethod<Rest extends RouteEndpoint[] ? Rest : [], Met> : ExtractRoutesByMethod<Rest extends RouteEndpoint[] ? Rest : [], Met> : never : false;
228
+ } ? MethodIncludes<M, Met> extends true ? P : never : never : never;
229
+ type ExtractRoutesByMethod<Defs extends RouteEndpoint[], Met extends HTTPMethod> = Defs extends unknown[] ? Defs extends [infer First, ...infer Rest] ? First extends RouteEndpoint<infer M, infer R> ? MethodIncludes<M, Met> extends true ? R | ExtractRoutesByMethod<Rest extends RouteEndpoint[] ? Rest : [], Met> : ExtractRoutesByMethod<Rest extends RouteEndpoint[] ? Rest : [], Met> : ExtractRoutesByMethod<Rest extends RouteEndpoint[] ? Rest : [], Met> : never : false;
230
230
  type InferZod<T> = T extends z.ZodTypeAny ? z.infer<T> : T;
231
231
  type ToInferZod<T> = {
232
232
  [K in keyof T]: InferZod<T[K]>;
@@ -234,7 +234,7 @@ type ToInferZod<T> = {
234
234
  type RemoveUndefined<T> = {
235
235
  [K in keyof T as undefined extends T[K] ? never : K]: T[K];
236
236
  };
237
- type Find<Defs extends RouteEndpoint[], Met extends HTTPMethod, Path extends string> = Defs extends unknown[] ? Defs extends [infer First, ...infer Rest] ? First extends RouteEndpoint<infer M, infer R, infer C> ? M extends Met ? R extends Path ? RemoveUndefined<ToInferZod<NonNullable<C["schemas"]>>> : Find<Rest extends RouteEndpoint[] ? Rest : [], Met, Path> : Find<Rest extends RouteEndpoint[] ? Rest : [], Met, Path> : Find<Rest extends RouteEndpoint[] ? Rest : [], Met, Path> : never : never;
237
+ type Find<Defs extends RouteEndpoint[], Met extends HTTPMethod, Path extends string> = Defs extends unknown[] ? Defs extends [infer First, ...infer Rest] ? First extends RouteEndpoint<infer M, infer R, infer C> ? MethodIncludes<M, Met> extends true ? R extends Path ? RemoveUndefined<ToInferZod<NonNullable<C["schemas"]>>> : Find<Rest extends RouteEndpoint[] ? Rest : [], Met, Path> : Find<Rest extends RouteEndpoint[] ? Rest : [], Met, Path> : Find<Rest extends RouteEndpoint[] ? Rest : [], Met, Path> : never : never;
238
238
  type Client<Defs extends RouteEndpoint[]> = {
239
239
  [M in InferMethod<Defs> as Lowercase<M>]: <T extends ExtractRoutesByMethod<Defs, M>, Config extends Find<Defs, M, T>>(...args: Config extends EndpointSchemas ? [path: T, ctx?: RequestInit] : [path: T, ctx: Prettify<Omit<RequestInit, "body"> & Config>]) => Promise<Response>;
240
240
  };
@@ -243,7 +243,9 @@ type Router<Endpoints extends RouteEndpoint[]> = GetHttpHandlers<Endpoints> & {
243
243
  readonly [endpointsSymbol]?: Endpoints;
244
244
  };
245
245
  type InferEndpoints<T> = T extends Router<infer E> ? E : never;
246
- interface ClientOptions {
246
+ /** @experimental */
247
+ type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
248
+ interface ClientOptions extends Pick<RequestInit, "cache" | "credentials" | "mode"> {
247
249
  /**
248
250
  * Base URL for the router client to make requests to the server.
249
251
  * This is useful when the server is hosted on a different origin.
@@ -258,7 +260,82 @@ interface ClientOptions {
258
260
  /**
259
261
  * Default headers to include in every request made by the client.
260
262
  */
261
- headers?: IncomingHttpHeaders;
263
+ headers?: RequestHeaders | (() => RequestHeaders | Promise<RequestHeaders>);
264
+ /**
265
+ * @experimental
266
+ * Custom fetch function to be used by the client instead of the global fetch.
267
+ */
268
+ fetch?: FetchLike;
269
+ }
270
+ interface RequestHeaders extends Record<string, number | string | string[] | undefined> {
271
+ accept?: string | undefined;
272
+ "accept-encoding"?: string | undefined;
273
+ "accept-language"?: string | undefined;
274
+ "accept-patch"?: string | undefined;
275
+ "accept-ranges"?: string | undefined;
276
+ "access-control-allow-credentials"?: string | undefined;
277
+ "access-control-allow-headers"?: string | undefined;
278
+ "access-control-allow-methods"?: string | undefined;
279
+ "access-control-allow-origin"?: string | undefined;
280
+ "access-control-expose-headers"?: string | undefined;
281
+ "access-control-max-age"?: string | undefined;
282
+ "access-control-request-headers"?: string | undefined;
283
+ "access-control-request-method"?: string | undefined;
284
+ age?: string | undefined;
285
+ allow?: string | undefined;
286
+ "alt-svc"?: string | undefined;
287
+ authorization?: string | undefined;
288
+ "cache-control"?: string | undefined;
289
+ connection?: string | undefined;
290
+ "content-disposition"?: string | undefined;
291
+ "content-encoding"?: string | undefined;
292
+ "content-language"?: string | undefined;
293
+ "content-length"?: string | undefined;
294
+ "content-location"?: string | undefined;
295
+ "content-range"?: string | undefined;
296
+ "content-type"?: string | undefined;
297
+ cookie?: string | undefined;
298
+ date?: string | undefined;
299
+ etag?: string | undefined;
300
+ expect?: string | undefined;
301
+ expires?: string | undefined;
302
+ forwarded?: string | undefined;
303
+ from?: string | undefined;
304
+ host?: string | undefined;
305
+ "if-match"?: string | undefined;
306
+ "if-modified-since"?: string | undefined;
307
+ "if-none-match"?: string | undefined;
308
+ "if-unmodified-since"?: string | undefined;
309
+ "last-modified"?: string | undefined;
310
+ location?: string | undefined;
311
+ origin?: string | undefined;
312
+ pragma?: string | undefined;
313
+ "proxy-authenticate"?: string | undefined;
314
+ "proxy-authorization"?: string | undefined;
315
+ "public-key-pins"?: string | undefined;
316
+ range?: string | undefined;
317
+ referer?: string | undefined;
318
+ "retry-after"?: string | undefined;
319
+ "sec-fetch-site"?: string | undefined;
320
+ "sec-fetch-mode"?: string | undefined;
321
+ "sec-fetch-user"?: string | undefined;
322
+ "sec-fetch-dest"?: string | undefined;
323
+ "sec-websocket-accept"?: string | undefined;
324
+ "sec-websocket-extensions"?: string | undefined;
325
+ "sec-websocket-key"?: string | undefined;
326
+ "sec-websocket-protocol"?: string | undefined;
327
+ "sec-websocket-version"?: string | undefined;
328
+ "set-cookie"?: string[] | undefined;
329
+ "strict-transport-security"?: string | undefined;
330
+ tk?: string | undefined;
331
+ trailer?: string | undefined;
332
+ "transfer-encoding"?: string | undefined;
333
+ upgrade?: string | undefined;
334
+ "user-agent"?: string | undefined;
335
+ vary?: string | undefined;
336
+ via?: string | undefined;
337
+ warning?: string | undefined;
338
+ "www-authenticate"?: string | undefined;
262
339
  }
263
340
 
264
- export type { Client, ClientOptions, ContentType, ContextBody, ContextParams, ContextSearchParams, EndpointConfig, EndpointSchemas, ExtractEndpoint, ExtractRoutesByMethod, Find, GetHttpHandlers, GetRouteParams, GlobalContext, GlobalCtx, GlobalMiddleware, GlobalMiddlewareContext, HTTPMethod, InferEndpoints, InferMethod, InferZod, MiddlewareFunction, Prettify, RemoveUndefined, RequestContext, RouteEndpoint, RouteHandler, RoutePattern, Router, RouterConfig, RoutesByMethod, ToInferZod };
341
+ export type { Client, ClientOptions, ContentType, ContextBody, ContextParams, ContextSearchParams, EndpointConfig, EndpointSchemas, ExtractEndpoint, ExtractRoutesByMethod, FetchLike, Find, GetHttpHandlers, GetRouteParams, GlobalContext, GlobalCtx, GlobalMiddleware, GlobalMiddlewareContext, HTTPMethod, InferEndpoints, InferMethod, InferZod, MiddlewareFunction, Prettify, RemoveUndefined, RequestContext, RequestHeaders, RouteEndpoint, RouteHandler, RoutePattern, Router, RouterConfig, RoutesByMethod, ToInferZod };
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@aura-stack/router",
3
- "version": "0.6.0-rc.2",
3
+ "version": "0.6.0",
4
+ "private": false,
4
5
  "type": "module",
5
6
  "description": "A lightweight TypeScript library for building, managing, and validating API routes and endpoints in Node.js applications.",
6
7
  "repository": {
@@ -68,7 +69,9 @@
68
69
  "./types": "./dist/types.d.ts"
69
70
  },
70
71
  "devDependencies": {
72
+ "@types/bun": "^1.3.9",
71
73
  "@types/node": "24.6.2",
74
+ "mitata": "^1.0.34",
72
75
  "prettier": "^3.6.2",
73
76
  "tsup": "^8.5.1",
74
77
  "typescript": "^5.9.3",
@@ -1,163 +0,0 @@
1
- import {
2
- executeGlobalMiddlewares,
3
- executeMiddlewares
4
- } from "./chunk-6CIHAUKJ.js";
5
- import {
6
- getBody,
7
- getRouteParams,
8
- getSearchParams
9
- } from "./chunk-SY4MM2AG.js";
10
- import {
11
- isInvalidZodSchemaError,
12
- isRouterError,
13
- isSupportedMethod
14
- } from "./chunk-3X2BFSRT.js";
15
- import {
16
- RouterError,
17
- statusText
18
- } from "./chunk-FJYSN2I6.js";
19
- import {
20
- HeadersBuilder
21
- } from "./chunk-6JNMFP4L.js";
22
-
23
- // src/router.ts
24
- var createNode = () => ({
25
- statics: /* @__PURE__ */ new Map(),
26
- endpoints: /* @__PURE__ */ new Map()
27
- });
28
- var insert = (root, endpoint) => {
29
- if (!root || !endpoint) return;
30
- let node = root;
31
- const segments = endpoint.route === "/" ? [] : endpoint.route.split("/").filter(Boolean);
32
- for (const segment of segments) {
33
- if (segment.startsWith(":")) {
34
- const name = segment.slice(1);
35
- if (!node.param) {
36
- node.param = { name, node: createNode() };
37
- } else if (node.param.name !== name) {
38
- throw new RouterError(
39
- "BAD_REQUEST",
40
- `Conflicting in the route by the dynamic segment "${node.param.name}" and "${name}"`
41
- );
42
- }
43
- node = node.param.node;
44
- } else {
45
- if (!node.statics.has(segment)) {
46
- node.statics.set(segment, createNode());
47
- }
48
- node = node.statics.get(segment);
49
- }
50
- }
51
- if (node.endpoints.has(endpoint.method)) {
52
- throw new RouterError("BAD_REQUEST", `Duplicate endpoint for ${endpoint?.method} ${endpoint?.route}`);
53
- }
54
- node.endpoints.set(endpoint.method, endpoint);
55
- };
56
- var search = (method, root, pathname) => {
57
- let node = root;
58
- const params = {};
59
- const segments = pathname === "/" ? [] : pathname.split("/").filter(Boolean);
60
- for (const segment of segments) {
61
- if (node?.statics.has(segment)) {
62
- node = node.statics.get(segment);
63
- } else if (node?.param) {
64
- params[node.param.name] = decodeURIComponent(segment);
65
- node = node.param.node;
66
- } else {
67
- throw new RouterError("NOT_FOUND", `No route found for path: ${pathname}`);
68
- }
69
- }
70
- if (!node.endpoints.has(method)) {
71
- throw new RouterError("NOT_FOUND", `No route found for path: ${pathname}`);
72
- }
73
- return { endpoint: node.endpoints.get(method), params };
74
- };
75
- var handleError = async (error, request, config) => {
76
- if (config.onError) {
77
- try {
78
- const response = await config.onError(error, request);
79
- return response;
80
- } catch {
81
- return Response.json(
82
- { message: "A critical failure occurred during error handling" },
83
- { status: 500, statusText: statusText.INTERNAL_SERVER_ERROR }
84
- );
85
- }
86
- }
87
- if (isInvalidZodSchemaError(error)) {
88
- const { errors, status, statusText: statusText2 } = error;
89
- return Response.json(
90
- {
91
- message: "Invalid request data",
92
- error: "validation_error",
93
- details: errors
94
- },
95
- { status, statusText: statusText2 }
96
- );
97
- }
98
- if (isRouterError(error)) {
99
- const { message, status, statusText: statusText2 } = error;
100
- return Response.json({ message }, { status, statusText: statusText2 });
101
- }
102
- return Response.json({ message: "Internal Server Error" }, { status: 500, statusText: statusText.INTERNAL_SERVER_ERROR });
103
- };
104
- var handleRequest = async (method, request, config, root) => {
105
- try {
106
- if (!isSupportedMethod(request.method)) {
107
- throw new RouterError("METHOD_NOT_ALLOWED", `The HTTP method '${request.method}' is not supported`);
108
- }
109
- const globalContext = { request, context: config.context ?? {} };
110
- const globalRequestContext = await executeGlobalMiddlewares(globalContext, config.middlewares);
111
- if (globalRequestContext instanceof Response) return globalRequestContext;
112
- const url = new URL(globalRequestContext.request.url);
113
- const pathnameWithBase = url.pathname;
114
- if (globalRequestContext.request.method !== method) {
115
- throw new RouterError("METHOD_NOT_ALLOWED", `The HTTP method '${globalRequestContext.request.method}' is not allowed`);
116
- }
117
- const { endpoint, params } = search(method, root, pathnameWithBase);
118
- if (endpoint.method !== globalRequestContext.request.method) {
119
- throw new RouterError("METHOD_NOT_ALLOWED", `The HTTP method '${globalRequestContext.request.method}' is not allowed`);
120
- }
121
- const dynamicParams = getRouteParams(params, endpoint.config);
122
- const body = await getBody(globalRequestContext.request, endpoint.config);
123
- const searchParams = getSearchParams(globalRequestContext.request.url, endpoint.config);
124
- const headers = new HeadersBuilder(globalRequestContext.request.headers);
125
- let context = {
126
- params: dynamicParams,
127
- searchParams,
128
- headers,
129
- body,
130
- request: globalRequestContext.request,
131
- url,
132
- method: globalRequestContext.request.method,
133
- route: endpoint.route,
134
- context: config.context ?? {}
135
- };
136
- context = await executeMiddlewares(context, endpoint.config.middlewares);
137
- const response = await endpoint.handler(context);
138
- return response;
139
- } catch (error) {
140
- return handleError(error, request, config);
141
- }
142
- };
143
- var createRouter = (endpoints, config = {}) => {
144
- const root = createNode();
145
- const server = {};
146
- const methods = /* @__PURE__ */ new Set();
147
- for (const endpoint of endpoints) {
148
- const withBasePath = config.basePath ? `${config.basePath}${endpoint.route}` : endpoint.route;
149
- insert(root, { ...endpoint, route: withBasePath });
150
- methods.add(endpoint.method);
151
- }
152
- for (const method of methods) {
153
- server[method] = (request) => handleRequest(method, request, config, root);
154
- }
155
- return server;
156
- };
157
-
158
- export {
159
- createNode,
160
- insert,
161
- search,
162
- createRouter
163
- };