@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/assert.cjs CHANGED
@@ -112,6 +112,9 @@ var supportedMethods = /* @__PURE__ */ new Set(["GET", "POST", "DELETE", "PUT",
112
112
  var supportedBodyMethods = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
113
113
  var supportedProtocols = /* @__PURE__ */ new Set(["http:", "https:"]);
114
114
  var isSupportedMethod = (method) => {
115
+ if (Array.isArray(method)) {
116
+ return method.every((meth) => supportedMethods.has(meth));
117
+ }
115
118
  return supportedMethods.has(method);
116
119
  };
117
120
  var isSupportedBodyMethod = (method) => {
package/dist/assert.d.ts CHANGED
@@ -3,7 +3,6 @@ import { HTTPMethod, RoutePattern, RouteHandler } from './types.js';
3
3
  import 'zod';
4
4
  import './headers.js';
5
5
  import 'cookie';
6
- import 'http';
7
6
 
8
7
  declare const supportedProtocols: Set<string>;
9
8
  /**
@@ -12,7 +11,7 @@ declare const supportedProtocols: Set<string>;
12
11
  * @param method - The HTTP method to check.
13
12
  * @returns True if the method is supported, false otherwise.
14
13
  */
15
- declare const isSupportedMethod: (method: string) => method is HTTPMethod;
14
+ declare const isSupportedMethod: (method: string | string[]) => method is HTTPMethod | HTTPMethod[];
16
15
  /**
17
16
  * Check if the provided method can includes a body as per HTTP specification.
18
17
  * @param method - The HTTP method to check.
package/dist/assert.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  isValidHandler,
8
8
  isValidRoute,
9
9
  supportedProtocols
10
- } from "./chunk-3X2BFSRT.js";
10
+ } from "./chunk-NFASFT4W.js";
11
11
  import "./chunk-FJYSN2I6.js";
12
12
  export {
13
13
  isInvalidZodSchemaError,
@@ -0,0 +1,87 @@
1
+ import {
2
+ executeGlobalMiddlewares,
3
+ executeMiddlewares
4
+ } from "./chunk-JAYQXZDB.js";
5
+ import {
6
+ onError
7
+ } from "./chunk-VRGPOTTV.js";
8
+ import {
9
+ TrieRouter
10
+ } from "./chunk-FS3EN7NZ.js";
11
+ import {
12
+ getBody,
13
+ getRouteParams,
14
+ getSearchParams
15
+ } from "./chunk-HHL2LY22.js";
16
+ import {
17
+ isSupportedMethod
18
+ } from "./chunk-NFASFT4W.js";
19
+ import {
20
+ RouterError
21
+ } from "./chunk-FJYSN2I6.js";
22
+ import {
23
+ HeadersBuilder
24
+ } from "./chunk-6JNMFP4L.js";
25
+
26
+ // src/router.ts
27
+ var handleRequest = async (method, request, config, router) => {
28
+ try {
29
+ if (!isSupportedMethod(request.method)) {
30
+ throw new RouterError("METHOD_NOT_ALLOWED", `The HTTP method '${request.method}' is not supported`);
31
+ }
32
+ const globalContext = { request, context: config.context ?? {} };
33
+ const globalRequestContext = await executeGlobalMiddlewares(globalContext, config.use);
34
+ if (globalRequestContext instanceof Response) return globalRequestContext;
35
+ const url = new URL(globalRequestContext.request.url);
36
+ const pathnameWithBase = url.pathname;
37
+ if (globalRequestContext.request.method !== method) {
38
+ throw new RouterError("METHOD_NOT_ALLOWED", `The HTTP method '${globalRequestContext.request.method}' is not allowed`);
39
+ }
40
+ const node = router.match(method, pathnameWithBase);
41
+ if (!node) {
42
+ throw new RouterError("NOT_FOUND", `No route found for path: ${pathnameWithBase}`);
43
+ }
44
+ const { endpoint, params } = node;
45
+ const dynamicParams = getRouteParams(params, endpoint.config);
46
+ const body = await getBody(globalRequestContext.request, endpoint.config);
47
+ const searchParams = getSearchParams(globalRequestContext.request.url, endpoint.config);
48
+ const headers = new HeadersBuilder(globalRequestContext.request.headers);
49
+ let context = {
50
+ params: dynamicParams,
51
+ searchParams,
52
+ headers,
53
+ body,
54
+ request: globalRequestContext.request,
55
+ url,
56
+ method: globalRequestContext.request.method,
57
+ route: endpoint.route,
58
+ context: config.context ?? {}
59
+ };
60
+ context = await executeMiddlewares(context, endpoint.config.use);
61
+ const response = await endpoint.handler(context);
62
+ return response;
63
+ } catch (error) {
64
+ return onError(error, request, config);
65
+ }
66
+ };
67
+ var createRouter = (endpoints, config = {}) => {
68
+ const router = new TrieRouter();
69
+ const server = {};
70
+ const methods = /* @__PURE__ */ new Set();
71
+ for (const endpoint of endpoints) {
72
+ const withBasePath = config.basePath ? `${config.basePath}${endpoint.route}` : endpoint.route;
73
+ router.add({ ...endpoint, route: withBasePath });
74
+ const endpointMethods = Array.isArray(endpoint.method) ? endpoint.method : [endpoint.method];
75
+ for (const method of endpointMethods) {
76
+ methods.add(method);
77
+ }
78
+ }
79
+ for (const method of methods) {
80
+ server[method] = (request) => handleRequest(method, request, config, router);
81
+ }
82
+ return server;
83
+ };
84
+
85
+ export {
86
+ createRouter
87
+ };
@@ -0,0 +1,114 @@
1
+ import {
2
+ RouterError
3
+ } from "./chunk-FJYSN2I6.js";
4
+
5
+ // src/trie.ts
6
+ var TrieNode = class {
7
+ param;
8
+ statics = /* @__PURE__ */ new Map();
9
+ endpoints = /* @__PURE__ */ new Map();
10
+ };
11
+ var TrieRouter = class {
12
+ root;
13
+ statics;
14
+ methods;
15
+ constructor() {
16
+ this.statics = /* @__PURE__ */ new Map();
17
+ this.methods = /* @__PURE__ */ new Set();
18
+ this.root = new TrieNode();
19
+ }
20
+ add(endpoint) {
21
+ const isDynamic = endpoint.route.includes(":");
22
+ const methods = Array.isArray(endpoint.method) ? endpoint.method : [endpoint.method];
23
+ if (!isDynamic) {
24
+ for (const method of methods) {
25
+ this.statics.set(`${method} ${endpoint.route}`, endpoint);
26
+ }
27
+ } else {
28
+ let node = this.root;
29
+ const route = endpoint.route;
30
+ const routeLength = route.length;
31
+ let prev = 0;
32
+ while (prev < routeLength) {
33
+ const curr = route.indexOf("/", prev);
34
+ const end = curr === -1 ? routeLength : curr;
35
+ if (end > prev) {
36
+ const segment = route.slice(prev, end);
37
+ if (segment[0] === ":") {
38
+ const name = segment.slice(1);
39
+ let param = node.param;
40
+ if (!param) {
41
+ param = { name, node: new TrieNode() };
42
+ node.param = param;
43
+ } else if (param.name !== name) {
44
+ throw new RouterError(
45
+ "BAD_REQUEST",
46
+ `Conflicting in the route by the dynamic segment "${param.name}" and "${name}"`
47
+ );
48
+ }
49
+ node = param.node;
50
+ } else {
51
+ let child = node.statics.get(segment);
52
+ if (!child) {
53
+ child = new TrieNode();
54
+ node.statics.set(segment, child);
55
+ }
56
+ node = child;
57
+ }
58
+ }
59
+ if (curr === -1) {
60
+ break;
61
+ }
62
+ prev = curr + 1;
63
+ }
64
+ for (const method of methods) {
65
+ node.endpoints.set(method, endpoint);
66
+ }
67
+ }
68
+ for (const method of methods) {
69
+ this.methods.add(method);
70
+ }
71
+ }
72
+ match(method, pathname) {
73
+ const staticEndpoint = this.statics.get(`${method} ${pathname}`);
74
+ if (staticEndpoint) {
75
+ return { endpoint: staticEndpoint, params: {} };
76
+ }
77
+ let node = this.root;
78
+ const params = {};
79
+ const pathLength = pathname.length;
80
+ let prev = 0;
81
+ while (prev < pathLength) {
82
+ const curr = pathname.indexOf("/", prev);
83
+ const end = curr === -1 ? pathLength : curr;
84
+ if (end > prev) {
85
+ const segment = pathname.slice(prev, end);
86
+ const staticNode = node.statics.get(segment);
87
+ if (staticNode) {
88
+ node = staticNode;
89
+ } else {
90
+ const param = node.param;
91
+ if (!param) {
92
+ return null;
93
+ }
94
+ params[param.name] = decodeURIComponent(segment);
95
+ node = param.node;
96
+ }
97
+ }
98
+ if (curr === -1) {
99
+ break;
100
+ }
101
+ prev = curr + 1;
102
+ }
103
+ const endpoint = node.endpoints.get(method);
104
+ if (!endpoint) {
105
+ return null;
106
+ }
107
+ return { endpoint, params };
108
+ }
109
+ };
110
+
111
+ export {
112
+ TrieNode,
113
+ TrieRouter
114
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isSupportedBodyMethod
3
- } from "./chunk-3X2BFSRT.js";
3
+ } from "./chunk-NFASFT4W.js";
4
4
  import {
5
5
  InvalidZodSchemaError,
6
6
  RouterError
@@ -1,6 +1,7 @@
1
1
  // src/client.ts
2
2
  function createClient(options) {
3
- const { baseURL, headers: defaultHeaders } = options;
3
+ const { baseURL, basePath, headers: defaultHeaders, fetch: customFetch, ...clientOptions } = options;
4
+ const fetchFn = customFetch ?? ((input, init) => globalThis.fetch(input, init));
4
5
  return new Proxy(
5
6
  {},
6
7
  {
@@ -8,7 +9,7 @@ function createClient(options) {
8
9
  const method = prop.toString().toUpperCase();
9
10
  return async (path, ctx) => {
10
11
  const searchParams = new URLSearchParams(ctx?.searchParams);
11
- let resolvedPath = `${options.basePath ?? ""}${path}`;
12
+ let resolvedPath = `${basePath ?? ""}${path}`;
12
13
  for (const [key, value] of Object.entries(ctx?.params ?? {})) {
13
14
  resolvedPath = resolvedPath.replace(`:${key}`, String(value));
14
15
  }
@@ -17,11 +18,13 @@ function createClient(options) {
17
18
  url.search = searchParams.toString();
18
19
  }
19
20
  const { params: _p, searchParams: _s, ...requestInit } = ctx ?? {};
20
- const response = await fetch(url.toString(), {
21
+ const headers = typeof defaultHeaders === "function" ? await defaultHeaders() : defaultHeaders;
22
+ const response = await fetchFn(url.toString(), {
23
+ ...clientOptions,
21
24
  ...requestInit,
22
25
  method,
23
26
  headers: {
24
- ...defaultHeaders,
27
+ ...headers,
25
28
  ...ctx?.headers
26
29
  },
27
30
  body: ctx?.body ? ctx.body instanceof FormData ? ctx.body : JSON.stringify(ctx.body) : void 0
@@ -3,9 +3,9 @@ import {
3
3
  } from "./chunk-FJYSN2I6.js";
4
4
 
5
5
  // src/middlewares.ts
6
- var executeGlobalMiddlewares = async (context, middlewares) => {
7
- if (!middlewares) return context;
8
- for (const middleware of middlewares) {
6
+ var executeGlobalMiddlewares = async (context, use) => {
7
+ if (!use) return context;
8
+ for (const middleware of use) {
9
9
  if (typeof middleware !== "function") {
10
10
  throw new RouterError("BAD_REQUEST", "Global middlewares must be functions");
11
11
  }
@@ -20,10 +20,10 @@ var executeGlobalMiddlewares = async (context, middlewares) => {
20
20
  }
21
21
  return context;
22
22
  };
23
- var executeMiddlewares = async (context, middlewares = []) => {
23
+ var executeMiddlewares = async (context, use = []) => {
24
24
  try {
25
25
  let ctx = context;
26
- for (const middleware of middlewares) {
26
+ for (const middleware of use) {
27
27
  if (typeof middleware !== "function") {
28
28
  throw new RouterError("BAD_REQUEST", "Middleware must be a function");
29
29
  }
@@ -8,6 +8,9 @@ var supportedMethods = /* @__PURE__ */ new Set(["GET", "POST", "DELETE", "PUT",
8
8
  var supportedBodyMethods = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
9
9
  var supportedProtocols = /* @__PURE__ */ new Set(["http:", "https:"]);
10
10
  var isSupportedMethod = (method) => {
11
+ if (Array.isArray(method)) {
12
+ return method.every((meth) => supportedMethods.has(meth));
13
+ }
11
14
  return supportedMethods.has(method);
12
15
  };
13
16
  var isSupportedBodyMethod = (method) => {
@@ -0,0 +1,42 @@
1
+ import {
2
+ isInvalidZodSchemaError,
3
+ isRouterError
4
+ } from "./chunk-NFASFT4W.js";
5
+ import {
6
+ statusText
7
+ } from "./chunk-FJYSN2I6.js";
8
+
9
+ // src/on-error.ts
10
+ var onError = async (error, request, config) => {
11
+ if (config.onError) {
12
+ try {
13
+ const response = await config.onError(error, request);
14
+ return response;
15
+ } catch {
16
+ return Response.json(
17
+ { message: "A critical failure occurred during error handling" },
18
+ { status: 500, statusText: statusText.INTERNAL_SERVER_ERROR }
19
+ );
20
+ }
21
+ }
22
+ if (isInvalidZodSchemaError(error)) {
23
+ const { errors, status, statusText: statusText2 } = error;
24
+ return Response.json(
25
+ {
26
+ message: "Invalid request data",
27
+ error: "validation_error",
28
+ details: errors
29
+ },
30
+ { status, statusText: statusText2 }
31
+ );
32
+ }
33
+ if (isRouterError(error)) {
34
+ const { message, status, statusText: statusText2 } = error;
35
+ return Response.json({ message }, { status, statusText: statusText2 });
36
+ }
37
+ return Response.json({ message: "Internal Server Error" }, { status: 500, statusText: statusText.INTERNAL_SERVER_ERROR });
38
+ };
39
+
40
+ export {
41
+ onError
42
+ };
@@ -2,7 +2,7 @@ import {
2
2
  isSupportedMethod,
3
3
  isValidHandler,
4
4
  isValidRoute
5
- } from "./chunk-3X2BFSRT.js";
5
+ } from "./chunk-NFASFT4W.js";
6
6
  import {
7
7
  RouterError
8
8
  } from "./chunk-FJYSN2I6.js";
package/dist/client.cjs CHANGED
@@ -24,7 +24,8 @@ __export(client_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(client_exports);
26
26
  function createClient(options) {
27
- const { baseURL, headers: defaultHeaders } = options;
27
+ const { baseURL, basePath, headers: defaultHeaders, fetch: customFetch, ...clientOptions } = options;
28
+ const fetchFn = customFetch ?? ((input, init) => globalThis.fetch(input, init));
28
29
  return new Proxy(
29
30
  {},
30
31
  {
@@ -32,7 +33,7 @@ function createClient(options) {
32
33
  const method = prop.toString().toUpperCase();
33
34
  return async (path, ctx) => {
34
35
  const searchParams = new URLSearchParams(ctx?.searchParams);
35
- let resolvedPath = `${options.basePath ?? ""}${path}`;
36
+ let resolvedPath = `${basePath ?? ""}${path}`;
36
37
  for (const [key, value] of Object.entries(ctx?.params ?? {})) {
37
38
  resolvedPath = resolvedPath.replace(`:${key}`, String(value));
38
39
  }
@@ -41,11 +42,13 @@ function createClient(options) {
41
42
  url.search = searchParams.toString();
42
43
  }
43
44
  const { params: _p, searchParams: _s, ...requestInit } = ctx ?? {};
44
- const response = await fetch(url.toString(), {
45
+ const headers = typeof defaultHeaders === "function" ? await defaultHeaders() : defaultHeaders;
46
+ const response = await fetchFn(url.toString(), {
47
+ ...clientOptions,
45
48
  ...requestInit,
46
49
  method,
47
50
  headers: {
48
- ...defaultHeaders,
51
+ ...headers,
49
52
  ...ctx?.headers
50
53
  },
51
54
  body: ctx?.body ? ctx.body instanceof FormData ? ctx.body : JSON.stringify(ctx.body) : void 0
package/dist/client.d.ts CHANGED
@@ -3,7 +3,6 @@ import 'zod';
3
3
  import './error.js';
4
4
  import './headers.js';
5
5
  import 'cookie';
6
- import 'http';
7
6
 
8
7
  /**
9
8
  * Creates a client API for making requests to the specified router. It provides type-safe methods
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createClient
3
- } from "./chunk-SP2NSNB2.js";
3
+ } from "./chunk-ISIML7WX.js";
4
4
  export {
5
5
  createClient
6
6
  };
package/dist/context.d.ts CHANGED
@@ -3,7 +3,6 @@ import { EndpointConfig, ContextSearchParams } from './types.js';
3
3
  import './error.js';
4
4
  import './headers.js';
5
5
  import 'cookie';
6
- import 'http';
7
6
 
8
7
  /**
9
8
  * @experimental
package/dist/context.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  getBody,
4
4
  getRouteParams,
5
5
  getSearchParams
6
- } from "./chunk-SY4MM2AG.js";
7
- import "./chunk-3X2BFSRT.js";
6
+ } from "./chunk-HHL2LY22.js";
7
+ import "./chunk-NFASFT4W.js";
8
8
  import "./chunk-FJYSN2I6.js";
9
9
  export {
10
10
  formatZodError,
package/dist/endpoint.cjs CHANGED
@@ -94,6 +94,9 @@ var RouterError = class extends AuraStackRouterError {
94
94
  // src/assert.ts
95
95
  var supportedMethods = /* @__PURE__ */ new Set(["GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD", "TRACE", "CONNECT"]);
96
96
  var isSupportedMethod = (method) => {
97
+ if (Array.isArray(method)) {
98
+ return method.every((meth) => supportedMethods.has(meth));
99
+ }
97
100
  return supportedMethods.has(method);
98
101
  };
99
102
  var isValidRoute = (route) => {
@@ -3,7 +3,6 @@ import 'zod';
3
3
  import './error.js';
4
4
  import './headers.js';
5
5
  import 'cookie';
6
- import 'http';
7
6
 
8
7
  /**
9
8
  * Defines an API endpoint for the router by specifying the HTTP method, route pattern,
@@ -21,7 +20,7 @@ import 'http';
21
20
  * return new Response("Signed in");
22
21
  * });
23
22
  */
24
- declare const createEndpoint: <const Method extends Uppercase<HTTPMethod>, const Route extends RoutePattern, const Schemas extends EndpointSchemas>(method: Method, route: Route, handler: RouteHandler<Route, {
23
+ declare const createEndpoint: <const Method extends Uppercase<HTTPMethod> | Uppercase<HTTPMethod>[], const Route extends RoutePattern, const Schemas extends EndpointSchemas>(method: Method, route: Route, handler: RouteHandler<Route, {
25
24
  schemas: Schemas;
26
25
  }>, config?: EndpointConfig<Route, Schemas>) => RouteEndpoint<Method, Route, {
27
26
  schemas?: Schemas;
@@ -38,7 +37,7 @@ declare const createEndpoint: <const Method extends Uppercase<HTTPMethod>, const
38
37
  * @example
39
38
  * // Without route pattern
40
39
  * const config = createEndpointConfig({
41
- * middlewares: [myMiddleware],
40
+ * use: [myMiddleware],
42
41
  * schemas: {
43
42
  * searchParams: z.object({
44
43
  * q: z.string().min(3),
@@ -52,7 +51,7 @@ declare const createEndpoint: <const Method extends Uppercase<HTTPMethod>, const
52
51
  *
53
52
  * // Overload with route pattern
54
53
  * const config = createEndpointConfig("/users/:userId", {
55
- * middlewares: [myMiddleware],
54
+ * use: [myMiddleware],
56
55
  * })
57
56
  *
58
57
  * const getUser = createEndpoint("GET", "/users/:userId", async (request, ctx) => {
package/dist/endpoint.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  createEndpoint,
3
3
  createEndpointConfig
4
- } from "./chunk-5F6FUKTB.js";
5
- import "./chunk-3X2BFSRT.js";
4
+ } from "./chunk-ZTWC6PXG.js";
5
+ import "./chunk-NFASFT4W.js";
6
6
  import "./chunk-FJYSN2I6.js";
7
7
  export {
8
8
  createEndpoint,