@bunary/http 0.0.2 → 0.0.5

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +170 -3
  3. package/dist/app.d.ts +7 -25
  4. package/dist/app.d.ts.map +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +344 -42
  8. package/dist/pathUtils.d.ts +34 -0
  9. package/dist/pathUtils.d.ts.map +1 -0
  10. package/dist/response.d.ts +26 -0
  11. package/dist/response.d.ts.map +1 -0
  12. package/dist/router.d.ts +49 -0
  13. package/dist/router.d.ts.map +1 -0
  14. package/dist/routes/builder.d.ts +17 -0
  15. package/dist/routes/builder.d.ts.map +1 -0
  16. package/dist/routes/find.d.ts +27 -0
  17. package/dist/routes/find.d.ts.map +1 -0
  18. package/dist/routes/group.d.ts +7 -0
  19. package/dist/routes/group.d.ts.map +1 -0
  20. package/dist/routes/index.d.ts +4 -0
  21. package/dist/routes/index.d.ts.map +1 -0
  22. package/dist/types/appOptions.d.ts +8 -0
  23. package/dist/types/appOptions.d.ts.map +1 -0
  24. package/dist/types/bunaryApp.d.ts +97 -0
  25. package/dist/types/bunaryApp.d.ts.map +1 -0
  26. package/dist/types/bunaryServer.d.ts +14 -0
  27. package/dist/types/bunaryServer.d.ts.map +1 -0
  28. package/dist/types/groupOptions.d.ts +13 -0
  29. package/dist/types/groupOptions.d.ts.map +1 -0
  30. package/dist/types/groupRouter.d.ts +26 -0
  31. package/dist/types/groupRouter.d.ts.map +1 -0
  32. package/dist/types/handlerResponse.d.ts +8 -0
  33. package/dist/types/handlerResponse.d.ts.map +1 -0
  34. package/dist/types/httpMethod.d.ts +5 -0
  35. package/dist/types/httpMethod.d.ts.map +1 -0
  36. package/dist/types/index.d.ts +19 -0
  37. package/dist/types/index.d.ts.map +1 -0
  38. package/dist/types/middleware.d.ts +21 -0
  39. package/dist/types/middleware.d.ts.map +1 -0
  40. package/dist/types/pathParams.d.ts +6 -0
  41. package/dist/types/pathParams.d.ts.map +1 -0
  42. package/dist/types/queryParams.d.ts +5 -0
  43. package/dist/types/queryParams.d.ts.map +1 -0
  44. package/dist/types/requestContext.d.ts +36 -0
  45. package/dist/types/requestContext.d.ts.map +1 -0
  46. package/dist/types/route.d.ts +27 -0
  47. package/dist/types/route.d.ts.map +1 -0
  48. package/dist/types/routeBuilder.d.ts +24 -0
  49. package/dist/types/routeBuilder.d.ts.map +1 -0
  50. package/dist/types/routeHandler.d.ts +17 -0
  51. package/dist/types/routeHandler.d.ts.map +1 -0
  52. package/dist/types/routeInfo.d.ts +13 -0
  53. package/dist/types/routeInfo.d.ts.map +1 -0
  54. package/package.json +5 -1
package/dist/index.js CHANGED
@@ -1,26 +1,5 @@
1
1
  // @bun
2
- // src/app.ts
3
- function compilePath(path) {
4
- const paramNames = [];
5
- const regexString = path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_match, paramName) => {
6
- paramNames.push(paramName);
7
- return "([^/]+)";
8
- });
9
- return {
10
- pattern: new RegExp(`^${regexString}$`),
11
- paramNames
12
- };
13
- }
14
- function extractParams(path, route) {
15
- const match = path.match(route.pattern);
16
- if (!match)
17
- return {};
18
- const params = {};
19
- for (let i = 0;i < route.paramNames.length; i++) {
20
- params[route.paramNames[i]] = match[i + 1];
21
- }
22
- return params;
23
- }
2
+ // src/response.ts
24
3
  function toResponse(result) {
25
4
  if (result instanceof Response) {
26
5
  return result;
@@ -45,34 +24,296 @@ function toResponse(result) {
45
24
  headers: { "Content-Type": "application/json" }
46
25
  });
47
26
  }
27
+
28
+ // src/router.ts
29
+ function compilePath(path) {
30
+ const paramNames = [];
31
+ const optionalParams = [];
32
+ let regexString = path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33
+ regexString = regexString.replace(/\/:([a-zA-Z_][a-zA-Z0-9_]*)(\\\?)?/g, (_match, paramName, isOptional) => {
34
+ if (paramNames.includes(paramName)) {
35
+ throw new Error(`Duplicate parameter name ":${paramName}" in route pattern "${path}". Each parameter name must be unique within a route.`);
36
+ }
37
+ paramNames.push(paramName);
38
+ if (isOptional) {
39
+ optionalParams.push(paramName);
40
+ return "(?:/([^/]+))?";
41
+ }
42
+ return "/([^/]+)";
43
+ });
44
+ regexString += "/?";
45
+ return {
46
+ pattern: new RegExp(`^${regexString}$`),
47
+ paramNames,
48
+ optionalParams
49
+ };
50
+ }
51
+ function extractParams(path, route) {
52
+ const match = path.match(route.pattern);
53
+ if (!match)
54
+ return {};
55
+ const params = {};
56
+ for (let i = 0;i < route.paramNames.length; i++) {
57
+ const value = match[i + 1];
58
+ if (value !== undefined && value !== "") {
59
+ params[route.paramNames[i]] = value;
60
+ }
61
+ }
62
+ return params;
63
+ }
64
+ function checkConstraints(params, constraints) {
65
+ if (!constraints)
66
+ return true;
67
+ for (const [param, pattern] of Object.entries(constraints)) {
68
+ const value = params[param];
69
+ if (value === undefined)
70
+ continue;
71
+ if (!pattern.test(value))
72
+ return false;
73
+ }
74
+ return true;
75
+ }
76
+
77
+ // src/routes/builder.ts
78
+ function compilePattern(pattern, param) {
79
+ try {
80
+ return new RegExp(pattern);
81
+ } catch (error) {
82
+ const message = error instanceof Error ? error.message : "Invalid pattern";
83
+ throw new Error(`Invalid regex pattern for parameter "${param}": ${message}`);
84
+ }
85
+ }
86
+ function createRouteBuilder(route, namedRoutes, app) {
87
+ function addConstraint(param, pattern) {
88
+ if (!route.constraints) {
89
+ route.constraints = {};
90
+ }
91
+ route.constraints[param] = pattern;
92
+ }
93
+ const builder = {
94
+ get get() {
95
+ return app.get;
96
+ },
97
+ get post() {
98
+ return app.post;
99
+ },
100
+ get put() {
101
+ return app.put;
102
+ },
103
+ get delete() {
104
+ return app.delete;
105
+ },
106
+ get patch() {
107
+ return app.patch;
108
+ },
109
+ get use() {
110
+ return app.use;
111
+ },
112
+ get group() {
113
+ return app.group;
114
+ },
115
+ get route() {
116
+ return app.route;
117
+ },
118
+ get hasRoute() {
119
+ return app.hasRoute;
120
+ },
121
+ get getRoutes() {
122
+ return app.getRoutes;
123
+ },
124
+ get listen() {
125
+ return app.listen;
126
+ },
127
+ get fetch() {
128
+ return app.fetch;
129
+ },
130
+ name: (name) => {
131
+ if (namedRoutes.has(name)) {
132
+ throw new Error(`Route name "${name}" is already defined`);
133
+ }
134
+ route.name = name;
135
+ namedRoutes.set(name, route);
136
+ return builder;
137
+ },
138
+ where: (paramOrConstraints, pattern) => {
139
+ if (typeof paramOrConstraints === "string") {
140
+ if (!pattern) {
141
+ throw new Error(`Pattern is required for constraint on "${paramOrConstraints}"`);
142
+ }
143
+ const regex = typeof pattern === "string" ? compilePattern(pattern, paramOrConstraints) : pattern;
144
+ addConstraint(paramOrConstraints, regex);
145
+ } else {
146
+ for (const [param, pat] of Object.entries(paramOrConstraints)) {
147
+ const regex = typeof pat === "string" ? compilePattern(pat, param) : pat;
148
+ addConstraint(param, regex);
149
+ }
150
+ }
151
+ return builder;
152
+ },
153
+ whereNumber: (param) => {
154
+ addConstraint(param, /^\d+$/);
155
+ return builder;
156
+ },
157
+ whereAlpha: (param) => {
158
+ addConstraint(param, /^[a-zA-Z]+$/);
159
+ return builder;
160
+ },
161
+ whereAlphaNumeric: (param) => {
162
+ addConstraint(param, /^[a-zA-Z0-9]+$/);
163
+ return builder;
164
+ },
165
+ whereUuid: (param) => {
166
+ addConstraint(param, /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
167
+ return builder;
168
+ },
169
+ whereUlid: (param) => {
170
+ addConstraint(param, /^[0-9A-HJKMNP-TV-Z]{26}$/);
171
+ return builder;
172
+ },
173
+ whereIn: (param, values) => {
174
+ if (values.length === 0) {
175
+ throw new Error(`whereIn requires at least one value for parameter "${param}"`);
176
+ }
177
+ const escaped = values.map((v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
178
+ addConstraint(param, new RegExp(`^(${escaped.join("|")})$`));
179
+ return builder;
180
+ }
181
+ };
182
+ return builder;
183
+ }
184
+ function wrapBuilderWithNamePrefix(builder, namePrefix) {
185
+ if (!namePrefix)
186
+ return builder;
187
+ return new Proxy(builder, {
188
+ get(target, prop) {
189
+ if (prop === "name") {
190
+ return (name) => {
191
+ return target.name(namePrefix + name);
192
+ };
193
+ }
194
+ return target[prop];
195
+ }
196
+ });
197
+ }
198
+ // src/routes/find.ts
199
+ function findRoute(routes, method, path) {
200
+ for (const route of routes) {
201
+ if (route.pattern.test(path)) {
202
+ if (route.method === method) {
203
+ const params = extractParams(path, route);
204
+ if (!checkConstraints(params, route.constraints)) {
205
+ continue;
206
+ }
207
+ return { route, params };
208
+ }
209
+ }
210
+ }
211
+ return null;
212
+ }
213
+ function hasMatchingPath(routes, path) {
214
+ return routes.some((route) => {
215
+ if (!route.pattern.test(path))
216
+ return false;
217
+ const params = extractParams(path, route);
218
+ return checkConstraints(params, route.constraints);
219
+ });
220
+ }
221
+ // src/pathUtils.ts
222
+ function normalizePrefix(prefix) {
223
+ let normalized = prefix;
224
+ if (!normalized.startsWith("/")) {
225
+ normalized = `/${normalized}`;
226
+ }
227
+ if (normalized.endsWith("/") && normalized.length > 1) {
228
+ normalized = normalized.slice(0, -1);
229
+ }
230
+ return normalized;
231
+ }
232
+ function joinPaths(prefix, path) {
233
+ const normalizedPrefix = normalizePrefix(prefix);
234
+ let normalizedPath = path;
235
+ if (!normalizedPath.startsWith("/") && normalizedPath !== "") {
236
+ normalizedPath = `/${normalizedPath}`;
237
+ }
238
+ if (normalizedPath === "/") {
239
+ return normalizedPrefix;
240
+ }
241
+ return normalizedPrefix + normalizedPath;
242
+ }
243
+
244
+ // src/routes/group.ts
245
+ function createGroupRouter(prefix, groupMiddleware, namePrefix, addRoute) {
246
+ const router = {
247
+ get: (path, handler) => {
248
+ const fullPath = joinPaths(prefix, path);
249
+ const builder = addRoute("GET", fullPath, handler, groupMiddleware);
250
+ return wrapBuilderWithNamePrefix(builder, namePrefix);
251
+ },
252
+ post: (path, handler) => {
253
+ const fullPath = joinPaths(prefix, path);
254
+ return wrapBuilderWithNamePrefix(addRoute("POST", fullPath, handler, groupMiddleware), namePrefix);
255
+ },
256
+ put: (path, handler) => {
257
+ const fullPath = joinPaths(prefix, path);
258
+ return wrapBuilderWithNamePrefix(addRoute("PUT", fullPath, handler, groupMiddleware), namePrefix);
259
+ },
260
+ delete: (path, handler) => {
261
+ const fullPath = joinPaths(prefix, path);
262
+ return wrapBuilderWithNamePrefix(addRoute("DELETE", fullPath, handler, groupMiddleware), namePrefix);
263
+ },
264
+ patch: (path, handler) => {
265
+ const fullPath = joinPaths(prefix, path);
266
+ return wrapBuilderWithNamePrefix(addRoute("PATCH", fullPath, handler, groupMiddleware), namePrefix);
267
+ },
268
+ group: (prefixOrOptions, callback) => {
269
+ const opts = typeof prefixOrOptions === "string" ? { prefix: prefixOrOptions } : prefixOrOptions;
270
+ const nestedPrefix = joinPaths(prefix, opts.prefix);
271
+ const nestedMiddleware = [...groupMiddleware, ...opts.middleware ?? []];
272
+ const nestedNamePrefix = namePrefix + (opts.name ?? "");
273
+ const nestedRouter = createGroupRouter(nestedPrefix, nestedMiddleware, nestedNamePrefix, addRoute);
274
+ callback(nestedRouter);
275
+ return router;
276
+ }
277
+ };
278
+ return router;
279
+ }
280
+ // src/app.ts
48
281
  function createApp() {
49
282
  const routes = [];
50
283
  const middlewares = [];
51
- function addRoute(method, path, handler) {
52
- const { pattern, paramNames } = compilePath(path);
53
- routes.push({ method, path, pattern, paramNames, handler });
54
- return app;
55
- }
56
- function findRoute(method, path) {
57
- for (const route of routes) {
58
- if (route.pattern.test(path)) {
59
- if (route.method === method) {
60
- return { route, params: extractParams(path, route) };
61
- }
62
- }
284
+ const namedRoutes = new Map;
285
+ let globalMiddlewareVersion = 0;
286
+ const middlewareCache = new WeakMap;
287
+ function getMiddlewareChain(route) {
288
+ const cached = middlewareCache.get(route);
289
+ if (cached && cached.version === globalMiddlewareVersion) {
290
+ return cached.chain;
63
291
  }
64
- return null;
292
+ const chain = route.middleware ? [...middlewares, ...route.middleware] : middlewares.length > 0 ? [...middlewares] : [];
293
+ middlewareCache.set(route, { version: globalMiddlewareVersion, chain });
294
+ return chain;
65
295
  }
66
- function hasMatchingPath(path) {
67
- return routes.some((route) => route.pattern.test(path));
296
+ function addRoute(method, path, handler, groupMiddleware = []) {
297
+ const { pattern, paramNames, optionalParams } = compilePath(path);
298
+ const route = {
299
+ method,
300
+ path,
301
+ pattern,
302
+ paramNames,
303
+ handler,
304
+ optionalParams: optionalParams.length > 0 ? optionalParams : undefined,
305
+ middleware: groupMiddleware.length > 0 ? [...groupMiddleware] : undefined
306
+ };
307
+ routes.push(route);
308
+ return createRouteBuilder(route, namedRoutes, app);
68
309
  }
69
310
  async function handleRequest(request) {
70
311
  const url = new URL(request.url);
71
312
  const path = url.pathname;
72
313
  const method = request.method;
73
- const match = findRoute(method, path);
314
+ const match = findRoute(routes, method, path);
74
315
  if (!match) {
75
- if (hasMatchingPath(path)) {
316
+ if (hasMatchingPath(routes, path)) {
76
317
  return new Response(JSON.stringify({ error: "Method not allowed" }), {
77
318
  status: 405,
78
319
  headers: { "Content-Type": "application/json" }
@@ -86,13 +327,15 @@ function createApp() {
86
327
  const ctx = {
87
328
  request,
88
329
  params: match.params,
89
- query: url.searchParams
330
+ query: url.searchParams,
331
+ locals: {}
90
332
  };
91
333
  try {
334
+ const allMiddleware = getMiddlewareChain(match.route);
92
335
  let index = 0;
93
336
  const next = async () => {
94
- if (index < middlewares.length) {
95
- const middleware = middlewares[index++];
337
+ if (index < allMiddleware.length) {
338
+ const middleware = allMiddleware[index++];
96
339
  return await middleware(ctx, next);
97
340
  }
98
341
  return await match.route.handler(ctx);
@@ -115,8 +358,67 @@ function createApp() {
115
358
  patch: (path, handler) => addRoute("PATCH", path, handler),
116
359
  use: (middleware) => {
117
360
  middlewares.push(middleware);
361
+ globalMiddlewareVersion++;
362
+ return app;
363
+ },
364
+ group: (prefixOrOptions, callback) => {
365
+ const opts = typeof prefixOrOptions === "string" ? { prefix: prefixOrOptions } : prefixOrOptions;
366
+ const groupRouter = createGroupRouter(opts.prefix, opts.middleware ?? [], opts.name ?? "", addRoute);
367
+ callback(groupRouter);
118
368
  return app;
119
369
  },
370
+ route: (name, params) => {
371
+ const route = namedRoutes.get(name);
372
+ if (!route) {
373
+ throw new Error(`Route "${name}" not found`);
374
+ }
375
+ if (params) {
376
+ for (const [key, value] of Object.entries(params)) {
377
+ const strValue = String(value);
378
+ if (strValue.includes("\r") || strValue.includes(`
379
+ `) || strValue.includes("\x00")) {
380
+ throw new Error(`Invalid character in parameter "${key}": control characters are not allowed`);
381
+ }
382
+ }
383
+ }
384
+ let url = route.path;
385
+ const queryParams = {};
386
+ const usedParams = new Set;
387
+ for (const paramName of route.paramNames) {
388
+ const isOptional = route.optionalParams?.includes(paramName);
389
+ const value = params?.[paramName];
390
+ if (value !== undefined) {
391
+ url = url.replace(new RegExp(`:${paramName}\\??`), encodeURIComponent(String(value)));
392
+ usedParams.add(paramName);
393
+ } else if (isOptional) {
394
+ url = url.replace(new RegExp(`/:${paramName}\\?`), "");
395
+ } else {
396
+ throw new Error(`Missing required param "${paramName}" for route "${name}"`);
397
+ }
398
+ }
399
+ if (params) {
400
+ for (const [key, value] of Object.entries(params)) {
401
+ if (!usedParams.has(key)) {
402
+ queryParams[key] = String(value);
403
+ }
404
+ }
405
+ }
406
+ if (Object.keys(queryParams).length > 0) {
407
+ const qs = new URLSearchParams(queryParams).toString();
408
+ url += `?${qs}`;
409
+ }
410
+ return url;
411
+ },
412
+ hasRoute: (name) => {
413
+ return namedRoutes.has(name);
414
+ },
415
+ getRoutes: () => {
416
+ return routes.map((route) => ({
417
+ name: route.name ?? null,
418
+ method: route.method,
419
+ path: route.path
420
+ }));
421
+ },
120
422
  listen: (port = 3000, hostname = "localhost") => {
121
423
  const server = Bun.serve({
122
424
  port,
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Path manipulation utilities for route handling.
3
+ */
4
+ /**
5
+ * Normalize a path prefix (ensure leading slash, no trailing slash).
6
+ *
7
+ * @param prefix - Path prefix to normalize
8
+ * @returns Normalized prefix with leading slash and no trailing slash
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * normalizePrefix("api") // "/api"
13
+ * normalizePrefix("/api/") // "/api"
14
+ * normalizePrefix("/") // "/"
15
+ * ```
16
+ */
17
+ export declare function normalizePrefix(prefix: string): string;
18
+ /**
19
+ * Join path segments, handling slashes correctly.
20
+ *
21
+ * @param prefix - Path prefix
22
+ * @param path - Path to append
23
+ * @returns Combined path
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * joinPaths("/api", "/users") // "/api/users"
28
+ * joinPaths("/api", "users") // "/api/users"
29
+ * joinPaths("/api/", "/users") // "/api/users"
30
+ * joinPaths("/", "/") // "/"
31
+ * ```
32
+ */
33
+ export declare function joinPaths(prefix: string, path: string): string;
34
+ //# sourceMappingURL=pathUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathUtils.d.ts","sourceRoot":"","sources":["../src/pathUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAStD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAc9D"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Response conversion utilities for route handlers.
3
+ */
4
+ import type { HandlerResponse } from "./types/index.js";
5
+ /**
6
+ * Convert a handler response to a proper Response object.
7
+ *
8
+ * Handles the following response types:
9
+ * - Response: passed through unchanged
10
+ * - null/undefined: 204 No Content
11
+ * - string: text/plain response
12
+ * - number: text/plain response
13
+ * - object/array: JSON response
14
+ *
15
+ * @param result - The handler return value
16
+ * @returns A proper Response object
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * toResponse({ message: "Hello" }); // JSON response
21
+ * toResponse("Hello"); // text/plain response
22
+ * toResponse(null); // 204 No Content
23
+ * ```
24
+ */
25
+ export declare function toResponse(result: HandlerResponse): Response;
26
+ //# sourceMappingURL=response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,QAAQ,CAgC5D"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Route matching utilities for compiling paths and extracting parameters.
3
+ */
4
+ import type { Route } from "./types/index.js";
5
+ /**
6
+ * Result of compiling a path pattern.
7
+ */
8
+ export interface CompiledPath {
9
+ /** Regex pattern for matching */
10
+ pattern: RegExp;
11
+ /** Parameter names in order */
12
+ paramNames: string[];
13
+ /** Names of optional parameters */
14
+ optionalParams: string[];
15
+ }
16
+ /**
17
+ * Compile a path pattern into a regex and extract parameter names.
18
+ * Supports optional parameters with :param? syntax.
19
+ *
20
+ * @param path - Route path pattern (e.g., "/users/:id" or "/users/:id?")
21
+ * @returns Object with regex pattern, parameter names, and optional param names
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const { pattern, paramNames, optionalParams } = compilePath("/users/:id?");
26
+ * // pattern matches "/users" and "/users/123"
27
+ * // paramNames = ["id"]
28
+ * // optionalParams = ["id"]
29
+ * ```
30
+ */
31
+ export declare function compilePath(path: string): CompiledPath;
32
+ /**
33
+ * Extract path parameters from a matched route.
34
+ * Handles optional parameters by only including them if they have values.
35
+ *
36
+ * @param path - The request path
37
+ * @param route - The matched route
38
+ * @returns Record of parameter names to values (undefined for missing optional params)
39
+ */
40
+ export declare function extractParams(path: string, route: Route): Record<string, string | undefined>;
41
+ /**
42
+ * Check if route constraints are satisfied.
43
+ *
44
+ * @param params - Extracted route parameters
45
+ * @param constraints - Parameter constraints (regex patterns)
46
+ * @returns True if all constraints pass
47
+ */
48
+ export declare function checkConstraints(params: Record<string, string | undefined>, constraints?: Record<string, RegExp>): boolean;
49
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,mCAAmC;IACnC,cAAc,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAmCtD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAa5F;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAC1C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAUT"}
@@ -0,0 +1,17 @@
1
+ import type { BunaryApp, Route, RouteBuilder } from "../types/index.js";
2
+ /**
3
+ * Safely compile a string pattern to RegExp with error handling.
4
+ * Provides better error messages for invalid regex patterns.
5
+ */
6
+ export declare function compilePattern(pattern: string, param: string): RegExp;
7
+ /**
8
+ * Create a RouteBuilder for a specific route.
9
+ * Each builder captures its own route reference to avoid shared mutable state issues.
10
+ */
11
+ export declare function createRouteBuilder(route: Route, namedRoutes: Map<string, Route>, app: BunaryApp): RouteBuilder;
12
+ /**
13
+ * Wrap a route builder to auto-apply name prefix.
14
+ * Uses a Proxy to maintain dynamic getter behavior from the original builder.
15
+ */
16
+ export declare function wrapBuilderWithNamePrefix(builder: RouteBuilder, namePrefix: string): RouteBuilder;
17
+ //# sourceMappingURL=builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/routes/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAExE;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAOrE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,EAC/B,GAAG,EAAE,SAAS,GACZ,YAAY,CAmHd;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,YAAY,CAajG"}
@@ -0,0 +1,27 @@
1
+ import type { Route } from "../types/index.js";
2
+ export interface RouteMatch {
3
+ route: Route;
4
+ params: Record<string, string | undefined>;
5
+ }
6
+ /**
7
+ * Find a matching route for the given method and path.
8
+ *
9
+ * **Complexity**: O(n) where n is the number of registered routes.
10
+ * Routes are tested sequentially until a match is found.
11
+ *
12
+ * This linear search is suitable for most applications (up to ~100 routes).
13
+ * For applications with hundreds of routes, consider:
14
+ * - Grouping routes by common prefixes (reduces regex tests per request)
15
+ * - Using method-based route maps (the 405 "Method Not Allowed" check already
16
+ * iterates separately, so grouping by method could help)
17
+ * - Implementing a radix/prefix tree for static path segments
18
+ *
19
+ * The current design prioritizes simplicity and correctness. Route order matters:
20
+ * the first matching route wins, allowing intentional route shadowing.
21
+ */
22
+ export declare function findRoute(routes: Route[], method: string, path: string): RouteMatch | null;
23
+ /**
24
+ * Check if any route matches the path (regardless of method).
25
+ */
26
+ export declare function hasMatchingPath(routes: Route[], path: string): boolean;
27
+ //# sourceMappingURL=find.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../src/routes/find.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,WAAW,UAAU;IAC1B,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAc1F;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAMtE"}
@@ -0,0 +1,7 @@
1
+ import type { GroupRouter, Middleware, RouteBuilder, RouteHandler } from "../types/index.js";
2
+ export type AddRouteFn = (method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH", path: string, handler: RouteHandler, groupMiddleware?: Middleware[]) => RouteBuilder;
3
+ /**
4
+ * Create a group router for defining routes within a group.
5
+ */
6
+ export declare function createGroupRouter(prefix: string, groupMiddleware: Middleware[], namePrefix: string, addRoute: AddRouteFn): GroupRouter;
7
+ //# sourceMappingURL=group.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/routes/group.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGX,WAAW,EACX,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,MAAM,mBAAmB,CAAC;AAG3B,MAAM,MAAM,UAAU,GAAG,CACxB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,EACnD,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,EACrB,eAAe,CAAC,EAAE,UAAU,EAAE,KAC1B,YAAY,CAAC;AAElB;;GAEG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,UAAU,EAAE,EAC7B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,UAAU,GAClB,WAAW,CAoDb"}
@@ -0,0 +1,4 @@
1
+ export { createRouteBuilder, compilePattern, wrapBuilderWithNamePrefix } from "./builder.js";
2
+ export { findRoute, hasMatchingPath, type RouteMatch } from "./find.js";
3
+ export { createGroupRouter, type AddRouteFn } from "./group.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Configuration options for creating a Bunary app.
3
+ */
4
+ export interface AppOptions {
5
+ /** Base path prefix for all routes (default: "") */
6
+ basePath?: string;
7
+ }
8
+ //# sourceMappingURL=appOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appOptions.d.ts","sourceRoot":"","sources":["../../src/types/appOptions.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB"}