@aura-stack/router 0.2.0 → 0.4.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/README.md +2 -2
- package/dist/assert.cjs +61 -3
- package/dist/assert.d.ts +21 -1
- package/dist/assert.js +8 -3
- package/dist/chunk-6PZEXNTS.js +31 -0
- package/dist/chunk-CFAIW6YL.js +41 -0
- package/dist/chunk-CL5D7UJU.js +149 -0
- package/dist/{chunk-RFYOPPMW.js → chunk-GJC3ODME.js} +15 -6
- package/dist/{chunk-JRJKKBSH.js → chunk-JNMXLKDG.js} +12 -2
- package/dist/chunk-PT4GU6PH.js +78 -0
- package/dist/context.cjs +50 -47
- package/dist/context.d.ts +6 -5
- package/dist/context.js +3 -4
- package/dist/endpoint.cjs +29 -29
- package/dist/endpoint.d.ts +17 -16
- package/dist/endpoint.js +5 -7
- package/dist/error.cjs +19 -7
- package/dist/error.d.ts +28 -3
- package/dist/error.js +9 -3
- package/dist/index.cjs +218 -130
- package/dist/index.d.ts +3 -1
- package/dist/index.js +19 -9
- package/dist/middlewares.cjs +23 -17
- package/dist/middlewares.d.ts +4 -3
- package/dist/middlewares.js +2 -2
- package/dist/router.cjs +206 -118
- package/dist/router.d.ts +24 -2
- package/dist/router.js +13 -8
- package/dist/types.d.ts +91 -13
- package/package.json +32 -13
- package/dist/assert.d.cts +0 -32
- package/dist/chunk-DR4C6QTF.js +0 -72
- package/dist/chunk-O6SY753N.js +0 -41
- package/dist/chunk-OXDCFAMF.js +0 -78
- package/dist/chunk-YUX3YHXF.js +0 -36
- package/dist/context.d.cts +0 -84
- package/dist/endpoint.d.cts +0 -59
- package/dist/error.d.cts +0 -40
- package/dist/index.d.cts +0 -4
- package/dist/middlewares.d.cts +0 -22
- package/dist/router.d.cts +0 -15
- package/dist/types.d.cts +0 -134
package/dist/router.cjs
CHANGED
|
@@ -20,16 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/router.ts
|
|
21
21
|
var router_exports = {};
|
|
22
22
|
__export(router_exports, {
|
|
23
|
-
|
|
23
|
+
createNode: () => createNode,
|
|
24
|
+
createRouter: () => createRouter,
|
|
25
|
+
insert: () => insert,
|
|
26
|
+
search: () => search
|
|
24
27
|
});
|
|
25
28
|
module.exports = __toCommonJS(router_exports);
|
|
26
29
|
|
|
27
|
-
// src/assert.ts
|
|
28
|
-
var supportedBodyMethods = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
|
|
29
|
-
var isSupportedBodyMethod = (method) => {
|
|
30
|
-
return supportedBodyMethods.has(method);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
30
|
// src/error.ts
|
|
34
31
|
var statusCode = {
|
|
35
32
|
OK: 200,
|
|
@@ -57,50 +54,90 @@ var statusCode = {
|
|
|
57
54
|
SERVICE_UNAVAILABLE: 503,
|
|
58
55
|
HTTP_VERSION_NOT_SUPPORTED: 505
|
|
59
56
|
};
|
|
60
|
-
var statusText = Object.
|
|
61
|
-
(previous,
|
|
62
|
-
return { ...previous, [
|
|
57
|
+
var statusText = Object.keys(statusCode).reduce(
|
|
58
|
+
(previous, status) => {
|
|
59
|
+
return { ...previous, [status]: status };
|
|
63
60
|
},
|
|
64
61
|
{}
|
|
65
62
|
);
|
|
66
63
|
var AuraStackRouterError = class extends Error {
|
|
67
64
|
constructor(type, message, name) {
|
|
68
65
|
super(message);
|
|
69
|
-
this.name = name ?? "
|
|
66
|
+
this.name = name ?? "RouterError";
|
|
70
67
|
this.status = statusCode[type];
|
|
71
|
-
this.statusText = statusText[
|
|
68
|
+
this.statusText = statusText[type];
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var RouterError = class extends AuraStackRouterError {
|
|
72
|
+
constructor(type, message, name) {
|
|
73
|
+
super(type, message, name);
|
|
74
|
+
this.name = name ?? "RouterError";
|
|
72
75
|
}
|
|
73
76
|
};
|
|
74
77
|
|
|
75
|
-
// src/
|
|
76
|
-
var
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
// src/assert.ts
|
|
79
|
+
var supportedMethods = /* @__PURE__ */ new Set(["GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD", "TRACE", "CONNECT"]);
|
|
80
|
+
var supportedBodyMethods = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
|
|
81
|
+
var isSupportedMethod = (method) => {
|
|
82
|
+
return supportedMethods.has(method);
|
|
83
|
+
};
|
|
84
|
+
var isSupportedBodyMethod = (method) => {
|
|
85
|
+
return supportedBodyMethods.has(method);
|
|
86
|
+
};
|
|
87
|
+
var isRouterError = (error) => {
|
|
88
|
+
return error instanceof RouterError;
|
|
89
|
+
};
|
|
90
|
+
|
|
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");
|
|
106
|
+
}
|
|
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");
|
|
115
|
+
}
|
|
116
|
+
ctx = await middleware(ctx);
|
|
117
|
+
}
|
|
118
|
+
return ctx;
|
|
119
|
+
} catch {
|
|
120
|
+
throw new RouterError("BAD_REQUEST", "Handler threw an error");
|
|
121
|
+
}
|
|
79
122
|
};
|
|
80
123
|
|
|
81
124
|
// src/context.ts
|
|
82
|
-
var getRouteParams = (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return params
|
|
91
|
-
(previous, now, idx) => ({
|
|
92
|
-
...previous,
|
|
93
|
-
[now]: decodeURIComponent(values?.[idx] ?? "")
|
|
94
|
-
}),
|
|
95
|
-
{}
|
|
96
|
-
);
|
|
125
|
+
var getRouteParams = (params, config) => {
|
|
126
|
+
if (config.schemas?.params) {
|
|
127
|
+
const parsed = config.schemas.params.safeParse(params);
|
|
128
|
+
if (!parsed.success) {
|
|
129
|
+
throw new RouterError("UNPROCESSABLE_ENTITY", "Invalid route parameters");
|
|
130
|
+
}
|
|
131
|
+
return parsed.data;
|
|
132
|
+
}
|
|
133
|
+
return params;
|
|
97
134
|
};
|
|
98
135
|
var getSearchParams = (url, config) => {
|
|
99
136
|
const route = new URL(url);
|
|
100
137
|
if (config.schemas?.searchParams) {
|
|
101
138
|
const parsed = config.schemas.searchParams.safeParse(Object.fromEntries(route.searchParams.entries()));
|
|
102
139
|
if (!parsed.success) {
|
|
103
|
-
throw new
|
|
140
|
+
throw new RouterError("UNPROCESSABLE_ENTITY", "Invalid search parameters");
|
|
104
141
|
}
|
|
105
142
|
return parsed.data;
|
|
106
143
|
}
|
|
@@ -111,120 +148,171 @@ var getHeaders = (request) => {
|
|
|
111
148
|
};
|
|
112
149
|
var getBody = async (request, config) => {
|
|
113
150
|
if (!isSupportedBodyMethod(request.method)) {
|
|
114
|
-
return
|
|
151
|
+
return null;
|
|
115
152
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
153
|
+
const clone = request.clone();
|
|
154
|
+
const contentType = clone.headers.get("Content-Type") ?? "";
|
|
155
|
+
if (contentType.includes("application/json") || config.schemas?.body) {
|
|
156
|
+
const json = await clone.json();
|
|
119
157
|
if (config.schemas?.body) {
|
|
120
158
|
const parsed = config.schemas.body.safeParse(json);
|
|
121
159
|
if (!parsed.success) {
|
|
122
|
-
throw new
|
|
160
|
+
throw new RouterError("UNPROCESSABLE_ENTITY", "Invalid request body");
|
|
123
161
|
}
|
|
124
162
|
return parsed.data;
|
|
125
163
|
}
|
|
126
164
|
return json;
|
|
127
165
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
166
|
+
try {
|
|
167
|
+
if (createContentTypeRegex(["application/x-www-form-urlencoded", "multipart/form-data"], contentType)) {
|
|
168
|
+
return await clone.formData();
|
|
169
|
+
}
|
|
170
|
+
if (createContentTypeRegex(["text/", "application/xml"], contentType)) {
|
|
171
|
+
return await clone.text();
|
|
172
|
+
}
|
|
173
|
+
if (createContentTypeRegex(["application/octet-stream"], contentType)) {
|
|
174
|
+
return await clone.arrayBuffer();
|
|
175
|
+
}
|
|
176
|
+
if (createContentTypeRegex(["image/", "video/", "audio/", "application/pdf"], contentType)) {
|
|
177
|
+
return await clone.blob();
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
} catch {
|
|
181
|
+
throw new RouterError("UNPROCESSABLE_ENTITY", "Invalid request body, the content-type does not match the body format");
|
|
133
182
|
}
|
|
134
|
-
|
|
135
|
-
|
|
183
|
+
};
|
|
184
|
+
var createContentTypeRegex = (contentTypes, contenType) => {
|
|
185
|
+
const regex = new RegExp(`${contentTypes.join("|")}`);
|
|
186
|
+
return regex.test(contenType);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// src/router.ts
|
|
190
|
+
var createNode = () => ({
|
|
191
|
+
statics: /* @__PURE__ */ new Map(),
|
|
192
|
+
endpoints: /* @__PURE__ */ new Map()
|
|
193
|
+
});
|
|
194
|
+
var insert = (root, endpoint) => {
|
|
195
|
+
if (!root || !endpoint) return;
|
|
196
|
+
let node = root;
|
|
197
|
+
const segments = endpoint.route === "/" ? [] : endpoint.route.split("/").filter(Boolean);
|
|
198
|
+
for (const segment of segments) {
|
|
199
|
+
if (segment.startsWith(":")) {
|
|
200
|
+
const name = segment.slice(1);
|
|
201
|
+
if (!node.param) {
|
|
202
|
+
node.param = { name, node: createNode() };
|
|
203
|
+
} else if (node.param.name !== name) {
|
|
204
|
+
throw new RouterError(
|
|
205
|
+
"BAD_REQUEST",
|
|
206
|
+
`Conflicting in the route by the dynamic segment "${node.param.name}" and "${name}"`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
node = node.param.node;
|
|
210
|
+
} else {
|
|
211
|
+
if (!node.statics.has(segment)) {
|
|
212
|
+
node.statics.set(segment, createNode());
|
|
213
|
+
}
|
|
214
|
+
node = node.statics.get(segment);
|
|
215
|
+
}
|
|
136
216
|
}
|
|
137
|
-
if (
|
|
138
|
-
|
|
217
|
+
if (node.endpoints.has(endpoint.method)) {
|
|
218
|
+
throw new RouterError("BAD_REQUEST", `Duplicate endpoint for ${endpoint?.method} ${endpoint?.route}`);
|
|
139
219
|
}
|
|
140
|
-
|
|
220
|
+
node.endpoints.set(endpoint.method, endpoint);
|
|
141
221
|
};
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
for (const
|
|
147
|
-
if (
|
|
148
|
-
|
|
222
|
+
var search = (method, root, pathname) => {
|
|
223
|
+
let node = root;
|
|
224
|
+
const params = {};
|
|
225
|
+
const segments = pathname === "/" ? [] : pathname.split("/").filter(Boolean);
|
|
226
|
+
for (const segment of segments) {
|
|
227
|
+
if (node?.statics.has(segment)) {
|
|
228
|
+
node = node.statics.get(segment);
|
|
229
|
+
} else if (node?.param) {
|
|
230
|
+
params[node.param.name] = decodeURIComponent(segment);
|
|
231
|
+
node = node.param.node;
|
|
232
|
+
} else {
|
|
233
|
+
throw new RouterError("NOT_FOUND", `No route found for path: ${pathname}`);
|
|
149
234
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
235
|
+
}
|
|
236
|
+
if (!node.endpoints.has(method)) {
|
|
237
|
+
throw new RouterError("NOT_FOUND", `No route found for path: ${pathname}`);
|
|
238
|
+
}
|
|
239
|
+
return { endpoint: node.endpoints.get(method), params };
|
|
240
|
+
};
|
|
241
|
+
var handleError = async (error, request, config) => {
|
|
242
|
+
if (config.onError) {
|
|
243
|
+
try {
|
|
244
|
+
const response = await config.onError(error, request);
|
|
245
|
+
return response;
|
|
246
|
+
} catch {
|
|
247
|
+
return Response.json(
|
|
248
|
+
{ message: "A critical failure occurred during error handling" },
|
|
249
|
+
{ status: 500, statusText: statusText.INTERNAL_SERVER_ERROR }
|
|
250
|
+
);
|
|
153
251
|
}
|
|
154
|
-
request = executed;
|
|
155
252
|
}
|
|
156
|
-
if (
|
|
157
|
-
|
|
253
|
+
if (isRouterError(error)) {
|
|
254
|
+
const { message, status, statusText: statusText2 } = error;
|
|
255
|
+
return Response.json({ message }, { status, statusText: statusText2 });
|
|
158
256
|
}
|
|
159
|
-
return
|
|
257
|
+
return Response.json({ message: "Internal Server Error" }, { status: 500, statusText: statusText.INTERNAL_SERVER_ERROR });
|
|
160
258
|
};
|
|
161
|
-
var
|
|
259
|
+
var handleRequest = async (method, request, config, root) => {
|
|
162
260
|
try {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (typeof middleware !== "function") {
|
|
166
|
-
throw new AuraStackRouterError("BAD_REQUEST", "Middleware must be a function");
|
|
167
|
-
}
|
|
168
|
-
ctx = await middleware(request, ctx);
|
|
261
|
+
if (!isSupportedMethod(request.method)) {
|
|
262
|
+
throw new RouterError("METHOD_NOT_ALLOWED", `The HTTP method '${request.method}' is not supported`);
|
|
169
263
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
264
|
+
const globalContext = { request, context: config.context ?? {} };
|
|
265
|
+
const globalRequestContext = await executeGlobalMiddlewares(globalContext, config.middlewares);
|
|
266
|
+
if (globalRequestContext instanceof Response) return globalRequestContext;
|
|
267
|
+
const url = new URL(globalRequestContext.request.url);
|
|
268
|
+
const pathnameWithBase = url.pathname;
|
|
269
|
+
if (globalRequestContext.request.method !== method) {
|
|
270
|
+
throw new RouterError("METHOD_NOT_ALLOWED", `The HTTP method '${globalRequestContext.request.method}' is not allowed`);
|
|
271
|
+
}
|
|
272
|
+
const { endpoint, params } = search(method, root, pathnameWithBase);
|
|
273
|
+
if (endpoint.method !== globalRequestContext.request.method) {
|
|
274
|
+
throw new RouterError("METHOD_NOT_ALLOWED", `The HTTP method '${globalRequestContext.request.method}' is not allowed`);
|
|
275
|
+
}
|
|
276
|
+
const dynamicParams = getRouteParams(params, endpoint.config);
|
|
277
|
+
const body = await getBody(globalRequestContext.request, endpoint.config);
|
|
278
|
+
const searchParams = getSearchParams(globalRequestContext.request.url, endpoint.config);
|
|
279
|
+
const headers = getHeaders(globalRequestContext.request);
|
|
280
|
+
let context = {
|
|
281
|
+
params: dynamicParams,
|
|
282
|
+
searchParams,
|
|
283
|
+
headers,
|
|
284
|
+
body,
|
|
285
|
+
request: globalRequestContext.request,
|
|
286
|
+
url,
|
|
287
|
+
method: globalRequestContext.request.method,
|
|
288
|
+
route: endpoint.route,
|
|
289
|
+
context: config.context ?? {}
|
|
290
|
+
};
|
|
291
|
+
context = await executeMiddlewares(context, endpoint.config.middlewares);
|
|
292
|
+
const response = await endpoint.handler(context);
|
|
293
|
+
return response;
|
|
294
|
+
} catch (error) {
|
|
295
|
+
return handleError(error, request, config);
|
|
173
296
|
}
|
|
174
297
|
};
|
|
175
|
-
|
|
176
|
-
// src/router.ts
|
|
177
298
|
var createRouter = (endpoints, config = {}) => {
|
|
299
|
+
const root = createNode();
|
|
178
300
|
const server = {};
|
|
179
|
-
const
|
|
301
|
+
const methods = /* @__PURE__ */ new Set();
|
|
180
302
|
for (const endpoint of endpoints) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
server[method] = async (request) => {
|
|
188
|
-
try {
|
|
189
|
-
const globalRequest = await executeGlobalMiddlewares(request, config.middlewares);
|
|
190
|
-
if (globalRequest instanceof Response) {
|
|
191
|
-
return globalRequest;
|
|
192
|
-
}
|
|
193
|
-
const url = new URL(globalRequest.url);
|
|
194
|
-
const pathname = url.pathname;
|
|
195
|
-
const endpoint = groups.get(method)?.find((endpoint2) => {
|
|
196
|
-
const withBasePath = config.basePath ? `${config.basePath}${endpoint2.route}` : endpoint2.route;
|
|
197
|
-
const regex = createRoutePattern(withBasePath);
|
|
198
|
-
return regex.test(pathname);
|
|
199
|
-
});
|
|
200
|
-
if (endpoint) {
|
|
201
|
-
const withBasePath = config.basePath ? `${config.basePath}${endpoint.route}` : endpoint.route;
|
|
202
|
-
const body = await getBody(globalRequest, endpoint.config);
|
|
203
|
-
const params = getRouteParams(withBasePath, pathname);
|
|
204
|
-
const searchParams = getSearchParams(globalRequest.url, endpoint.config);
|
|
205
|
-
const headers = getHeaders(globalRequest);
|
|
206
|
-
const context = {
|
|
207
|
-
params,
|
|
208
|
-
searchParams,
|
|
209
|
-
headers,
|
|
210
|
-
body
|
|
211
|
-
};
|
|
212
|
-
await executeMiddlewares(globalRequest, context, endpoint.config.middlewares);
|
|
213
|
-
return endpoint.handler(globalRequest, context);
|
|
214
|
-
}
|
|
215
|
-
return Response.json({ message: "Not Found" }, { status: 404 });
|
|
216
|
-
} catch (error) {
|
|
217
|
-
if (error instanceof AuraStackRouterError) {
|
|
218
|
-
const { message, status, statusText: statusText2 } = error;
|
|
219
|
-
return Response.json({ message }, { status, statusText: statusText2 });
|
|
220
|
-
}
|
|
221
|
-
return Response.json({ message: "Internal Server Error" }, { status: 500 });
|
|
222
|
-
}
|
|
223
|
-
};
|
|
303
|
+
const withBasePath = config.basePath ? `${config.basePath}${endpoint.route}` : endpoint.route;
|
|
304
|
+
insert(root, { ...endpoint, route: withBasePath });
|
|
305
|
+
methods.add(endpoint.method);
|
|
306
|
+
}
|
|
307
|
+
for (const method of methods) {
|
|
308
|
+
server[method] = (request) => handleRequest(method, request, config, root);
|
|
224
309
|
}
|
|
225
310
|
return server;
|
|
226
311
|
};
|
|
227
312
|
// Annotate the CommonJS export names for ESM import in node:
|
|
228
313
|
0 && (module.exports = {
|
|
229
|
-
|
|
314
|
+
createNode,
|
|
315
|
+
createRouter,
|
|
316
|
+
insert,
|
|
317
|
+
search
|
|
230
318
|
});
|
package/dist/router.d.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
|
-
import { RouteEndpoint, RouterConfig, GetHttpHandlers } from './types.js';
|
|
1
|
+
import { RouteEndpoint, RouterConfig, GetHttpHandlers, HTTPMethod, RoutePattern, EndpointSchemas, MiddlewareFunction } from './types.js';
|
|
2
2
|
import 'zod';
|
|
3
|
+
import './error.js';
|
|
3
4
|
|
|
5
|
+
interface TrieNode {
|
|
6
|
+
statics: Map<string, TrieNode>;
|
|
7
|
+
param?: {
|
|
8
|
+
name: string;
|
|
9
|
+
node: TrieNode;
|
|
10
|
+
};
|
|
11
|
+
endpoints: Map<HTTPMethod, RouteEndpoint>;
|
|
12
|
+
}
|
|
13
|
+
declare const createNode: () => TrieNode;
|
|
14
|
+
declare const insert: (root: TrieNode, endpoint: RouteEndpoint) => void;
|
|
15
|
+
declare const search: (method: HTTPMethod, root: TrieNode, pathname: string) => {
|
|
16
|
+
endpoint: RouteEndpoint<HTTPMethod, RoutePattern, {
|
|
17
|
+
schemas?: EndpointSchemas | undefined;
|
|
18
|
+
middlewares?: MiddlewareFunction<{} | {
|
|
19
|
+
[x: string]: string;
|
|
20
|
+
}, {
|
|
21
|
+
schemas: EndpointSchemas;
|
|
22
|
+
}>[] | undefined;
|
|
23
|
+
}>;
|
|
24
|
+
params: Record<string, string>;
|
|
25
|
+
};
|
|
4
26
|
/**
|
|
5
27
|
* Creates the entry point for the server, handling the endpoints defined in the router.
|
|
6
28
|
* It groups endpoints by HTTP method and matches incoming requests to the appropriate endpoint.
|
|
@@ -12,4 +34,4 @@ import 'zod';
|
|
|
12
34
|
*/
|
|
13
35
|
declare const createRouter: <const Endpoints extends RouteEndpoint[]>(endpoints: Endpoints, config?: RouterConfig) => GetHttpHandlers<Endpoints>;
|
|
14
36
|
|
|
15
|
-
export { createRouter };
|
|
37
|
+
export { createNode, createRouter, insert, search };
|
package/dist/router.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
2
|
+
createNode,
|
|
3
|
+
createRouter,
|
|
4
|
+
insert,
|
|
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";
|
|
9
11
|
export {
|
|
10
|
-
|
|
12
|
+
createNode,
|
|
13
|
+
createRouter,
|
|
14
|
+
insert,
|
|
15
|
+
search
|
|
11
16
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ZodObject, z } from 'zod';
|
|
2
|
+
import { RouterError } from './error.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Route pattern must start with a slash and can contain parameters prefixed with a colon.
|
|
@@ -8,13 +9,14 @@ import { ZodObject, z } from 'zod';
|
|
|
8
9
|
*/
|
|
9
10
|
type RoutePattern = `/${string}` | `/${string}/:${string}`;
|
|
10
11
|
/**
|
|
11
|
-
* HTTP methods
|
|
12
|
+
* HTTP methods defined in HTTP/1.1 specification.
|
|
13
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7231#section-4.3
|
|
12
14
|
*/
|
|
13
|
-
type HTTPMethod = "GET" | "POST" | "DELETE" | "PUT" | "PATCH";
|
|
15
|
+
type HTTPMethod = "GET" | "POST" | "DELETE" | "PUT" | "PATCH" | "OPTIONS" | "HEAD" | "TRACE" | "CONNECT";
|
|
14
16
|
/**
|
|
15
17
|
* Content types supported by the router.
|
|
16
18
|
*/
|
|
17
|
-
type ContentType = "application/json" | "application/x-www-form-urlencoded" | "text/plain";
|
|
19
|
+
type ContentType = "application/json" | "application/x-www-form-urlencoded" | "text/plain" | "multipart/form-data" | "application/xml" | "application/octet-stream" | `text/${string}` | `image/${string}` | `video/${string}` | `audio/${string}` | "application/pdf";
|
|
18
20
|
type Prettify<Obj extends object> = {
|
|
19
21
|
[Key in keyof Obj]: Obj[Key];
|
|
20
22
|
} & {};
|
|
@@ -33,20 +35,27 @@ type Prettify<Obj extends object> = {
|
|
|
33
35
|
* // Expected: {}
|
|
34
36
|
* type NoParams = Params<"/about">;
|
|
35
37
|
*/
|
|
36
|
-
type
|
|
38
|
+
type GetRouteParams<Route extends RoutePattern> = Route extends `/${string}/:${infer Param}/${infer Str}` ? Prettify<{
|
|
37
39
|
[K in Param]: string;
|
|
38
|
-
} &
|
|
40
|
+
} & GetRouteParams<`/${Str}`>> : Route extends `/${string}/:${infer Param}` ? Prettify<{
|
|
39
41
|
[K in Param]: string;
|
|
40
42
|
}> : Route extends `/:${infer Param}` ? Prettify<{
|
|
41
43
|
[K in Param]: string;
|
|
42
44
|
}> : {};
|
|
43
|
-
type GetRouteParams<T extends RoutePattern> = Params<T>;
|
|
44
45
|
/**
|
|
45
46
|
* Available schemas validation for an endpoint. It can include body and searchParams schemas.
|
|
46
47
|
*/
|
|
47
48
|
interface EndpointSchemas {
|
|
48
49
|
body?: ZodObject<any>;
|
|
49
50
|
searchParams?: ZodObject<any>;
|
|
51
|
+
params?: ZodObject<any>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Global context type that can be extended when creating the router. It allows passing
|
|
55
|
+
* additional data to all route handlers and middlewares. For a type-inference use module
|
|
56
|
+
* augmentation to extend the GlobalContext interface.
|
|
57
|
+
*/
|
|
58
|
+
interface GlobalContext {
|
|
50
59
|
}
|
|
51
60
|
/**
|
|
52
61
|
* Configuration for an endpoint, including optional schemas for request validation and middlewares.
|
|
@@ -77,31 +86,50 @@ type ContextBody<Schemas extends EndpointConfig["schemas"]> = Schemas extends {
|
|
|
77
86
|
} : {
|
|
78
87
|
body: undefined;
|
|
79
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* Infer the type of route parameters from the provided value in the `EndpointConfig`.
|
|
91
|
+
*/
|
|
92
|
+
type ContextParams<Schemas extends EndpointConfig["schemas"], Default = Record<string, string>> = Schemas extends {
|
|
93
|
+
params: ZodObject;
|
|
94
|
+
} ? {
|
|
95
|
+
params: z.infer<Schemas["params"]>;
|
|
96
|
+
} : {
|
|
97
|
+
params: Default;
|
|
98
|
+
};
|
|
80
99
|
/**
|
|
81
100
|
* Context object passed to route handlers and middlewares defined in the
|
|
82
101
|
* `createEndpoint/createEndpointConfig` function or globally in the `createRouter` function.
|
|
83
102
|
*/
|
|
84
103
|
interface RequestContext<RouteParams = Record<string, string>, Config extends EndpointConfig = EndpointConfig> {
|
|
85
|
-
params: RouteParams;
|
|
104
|
+
params: ContextParams<Config["schemas"], RouteParams>["params"];
|
|
86
105
|
headers: Headers;
|
|
87
106
|
body: ContextBody<Config["schemas"]>["body"];
|
|
88
107
|
searchParams: ContextSearchParams<Config["schemas"]>["searchParams"];
|
|
108
|
+
request: Request;
|
|
109
|
+
url: URL;
|
|
110
|
+
method: HTTPMethod;
|
|
111
|
+
route: RoutePattern;
|
|
112
|
+
context: GlobalContext;
|
|
113
|
+
}
|
|
114
|
+
interface GlobalMiddlewareContext {
|
|
115
|
+
request: Request;
|
|
116
|
+
context: GlobalContext;
|
|
89
117
|
}
|
|
90
118
|
/**
|
|
91
119
|
* Global middleware function type that represent a function that runs before the route matching.
|
|
92
120
|
*/
|
|
93
|
-
type GlobalMiddleware = (
|
|
121
|
+
type GlobalMiddleware = (ctx: GlobalMiddlewareContext) => Response | GlobalMiddlewareContext | Promise<Response | GlobalMiddlewareContext>;
|
|
94
122
|
/**
|
|
95
123
|
* Middleware function type that represent a function that runs before the route handler
|
|
96
124
|
* defined in the `createEndpoint/createEndpointConfig` function or globally in the `createRouter` function.
|
|
97
125
|
*/
|
|
98
|
-
type MiddlewareFunction<RouteParams = Record<string, string>, Config extends EndpointConfig = EndpointConfig> = (
|
|
126
|
+
type MiddlewareFunction<RouteParams = Record<string, string>, Config extends EndpointConfig = EndpointConfig> = (ctx: Prettify<RequestContext<RouteParams, Config>>) => Response | RequestContext<RouteParams, Config> | Promise<Response | RequestContext<RouteParams, Config>>;
|
|
99
127
|
/**
|
|
100
128
|
* Defines a route handler function that processes an incoming request and returns a response.
|
|
101
129
|
* The handler receives the request object and a context containing route parameters, headers,
|
|
102
130
|
* and optionally validated body and search parameters based on the endpoint configuration.
|
|
103
131
|
*/
|
|
104
|
-
type RouteHandler<Route extends RoutePattern, Config extends EndpointConfig> = (
|
|
132
|
+
type RouteHandler<Route extends RoutePattern, Config extends EndpointConfig> = (ctx: Prettify<RequestContext<GetRouteParams<Route>, Config>>) => Response | Promise<Response>;
|
|
105
133
|
/**
|
|
106
134
|
* Represents a route endpoint definition, specifying the HTTP method, route pattern,
|
|
107
135
|
* handler function with inferred context types, and associated configuration.
|
|
@@ -121,14 +149,64 @@ type InferMethod<Endpoints extends RouteEndpoint[]> = Endpoints extends unknown[
|
|
|
121
149
|
* Each method is a function that takes a request and context, returning a promise of a response.
|
|
122
150
|
*/
|
|
123
151
|
type GetHttpHandlers<Endpoints extends RouteEndpoint[]> = {
|
|
124
|
-
[Method in InferMethod<Endpoints>]: (req: Request) => Promise<Response>;
|
|
152
|
+
[Method in InferMethod<Endpoints>]: (req: Request) => Response | Promise<Response>;
|
|
153
|
+
};
|
|
154
|
+
type GlobalCtx = keyof GlobalContext extends never ? {
|
|
155
|
+
context?: GlobalContext;
|
|
156
|
+
} : {
|
|
157
|
+
context: GlobalContext;
|
|
125
158
|
};
|
|
126
159
|
/**
|
|
127
160
|
* Configuration options for `createRouter` function.
|
|
128
161
|
*/
|
|
129
|
-
interface RouterConfig {
|
|
162
|
+
interface RouterConfig extends GlobalCtx {
|
|
163
|
+
/**
|
|
164
|
+
* Prefix path for all routes/endpoints defined in the router.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* basePath: "/api/v1"
|
|
168
|
+
*
|
|
169
|
+
* // will match the "/users" endpoint.
|
|
170
|
+
* new Request("https://example.com/api/v1/users")
|
|
171
|
+
*
|
|
172
|
+
* // will NOT match the "/users" endpoint.
|
|
173
|
+
* new Request("https://example.com/users")
|
|
174
|
+
*/
|
|
130
175
|
basePath?: RoutePattern;
|
|
176
|
+
/**
|
|
177
|
+
* Global middlewares that run before route matching for all endpoints in the router.
|
|
178
|
+
* You can use this to modify the request or return a response early.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* middlewares: [
|
|
182
|
+
* async (request) => {
|
|
183
|
+
* if(request.headers.get("Authorization")?.startsWith("Bearer ")) {
|
|
184
|
+
* return Response.json({ message: "Unauthorized" }, { status: 401 })
|
|
185
|
+
* }
|
|
186
|
+
* return request
|
|
187
|
+
* }
|
|
188
|
+
* ]
|
|
189
|
+
*/
|
|
131
190
|
middlewares?: GlobalMiddleware[];
|
|
191
|
+
/**
|
|
192
|
+
* Error handler function that runs when an error is thrown in a router handler or middleware.
|
|
193
|
+
* It can be used to customize the default error response provided by the router. If is an internal
|
|
194
|
+
* error the error is from the `RouterError` class, otherwise the error is a generic
|
|
195
|
+
* `Error` instance which was caused by a handler or middleware, for how to distinguish them you can use
|
|
196
|
+
* the `isRouterError` function from the `assert` module.
|
|
197
|
+
*
|
|
198
|
+
* @param error - The error thrown in the router
|
|
199
|
+
* @param request - The original request that caused the error
|
|
200
|
+
* @returns A response to be sent back to the client
|
|
201
|
+
* @example
|
|
202
|
+
* onError: (error, request) => {
|
|
203
|
+
* if(isRouterError(error)) {
|
|
204
|
+
* return Response.json({ message: error.message }, { status: error.statusCode })
|
|
205
|
+
* }
|
|
206
|
+
* return Response.json({ message: "Internal Server Error" }, { status: 500 })
|
|
207
|
+
* }
|
|
208
|
+
*/
|
|
209
|
+
onError?: (error: Error | RouterError, request: Request) => Response | Promise<Response>;
|
|
132
210
|
}
|
|
133
211
|
|
|
134
|
-
export type { ContentType, ContextBody, ContextSearchParams, EndpointConfig, EndpointSchemas, GetHttpHandlers, GetRouteParams, GlobalMiddleware, HTTPMethod, InferMethod, MiddlewareFunction,
|
|
212
|
+
export type { ContentType, ContextBody, ContextParams, ContextSearchParams, EndpointConfig, EndpointSchemas, GetHttpHandlers, GetRouteParams, GlobalContext, GlobalCtx, GlobalMiddleware, GlobalMiddlewareContext, HTTPMethod, InferMethod, MiddlewareFunction, Prettify, RequestContext, RouteEndpoint, RouteHandler, RoutePattern, RouterConfig };
|