@aura-stack/router 0.4.0 → 0.6.0-rc.1

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.
@@ -1,6 +1,9 @@
1
1
  import { GlobalMiddlewareContext, RouterConfig, EndpointConfig, RequestContext, MiddlewareFunction } from './types.js';
2
2
  import 'zod';
3
3
  import './error.js';
4
+ import './headers.js';
5
+ import 'cookie';
6
+ import 'http';
4
7
 
5
8
  /**
6
9
  * Executes the middlewares in sequence, passing the request to each middleware.
@@ -9,7 +12,7 @@ import './error.js';
9
12
  * @param middlewares - Array of global middleware functions to be executed
10
13
  * @returns - The modified request after all middlewares have been executed
11
14
  */
12
- declare const executeGlobalMiddlewares: (context: GlobalMiddlewareContext, middlewares: RouterConfig["middlewares"]) => Promise<GlobalMiddlewareContext | Response>;
15
+ declare const executeGlobalMiddlewares: (context: GlobalMiddlewareContext, middlewares: RouterConfig["middlewares"]) => Promise<Response | GlobalMiddlewareContext>;
13
16
  /**
14
17
  * Executes middlewares in sequence, passing the request and context to each middleware.
15
18
  *
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  executeGlobalMiddlewares,
3
3
  executeMiddlewares
4
- } from "./chunk-CFAIW6YL.js";
5
- import "./chunk-GJC3ODME.js";
4
+ } from "./chunk-6CIHAUKJ.js";
5
+ import "./chunk-FJYSN2I6.js";
6
6
  export {
7
7
  executeGlobalMiddlewares,
8
8
  executeMiddlewares
package/dist/router.cjs CHANGED
@@ -61,6 +61,24 @@ var statusText = Object.keys(statusCode).reduce(
61
61
  {}
62
62
  );
63
63
  var AuraStackRouterError = class extends Error {
64
+ /**
65
+ * The HTTP status code associated with the error.
66
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status
67
+ * @example
68
+ * NOT_FOUND: 404
69
+ * METHOD_NOT_ALLOWED: 405
70
+ * INTERNAL_SERVER_ERROR: 500
71
+ */
72
+ status;
73
+ /**
74
+ * The HTTP status text associated with the status code of the error.
75
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status
76
+ * @example
77
+ * NOT_FOUND: NOT_FOUND
78
+ * METHOD_NOT_ALLOWED: METHOD_NOT_ALLOWED
79
+ * INTERNAL_SERVER_ERROR: INTERNAL_SERVER_ERROR
80
+ */
81
+ statusText;
64
82
  constructor(type, message, name) {
65
83
  super(message);
66
84
  this.name = name ?? "RouterError";
@@ -74,6 +92,51 @@ var RouterError = class extends AuraStackRouterError {
74
92
  this.name = name ?? "RouterError";
75
93
  }
76
94
  };
95
+ var InvalidZodSchemaError = class {
96
+ status;
97
+ statusText;
98
+ errors;
99
+ constructor(type, errors) {
100
+ this.status = statusCode[type];
101
+ this.statusText = statusText[type];
102
+ this.errors = errors;
103
+ }
104
+ };
105
+
106
+ // src/headers.ts
107
+ var import_cookie = require("cookie");
108
+ var HeadersBuilder = class {
109
+ headers;
110
+ constructor(initialHeaders) {
111
+ this.headers = new Headers(initialHeaders);
112
+ }
113
+ setHeader(name, value) {
114
+ this.headers.set(name, value);
115
+ return this;
116
+ }
117
+ setCookie(name, value, options) {
118
+ this.headers.append("Set-Cookie", (0, import_cookie.serialize)(name, value, options));
119
+ return this;
120
+ }
121
+ getHeader(name) {
122
+ return this.headers.get(name);
123
+ }
124
+ getCookie(name) {
125
+ const cookies = (0, import_cookie.parse)(this.headers.get("cookie") ?? "");
126
+ return cookies[name];
127
+ }
128
+ getSetCookie(name) {
129
+ const cookies = this.headers.getSetCookie();
130
+ const cookie = cookies.find((cookie2) => cookie2.startsWith(name + "="));
131
+ return cookie ? (0, import_cookie.parseSetCookie)(cookie).value : void 0;
132
+ }
133
+ toHeaders() {
134
+ return new Headers(this.headers);
135
+ }
136
+ toCookies() {
137
+ return (0, import_cookie.parse)(this.headers.get("cookie") ?? "");
138
+ }
139
+ };
77
140
 
78
141
  // src/assert.ts
79
142
  var supportedMethods = /* @__PURE__ */ new Set(["GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD", "TRACE", "CONNECT"]);
@@ -87,46 +150,31 @@ var isSupportedBodyMethod = (method) => {
87
150
  var isRouterError = (error) => {
88
151
  return error instanceof RouterError;
89
152
  };
153
+ var isInvalidZodSchemaError = (error) => {
154
+ return error instanceof InvalidZodSchemaError;
155
+ };
90
156
 
91
- // src/middlewares.ts
92
- var executeGlobalMiddlewares = async (context, middlewares) => {
93
- if (!middlewares) return context;
94
- for (const middleware of middlewares) {
95
- if (typeof middleware !== "function") {
96
- throw new RouterError("BAD_REQUEST", "Global middlewares must be functions");
97
- }
98
- const executed = await middleware(context);
99
- if (executed instanceof Response) {
100
- return executed;
101
- }
102
- context = executed;
103
- }
104
- if (!context || !(context.request instanceof Request)) {
105
- throw new RouterError("BAD_REQUEST", "Global middleware must return a Request or Response object");
157
+ // src/context.ts
158
+ var formatZodError = (error) => {
159
+ if (!error.issues || error.issues.length === 0) {
160
+ return {};
106
161
  }
107
- return context;
108
- };
109
- var executeMiddlewares = async (context, middlewares = []) => {
110
- try {
111
- let ctx = context;
112
- for (const middleware of middlewares) {
113
- if (typeof middleware !== "function") {
114
- throw new RouterError("BAD_REQUEST", "Middleware must be a function");
162
+ return error.issues.reduce((previous, issue) => {
163
+ const key = issue.path.join(".");
164
+ return {
165
+ ...previous,
166
+ [key]: {
167
+ code: issue.code,
168
+ message: issue.message
115
169
  }
116
- ctx = await middleware(ctx);
117
- }
118
- return ctx;
119
- } catch {
120
- throw new RouterError("BAD_REQUEST", "Handler threw an error");
121
- }
170
+ };
171
+ }, {});
122
172
  };
123
-
124
- // src/context.ts
125
173
  var getRouteParams = (params, config) => {
126
174
  if (config.schemas?.params) {
127
175
  const parsed = config.schemas.params.safeParse(params);
128
176
  if (!parsed.success) {
129
- throw new RouterError("UNPROCESSABLE_ENTITY", "Invalid route parameters");
177
+ throw new InvalidZodSchemaError("UNPROCESSABLE_ENTITY", formatZodError(parsed.error));
130
178
  }
131
179
  return parsed.data;
132
180
  }
@@ -137,15 +185,12 @@ var getSearchParams = (url, config) => {
137
185
  if (config.schemas?.searchParams) {
138
186
  const parsed = config.schemas.searchParams.safeParse(Object.fromEntries(route.searchParams.entries()));
139
187
  if (!parsed.success) {
140
- throw new RouterError("UNPROCESSABLE_ENTITY", "Invalid search parameters");
188
+ throw new InvalidZodSchemaError("UNPROCESSABLE_ENTITY", formatZodError(parsed.error));
141
189
  }
142
190
  return parsed.data;
143
191
  }
144
192
  return new URLSearchParams(route.searchParams.toString());
145
193
  };
146
- var getHeaders = (request) => {
147
- return new Headers(request.headers);
148
- };
149
194
  var getBody = async (request, config) => {
150
195
  if (!isSupportedBodyMethod(request.method)) {
151
196
  return null;
@@ -157,7 +202,7 @@ var getBody = async (request, config) => {
157
202
  if (config.schemas?.body) {
158
203
  const parsed = config.schemas.body.safeParse(json);
159
204
  if (!parsed.success) {
160
- throw new RouterError("UNPROCESSABLE_ENTITY", "Invalid request body");
205
+ throw new InvalidZodSchemaError("UNPROCESSABLE_ENTITY", formatZodError(parsed.error));
161
206
  }
162
207
  return parsed.data;
163
208
  }
@@ -186,6 +231,39 @@ var createContentTypeRegex = (contentTypes, contenType) => {
186
231
  return regex.test(contenType);
187
232
  };
188
233
 
234
+ // src/middlewares.ts
235
+ var executeGlobalMiddlewares = async (context, middlewares) => {
236
+ if (!middlewares) return context;
237
+ for (const middleware of middlewares) {
238
+ if (typeof middleware !== "function") {
239
+ throw new RouterError("BAD_REQUEST", "Global middlewares must be functions");
240
+ }
241
+ const executed = await middleware(context);
242
+ if (executed instanceof Response) {
243
+ return executed;
244
+ }
245
+ context = executed;
246
+ }
247
+ if (!context || !(context.request instanceof Request)) {
248
+ throw new RouterError("BAD_REQUEST", "Global middleware must return a Request or Response object");
249
+ }
250
+ return context;
251
+ };
252
+ var executeMiddlewares = async (context, middlewares = []) => {
253
+ try {
254
+ let ctx = context;
255
+ for (const middleware of middlewares) {
256
+ if (typeof middleware !== "function") {
257
+ throw new RouterError("BAD_REQUEST", "Middleware must be a function");
258
+ }
259
+ ctx = await middleware(ctx);
260
+ }
261
+ return ctx;
262
+ } catch {
263
+ throw new RouterError("BAD_REQUEST", "Handler threw an error");
264
+ }
265
+ };
266
+
189
267
  // src/router.ts
190
268
  var createNode = () => ({
191
269
  statics: /* @__PURE__ */ new Map(),
@@ -250,6 +328,17 @@ var handleError = async (error, request, config) => {
250
328
  );
251
329
  }
252
330
  }
331
+ if (isInvalidZodSchemaError(error)) {
332
+ const { errors, status, statusText: statusText2 } = error;
333
+ return Response.json(
334
+ {
335
+ message: "Invalid request data",
336
+ error: "validation_error",
337
+ details: errors
338
+ },
339
+ { status, statusText: statusText2 }
340
+ );
341
+ }
253
342
  if (isRouterError(error)) {
254
343
  const { message, status, statusText: statusText2 } = error;
255
344
  return Response.json({ message }, { status, statusText: statusText2 });
@@ -276,7 +365,7 @@ var handleRequest = async (method, request, config, root) => {
276
365
  const dynamicParams = getRouteParams(params, endpoint.config);
277
366
  const body = await getBody(globalRequestContext.request, endpoint.config);
278
367
  const searchParams = getSearchParams(globalRequestContext.request.url, endpoint.config);
279
- const headers = getHeaders(globalRequestContext.request);
368
+ const headers = new HeadersBuilder(globalRequestContext.request.headers);
280
369
  let context = {
281
370
  params: dynamicParams,
282
371
  searchParams,
package/dist/router.d.ts CHANGED
@@ -1,6 +1,9 @@
1
- import { RouteEndpoint, RouterConfig, GetHttpHandlers, HTTPMethod, RoutePattern, EndpointSchemas, MiddlewareFunction } from './types.js';
1
+ import { RouteEndpoint, RouterConfig, Router, HTTPMethod, RoutePattern, EndpointSchemas, MiddlewareFunction } from './types.js';
2
2
  import 'zod';
3
3
  import './error.js';
4
+ import './headers.js';
5
+ import 'cookie';
6
+ import 'http';
4
7
 
5
8
  interface TrieNode {
6
9
  statics: Map<string, TrieNode>;
@@ -32,6 +35,6 @@ declare const search: (method: HTTPMethod, root: TrieNode, pathname: string) =>
32
35
  * @param config - Optional configuration object for the router
33
36
  * @returns An object with methods corresponding to HTTP methods, each handling requests for that method
34
37
  */
35
- declare const createRouter: <const Endpoints extends RouteEndpoint[]>(endpoints: Endpoints, config?: RouterConfig) => GetHttpHandlers<Endpoints>;
38
+ declare const createRouter: <const Endpoints extends RouteEndpoint[]>(endpoints: Endpoints, config?: RouterConfig) => Router<Endpoints>;
36
39
 
37
40
  export { createNode, createRouter, insert, search };
package/dist/router.js CHANGED
@@ -3,11 +3,12 @@ import {
3
3
  createRouter,
4
4
  insert,
5
5
  search
6
- } from "./chunk-CL5D7UJU.js";
7
- import "./chunk-PT4GU6PH.js";
8
- import "./chunk-JNMXLKDG.js";
9
- import "./chunk-CFAIW6YL.js";
10
- import "./chunk-GJC3ODME.js";
6
+ } from "./chunk-WJXMLTGP.js";
7
+ import "./chunk-6CIHAUKJ.js";
8
+ import "./chunk-SY4MM2AG.js";
9
+ import "./chunk-3X2BFSRT.js";
10
+ import "./chunk-FJYSN2I6.js";
11
+ import "./chunk-6JNMFP4L.js";
11
12
  export {
12
13
  createNode,
13
14
  createRouter,
package/dist/types.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import { ZodObject, z } from 'zod';
2
2
  import { RouterError } from './error.js';
3
+ import { HeadersBuilder } from './headers.js';
4
+ import { IncomingHttpHeaders } from 'http';
5
+ import 'cookie';
3
6
 
4
7
  /**
5
8
  * Route pattern must start with a slash and can contain parameters prefixed with a colon.
@@ -102,7 +105,7 @@ type ContextParams<Schemas extends EndpointConfig["schemas"], Default = Record<s
102
105
  */
103
106
  interface RequestContext<RouteParams = Record<string, string>, Config extends EndpointConfig = EndpointConfig> {
104
107
  params: ContextParams<Config["schemas"], RouteParams>["params"];
105
- headers: Headers;
108
+ headers: HeadersBuilder;
106
109
  body: ContextBody<Config["schemas"]>["body"];
107
110
  searchParams: ContextSearchParams<Config["schemas"]>["searchParams"];
108
111
  request: Request;
@@ -208,5 +211,50 @@ interface RouterConfig extends GlobalCtx {
208
211
  */
209
212
  onError?: (error: Error | RouterError, request: Request) => Response | Promise<Response>;
210
213
  }
214
+ /**
215
+ * @experimental
216
+ */
217
+ type ExtractEndpoint<T> = T extends RouteEndpoint<infer M, infer P, infer C> ? {
218
+ method: M;
219
+ path: P;
220
+ config: C;
221
+ } : never;
222
+ /**
223
+ * @experimental
224
+ */
225
+ type RoutesByMethod<Defs extends readonly RouteEndpoint[], Met extends HTTPMethod> = ExtractEndpoint<Defs[number]> extends infer E ? (E extends {
226
+ method: Met;
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;
230
+ type InferZod<T> = T extends z.ZodTypeAny ? z.infer<T> : T;
231
+ type ToInferZod<T> = {
232
+ [K in keyof T]: InferZod<T[K]>;
233
+ };
234
+ type RemoveUndefined<T> = {
235
+ [K in keyof T as undefined extends T[K] ? never : K]: T[K];
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;
238
+ type Client<Defs extends RouteEndpoint[]> = {
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
+ };
241
+ declare const endpointsSymbol: unique symbol;
242
+ type Router<Endpoints extends RouteEndpoint[]> = GetHttpHandlers<Endpoints> & {
243
+ readonly [endpointsSymbol]?: Endpoints;
244
+ };
245
+ type InferEndpoints<T> = T extends Router<infer E> ? E : never;
246
+ interface ClientOptions {
247
+ /**
248
+ * Base URL for the router client to make requests to the server.
249
+ * This is useful when the server is hosted on a different origin.
250
+ *
251
+ * baseURL: "https://api.example.com"
252
+ */
253
+ baseURL: string;
254
+ /**
255
+ * Default headers to include in every request made by the client.
256
+ */
257
+ headers?: IncomingHttpHeaders;
258
+ }
211
259
 
212
- export type { ContentType, ContextBody, ContextParams, ContextSearchParams, EndpointConfig, EndpointSchemas, GetHttpHandlers, GetRouteParams, GlobalContext, GlobalCtx, GlobalMiddleware, GlobalMiddlewareContext, HTTPMethod, InferMethod, MiddlewareFunction, Prettify, RequestContext, RouteEndpoint, RouteHandler, RoutePattern, RouterConfig };
260
+ 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aura-stack/router",
3
- "version": "0.4.0",
3
+ "version": "0.6.0-rc.1",
4
4
  "type": "module",
5
5
  "description": "A lightweight TypeScript library for building, managing, and validating API routes and endpoints in Node.js applications.",
6
6
  "repository": {
@@ -50,15 +50,34 @@
50
50
  "import": "./dist/error.js",
51
51
  "require": "./dist/error.cjs"
52
52
  },
53
+ "./headers": {
54
+ "types": "./dist/headers.d.ts",
55
+ "import": "./dist/headers.js",
56
+ "require": "./dist/headers.cjs"
57
+ },
58
+ "./cookie": {
59
+ "types": "./dist/cookie.d.ts",
60
+ "import": "./dist/cookie.js",
61
+ "require": "./dist/cookie.cjs"
62
+ },
63
+ "./client": {
64
+ "types": "./dist/client.d.ts",
65
+ "import": "./dist/client.js",
66
+ "require": "./dist/client.cjs"
67
+ },
53
68
  "./types": "./dist/types.d.ts"
54
69
  },
55
70
  "devDependencies": {
71
+ "@types/node": "24.6.2",
56
72
  "prettier": "^3.6.2",
57
- "tsup": "^8.5.0",
73
+ "tsup": "^8.5.1",
58
74
  "typescript": "^5.9.3",
59
- "vitest": "^3.2.4",
75
+ "vitest": "^4.0.18",
60
76
  "zod": "^4.1.11"
61
77
  },
78
+ "dependencies": {
79
+ "cookie": "^1.1.1"
80
+ },
62
81
  "scripts": {
63
82
  "dev": "tsup --watch",
64
83
  "dev:docs": "pnpm --filter docs dev",