@fresho/router 0.5.0 → 0.6.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.
@@ -0,0 +1,738 @@
1
+ import { n as runMiddleware } from "./middleware-B-_0PLA6.mjs";
2
+
3
+ //#region src/schema.ts
4
+ /** Valid base types for primitives and arrays. */
5
+ const PRIMITIVE_TYPES = [
6
+ "string",
7
+ "number",
8
+ "boolean",
9
+ "unknown"
10
+ ];
11
+ /** Checks if a schema field is a nested object (not a type string or [type, description] tuple). */
12
+ function isNestedSchema(field) {
13
+ return typeof field === "object" && field !== null && !Array.isArray(field);
14
+ }
15
+ /** Unpacks a schema field into its type string and optional description. */
16
+ function unpackField(field) {
17
+ return Array.isArray(field) ? [field[0], field[1]] : [field, void 0];
18
+ }
19
+ /**
20
+ * Validates and coerces a single primitive value.
21
+ * Returns [success, result, errorMessage].
22
+ */
23
+ function validatePrimitive(value, baseType) {
24
+ switch (baseType) {
25
+ case "string":
26
+ if (typeof value !== "string") return [
27
+ false,
28
+ void 0,
29
+ "Expected string"
30
+ ];
31
+ return [
32
+ true,
33
+ value,
34
+ null
35
+ ];
36
+ case "number": {
37
+ if (typeof value !== "number" && typeof value !== "string" || typeof value === "string" && value.trim() === "") return [
38
+ false,
39
+ void 0,
40
+ "Expected number"
41
+ ];
42
+ const num = Number(value);
43
+ if (!Number.isFinite(num)) return [
44
+ false,
45
+ void 0,
46
+ "Expected number"
47
+ ];
48
+ return [
49
+ true,
50
+ num,
51
+ null
52
+ ];
53
+ }
54
+ case "boolean":
55
+ if (value === "true" || value === "1" || value === true) return [
56
+ true,
57
+ true,
58
+ null
59
+ ];
60
+ else if (value === "false" || value === "0" || value === false) return [
61
+ true,
62
+ false,
63
+ null
64
+ ];
65
+ return [
66
+ false,
67
+ void 0,
68
+ "Expected boolean (true/false)"
69
+ ];
70
+ case "unknown": return [
71
+ true,
72
+ value,
73
+ null
74
+ ];
75
+ default: return [
76
+ false,
77
+ void 0,
78
+ `Unknown type: ${baseType}`
79
+ ];
80
+ }
81
+ }
82
+ /**
83
+ * Validates a schema field value (primitive, array, or nested object).
84
+ * Returns [success, result, errors].
85
+ */
86
+ function validateField(key, field, value, parentPath = "") {
87
+ const fieldPath = parentPath ? `${parentPath}.${key}` : key;
88
+ const errors = {};
89
+ if (isNestedSchema(field)) {
90
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
91
+ errors[fieldPath] = ["Expected object"];
92
+ return [
93
+ false,
94
+ void 0,
95
+ errors
96
+ ];
97
+ }
98
+ const nestedResult = validateSchema(field, value, fieldPath);
99
+ return [
100
+ nestedResult.success,
101
+ nestedResult.data,
102
+ nestedResult.errors
103
+ ];
104
+ }
105
+ const [typeStr] = unpackField(field);
106
+ const isOptional = typeStr.endsWith("?");
107
+ const isArray = typeStr.includes("[]");
108
+ let baseType = isOptional ? typeStr.slice(0, -1) : typeStr;
109
+ if (isArray) baseType = baseType.replace("[]", "");
110
+ if (value === void 0 || value === null || value === "") {
111
+ if (!isOptional) {
112
+ errors[fieldPath] = ["Required"];
113
+ return [
114
+ false,
115
+ void 0,
116
+ errors
117
+ ];
118
+ }
119
+ return [
120
+ true,
121
+ void 0,
122
+ {}
123
+ ];
124
+ }
125
+ if (isArray) {
126
+ if (!Array.isArray(value)) {
127
+ errors[fieldPath] = ["Expected array"];
128
+ return [
129
+ false,
130
+ void 0,
131
+ errors
132
+ ];
133
+ }
134
+ const resultArray = [];
135
+ for (let i = 0; i < value.length; i++) {
136
+ const [success$1, coerced$1, errorMsg$1] = validatePrimitive(value[i], baseType);
137
+ if (!success$1) {
138
+ errors[`${fieldPath}[${i}]`] = [errorMsg$1];
139
+ return [
140
+ false,
141
+ void 0,
142
+ errors
143
+ ];
144
+ }
145
+ resultArray.push(coerced$1);
146
+ }
147
+ return [
148
+ true,
149
+ resultArray,
150
+ {}
151
+ ];
152
+ }
153
+ const [success, coerced, errorMsg] = validatePrimitive(value, baseType);
154
+ if (!success) {
155
+ errors[fieldPath] = [errorMsg];
156
+ return [
157
+ false,
158
+ void 0,
159
+ errors
160
+ ];
161
+ }
162
+ return [
163
+ true,
164
+ coerced,
165
+ {}
166
+ ];
167
+ }
168
+ /**
169
+ * Validates a full schema against data.
170
+ * Returns { success, data, errors }.
171
+ */
172
+ function validateSchema(schema, data, parentPath = "") {
173
+ const result = {};
174
+ const allErrors = {};
175
+ const source = typeof data === "object" && data !== null && !Array.isArray(data) ? data : {};
176
+ for (const [key, field] of Object.entries(schema)) {
177
+ const [success, coerced, fieldErrors] = validateField(key, field, Object.hasOwn(source, key) ? source[key] : void 0, parentPath);
178
+ if (!success) Object.assign(allErrors, fieldErrors);
179
+ else if (coerced !== void 0) result[key] = coerced;
180
+ }
181
+ return {
182
+ success: Object.keys(allErrors).length === 0,
183
+ data: result,
184
+ errors: allErrors
185
+ };
186
+ }
187
+ /** Validates schema definition at compile time. */
188
+ function validateSchemaDefinition(schema, path = "") {
189
+ for (const [key, field] of Object.entries(schema)) {
190
+ const fieldPath = path ? `${path}.${key}` : key;
191
+ if (isNestedSchema(field)) validateSchemaDefinition(field, fieldPath);
192
+ else {
193
+ if (Array.isArray(field) && (field.length !== 2 || typeof field[0] !== "string" || typeof field[1] !== "string")) throw new Error(`Invalid field definition at '${fieldPath}'`);
194
+ const [typeStr] = unpackField(field);
195
+ if (typeof typeStr !== "string") throw new Error(`Unknown type '${String(typeStr)}' at '${fieldPath}'`);
196
+ let baseType = typeStr.endsWith("?") ? typeStr.slice(0, -1) : typeStr;
197
+ baseType = baseType.replace("[]", "");
198
+ if (!PRIMITIVE_TYPES.includes(baseType)) throw new Error(`Unknown type '${typeStr}' at '${fieldPath}'`);
199
+ }
200
+ }
201
+ }
202
+ /**
203
+ * Compiles a schema definition into a validator.
204
+ *
205
+ * Used internally by the router to validate query parameters and request bodies.
206
+ * Provides a Zod-compatible `safeParse` API for consistency.
207
+ *
208
+ * @param schema - The schema definition object
209
+ * @returns A compiled schema with `safeParse` method
210
+ *
211
+ * @example
212
+ * ```typescript
213
+ * const userSchema = compileSchema({
214
+ * name: 'string',
215
+ * age: 'number',
216
+ * email: 'string?',
217
+ * });
218
+ *
219
+ * const result = userSchema.safeParse({ name: 'Alice', age: 30 });
220
+ * if (result.success) {
221
+ * console.log(result.data.name); // 'Alice'
222
+ * } else {
223
+ * console.log(result.error.flatten().fieldErrors);
224
+ * }
225
+ * ```
226
+ */
227
+ function compileSchema(schema) {
228
+ validateSchemaDefinition(schema);
229
+ return { safeParse(data) {
230
+ const { success, data: result, errors } = validateSchema(schema, data);
231
+ if (!success) return {
232
+ success: false,
233
+ error: { flatten: () => ({ fieldErrors: errors }) }
234
+ };
235
+ return {
236
+ success: true,
237
+ data: result
238
+ };
239
+ } };
240
+ }
241
+ /**
242
+ * Converts a schema definition to JSON Schema, recursing into nested objects.
243
+ */
244
+ function toJsonSchema(schema) {
245
+ const properties = {};
246
+ const required = [];
247
+ for (const [key, field] of Object.entries(schema)) {
248
+ if (isNestedSchema(field)) {
249
+ properties[key] = toJsonSchema(field);
250
+ required.push(key);
251
+ continue;
252
+ }
253
+ const [typeStr, description] = unpackField(field);
254
+ const described = description === void 0 ? {} : { description };
255
+ const optional = typeStr.endsWith("?");
256
+ const base = optional ? typeStr.slice(0, -1) : typeStr;
257
+ const primitive = base.replace("[]", "");
258
+ const item = primitive === "unknown" ? {} : { type: primitive };
259
+ properties[key] = base.endsWith("[]") ? {
260
+ type: "array",
261
+ items: item,
262
+ ...described
263
+ } : {
264
+ ...item,
265
+ ...described
266
+ };
267
+ if (!optional) required.push(key);
268
+ }
269
+ return {
270
+ type: "object",
271
+ properties,
272
+ ...required.length > 0 && { required }
273
+ };
274
+ }
275
+
276
+ //#endregion
277
+ //#region src/types.ts
278
+ /** HTTP method property names. */
279
+ const HTTP_METHODS = new Set([
280
+ "get",
281
+ "post",
282
+ "put",
283
+ "patch",
284
+ "delete",
285
+ "options",
286
+ "head"
287
+ ]);
288
+ /** Symbol to mark an object as a route definition. */
289
+ const ROUTE_MARKER = Symbol.for("@fresho/router:route");
290
+ /** Symbol to mark an object as a router. */
291
+ const ROUTER_MARKER = Symbol.for("@fresho/router:router");
292
+ /** Checks if a value is a function. */
293
+ function isFunction(value) {
294
+ return typeof value === "function";
295
+ }
296
+ /** Checks if entry is a Route (marked with ROUTE_MARKER). */
297
+ function isRoute(entry) {
298
+ return typeof entry === "object" && entry !== null && ROUTE_MARKER in entry;
299
+ }
300
+ /** Checks if entry is a Router (marked with ROUTER_MARKER). */
301
+ function isRouter(entry) {
302
+ return typeof entry === "object" && entry !== null && ROUTER_MARKER in entry;
303
+ }
304
+
305
+ //#endregion
306
+ //#region src/handler.ts
307
+ /**
308
+ * Escapes special regex characters in a string.
309
+ */
310
+ function escapeRegex(str) {
311
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
312
+ }
313
+ /**
314
+ * Converts a path with :param segments to a regex pattern.
315
+ */
316
+ function pathToRegex(path) {
317
+ const paramNames = [];
318
+ const parts = path.split(/:(\w+)/g);
319
+ let regexStr = "";
320
+ for (let i = 0; i < parts.length; i++) if (i % 2 === 0) regexStr += escapeRegex(parts[i]);
321
+ else {
322
+ paramNames.push(parts[i]);
323
+ regexStr += "([^/]+)";
324
+ }
325
+ return {
326
+ pattern: /* @__PURE__ */ new RegExp(`^${regexStr}$`),
327
+ paramNames
328
+ };
329
+ }
330
+ /**
331
+ * Converts a property name to a path segment.
332
+ * - `$param` becomes `:param`
333
+ * - Other names become literal segments
334
+ */
335
+ function propertyToSegment$1(prop) {
336
+ if (prop.startsWith("$")) return `:${prop.slice(1)}`;
337
+ return prop;
338
+ }
339
+ /** Collects and compiles all routes from a router tree. */
340
+ function compileRoutes(routerDef, parentPath = "", parentMiddleware = []) {
341
+ const currentMiddleware = [...parentMiddleware, ...routerDef.middleware || []];
342
+ const compiled = [];
343
+ for (const [prop, entry] of Object.entries(routerDef.routes)) if (isRouter(entry)) {
344
+ const segment = propertyToSegment$1(prop);
345
+ const newPath = parentPath ? `${parentPath}/${segment}` : `/${segment}`;
346
+ compiled.push(...compileRoutes(entry, newPath, currentMiddleware));
347
+ } else if (HTTP_METHODS.has(prop)) {
348
+ const method = prop.toUpperCase();
349
+ const { pattern, paramNames } = pathToRegex(parentPath || "/");
350
+ if (isFunction(entry)) compiled.push({
351
+ method,
352
+ pattern,
353
+ paramNames,
354
+ handler: entry,
355
+ middleware: currentMiddleware
356
+ });
357
+ else if (isRoute(entry)) {
358
+ const routeDef = entry;
359
+ compiled.push({
360
+ method,
361
+ pattern,
362
+ paramNames,
363
+ handler: routeDef.handler,
364
+ middleware: currentMiddleware,
365
+ querySchema: routeDef.query ? compileSchema(routeDef.query) : void 0,
366
+ bodySchema: routeDef.body ? compileSchema(routeDef.body) : void 0
367
+ });
368
+ }
369
+ }
370
+ return compiled;
371
+ }
372
+ /**
373
+ * Strips the body from a response for HEAD requests.
374
+ */
375
+ function stripBodyForHead(response, isHead) {
376
+ if (!isHead) return response;
377
+ return new Response(null, {
378
+ status: response.status,
379
+ statusText: response.statusText,
380
+ headers: response.headers
381
+ });
382
+ }
383
+ /**
384
+ * Creates a fetch handler from a router definition.
385
+ *
386
+ * @example
387
+ * ```typescript
388
+ * const api = router({
389
+ * health: router({
390
+ * get: async () => ({ status: 'ok' }),
391
+ * }),
392
+ * users: router({
393
+ * get: async () => [...],
394
+ * $id: router({
395
+ * get: async (c) => ({ id: c.path.id }),
396
+ * }),
397
+ * }),
398
+ * });
399
+ *
400
+ * export default { fetch: api.handler() };
401
+ * ```
402
+ */
403
+ function createHandler(routerDef) {
404
+ const compiledRoutes = compileRoutes(routerDef);
405
+ return async (request, env, ctx) => {
406
+ const url = new URL(request.url);
407
+ const method = request.method.toUpperCase();
408
+ const isHead = method === "HEAD";
409
+ for (const compiled of compiledRoutes) {
410
+ if (!(compiled.method === method || method === "OPTIONS" || method === "HEAD" && compiled.method === "GET")) continue;
411
+ const match = compiled.pattern.exec(url.pathname);
412
+ if (!match) continue;
413
+ const path = {};
414
+ for (let i = 0; i < compiled.paramNames.length; i++) path[compiled.paramNames[i]] = decodeURIComponent(match[i + 1]);
415
+ const context = {
416
+ request,
417
+ path,
418
+ query: {},
419
+ body: {},
420
+ env,
421
+ executionCtx: ctx
422
+ };
423
+ if (compiled.middleware.length > 0 || compiled.handler || compiled.querySchema || compiled.bodySchema) return stripBodyForHead(await runMiddleware(compiled.middleware, context, async () => {
424
+ const routeId = `${compiled.method} ${url.pathname}`;
425
+ let query = {};
426
+ if (compiled.querySchema) {
427
+ const raw = Object.fromEntries(url.searchParams);
428
+ const result = compiled.querySchema.safeParse(raw);
429
+ if (!result.success) return Response.json({
430
+ error: "Invalid query parameters",
431
+ route: routeId,
432
+ details: result.error.flatten()
433
+ }, { status: 400 });
434
+ query = result.data;
435
+ }
436
+ let body = {};
437
+ if (compiled.bodySchema && [
438
+ "POST",
439
+ "PUT",
440
+ "PATCH"
441
+ ].includes(method)) {
442
+ const raw = await request.json().catch(() => ({}));
443
+ const result = compiled.bodySchema.safeParse(raw);
444
+ if (!result.success) return Response.json({
445
+ error: "Invalid request body",
446
+ route: routeId,
447
+ details: result.error.flatten()
448
+ }, { status: 400 });
449
+ body = result.data;
450
+ }
451
+ context.query = query;
452
+ context.body = body;
453
+ if (compiled.handler) {
454
+ const handlerContext = {
455
+ request,
456
+ path,
457
+ query,
458
+ body,
459
+ env,
460
+ executionCtx: ctx
461
+ };
462
+ for (const key of Object.keys(context)) if (![
463
+ "request",
464
+ "path",
465
+ "query",
466
+ "body",
467
+ "env",
468
+ "executionCtx"
469
+ ].includes(key)) handlerContext[key] = context[key];
470
+ const result = await compiled.handler(handlerContext);
471
+ if (result instanceof Response) return result;
472
+ return Response.json(result);
473
+ }
474
+ return Response.json({});
475
+ }), isHead);
476
+ return stripBodyForHead(Response.json({}), isHead);
477
+ }
478
+ return Response.json({ error: "Not Found" }, { status: 404 });
479
+ };
480
+ }
481
+
482
+ //#endregion
483
+ //#region src/core.ts
484
+ /**
485
+ * @fileoverview Core route and router creation functions.
486
+ *
487
+ * ## Path Convention
488
+ * - Property names = URL path segments
489
+ * - `$param` prefix = dynamic segment (`:param`)
490
+ * - `get`, `post`, `put`, `patch`, `delete` = HTTP method handlers
491
+ *
492
+ * ## Typing Best Practices
493
+ *
494
+ * **Schemas** (`query`/`body`) provide runtime validation AND type inference.
495
+ * Use them for any input that needs validation:
496
+ * ```typescript
497
+ * get: route({
498
+ * query: { limit: 'number?' },
499
+ * handler: async (c) => c.query.limit, // limit: number | undefined
500
+ * })
501
+ * ```
502
+ *
503
+ * **Context** (`route.ctx<T>()`) provides types only (no validation).
504
+ * Use it for path params, env bindings, and middleware-injected values:
505
+ * ```typescript
506
+ * interface MyContext {
507
+ * path: { id: string };
508
+ * env: { DB: Database };
509
+ * user: { name: string }; // from auth middleware
510
+ * }
511
+ * get: route.ctx<MyContext>()({
512
+ * handler: async (c) => ({ id: c.path.id, user: c.user.name }),
513
+ * })
514
+ * ```
515
+ *
516
+ * **Important:** Don't add explicit type annotations to handler parameters.
517
+ * Let types flow from schemas and context:
518
+ * ```typescript
519
+ * // GOOD: types inferred from schema
520
+ * handler: async (c) => c.query.limit
521
+ *
522
+ * // BAD: redundant type annotation
523
+ * handler: async (c: { query: { limit?: number } }) => c.query.limit
524
+ * ```
525
+ */
526
+ /**
527
+ * Creates a route with query/body validation schemas.
528
+ *
529
+ * Use `route()` when you need request validation. For simple handlers
530
+ * without validation, you can use bare functions directly in the router.
531
+ *
532
+ * @example
533
+ * ```typescript
534
+ * // Route with query validation
535
+ * get: route({
536
+ * query: { limit: 'number?', offset: 'number?' },
537
+ * handler: async (c) => {
538
+ * const items = await db.list(c.query.limit, c.query.offset);
539
+ * return { items };
540
+ * },
541
+ * })
542
+ *
543
+ * // Route with body validation
544
+ * post: route({
545
+ * body: { name: 'string', email: 'string' },
546
+ * handler: async (c) => {
547
+ * const user = await db.create(c.body);
548
+ * return user;
549
+ * },
550
+ * })
551
+ *
552
+ * // Simple handler without validation (bare function)
553
+ * get: async () => ({ status: 'ok' })
554
+ * ```
555
+ */
556
+ function route(definition) {
557
+ return {
558
+ ...definition,
559
+ [ROUTE_MARKER]: true
560
+ };
561
+ }
562
+ /** Creates a chainable context builder. */
563
+ function createCtxBuilder() {
564
+ const builder = (definition) => ({
565
+ ...definition,
566
+ [ROUTE_MARKER]: true
567
+ });
568
+ builder.ctx = () => createCtxBuilder();
569
+ return builder;
570
+ }
571
+ /**
572
+ * Creates a route with typed context for values not covered by schemas.
573
+ *
574
+ * Use `route.ctx<T>()` to type:
575
+ * - **Path params**: `{ path: { id: string } }` for `$id` segments
576
+ * - **Env bindings**: `{ env: { DB: Database } }` for runtime environment
577
+ * - **Middleware values**: `{ user: User }` for auth middleware, etc.
578
+ *
579
+ * Context provides types only, not runtime validation. For validated inputs,
580
+ * use `query` and `body` schemas instead.
581
+ *
582
+ * @example
583
+ * ```typescript
584
+ * interface AppContext {
585
+ * path: { id: string };
586
+ * env: { DB: D1Database };
587
+ * user: { id: string }; // injected by auth middleware
588
+ * }
589
+ *
590
+ * $id: router({
591
+ * get: route.ctx<AppContext>()({
592
+ * query: { include: 'string?' }, // schema for validation
593
+ * handler: async (c) => {
594
+ * // c.path.id, c.env.DB, c.user.id - from context
595
+ * // c.query.include - from schema
596
+ * return c.env.DB.get(c.path.id);
597
+ * },
598
+ * }),
599
+ * })
600
+ * ```
601
+ */
602
+ route.ctx = () => createCtxBuilder();
603
+ /**
604
+ * Creates a router that groups paths and applies middleware.
605
+ *
606
+ * @example
607
+ * ```typescript
608
+ * const api = router({
609
+ * health: router({
610
+ * get: async () => ({ status: 'ok' }),
611
+ * }),
612
+ *
613
+ * users: router({
614
+ * // GET /users - list users
615
+ * get: route({
616
+ * query: { limit: 'number?' },
617
+ * handler: async (c) => db.users.list(c.query.limit),
618
+ * }),
619
+ *
620
+ * // POST /users - create user
621
+ * post: route({
622
+ * body: { name: 'string', email: 'string' },
623
+ * handler: async (c) => db.users.create(c.body),
624
+ * }),
625
+ *
626
+ * // /users/:id
627
+ * $id: router({
628
+ * get: async (c) => db.users.get(c.path.id),
629
+ * delete: async (c) => db.users.delete(c.path.id),
630
+ * }),
631
+ * }),
632
+ * });
633
+ *
634
+ * export default { fetch: api.handler() };
635
+ * ```
636
+ */
637
+ function router(routes, ...middleware) {
638
+ const self = {
639
+ routes,
640
+ middleware: middleware.length > 0 ? middleware : void 0,
641
+ handler() {
642
+ return createHandler(self);
643
+ }
644
+ };
645
+ self[ROUTER_MARKER] = true;
646
+ return self;
647
+ }
648
+
649
+ //#endregion
650
+ //#region src/docs.ts
651
+ /**
652
+ * @fileoverview OpenAPI documentation generation.
653
+ *
654
+ * Generates OpenAPI 3.0 documentation from router definitions.
655
+ *
656
+ * **Path Convention:**
657
+ * - Property names = URL path segments
658
+ * - `$param` prefix = dynamic segment (`{param}` in OpenAPI)
659
+ * - `get`, `post`, `put`, `patch`, `delete` = HTTP method handlers
660
+ */
661
+ /** Converts property name to path segment, handling $param convention. */
662
+ function propertyToSegment(prop) {
663
+ if (prop.startsWith("$")) return `{${prop.slice(1)}}`;
664
+ return prop;
665
+ }
666
+ /** Collects all routes from a router tree, with `{param}` path segments. */
667
+ function collectRoutes(routerDef, parentPath = "") {
668
+ const collectedRoutes = [];
669
+ for (const [prop, entry] of Object.entries(routerDef.routes)) if (HTTP_METHODS.has(prop)) {
670
+ const path = parentPath || "/";
671
+ if (isFunction(entry)) collectedRoutes.push({
672
+ path,
673
+ method: prop,
674
+ route: null
675
+ });
676
+ else if (isRoute(entry)) collectedRoutes.push({
677
+ path,
678
+ method: prop,
679
+ route: entry
680
+ });
681
+ } else if (isRouter(entry)) {
682
+ const segment = propertyToSegment(prop);
683
+ const newPath = parentPath ? `${parentPath}/${segment}` : `/${segment}`;
684
+ collectedRoutes.push(...collectRoutes(entry, newPath));
685
+ }
686
+ return collectedRoutes;
687
+ }
688
+ /** Generates OpenAPI documentation from a router. */
689
+ function generateDocs(config) {
690
+ const paths = {};
691
+ const allRoutes = collectRoutes(config.router);
692
+ for (const { path, method, route: route$1 } of allRoutes) {
693
+ if (!paths[path]) paths[path] = {};
694
+ const operation = {
695
+ operationId: route$1?.operationId,
696
+ description: route$1?.description,
697
+ responses: {
698
+ 200: { description: "Successful response" },
699
+ 400: { description: "Validation error" }
700
+ }
701
+ };
702
+ if (route$1?.query) {
703
+ const querySchema = route$1.query;
704
+ operation.parameters = Object.entries(querySchema).filter(([, field]) => !isNestedSchema(field)).map(([name, field]) => {
705
+ const [type, description] = unpackField(field);
706
+ return {
707
+ name,
708
+ in: "query",
709
+ required: !type.endsWith("?"),
710
+ description,
711
+ schema: { type: type.replace("?", "").replace("[]", "") }
712
+ };
713
+ });
714
+ }
715
+ if (route$1?.body && [
716
+ "post",
717
+ "put",
718
+ "patch"
719
+ ].includes(method)) operation.requestBody = {
720
+ required: true,
721
+ content: { "application/json": { schema: toJsonSchema(route$1.body) } }
722
+ };
723
+ paths[path][method] = operation;
724
+ }
725
+ return {
726
+ openapi: "3.0.0",
727
+ info: {
728
+ title: config.title,
729
+ version: config.version,
730
+ description: config.description
731
+ },
732
+ paths
733
+ };
734
+ }
735
+
736
+ //#endregion
737
+ export { isFunction as a, compileSchema as c, unpackField as d, router as i, isNestedSchema as l, generateDocs as n, isRoute as o, route as r, isRouter as s, collectRoutes as t, toJsonSchema as u };
738
+ //# sourceMappingURL=docs-C77XQzF3.mjs.map