@fresho/router 0.3.2 → 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 +20 -1
- package/dist/index.d.mts +15 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +41 -10
- package/dist/index.mjs.map +1 -1
- package/dist/middleware/index.d.mts +50 -1
- package/dist/middleware/index.d.mts.map +1 -1
- package/dist/middleware/index.mjs +82 -1
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/middleware-Bxq0LHLS.d.mts.map +1 -1
- package/docs/middleware.md +43 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -210,7 +210,7 @@ const api = router(
|
|
|
210
210
|
);
|
|
211
211
|
```
|
|
212
212
|
|
|
213
|
-
Built-in middleware: `cors`, `errorHandler`, `logger`, `rateLimit`, `requestId`, `timeout`, `basicAuth`, `bearerAuth`, `jwtAuth`, `contentType`.
|
|
213
|
+
Built-in middleware: `cors`, `errorHandler`, `logger`, `rateLimit`, `requestId`, `timeout`, `basicAuth`, `bearerAuth`, `jwtAuth`, `contentType`, `cookies`.
|
|
214
214
|
|
|
215
215
|
See **[Middleware Documentation](docs/middleware.md)** for detailed usage.
|
|
216
216
|
|
|
@@ -295,6 +295,25 @@ client.api.get.$get(); // GET /api/get (navigate to "get", then execute GET)
|
|
|
295
295
|
|
|
296
296
|
This allows routes with path segments named after HTTP methods (e.g., `/api/get`, `/resources/delete`).
|
|
297
297
|
|
|
298
|
+
### Building URLs Without Fetching
|
|
299
|
+
|
|
300
|
+
Use `$url` to derive a URL string from the typed routes without firing a request. It accepts the same `path`/`query` options as a real call, so path params stay type-checked:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// Absolute URL when baseUrl is configured:
|
|
304
|
+
client.media.playlist.$url({ query: { channel: "classics", chunks: 1 } });
|
|
305
|
+
// → "https://api.example.com/media/playlist?channel=classics&chunks=1"
|
|
306
|
+
|
|
307
|
+
client.users.$id.$url({ path: { id: "123" } });
|
|
308
|
+
// → "https://api.example.com/users/123"
|
|
309
|
+
|
|
310
|
+
// Relative URL (pathname + search) when no baseUrl is set:
|
|
311
|
+
createHttpClient<typeof api>({}).users.$id.$url({ path: { id: "123" } });
|
|
312
|
+
// → "/users/123"
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
This keeps route paths as the single source of truth for URLs embedded in response bodies, deep links, or server-side URL generation — cases where `raw: true` can't help, since `Response.url` only exists after a request runs.
|
|
316
|
+
|
|
298
317
|
## Local Client
|
|
299
318
|
|
|
300
319
|
Test handlers directly without HTTP overhead:
|
package/dist/index.d.mts
CHANGED
|
@@ -139,8 +139,22 @@ type ImplicitGetCall<T extends RouterRoutes, Extra, Path extends string[] = []>
|
|
|
139
139
|
} & {
|
|
140
140
|
query?: Record<string, unknown>;
|
|
141
141
|
} & Extra) => Promise<ExtractReturn<ExtractMethod<T['get']>>> : (options?: RequestOptions<Extra>) => Promise<ExtractReturn<ExtractMethod<T['get']>>> : (options?: RequestOptions<Extra>) => Promise<unknown> : (options?: RequestOptions<Extra>) => Promise<unknown>;
|
|
142
|
+
/**
|
|
143
|
+
* Builds a URL string from the route path without executing a request.
|
|
144
|
+
*
|
|
145
|
+
* Path params are required when the path contains `$param` segments. Query
|
|
146
|
+
* params are always optional and untyped, since `$url` is method-agnostic.
|
|
147
|
+
*/
|
|
148
|
+
type UrlMethod<Path extends string[]> = HasParams<Path> extends true ? (options: {
|
|
149
|
+
path: Record<string, string>;
|
|
150
|
+
query?: Record<string, unknown>;
|
|
151
|
+
}) => string : (options?: {
|
|
152
|
+
query?: Record<string, unknown>;
|
|
153
|
+
}) => string;
|
|
142
154
|
/** Client type for a router. */
|
|
143
|
-
type RouterClient<T extends RouterRoutes, Extra, Path extends string[] = []> = {
|
|
155
|
+
type RouterClient<T extends RouterRoutes, Extra, Path extends string[] = []> = {
|
|
156
|
+
$url: UrlMethod<Path>;
|
|
157
|
+
} & { [K in keyof RemoveIndex<T> as K extends LowercaseMethods ? PrefixedMethod<K> : never]: ExtractMethod<T[K]> extends infer M ? M extends RouteDefinition<any, any, any, any, any> | ((...args: unknown[]) => unknown) ? MethodClient<M, Extra, HasParams<Path>> : never : never } & { [K in keyof RemoveIndex<T>]: IsAny<T[K]> extends true ? never : Extract<T[K], Router<any>> extends infer R ? R extends Router<infer Routes> ? RouterClient<Routes, Extra, [...Path, K & string]> & ImplicitGetCall<Routes, Extra, [...Path, K & string]> : never : never } & ImplicitGetCall<T, Extra, Path>;
|
|
144
158
|
//#endregion
|
|
145
159
|
//#region src/http-client.d.ts
|
|
146
160
|
/** HTTP client configuration. */
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/core.ts","../src/docs.ts","../src/client-types.ts","../src/http-client.ts","../src/local-client.ts","../src/streaming.ts"],"sourcesContent":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/core.ts","../src/docs.ts","../src/client-types.ts","../src/http-client.ts","../src/local-client.ts","../src/streaming.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -706,6 +706,7 @@ function createRecursiveProxy(config, segments = []) {
|
|
|
706
706
|
get(_target, prop) {
|
|
707
707
|
if (typeof prop !== "string") return void 0;
|
|
708
708
|
if (prop === "apply" || prop === "call" || prop === "bind" || prop === "toString" || prop === "toJSON") return _target[prop];
|
|
709
|
+
if (prop === "$url" && config.onBuildUrl) return (options) => config.onBuildUrl(segments, options);
|
|
709
710
|
if (PREFIXED_HTTP_METHODS.has(prop)) {
|
|
710
711
|
const method = prop.slice(1);
|
|
711
712
|
return (options) => {
|
|
@@ -801,9 +802,12 @@ function createHttpClient(config = {}) {
|
|
|
801
802
|
const client = { configure(newConfig) {
|
|
802
803
|
Object.assign(sharedConfig.current, newConfig);
|
|
803
804
|
} };
|
|
804
|
-
const proxy = createRecursiveProxy({
|
|
805
|
-
|
|
806
|
-
|
|
805
|
+
const proxy = createRecursiveProxy({
|
|
806
|
+
onRequest: (segments, method, options) => {
|
|
807
|
+
return executeRequest(sharedConfig, segments, method, options);
|
|
808
|
+
},
|
|
809
|
+
onBuildUrl: (segments, options) => buildUrl(sharedConfig.current, segments, options)
|
|
810
|
+
});
|
|
807
811
|
return new Proxy(client, {
|
|
808
812
|
get(target, prop) {
|
|
809
813
|
if (prop === "configure") return target.configure;
|
|
@@ -825,15 +829,24 @@ function buildPath(segments, pathParams) {
|
|
|
825
829
|
} else parts.push(segment);
|
|
826
830
|
return `/${parts.join("/")}`;
|
|
827
831
|
}
|
|
828
|
-
/**
|
|
829
|
-
|
|
830
|
-
|
|
832
|
+
/**
|
|
833
|
+
* Builds a URL string from path segments and options without executing a request.
|
|
834
|
+
*
|
|
835
|
+
* Returns an absolute URL when a `baseUrl` is configured, otherwise a relative
|
|
836
|
+
* `pathname + search` string suitable for same-origin fetches.
|
|
837
|
+
*/
|
|
838
|
+
function buildUrl(config, segments, options) {
|
|
831
839
|
const path = buildPath(segments, options?.path);
|
|
832
840
|
const baseUrl = config.baseUrl || (typeof window !== "undefined" ? window.location?.origin : "");
|
|
833
841
|
const url = new URL(path, baseUrl || "http://localhost");
|
|
834
842
|
if (options?.query) {
|
|
835
843
|
for (const [key, value] of Object.entries(options.query)) if (value !== void 0) url.searchParams.set(key, String(value));
|
|
836
844
|
}
|
|
845
|
+
return config.baseUrl ? url.toString() : url.pathname + url.search;
|
|
846
|
+
}
|
|
847
|
+
/** Executes an HTTP request. */
|
|
848
|
+
async function executeRequest(sharedConfig, segments, method, options) {
|
|
849
|
+
const config = sharedConfig.current;
|
|
837
850
|
const headers = new Headers();
|
|
838
851
|
if (config.headers) for (const [key, value] of Object.entries(config.headers)) {
|
|
839
852
|
const resolved = typeof value === "function" ? await value() : value;
|
|
@@ -849,7 +862,7 @@ async function executeRequest(sharedConfig, segments, method, options) {
|
|
|
849
862
|
headers.set("Content-Type", "application/json");
|
|
850
863
|
init.body = JSON.stringify(options.body);
|
|
851
864
|
}
|
|
852
|
-
const fetchUrl = config
|
|
865
|
+
const fetchUrl = buildUrl(config, segments, options);
|
|
853
866
|
const response = await (config.fetch || fetch)(fetchUrl, init);
|
|
854
867
|
if (!response.ok) throw new Error(await response.text() || response.statusText);
|
|
855
868
|
if (options?.raw) return response;
|
|
@@ -938,9 +951,12 @@ function createLocalClient(routerDef) {
|
|
|
938
951
|
const client = { configure(config) {
|
|
939
952
|
Object.assign(sharedConfig.current, config);
|
|
940
953
|
} };
|
|
941
|
-
const proxy = createRecursiveProxy({
|
|
942
|
-
|
|
943
|
-
|
|
954
|
+
const proxy = createRecursiveProxy({
|
|
955
|
+
onRequest: (segments, method, options) => {
|
|
956
|
+
return invokeHandler(sharedConfig, routerDef, segments, method, options);
|
|
957
|
+
},
|
|
958
|
+
onBuildUrl: (segments, options) => buildLocalUrl(segments, options)
|
|
959
|
+
});
|
|
944
960
|
return new Proxy(client, {
|
|
945
961
|
get(target, prop) {
|
|
946
962
|
if (prop === "configure") return target.configure;
|
|
@@ -951,6 +967,21 @@ function createLocalClient(routerDef) {
|
|
|
951
967
|
}
|
|
952
968
|
});
|
|
953
969
|
}
|
|
970
|
+
/**
|
|
971
|
+
* Builds a relative URL string from path segments and options.
|
|
972
|
+
*
|
|
973
|
+
* The local client has no baseUrl, so it returns a relative `pathname + search`
|
|
974
|
+
* string. Mirrors the synthetic URL construction used in {@link invokeHandler}.
|
|
975
|
+
*/
|
|
976
|
+
function buildLocalUrl(segments, options) {
|
|
977
|
+
const pathParams = collectPathParams(segments, options?.path);
|
|
978
|
+
const pathParts = segments.map((s) => s.startsWith("$") ? encodeURIComponent(pathParams[s.slice(1)]) : s);
|
|
979
|
+
const url = new URL(`/${pathParts.join("/")}`, "http://localhost");
|
|
980
|
+
if (options?.query) {
|
|
981
|
+
for (const [key, value] of Object.entries(options.query)) if (value !== void 0) url.searchParams.set(key, String(value));
|
|
982
|
+
}
|
|
983
|
+
return url.pathname + url.search;
|
|
984
|
+
}
|
|
954
985
|
/** Invokes a handler directly. */
|
|
955
986
|
async function invokeHandler(sharedConfig, routerDef, segments, method, options) {
|
|
956
987
|
const config = sharedConfig.current;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["errors: Record<string, string[]>","resultArray: unknown[]","success","coerced","errorMsg","result: Record<string, unknown>","allErrors: Record<string, string[]>","paramNames: string[]","propertyToSegment","compiled: CompiledRoute[]","path: Record<string, string>","context: MiddlewareContext","query: unknown","body: unknown","collectedRoutes: CollectedRoute[]","paths: Record<string, Record<string, object>>","operation: Record<string, unknown>","route","sharedConfig: SharedConfig","parts: string[]","init: RequestInit","current: Router<RouterRoutes> | undefined","entry: unknown","entry","params: Record<string, string>","sharedConfig: SharedConfig","query: unknown","result","body: unknown","requestInit: RequestInit","lines: string[]","keepAliveTimer: ReturnType<typeof setInterval> | undefined"],"sources":["../src/schema.ts","../src/types.ts","../src/handler.ts","../src/core.ts","../src/docs.ts","../src/client-proxy.ts","../src/http-client.ts","../src/local-client.ts","../src/streaming.ts"],"sourcesContent":["/**\n * @fileoverview Schema types and validation utilities.\n *\n * Provides shorthand schema definitions for validating query parameters and request bodies.\n * No external dependencies - uses simple string-based type definitions.\n *\n * Supported types:\n * - Primitives: `'string'`, `'number'`, `'boolean'`\n * - Optional: `'string?'`, `'number?'`, `'boolean?'`\n * - Arrays: `'string[]'`, `'number[]'`, `'boolean[]'`\n * - Optional arrays: `'string[]?'`, `'number[]?'`, `'boolean[]?'`\n * - Nested objects: `{ field: { nested: 'string' } }`\n *\n * @example\n * ```typescript\n * // In route definitions\n * const api = router({\n * users: router({\n * post: route({\n * query: { page: 'number?', limit: 'number?' },\n * body: {\n * name: 'string',\n * age: 'number',\n * tags: 'string[]?',\n * address: { city: 'string', zip: 'string?' },\n * },\n * handler: async (c) => {\n * c.query.page; // number | undefined\n * c.body.name; // string\n * c.body.address.city // string\n * },\n * }),\n * }),\n * });\n * ```\n */\n\n/** Primitive type strings. */\nexport type PrimitiveType = 'string' | 'number' | 'boolean' | 'unknown';\n\n/** Optional primitive type strings. */\nexport type OptionalPrimitiveType = 'string?' | 'number?' | 'boolean?' | 'unknown?';\n\n/** Array type strings. */\nexport type ArrayType = 'string[]' | 'number[]' | 'boolean[]' | 'unknown[]';\n\n/** Optional array type strings. */\nexport type OptionalArrayType = 'string[]?' | 'number[]?' | 'boolean[]?' | 'unknown[]?';\n\n/** All supported type strings. */\nexport type SchemaType = PrimitiveType | OptionalPrimitiveType | ArrayType | OptionalArrayType;\n\n/** Schema definition using shorthand syntax. Supports nested objects. */\nexport interface SchemaDefinition {\n [key: string]: SchemaType | SchemaDefinition;\n}\n\n/** Maps primitive type strings to TypeScript types. */\nexport type SchemaTypeMap = {\n string: string;\n 'string?': string | undefined;\n number: number;\n 'number?': number | undefined;\n boolean: boolean;\n 'boolean?': boolean | undefined;\n unknown: unknown;\n 'unknown?': unknown | undefined;\n 'string[]': string[];\n 'string[]?': string[] | undefined;\n 'number[]': number[];\n 'number[]?': number[] | undefined;\n 'boolean[]': boolean[];\n 'boolean[]?': boolean[] | undefined;\n 'unknown[]': unknown[];\n 'unknown[]?': unknown[] | undefined;\n};\n\n/** Checks if a schema field type is optional (ends with ?). */\ntype IsOptionalField<T> = T extends `${string}?` ? true : false;\n\n/** Infers TypeScript type from a schema field (type string or nested object). */\nexport type InferSchemaField<T> = T extends SchemaType\n ? SchemaTypeMap[T]\n : T extends SchemaDefinition\n ? InferSchema<T>\n : never;\n\n/** Gets keys of required fields (non-optional types). */\ntype RequiredKeys<T extends SchemaDefinition> = {\n [K in keyof T]: IsOptionalField<T[K]> extends true ? never : K;\n}[keyof T];\n\n/** Gets keys of optional fields (types ending with ?). */\ntype OptionalKeys<T extends SchemaDefinition> = {\n [K in keyof T]: IsOptionalField<T[K]> extends true ? K : never;\n}[keyof T];\n\n/**\n * Infers TypeScript type from schema definition.\n *\n * Required fields become required properties, optional fields (ending with ?)\n * become optional properties with `| undefined`.\n */\nexport type InferSchema<T extends SchemaDefinition> = {\n [K in RequiredKeys<T>]: InferSchemaField<T[K]>;\n} & {\n [K in OptionalKeys<T>]?: InferSchemaField<T[K]>;\n};\n\n/** Error object returned when validation fails. */\nexport interface ValidationError {\n flatten: () => { fieldErrors: Record<string, string[]> };\n}\n\n/** Validation result matching Zod's safeParse API for compatibility. */\nexport type ValidationResult<T> =\n | { success: true; data: T; error?: undefined }\n | { success: false; data?: undefined; error: ValidationError };\n\n/** Compiled schema with validate method. */\nexport interface CompiledSchema<T> {\n safeParse: (data: object) => ValidationResult<T>;\n}\n\n/** Valid base types for primitives and arrays. */\nconst PRIMITIVE_TYPES = ['string', 'number', 'boolean', 'unknown'];\n\n/** Checks if a schema field is a nested object (not a type string). */\nfunction isNestedSchema(field: SchemaType | SchemaDefinition): field is SchemaDefinition {\n return typeof field === 'object' && field !== null;\n}\n\n/**\n * Validates and coerces a single primitive value.\n * Returns [success, result, errorMessage].\n */\nfunction validatePrimitive(value: unknown, baseType: string): [boolean, unknown, string | null] {\n switch (baseType) {\n case 'string':\n return [true, String(value), null];\n\n case 'number': {\n const num = Number(value);\n if (Number.isNaN(num)) {\n return [false, undefined, 'Expected number'];\n }\n return [true, num, null];\n }\n\n case 'boolean': {\n if (value === 'true' || value === '1' || value === true) {\n return [true, true, null];\n } else if (value === 'false' || value === '0' || value === false) {\n return [true, false, null];\n }\n return [false, undefined, 'Expected boolean (true/false)'];\n }\n\n case 'unknown':\n // Pass through any value without validation.\n return [true, value, null];\n\n default:\n return [false, undefined, `Unknown type: ${baseType}`];\n }\n}\n\n/**\n * Validates a schema field value (primitive, array, or nested object).\n * Returns [success, result, errors].\n */\nfunction validateField(\n key: string,\n field: SchemaType | SchemaDefinition,\n value: unknown,\n parentPath: string = '',\n): [boolean, unknown, Record<string, string[]>] {\n const fieldPath = parentPath ? `${parentPath}.${key}` : key;\n const errors: Record<string, string[]> = {};\n\n // Handle nested object schemas.\n if (isNestedSchema(field)) {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n errors[fieldPath] = ['Expected object'];\n return [false, undefined, errors];\n }\n // Recursively validate nested schema.\n const nestedResult = validateSchema(field, value as Record<string, unknown>, fieldPath);\n return [nestedResult.success, nestedResult.data, nestedResult.errors];\n }\n\n // It's a type string.\n const typeStr = field as string;\n const isOptional = typeStr.endsWith('?');\n const isArray = typeStr.includes('[]');\n\n // Strip optional marker for base type extraction.\n let baseType = isOptional ? typeStr.slice(0, -1) : typeStr;\n\n // Strip array marker.\n if (isArray) {\n baseType = baseType.replace('[]', '');\n }\n\n // Handle missing/empty values.\n if (value === undefined || value === '') {\n if (!isOptional) {\n errors[fieldPath] = ['Required'];\n return [false, undefined, errors];\n }\n return [true, undefined, {}];\n }\n\n // Handle array types.\n if (isArray) {\n if (!Array.isArray(value)) {\n errors[fieldPath] = ['Expected array'];\n return [false, undefined, errors];\n }\n\n const resultArray: unknown[] = [];\n for (let i = 0; i < value.length; i++) {\n const [success, coerced, errorMsg] = validatePrimitive(value[i], baseType);\n if (!success) {\n errors[`${fieldPath}[${i}]`] = [errorMsg!];\n return [false, undefined, errors];\n }\n resultArray.push(coerced);\n }\n return [true, resultArray, {}];\n }\n\n // Handle primitive types.\n const [success, coerced, errorMsg] = validatePrimitive(value, baseType);\n if (!success) {\n errors[fieldPath] = [errorMsg!];\n return [false, undefined, errors];\n }\n return [true, coerced, {}];\n}\n\n/**\n * Validates a full schema against data.\n * Returns { success, data, errors }.\n */\nfunction validateSchema(\n schema: SchemaDefinition,\n data: Record<string, unknown>,\n parentPath: string = '',\n): { success: boolean; data: Record<string, unknown>; errors: Record<string, string[]> } {\n const result: Record<string, unknown> = {};\n const allErrors: Record<string, string[]> = {};\n\n for (const [key, field] of Object.entries(schema)) {\n const value = data[key];\n const [success, coerced, fieldErrors] = validateField(key, field, value, parentPath);\n\n if (!success) {\n Object.assign(allErrors, fieldErrors);\n } else if (coerced !== undefined) {\n result[key] = coerced;\n }\n }\n\n return {\n success: Object.keys(allErrors).length === 0,\n data: result,\n errors: allErrors,\n };\n}\n\n/** Validates schema definition at compile time. */\nfunction validateSchemaDefinition(schema: SchemaDefinition, path: string = ''): void {\n for (const [key, field] of Object.entries(schema)) {\n const fieldPath = path ? `${path}.${key}` : key;\n\n if (isNestedSchema(field)) {\n validateSchemaDefinition(field, fieldPath);\n } else {\n const typeStr = field as string;\n const isOptional = typeStr.endsWith('?');\n let baseType = isOptional ? typeStr.slice(0, -1) : typeStr;\n baseType = baseType.replace('[]', '');\n\n if (!PRIMITIVE_TYPES.includes(baseType)) {\n throw new Error(`Unknown type '${typeStr}' at '${fieldPath}'`);\n }\n }\n }\n}\n\n/**\n * Compiles a schema definition into a validator.\n *\n * Used internally by the router to validate query parameters and request bodies.\n * Provides a Zod-compatible `safeParse` API for consistency.\n *\n * @param schema - The schema definition object\n * @returns A compiled schema with `safeParse` method\n *\n * @example\n * ```typescript\n * const userSchema = compileSchema({\n * name: 'string',\n * age: 'number',\n * email: 'string?',\n * });\n *\n * const result = userSchema.safeParse({ name: 'Alice', age: 30 });\n * if (result.success) {\n * console.log(result.data.name); // 'Alice'\n * } else {\n * console.log(result.error.flatten().fieldErrors);\n * }\n * ```\n */\nexport function compileSchema<T extends SchemaDefinition>(\n schema: T,\n): CompiledSchema<InferSchema<T>> {\n // Validate schema structure at compile time.\n validateSchemaDefinition(schema);\n\n return {\n safeParse(data: object): ValidationResult<InferSchema<T>> {\n const {\n success,\n data: result,\n errors,\n } = validateSchema(schema, data as Record<string, unknown>);\n\n if (!success) {\n return {\n success: false,\n error: { flatten: () => ({ fieldErrors: errors }) },\n };\n }\n\n return { success: true, data: result as InferSchema<T> };\n },\n };\n}\n","/**\n * @fileoverview Core type definitions for the routing library.\n *\n * **Path Convention:**\n * - Property names = URL path segments\n * - `$param` prefix = dynamic segment (`:param`)\n * - `get`, `post`, `put`, `patch`, `delete` = HTTP method handlers\n *\n * **Structure:**\n * - `router({ ... })` - groups paths, can have middleware\n * - `route({ query?, body?, handler })` - single method with schemas\n * - Bare functions as shorthand for handlers without schemas\n */\n\nimport type { Middleware } from './middleware.js';\nimport type { InferSchema, SchemaDefinition } from './schema.js';\n\n/** Standard HTTP methods. */\nexport type Method = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'options' | 'head';\n\n/** HTTP method property names. */\nexport const HTTP_METHODS = new Set<string>([\n 'get',\n 'post',\n 'put',\n 'patch',\n 'delete',\n 'options',\n 'head',\n]);\n\n/** Symbol to mark an object as a route definition. */\nexport const ROUTE_MARKER = Symbol.for('@fresho/router:route');\n\n/** Symbol to mark an object as a router. */\nexport const ROUTER_MARKER = Symbol.for('@fresho/router:router');\n\n/**\n * Brand symbol for Router type identification across module boundaries.\n * This enables TypeScript to recognize Router types even when generic\n * type information is lost during cross-package imports.\n */\ndeclare const ROUTER_BRAND: unique symbol;\n\n/** Brand type for Router identification in conditional types. */\nexport type RouterBrand = { readonly [ROUTER_BRAND]: true };\n\n/**\n * Extracts the parameter name from a `$param` property name.\n */\nexport type ExtractParamFromProperty<T extends string> = T extends `$${infer Param}`\n ? Param\n : never;\n\n/**\n * Checks if a property name is a dynamic parameter (starts with $).\n */\nexport type IsParamProperty<T extends string> = T extends `$${string}` ? true : false;\n\n/**\n * Collects path params from a property path array.\n */\nexport type CollectPathParams<Path extends string[]> = Path extends [\n infer Head extends string,\n ...infer Rest extends string[],\n]\n ? (Head extends `$${infer Param}` ? { [K in Param]: string } : {}) & CollectPathParams<Rest>\n : {};\n\n/** Execution context for background tasks (Cloudflare Workers compatible). */\nexport interface ExecutionContext {\n waitUntil(promise: Promise<unknown>): void;\n passThroughOnException(): void;\n}\n\n/**\n * Unified context object passed to handlers.\n */\nexport type Context<\n Q = unknown,\n B = unknown,\n P extends Record<string, string> = Record<string, string>,\n Ctx = {},\n> = {\n /** The original Request. */\n request: Request;\n /** URL path parameters from `$param` segments. */\n path: P;\n /** Query string parameters. */\n query: Q;\n /** Request body. */\n body: B;\n /** Execution context for background tasks. */\n executionCtx?: ExecutionContext;\n /** Environment bindings. */\n env: Ctx extends { env: infer E } ? E : unknown;\n} & Omit<Ctx, 'env'>;\n\n/**\n * Typed response wrapper that carries the response body type.\n */\nexport type TypedResponse<T> = Response & { __responseType?: T };\n\n/**\n * Handler function type.\n */\nexport type TypedHandler<\n Q = unknown,\n B = unknown,\n P extends Record<string, string> = Record<string, string>,\n R = unknown,\n Ctx = {},\n> = (\n context: Context<Q, B, P, Ctx>,\n) => R | Response | TypedResponse<R> | Promise<R | Response | TypedResponse<R>>;\n\n/**\n * Route definition - a single HTTP method handler with optional schemas.\n *\n * Use `route()` when you need query or body validation schemas.\n * For simple handlers without schemas, use bare functions in the router.\n *\n * @example\n * ```typescript\n * // With schemas\n * get: route({\n * query: { limit: 'number?' },\n * handler: async (c) => ({ items: [], limit: c.query.limit }),\n * })\n *\n * // Without schemas (bare function shorthand)\n * get: async (c) => ({ items: [] })\n * ```\n */\nexport interface RouteDefinition<\n Q extends SchemaDefinition = {},\n B extends SchemaDefinition = {},\n R = unknown,\n P extends Record<string, string> = Record<string, string>,\n Ctx = {},\n> {\n /** Query parameter schema for validation. */\n query?: Q;\n /** Request body schema for validation. */\n body?: B;\n /** Optional description for documentation. */\n description?: string;\n /** The request handler function. */\n handler: TypedHandler<InferSchema<Q>, InferSchema<B>, P, R, Ctx>;\n}\n\n/** A bare handler function (shorthand for route without schemas). */\nexport type BareHandler = (\n context: Context<unknown, unknown, Record<string, string>, unknown>,\n) => unknown;\n\n/** Checks if a value is a function. */\nexport function isFunction(value: unknown): value is BareHandler {\n return typeof value === 'function';\n}\n\n/** Checks if entry is a Route (marked with ROUTE_MARKER). */\nexport function isRoute(entry: unknown): entry is RouteDefinition {\n return typeof entry === 'object' && entry !== null && ROUTE_MARKER in entry;\n}\n\n/** Checks if entry is a Router (marked with ROUTER_MARKER). */\nexport function isRouter(entry: unknown): entry is Router<RouterRoutes> {\n return typeof entry === 'object' && entry !== null && ROUTER_MARKER in entry;\n}\n\n/**\n * A method handler - either a route with schemas or a bare function.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type MethodEntry = RouteDefinition<any, any, any, any, any> | BareHandler;\n\n/**\n * Router routes record.\n *\n * Can contain:\n * - `get`, `post`, `put`, `patch`, `delete` - method handlers (route or function)\n * - Other property names - nested routers (path segments)\n * - `$param` properties - dynamic path segments\n */\nexport type RouterRoutes = {\n get?: MethodEntry;\n post?: MethodEntry;\n put?: MethodEntry;\n patch?: MethodEntry;\n delete?: MethodEntry;\n options?: MethodEntry;\n head?: MethodEntry;\n} & {\n [key: string]: MethodEntry | Router<RouterRoutes>;\n};\n\n/** Handler function signature for the standalone router. */\nexport type FetchHandler = (\n request: Request,\n env?: unknown,\n ctx?: ExecutionContext,\n) => Response | Promise<Response>;\n\n/**\n * Router - groups paths and can have middleware.\n *\n * @example\n * ```typescript\n * const api = router({\n * // Method handlers for this path\n * get: async (c) => [...],\n * post: route({ body: { name: 'string' }, handler: async (c) => c.body }),\n *\n * // Nested path segments\n * users: router({\n * get: async (c) => [...],\n * $id: router({\n * get: async (c) => ({ id: c.path.id }),\n * }),\n * }),\n * });\n * ```\n */\nexport interface Router<T extends RouterRoutes> extends RouterBrand {\n /** Route definitions and nested routers. */\n readonly routes: T;\n /** Middleware applied to all routes in this router. */\n readonly middleware?: Middleware[];\n /**\n * Creates a fetch handler for use with Cloudflare Workers, Deno, Bun, etc.\n */\n handler(): FetchHandler;\n}\n","/**\n * @fileoverview HTTP request handler for @fresho/router.\n *\n * Builds paths from property names:\n * - Regular properties = path segments\n * - `$param` properties = dynamic segments (`:param`)\n * - `get`, `post`, etc. = method handlers\n */\n\nimport type { Middleware, MiddlewareContext } from './middleware.js';\nimport { runMiddleware } from './middleware.js';\nimport type { CompiledSchema } from './schema.js';\nimport { compileSchema } from './schema.js';\nimport type {\n ExecutionContext,\n FetchHandler,\n RouteDefinition,\n Router,\n RouterRoutes,\n} from './types.js';\nimport { HTTP_METHODS, isFunction, isRoute, isRouter } from './types.js';\n\n/** Compiled route with pattern matcher and handler. */\ninterface CompiledRoute {\n method: string;\n pattern: RegExp;\n paramNames: string[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (context: any) => unknown;\n middleware: Middleware[];\n querySchema?: CompiledSchema<unknown>;\n bodySchema?: CompiledSchema<unknown>;\n}\n\n/**\n * Escapes special regex characters in a string.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Converts a path with :param segments to a regex pattern.\n */\nfunction pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {\n const paramNames: string[] = [];\n const parts = path.split(/:(\\w+)/g);\n let regexStr = '';\n\n for (let i = 0; i < parts.length; i++) {\n if (i % 2 === 0) {\n regexStr += escapeRegex(parts[i]);\n } else {\n paramNames.push(parts[i]);\n regexStr += '([^/]+)';\n }\n }\n\n return {\n pattern: new RegExp(`^${regexStr}$`),\n paramNames,\n };\n}\n\n/**\n * Converts a property name to a path segment.\n * - `$param` becomes `:param`\n * - Other names become literal segments\n */\nfunction propertyToSegment(prop: string): string {\n if (prop.startsWith('$')) {\n return `:${prop.slice(1)}`;\n }\n return prop;\n}\n\n/** Collects and compiles all routes from a router tree. */\nfunction compileRoutes(\n routerDef: Router<RouterRoutes>,\n parentPath = '',\n parentMiddleware: Middleware[] = [],\n): CompiledRoute[] {\n const currentMiddleware = [...parentMiddleware, ...(routerDef.middleware || [])];\n const compiled: CompiledRoute[] = [];\n\n for (const [prop, entry] of Object.entries(routerDef.routes)) {\n // Check if entry is a nested router first (takes precedence over method name check).\n // This allows path segments named 'get', 'post', etc. when they contain routers.\n if (isRouter(entry)) {\n // Nested router - recurse with updated path.\n const segment = propertyToSegment(prop);\n const newPath = parentPath ? `${parentPath}/${segment}` : `/${segment}`;\n compiled.push(...compileRoutes(entry, newPath, currentMiddleware));\n } else if (HTTP_METHODS.has(prop)) {\n // Method handler (get, post, etc.) - only when entry is a route or function.\n const method = prop.toUpperCase();\n const path = parentPath || '/';\n const { pattern, paramNames } = pathToRegex(path);\n\n if (isFunction(entry)) {\n // Bare function handler.\n compiled.push({\n method,\n pattern,\n paramNames,\n handler: entry,\n middleware: currentMiddleware,\n });\n } else if (isRoute(entry)) {\n // Route with schemas.\n const routeDef = entry as RouteDefinition;\n compiled.push({\n method,\n pattern,\n paramNames,\n handler: routeDef.handler,\n middleware: currentMiddleware,\n querySchema: routeDef.query ? compileSchema(routeDef.query) : undefined,\n bodySchema: routeDef.body ? compileSchema(routeDef.body) : undefined,\n });\n }\n }\n }\n\n return compiled;\n}\n\n/**\n * Strips the body from a response for HEAD requests.\n */\nfunction stripBodyForHead(response: Response, isHead: boolean): Response {\n if (!isHead) return response;\n return new Response(null, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n}\n\n/**\n * Creates a fetch handler from a router definition.\n *\n * @example\n * ```typescript\n * const api = router({\n * health: router({\n * get: async () => ({ status: 'ok' }),\n * }),\n * users: router({\n * get: async () => [...],\n * $id: router({\n * get: async (c) => ({ id: c.path.id }),\n * }),\n * }),\n * });\n *\n * export default { fetch: api.handler() };\n * ```\n */\nexport function createHandler<T extends RouterRoutes>(routerDef: Router<T>): FetchHandler {\n const compiledRoutes = compileRoutes(routerDef);\n\n return async (request: Request, env?: unknown, ctx?: ExecutionContext): Promise<Response> => {\n const url = new URL(request.url);\n const method = request.method.toUpperCase();\n const isHead = method === 'HEAD';\n\n // Find matching route.\n for (const compiled of compiledRoutes) {\n const methodMatches =\n compiled.method === method ||\n method === 'OPTIONS' ||\n (method === 'HEAD' && compiled.method === 'GET');\n if (!methodMatches) continue;\n\n const match = compiled.pattern.exec(url.pathname);\n if (!match) continue;\n\n // Extract path parameters.\n const path: Record<string, string> = {};\n for (let i = 0; i < compiled.paramNames.length; i++) {\n path[compiled.paramNames[i]] = decodeURIComponent(match[i + 1]);\n }\n\n // Create middleware context.\n const context: MiddlewareContext = {\n request,\n path,\n query: {},\n body: {},\n env,\n executionCtx: ctx,\n };\n\n // Run middleware and handler.\n if (\n compiled.middleware.length > 0 ||\n compiled.handler ||\n compiled.querySchema ||\n compiled.bodySchema\n ) {\n const response = await runMiddleware(compiled.middleware, context, async () => {\n const routeId = `${compiled.method} ${url.pathname}`;\n\n // Validate query params.\n let query: unknown = {};\n if (compiled.querySchema) {\n const raw = Object.fromEntries(url.searchParams);\n const result = compiled.querySchema.safeParse(raw);\n if (!result.success) {\n return Response.json(\n {\n error: 'Invalid query parameters',\n route: routeId,\n details: result.error.flatten(),\n },\n { status: 400 },\n );\n }\n query = result.data;\n }\n\n // Validate body.\n let body: unknown = {};\n if (compiled.bodySchema && ['POST', 'PUT', 'PATCH'].includes(method)) {\n const raw = await request.json().catch(() => ({}));\n const result = compiled.bodySchema.safeParse(raw);\n if (!result.success) {\n return Response.json(\n { error: 'Invalid request body', route: routeId, details: result.error.flatten() },\n { status: 400 },\n );\n }\n body = result.data;\n }\n\n context.query = query;\n context.body = body;\n\n if (compiled.handler) {\n // Build handler context with middleware extensions.\n const handlerContext = {\n request,\n path,\n query,\n body,\n env,\n executionCtx: ctx,\n };\n\n // Copy middleware extensions.\n for (const key of Object.keys(context)) {\n if (!['request', 'path', 'query', 'body', 'env', 'executionCtx'].includes(key)) {\n (handlerContext as Record<string, unknown>)[key] = (\n context as Record<string, unknown>\n )[key];\n }\n }\n\n const result = await compiled.handler(handlerContext);\n if (result instanceof Response) {\n return result;\n }\n return Response.json(result);\n }\n\n return Response.json({});\n });\n return stripBodyForHead(response, isHead);\n }\n\n return stripBodyForHead(Response.json({}), isHead);\n }\n\n return Response.json({ error: 'Not Found' }, { status: 404 });\n };\n}\n","/**\n * @fileoverview Core route and router creation functions.\n *\n * ## Path Convention\n * - Property names = URL path segments\n * - `$param` prefix = dynamic segment (`:param`)\n * - `get`, `post`, `put`, `patch`, `delete` = HTTP method handlers\n *\n * ## Typing Best Practices\n *\n * **Schemas** (`query`/`body`) provide runtime validation AND type inference.\n * Use them for any input that needs validation:\n * ```typescript\n * get: route({\n * query: { limit: 'number?' },\n * handler: async (c) => c.query.limit, // limit: number | undefined\n * })\n * ```\n *\n * **Context** (`route.ctx<T>()`) provides types only (no validation).\n * Use it for path params, env bindings, and middleware-injected values:\n * ```typescript\n * interface MyContext {\n * path: { id: string };\n * env: { DB: Database };\n * user: { name: string }; // from auth middleware\n * }\n * get: route.ctx<MyContext>()({\n * handler: async (c) => ({ id: c.path.id, user: c.user.name }),\n * })\n * ```\n *\n * **Important:** Don't add explicit type annotations to handler parameters.\n * Let types flow from schemas and context:\n * ```typescript\n * // GOOD: types inferred from schema\n * handler: async (c) => c.query.limit\n *\n * // BAD: redundant type annotation\n * handler: async (c: { query: { limit?: number } }) => c.query.limit\n * ```\n */\n\nimport { createHandler } from './handler.js';\nimport type { Middleware } from './middleware.js';\nimport type { SchemaDefinition } from './schema.js';\nimport type { RouteDefinition, Router, RouterRoutes } from './types.js';\nimport { ROUTE_MARKER, ROUTER_MARKER } from './types.js';\n\n/**\n * Creates a route with query/body validation schemas.\n *\n * Use `route()` when you need request validation. For simple handlers\n * without validation, you can use bare functions directly in the router.\n *\n * @example\n * ```typescript\n * // Route with query validation\n * get: route({\n * query: { limit: 'number?', offset: 'number?' },\n * handler: async (c) => {\n * const items = await db.list(c.query.limit, c.query.offset);\n * return { items };\n * },\n * })\n *\n * // Route with body validation\n * post: route({\n * body: { name: 'string', email: 'string' },\n * handler: async (c) => {\n * const user = await db.create(c.body);\n * return user;\n * },\n * })\n *\n * // Simple handler without validation (bare function)\n * get: async () => ({ status: 'ok' })\n * ```\n */\nexport function route<\n const Q extends SchemaDefinition = {},\n const B extends SchemaDefinition = {},\n R = unknown,\n>(definition: RouteDefinition<Q, B, R, {}, {}>): RouteDefinition<Q, B, R, {}, {}> {\n return { ...definition, [ROUTE_MARKER]: true } as RouteDefinition<Q, B, R, {}, {}>;\n}\n\n/** Return type of route.ctx<T>() - callable and chainable. */\ninterface CtxBuilder<Ctx> {\n <const Q extends SchemaDefinition = {}, const B extends SchemaDefinition = {}, R = unknown>(\n definition: RouteDefinition<Q, B, R, {}, Ctx>,\n ): RouteDefinition<Q, B, R, {}, Ctx>;\n\n ctx<AdditionalCtx>(): CtxBuilder<Ctx & AdditionalCtx>;\n}\n\n/** Creates a chainable context builder. */\nfunction createCtxBuilder<Ctx>(): CtxBuilder<Ctx> {\n const builder = <\n const Q extends SchemaDefinition = {},\n const B extends SchemaDefinition = {},\n R = unknown,\n >(\n definition: RouteDefinition<Q, B, R, {}, Ctx>,\n ): RouteDefinition<Q, B, R, {}, Ctx> =>\n ({ ...definition, [ROUTE_MARKER]: true }) as RouteDefinition<Q, B, R, {}, Ctx>;\n\n builder.ctx = <AdditionalCtx>() => createCtxBuilder<Ctx & AdditionalCtx>();\n\n return builder as CtxBuilder<Ctx>;\n}\n\n/**\n * Creates a route with typed context for values not covered by schemas.\n *\n * Use `route.ctx<T>()` to type:\n * - **Path params**: `{ path: { id: string } }` for `$id` segments\n * - **Env bindings**: `{ env: { DB: Database } }` for runtime environment\n * - **Middleware values**: `{ user: User }` for auth middleware, etc.\n *\n * Context provides types only, not runtime validation. For validated inputs,\n * use `query` and `body` schemas instead.\n *\n * @example\n * ```typescript\n * interface AppContext {\n * path: { id: string };\n * env: { DB: D1Database };\n * user: { id: string }; // injected by auth middleware\n * }\n *\n * $id: router({\n * get: route.ctx<AppContext>()({\n * query: { include: 'string?' }, // schema for validation\n * handler: async (c) => {\n * // c.path.id, c.env.DB, c.user.id - from context\n * // c.query.include - from schema\n * return c.env.DB.get(c.path.id);\n * },\n * }),\n * })\n * ```\n */\nroute.ctx = <Ctx>(): CtxBuilder<Ctx> => createCtxBuilder<Ctx>();\n\n/**\n * Creates a router that groups paths and applies middleware.\n *\n * @example\n * ```typescript\n * const api = router({\n * health: router({\n * get: async () => ({ status: 'ok' }),\n * }),\n *\n * users: router({\n * // GET /users - list users\n * get: route({\n * query: { limit: 'number?' },\n * handler: async (c) => db.users.list(c.query.limit),\n * }),\n *\n * // POST /users - create user\n * post: route({\n * body: { name: 'string', email: 'string' },\n * handler: async (c) => db.users.create(c.body),\n * }),\n *\n * // /users/:id\n * $id: router({\n * get: async (c) => db.users.get(c.path.id),\n * delete: async (c) => db.users.delete(c.path.id),\n * }),\n * }),\n * });\n *\n * export default { fetch: api.handler() };\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function router<T extends RouterRoutes, M extends Middleware<any>[]>(\n routes: T,\n ...middleware: M\n): Router<T> {\n // Cast needed because RouterBrand uses a type-only symbol declaration.\n const self = {\n routes,\n middleware: middleware.length > 0 ? middleware : undefined,\n handler() {\n return createHandler(self);\n },\n } as unknown as Router<T>;\n // Add marker for runtime type checking.\n (self as unknown as Record<symbol, boolean>)[ROUTER_MARKER] = true;\n return self;\n}\n","/**\n * @fileoverview OpenAPI documentation generation.\n *\n * Generates OpenAPI 3.0 documentation from router definitions.\n *\n * **Path Convention:**\n * - Property names = URL path segments\n * - `$param` prefix = dynamic segment (`{param}` in OpenAPI)\n * - `get`, `post`, `put`, `patch`, `delete` = HTTP method handlers\n */\n\nimport type { SchemaDefinition } from './schema.js';\nimport type { RouteDefinition, Router, RouterRoutes } from './types.js';\nimport { HTTP_METHODS, isFunction, isRoute, isRouter } from './types.js';\n\n/** Collected route info for documentation. */\ninterface CollectedRoute {\n path: string;\n method: string;\n route: RouteDefinition | null;\n}\n\n/** Converts property name to path segment, handling $param convention. */\nfunction propertyToSegment(prop: string): string {\n if (prop.startsWith('$')) {\n return `{${prop.slice(1)}}`;\n }\n return prop;\n}\n\n/** Collects all routes from a router tree. */\nfunction collectRoutes(routerDef: Router<RouterRoutes>, parentPath = ''): CollectedRoute[] {\n const collectedRoutes: CollectedRoute[] = [];\n\n for (const [prop, entry] of Object.entries(routerDef.routes)) {\n // Check if this is a method handler.\n if (HTTP_METHODS.has(prop)) {\n const path = parentPath || '/';\n if (isFunction(entry)) {\n collectedRoutes.push({ path, method: prop, route: null });\n } else if (isRoute(entry)) {\n collectedRoutes.push({ path, method: prop, route: entry });\n }\n } else if (isRouter(entry)) {\n // Nested router - recurse with updated path.\n const segment = propertyToSegment(prop);\n const newPath = parentPath ? `${parentPath}/${segment}` : `/${segment}`;\n collectedRoutes.push(...collectRoutes(entry, newPath));\n }\n }\n\n return collectedRoutes;\n}\n\n/** Generates OpenAPI documentation from a router. */\nexport function generateDocs(config: {\n title: string;\n version: string;\n description?: string;\n router: Router<RouterRoutes>;\n}): object {\n const paths: Record<string, Record<string, object>> = {};\n const allRoutes = collectRoutes(config.router);\n\n for (const { path, method, route } of allRoutes) {\n if (!paths[path]) {\n paths[path] = {};\n }\n\n const operation: Record<string, unknown> = {\n description: route?.description,\n responses: {\n 200: { description: 'Successful response' },\n 400: { description: 'Validation error' },\n },\n };\n\n // Add query parameters (only primitive types supported in query strings).\n if (route?.query) {\n const querySchema = route.query as SchemaDefinition;\n operation.parameters = Object.entries(querySchema)\n .filter(([, type]) => typeof type === 'string')\n .map(([name, type]) => ({\n name,\n in: 'query',\n required: !(type as string).endsWith('?'),\n schema: { type: (type as string).replace('?', '').replace('[]', '') },\n }));\n }\n\n // Add request body.\n if (route?.body && ['post', 'put', 'patch'].includes(method)) {\n const bodySchema = route.body as SchemaDefinition;\n operation.requestBody = {\n required: true,\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: Object.fromEntries(\n Object.entries(bodySchema).map(([name, type]) => {\n if (typeof type === 'string') {\n return [name, { type: type.replace('?', '').replace('[]', '') }];\n }\n // Nested object - simplified representation.\n return [name, { type: 'object' }];\n }),\n ),\n required: Object.entries(bodySchema)\n .filter(([, type]) => typeof type === 'string' && !type.endsWith('?'))\n .map(([name]) => name),\n },\n },\n },\n };\n }\n\n paths[path][method] = operation;\n }\n\n return {\n openapi: '3.0.0',\n info: {\n title: config.title,\n version: config.version,\n description: config.description,\n },\n paths,\n };\n}\n","import type { Method } from './types.js';\n\n// =============================================================================\n// Proxy Types\n// =============================================================================\n\nconst PREFIXED_HTTP_METHODS = new Set([\n '$get',\n '$post',\n '$put',\n '$patch',\n '$delete',\n '$options',\n '$head',\n]);\n\n/**\n * Configuration for the recursive proxy.\n */\nexport interface RecursiveProxyOptions {\n /**\n * Callback to execute when a method is invoked.\n * @param segments - The path segments accumulated so far.\n * @param method - The HTTP method to execute (e.g. 'get', 'post').\n * @param options - The arguments passed to the method execution (e.g. { query, body }).\n */\n onRequest: (segments: string[], method: Method, options?: unknown) => Promise<unknown>;\n}\n\n/**\n * Creates a recursive proxy that accumulates path segments and traps method calls.\n *\n * Use cases:\n * 1. HTTP Client: Accumulates path segments and executes HTTP requests on method calls.\n * 2. Local Client: Accumulates path segments and invokes local handlers on method calls.\n *\n * @param config - The proxy configuration.\n * @param segments - The initial path segments (default: []).\n */\nexport function createRecursiveProxy(\n config: RecursiveProxyOptions,\n segments: string[] = [],\n): unknown {\n // The proxy target is a function so that 'apply' can be intercepted (for the root call or recursive calls).\n // For the root client, this allows `client()` to be called (implicit GET /).\n // For nested paths, it allows `client.some.path()` (implicit GET /some/path).\n const callable = (options?: unknown) => {\n return config.onRequest(segments, 'get', options);\n };\n\n return new Proxy(callable, {\n get(_target, prop) {\n if (typeof prop !== 'string') return undefined;\n\n // Handle standard function methods and toJSON\n // This prevents 'apply', 'call', etc. from being treated as path segments.\n if (\n prop === 'apply' ||\n prop === 'call' ||\n prop === 'bind' ||\n prop === 'toString' ||\n prop === 'toJSON'\n ) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (_target as any)[prop];\n }\n\n // Check if this property is a method trap.\n if (PREFIXED_HTTP_METHODS.has(prop)) {\n const method = prop.slice(1) as Method;\n return (options?: unknown) => {\n return config.onRequest(segments, method, options);\n };\n }\n\n // Otherwise = nested path segment.\n return createRecursiveProxy(config, [...segments, prop]);\n },\n\n // Trap direct function calls (e.g. client() or client.path())\n apply(_target, _thisArg, args) {\n return callable(args[0]);\n },\n });\n}\n","/**\n * @fileoverview HTTP client for @fresho/router.\n *\n * Provides a typed HTTP client using type-only imports.\n * URLs are built from property access chains:\n * - Property names = path segments (including `get`, `post`, etc.)\n * - `$param` = dynamic segment (substituted from path options)\n * - `$get()`, `$post()`, etc. = HTTP method execution\n * - Direct call `()` = implicit GET\n *\n * The `$` prefix distinguishes HTTP method execution from path navigation,\n * allowing routes with path segments named after HTTP methods (e.g., `/api/get`).\n *\n * @example\n * ```typescript\n * // Type-only import - no server code in bundle!\n * import type { api } from './server/api.js';\n * import { createHttpClient } from '@fresho/router';\n *\n * const client = createHttpClient<typeof api>({\n * baseUrl: 'https://api.example.com',\n * });\n *\n * // URLs from property chains:\n * await client.health(); // GET /health (implicit)\n * await client.users.$get(); // GET /users (explicit)\n * await client.users.$post({ body: {...} }); // POST /users\n * await client.users.$id({ path: { id: '123' } }); // GET /users/123\n *\n * // Navigate to path segments named after HTTP methods:\n * await client.api.get.$get(); // GET /api/get\n * await client.resources.delete.$get(); // GET /resources/delete\n * ```\n */\n\nimport { createRecursiveProxy } from './client-proxy.js';\nimport type { HeaderValue, RequestOptions, RouterClient } from './client-types.js';\nimport type { Method, Router, RouterRoutes } from './types.js';\n\n// =============================================================================\n// Configuration Types\n// =============================================================================\n\n/** HTTP client configuration. */\nexport interface HttpClientConfig {\n baseUrl?: string;\n headers?: Record<string, HeaderValue>;\n credentials?: RequestCredentials;\n /** Custom fetch implementation for interception or environment-specific behavior. */\n fetch?: typeof fetch;\n}\n\n/** Extra options for HTTP requests (headers). */\nexport interface HttpExtraOptions {\n headers?: HeadersInit;\n /** If true, returns the raw Response object instead of parsing as JSON. */\n raw?: boolean;\n}\n\n/** Options for an HTTP request. */\nexport type HttpRequestOptions = RequestOptions<HttpExtraOptions>;\n\n// =============================================================================\n// Client Type Construction\n// =============================================================================\n\n/** Top-level HTTP client type. */\nexport type HttpClient<T extends Router<RouterRoutes>> = {\n configure(config: HttpClientConfig): void;\n} & RouterClient<T['routes'], HttpExtraOptions>;\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\ninterface SharedConfig {\n current: HttpClientConfig;\n}\n\nconst BODY_METHODS = new Set(['post', 'put', 'patch']);\n\n/**\n * Creates a typed HTTP client.\n *\n * HTTP methods are prefixed with `$` to distinguish them from path segments:\n * - `.$get()`, `.$post()`, `.$put()`, `.$patch()`, `.$delete()` - execute HTTP methods\n * - `.propertyName` - navigate to path segment (works for any name including `get`, `post`, etc.)\n * - Direct call `()` - implicit GET request\n *\n * @example\n * ```typescript\n * import type { api } from './server';\n * import { createHttpClient } from '@fresho/router';\n *\n * const client = createHttpClient<typeof api>({\n * baseUrl: 'https://api.example.com',\n * headers: { Authorization: () => `Bearer ${getToken()}` },\n * });\n *\n * // GET /health (implicit)\n * await client.health();\n *\n * // GET /users (explicit)\n * await client.users.$get();\n *\n * // POST /users\n * await client.users.$post({ body: { name: 'Alice' } });\n *\n * // GET /users/123\n * await client.users.$id({ path: { id: '123' } });\n *\n * // Navigate through path segments named after HTTP methods:\n * // GET /api/get\n * await client.api.get.$get();\n * ```\n */\nexport function createHttpClient<T extends Router<RouterRoutes>>(\n config: HttpClientConfig = {},\n): HttpClient<T> {\n const sharedConfig: SharedConfig = { current: { ...config } };\n\n const client = {\n configure(newConfig: HttpClientConfig) {\n Object.assign(sharedConfig.current, newConfig);\n },\n } as HttpClient<T>;\n\n const proxy = createRecursiveProxy({\n onRequest: (segments, method, options) => {\n return executeRequest(\n sharedConfig,\n segments,\n method as Method,\n options as HttpRequestOptions,\n );\n },\n });\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === 'configure') return target.configure;\n // Delegate to recursive proxy for path building\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (proxy as any)[prop];\n },\n apply(_target, _thisArg, args) {\n // Direct call on root = GET /\n return executeRequest(sharedConfig, [], 'get', args[0] as HttpRequestOptions | undefined);\n },\n }) as HttpClient<T>;\n}\n\n/** Builds a URL path from segments, substituting $param with values. */\nfunction buildPath(segments: string[], pathParams?: Record<string, string>): string {\n const parts: string[] = [];\n\n for (const segment of segments) {\n if (segment.startsWith('$')) {\n const paramName = segment.slice(1);\n const value = pathParams?.[paramName];\n if (!value) {\n throw new Error(`Missing path parameter: ${paramName}`);\n }\n parts.push(encodeURIComponent(value));\n } else {\n parts.push(segment);\n }\n }\n\n return `/${parts.join('/')}`;\n}\n\n/** Executes an HTTP request. */\nasync function executeRequest(\n sharedConfig: SharedConfig,\n segments: string[],\n method: Method,\n options?: HttpRequestOptions,\n): Promise<unknown> {\n const config = sharedConfig.current;\n const path = buildPath(segments, options?.path);\n\n // Build URL.\n const baseUrl = config.baseUrl || (typeof window !== 'undefined' ? window.location?.origin : '');\n const url = new URL(path, baseUrl || 'http://localhost');\n\n // Add query params.\n if (options?.query) {\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n // Build headers.\n const headers = new Headers();\n if (config.headers) {\n for (const [key, value] of Object.entries(config.headers)) {\n const resolved = typeof value === 'function' ? await value() : value;\n if (resolved != null) {\n headers.set(key, resolved);\n }\n }\n }\n if (options?.headers) {\n new Headers(options.headers).forEach((v, k) => headers.set(k, v));\n }\n\n // Build request.\n const init: RequestInit = {\n method: method.toUpperCase(),\n headers,\n credentials: config.credentials,\n };\n\n // Add body.\n if (options?.body && BODY_METHODS.has(method)) {\n headers.set('Content-Type', 'application/json');\n init.body = JSON.stringify(options.body);\n }\n\n // Execute.\n const fetchUrl = config.baseUrl ? url.toString() : url.pathname + url.search;\n const fetchFn = config.fetch || fetch;\n const response = await fetchFn(fetchUrl, init);\n\n if (!response.ok) {\n throw new Error((await response.text()) || response.statusText);\n }\n\n if (options?.raw) {\n return response;\n }\n\n return response.json();\n}\n","/**\n * @fileoverview Local client for @fresho/router.\n *\n * Provides a typed client that invokes route handlers directly without HTTP.\n * Uses the same API as createHttpClient for consistency.\n *\n * @example\n * ```typescript\n * import { createLocalClient } from '@fresho/router';\n * import { api } from './api.js';\n *\n * const client = createLocalClient(api);\n *\n * // Same API as HTTP client\n * await client.health();\n * await client.users();\n * await client.users.post({ body: { name: 'Alice' } });\n * await client.users.$id({ path: { id: '123' } });\n * ```\n */\n\nimport { createRecursiveProxy } from './client-proxy.js';\nimport type { RequestOptions, RouterClient } from './client-types.js';\nimport { compileSchema } from './schema.js';\nimport type { ExecutionContext, RouteDefinition, Router, RouterRoutes } from './types.js';\nimport { isFunction, isRoute, isRouter } from './types.js';\n\n// =============================================================================\n// Configuration Types\n// =============================================================================\n\n/** Local client configuration. */\nexport interface LocalClientConfig {\n env?: unknown;\n ctx?: ExecutionContext;\n}\n\n/** Options for a local client request. */\nexport type LocalRequestOptions = RequestOptions<LocalClientConfig>;\n\n// =============================================================================\n// Client Type Construction (mirrors http-client types)\n// =============================================================================\n\nexport type LocalClient<T extends Router<RouterRoutes>> = {\n configure(config: LocalClientConfig): void;\n} & RouterClient<T['routes'], LocalClientConfig>;\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\ninterface SharedConfig {\n current: LocalClientConfig;\n}\n\ninterface RouteInfo {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (context: any) => unknown;\n querySchema?: ReturnType<typeof compileSchema>;\n bodySchema?: ReturnType<typeof compileSchema>;\n}\n\n/**\n * Finds a method handler in a router tree.\n */\nfunction findHandler(\n routerDef: Router<RouterRoutes>,\n segments: string[],\n method: string,\n): RouteInfo | null {\n let current: Router<RouterRoutes> | undefined = routerDef;\n\n // Navigate through path segments.\n for (const segment of segments) {\n if (!current) return null;\n\n const entry: unknown = current.routes[segment];\n if (isRouter(entry)) {\n current = entry;\n } else {\n return null;\n }\n }\n\n if (!current) return null;\n\n // Find the method handler.\n const entry = current.routes[method];\n if (!entry) return null;\n\n if (isFunction(entry)) {\n return { handler: entry };\n }\n\n if (isRoute(entry)) {\n const routeDef = entry as RouteDefinition;\n return {\n handler: routeDef.handler,\n querySchema: routeDef.query ? compileSchema(routeDef.query) : undefined,\n bodySchema: routeDef.body ? compileSchema(routeDef.body) : undefined,\n };\n }\n\n return null;\n}\n\n/**\n * Collects path parameters from segments.\n */\nfunction collectPathParams(\n segments: string[],\n pathValues?: Record<string, string>,\n): Record<string, string> {\n const params: Record<string, string> = {};\n\n for (const segment of segments) {\n if (segment.startsWith('$')) {\n const paramName = segment.slice(1);\n const value = pathValues?.[paramName];\n if (!value) {\n throw new Error(`Missing path parameter: ${paramName}`);\n }\n params[paramName] = value;\n }\n }\n\n return params;\n}\n\n/**\n * Creates a typed local client from a router definition.\n *\n * @example\n * ```typescript\n * import { createLocalClient } from '@fresho/router';\n * import { api } from './api.js';\n *\n * const client = createLocalClient(api);\n * client.configure({ env: { DB: mockDatabase } });\n *\n * await client.health();\n * await client.users();\n * await client.users.post({ body: { name: 'Alice' } });\n * await client.users.$id({ path: { id: '123' } });\n * ```\n */\nexport function createLocalClient<T extends Router<RouterRoutes>>(routerDef: T): LocalClient<T> {\n const sharedConfig: SharedConfig = { current: {} };\n\n const client = {\n configure(config: LocalClientConfig) {\n Object.assign(sharedConfig.current, config);\n },\n } as LocalClient<T>;\n\n const proxy = createRecursiveProxy({\n onRequest: (segments, method, options) => {\n return invokeHandler(\n sharedConfig,\n routerDef,\n segments,\n method,\n options as LocalRequestOptions | undefined,\n );\n },\n });\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === 'configure') return target.configure;\n // Delegate to recursive proxy for path building\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (proxy as any)[prop];\n },\n apply(_target, _thisArg, args) {\n return invokeHandler(\n sharedConfig,\n routerDef,\n [],\n 'get',\n args[0] as LocalRequestOptions | undefined,\n );\n },\n }) as LocalClient<T>;\n}\n\n/** Invokes a handler directly. */\nasync function invokeHandler(\n sharedConfig: SharedConfig,\n routerDef: Router<RouterRoutes>,\n segments: string[],\n method: string,\n options?: LocalRequestOptions,\n): Promise<unknown> {\n const config = sharedConfig.current;\n const routeInfo = findHandler(routerDef, segments, method);\n\n if (!routeInfo) {\n throw new Error(`No handler found for ${method.toUpperCase()} /${segments.join('/')}`);\n }\n\n // Collect path params.\n const pathParams = collectPathParams(segments, options?.path);\n\n // Validate query.\n let query: unknown = {};\n if (routeInfo.querySchema) {\n const result = routeInfo.querySchema.safeParse(options?.query ?? {});\n if (!result.success) {\n throw new Error(`Invalid query parameters: ${JSON.stringify(result.error.flatten())}`);\n }\n query = result.data;\n }\n\n // Validate body.\n let body: unknown = {};\n if (routeInfo.bodySchema) {\n const result = routeInfo.bodySchema.safeParse((options?.body ?? {}) as object);\n if (!result.success) {\n throw new Error(`Invalid request body: ${JSON.stringify(result.error.flatten())}`);\n }\n body = result.data;\n }\n\n // Build path for synthetic request URL.\n const pathParts = segments.map((s) =>\n s.startsWith('$') ? encodeURIComponent(pathParams[s.slice(1)] || '') : s,\n );\n const urlPath = `/${pathParts.join('/')}`;\n const url = new URL(urlPath, 'http://localhost');\n if (options?.query) {\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n // Build request.\n const requestInit: RequestInit = {\n method: method.toUpperCase(),\n };\n if (options?.body && ['post', 'put', 'patch'].includes(method)) {\n requestInit.headers = { 'Content-Type': 'application/json' };\n requestInit.body = JSON.stringify(options.body);\n }\n\n const request = new Request(url.toString(), requestInit);\n\n // Build context.\n const context = {\n request,\n path: pathParams,\n query,\n body,\n env: options?.env ?? config.env,\n executionCtx: options?.ctx ?? config.ctx,\n };\n\n // Call handler.\n const result = await routeInfo.handler(context);\n\n // Parse response if needed.\n if (result instanceof Response) {\n return result.json();\n }\n\n return result;\n}\n","/**\n * @fileoverview Streaming response utilities for @fresho/router.\n *\n * Provides helpers for Server-Sent Events (SSE) and newline-delimited JSON (NDJSON)\n * streaming responses. These are useful for real-time updates, progress indicators,\n * and streaming large datasets.\n *\n * Available utilities:\n * - {@link sseResponse} - Server-Sent Events for real-time browser updates\n * - {@link streamJsonLines} - NDJSON for streaming JSON objects line by line\n *\n * @example\n * ```typescript\n * import { router, sseResponse, streamJsonLines } from '@fresho/router';\n *\n * const api = router({\n * // SSE for real-time updates\n * events: router({\n * get: async () => sseResponse(async (send, close) => {\n * for (let i = 0; i < 10; i++) {\n * send({ event: 'tick', data: { count: i } });\n * await sleep(1000);\n * }\n * close();\n * }),\n * }),\n *\n * // NDJSON for streaming data\n * export: router({\n * get: async () => streamJsonLines(async (send, close) => {\n * for await (const row of database.stream()) {\n * send(row);\n * }\n * close();\n * }),\n * }),\n * });\n * ```\n */\n\n/**\n * Options for SSE response.\n */\nexport interface SSEOptions {\n /** Custom headers to include in the response. */\n headers?: HeadersInit;\n /** Interval in milliseconds to send keep-alive comments (default: 30000). */\n keepAliveInterval?: number;\n}\n\n/**\n * Server-Sent Event message structure.\n */\nexport interface SSEMessage {\n /** Event type (optional). */\n event?: string;\n /** Event data. */\n data: string | object;\n /** Event ID (optional). */\n id?: string;\n /** Retry interval hint for client (optional). */\n retry?: number;\n}\n\n/**\n * Formats an SSE message for transmission.\n */\nfunction formatSSEMessage(message: SSEMessage): string {\n const lines: string[] = [];\n\n if (message.event) {\n lines.push(`event: ${message.event}`);\n }\n\n if (message.id) {\n lines.push(`id: ${message.id}`);\n }\n\n if (message.retry !== undefined) {\n lines.push(`retry: ${message.retry}`);\n }\n\n // Data can be a string or object (will be JSON stringified).\n const data = typeof message.data === 'string' ? message.data : JSON.stringify(message.data);\n\n // Split data by newlines and prefix each line with 'data: '.\n for (const line of data.split('\\n')) {\n lines.push(`data: ${line}`);\n }\n\n return `${lines.join('\\n')}\\n\\n`;\n}\n\n/**\n * Creates a Server-Sent Events (SSE) response.\n *\n * @example\n * ```typescript\n * const handler = async (c) => {\n * return sseResponse(async (send, close) => {\n * for (let i = 0; i < 5; i++) {\n * await send({ data: { count: i } });\n * await new Promise(r => setTimeout(r, 1000));\n * }\n * close();\n * });\n * };\n * ```\n */\nexport function sseResponse(\n handler: (send: (message: SSEMessage) => void, close: () => void) => void | Promise<void>,\n options: SSEOptions = {},\n): Response {\n const encoder = new TextEncoder();\n let isClosed = false;\n\n const stream = new ReadableStream({\n async start(controller) {\n // Send function for the handler.\n const send = (message: SSEMessage) => {\n if (!isClosed) {\n controller.enqueue(encoder.encode(formatSSEMessage(message)));\n }\n };\n\n // Close function for the handler.\n const close = () => {\n if (!isClosed) {\n isClosed = true;\n controller.close();\n }\n };\n\n // Optional keep-alive interval.\n const keepAliveInterval = options.keepAliveInterval ?? 30_000;\n let keepAliveTimer: ReturnType<typeof setInterval> | undefined;\n\n if (keepAliveInterval > 0) {\n keepAliveTimer = setInterval(() => {\n if (!isClosed) {\n // Send a comment as keep-alive.\n controller.enqueue(encoder.encode(': keep-alive\\n\\n'));\n }\n }, keepAliveInterval);\n }\n\n try {\n await handler(send, close);\n } finally {\n if (keepAliveTimer) {\n clearInterval(keepAliveTimer);\n }\n if (!isClosed) {\n controller.close();\n }\n }\n },\n });\n\n const headers = new Headers(options.headers);\n headers.set('Content-Type', 'text/event-stream');\n headers.set('Cache-Control', 'no-cache');\n headers.set('Connection', 'keep-alive');\n\n return new Response(stream, { headers });\n}\n\n/**\n * Creates a streaming JSON response where each line is a JSON object.\n *\n * Useful for streaming large datasets or real-time data where each item\n * can be processed as it arrives (NDJSON format).\n *\n * @example\n * ```typescript\n * const handler = async (c) => {\n * return streamJsonLines(async (send, close) => {\n * for (const item of largeDataset) {\n * await send(item);\n * }\n * close();\n * });\n * };\n * ```\n */\nexport function streamJsonLines(\n handler: (send: (data: object) => void, close: () => void) => void | Promise<void>,\n options: { headers?: HeadersInit } = {},\n): Response {\n const encoder = new TextEncoder();\n let isClosed = false;\n\n const stream = new ReadableStream({\n async start(controller) {\n const send = (data: object) => {\n if (!isClosed) {\n controller.enqueue(encoder.encode(`${JSON.stringify(data)}\\n`));\n }\n };\n\n const close = () => {\n if (!isClosed) {\n isClosed = true;\n controller.close();\n }\n };\n\n try {\n await handler(send, close);\n } finally {\n if (!isClosed) {\n controller.close();\n }\n }\n },\n });\n\n const headers = new Headers(options.headers);\n headers.set('Content-Type', 'application/x-ndjson');\n\n return new Response(stream, { headers });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["errors: Record<string, string[]>","resultArray: unknown[]","success","coerced","errorMsg","result: Record<string, unknown>","allErrors: Record<string, string[]>","paramNames: string[]","propertyToSegment","compiled: CompiledRoute[]","path: Record<string, string>","context: MiddlewareContext","query: unknown","body: unknown","collectedRoutes: CollectedRoute[]","paths: Record<string, Record<string, object>>","operation: Record<string, unknown>","route","sharedConfig: SharedConfig","parts: string[]","init: RequestInit","current: Router<RouterRoutes> | undefined","entry: unknown","entry","params: Record<string, string>","sharedConfig: SharedConfig","query: unknown","result","body: unknown","requestInit: RequestInit","lines: string[]","keepAliveTimer: ReturnType<typeof setInterval> | undefined"],"sources":["../src/schema.ts","../src/types.ts","../src/handler.ts","../src/core.ts","../src/docs.ts","../src/client-proxy.ts","../src/http-client.ts","../src/local-client.ts","../src/streaming.ts"],"sourcesContent":["/**\n * @fileoverview Schema types and validation utilities.\n *\n * Provides shorthand schema definitions for validating query parameters and request bodies.\n * No external dependencies - uses simple string-based type definitions.\n *\n * Supported types:\n * - Primitives: `'string'`, `'number'`, `'boolean'`\n * - Optional: `'string?'`, `'number?'`, `'boolean?'`\n * - Arrays: `'string[]'`, `'number[]'`, `'boolean[]'`\n * - Optional arrays: `'string[]?'`, `'number[]?'`, `'boolean[]?'`\n * - Nested objects: `{ field: { nested: 'string' } }`\n *\n * @example\n * ```typescript\n * // In route definitions\n * const api = router({\n * users: router({\n * post: route({\n * query: { page: 'number?', limit: 'number?' },\n * body: {\n * name: 'string',\n * age: 'number',\n * tags: 'string[]?',\n * address: { city: 'string', zip: 'string?' },\n * },\n * handler: async (c) => {\n * c.query.page; // number | undefined\n * c.body.name; // string\n * c.body.address.city // string\n * },\n * }),\n * }),\n * });\n * ```\n */\n\n/** Primitive type strings. */\nexport type PrimitiveType = 'string' | 'number' | 'boolean' | 'unknown';\n\n/** Optional primitive type strings. */\nexport type OptionalPrimitiveType = 'string?' | 'number?' | 'boolean?' | 'unknown?';\n\n/** Array type strings. */\nexport type ArrayType = 'string[]' | 'number[]' | 'boolean[]' | 'unknown[]';\n\n/** Optional array type strings. */\nexport type OptionalArrayType = 'string[]?' | 'number[]?' | 'boolean[]?' | 'unknown[]?';\n\n/** All supported type strings. */\nexport type SchemaType = PrimitiveType | OptionalPrimitiveType | ArrayType | OptionalArrayType;\n\n/** Schema definition using shorthand syntax. Supports nested objects. */\nexport interface SchemaDefinition {\n [key: string]: SchemaType | SchemaDefinition;\n}\n\n/** Maps primitive type strings to TypeScript types. */\nexport type SchemaTypeMap = {\n string: string;\n 'string?': string | undefined;\n number: number;\n 'number?': number | undefined;\n boolean: boolean;\n 'boolean?': boolean | undefined;\n unknown: unknown;\n 'unknown?': unknown | undefined;\n 'string[]': string[];\n 'string[]?': string[] | undefined;\n 'number[]': number[];\n 'number[]?': number[] | undefined;\n 'boolean[]': boolean[];\n 'boolean[]?': boolean[] | undefined;\n 'unknown[]': unknown[];\n 'unknown[]?': unknown[] | undefined;\n};\n\n/** Checks if a schema field type is optional (ends with ?). */\ntype IsOptionalField<T> = T extends `${string}?` ? true : false;\n\n/** Infers TypeScript type from a schema field (type string or nested object). */\nexport type InferSchemaField<T> = T extends SchemaType\n ? SchemaTypeMap[T]\n : T extends SchemaDefinition\n ? InferSchema<T>\n : never;\n\n/** Gets keys of required fields (non-optional types). */\ntype RequiredKeys<T extends SchemaDefinition> = {\n [K in keyof T]: IsOptionalField<T[K]> extends true ? never : K;\n}[keyof T];\n\n/** Gets keys of optional fields (types ending with ?). */\ntype OptionalKeys<T extends SchemaDefinition> = {\n [K in keyof T]: IsOptionalField<T[K]> extends true ? K : never;\n}[keyof T];\n\n/**\n * Infers TypeScript type from schema definition.\n *\n * Required fields become required properties, optional fields (ending with ?)\n * become optional properties with `| undefined`.\n */\nexport type InferSchema<T extends SchemaDefinition> = {\n [K in RequiredKeys<T>]: InferSchemaField<T[K]>;\n} & {\n [K in OptionalKeys<T>]?: InferSchemaField<T[K]>;\n};\n\n/** Error object returned when validation fails. */\nexport interface ValidationError {\n flatten: () => { fieldErrors: Record<string, string[]> };\n}\n\n/** Validation result matching Zod's safeParse API for compatibility. */\nexport type ValidationResult<T> =\n | { success: true; data: T; error?: undefined }\n | { success: false; data?: undefined; error: ValidationError };\n\n/** Compiled schema with validate method. */\nexport interface CompiledSchema<T> {\n safeParse: (data: object) => ValidationResult<T>;\n}\n\n/** Valid base types for primitives and arrays. */\nconst PRIMITIVE_TYPES = ['string', 'number', 'boolean', 'unknown'];\n\n/** Checks if a schema field is a nested object (not a type string). */\nfunction isNestedSchema(field: SchemaType | SchemaDefinition): field is SchemaDefinition {\n return typeof field === 'object' && field !== null;\n}\n\n/**\n * Validates and coerces a single primitive value.\n * Returns [success, result, errorMessage].\n */\nfunction validatePrimitive(value: unknown, baseType: string): [boolean, unknown, string | null] {\n switch (baseType) {\n case 'string':\n return [true, String(value), null];\n\n case 'number': {\n const num = Number(value);\n if (Number.isNaN(num)) {\n return [false, undefined, 'Expected number'];\n }\n return [true, num, null];\n }\n\n case 'boolean': {\n if (value === 'true' || value === '1' || value === true) {\n return [true, true, null];\n } else if (value === 'false' || value === '0' || value === false) {\n return [true, false, null];\n }\n return [false, undefined, 'Expected boolean (true/false)'];\n }\n\n case 'unknown':\n // Pass through any value without validation.\n return [true, value, null];\n\n default:\n return [false, undefined, `Unknown type: ${baseType}`];\n }\n}\n\n/**\n * Validates a schema field value (primitive, array, or nested object).\n * Returns [success, result, errors].\n */\nfunction validateField(\n key: string,\n field: SchemaType | SchemaDefinition,\n value: unknown,\n parentPath: string = '',\n): [boolean, unknown, Record<string, string[]>] {\n const fieldPath = parentPath ? `${parentPath}.${key}` : key;\n const errors: Record<string, string[]> = {};\n\n // Handle nested object schemas.\n if (isNestedSchema(field)) {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n errors[fieldPath] = ['Expected object'];\n return [false, undefined, errors];\n }\n // Recursively validate nested schema.\n const nestedResult = validateSchema(field, value as Record<string, unknown>, fieldPath);\n return [nestedResult.success, nestedResult.data, nestedResult.errors];\n }\n\n // It's a type string.\n const typeStr = field as string;\n const isOptional = typeStr.endsWith('?');\n const isArray = typeStr.includes('[]');\n\n // Strip optional marker for base type extraction.\n let baseType = isOptional ? typeStr.slice(0, -1) : typeStr;\n\n // Strip array marker.\n if (isArray) {\n baseType = baseType.replace('[]', '');\n }\n\n // Handle missing/empty values.\n if (value === undefined || value === '') {\n if (!isOptional) {\n errors[fieldPath] = ['Required'];\n return [false, undefined, errors];\n }\n return [true, undefined, {}];\n }\n\n // Handle array types.\n if (isArray) {\n if (!Array.isArray(value)) {\n errors[fieldPath] = ['Expected array'];\n return [false, undefined, errors];\n }\n\n const resultArray: unknown[] = [];\n for (let i = 0; i < value.length; i++) {\n const [success, coerced, errorMsg] = validatePrimitive(value[i], baseType);\n if (!success) {\n errors[`${fieldPath}[${i}]`] = [errorMsg!];\n return [false, undefined, errors];\n }\n resultArray.push(coerced);\n }\n return [true, resultArray, {}];\n }\n\n // Handle primitive types.\n const [success, coerced, errorMsg] = validatePrimitive(value, baseType);\n if (!success) {\n errors[fieldPath] = [errorMsg!];\n return [false, undefined, errors];\n }\n return [true, coerced, {}];\n}\n\n/**\n * Validates a full schema against data.\n * Returns { success, data, errors }.\n */\nfunction validateSchema(\n schema: SchemaDefinition,\n data: Record<string, unknown>,\n parentPath: string = '',\n): { success: boolean; data: Record<string, unknown>; errors: Record<string, string[]> } {\n const result: Record<string, unknown> = {};\n const allErrors: Record<string, string[]> = {};\n\n for (const [key, field] of Object.entries(schema)) {\n const value = data[key];\n const [success, coerced, fieldErrors] = validateField(key, field, value, parentPath);\n\n if (!success) {\n Object.assign(allErrors, fieldErrors);\n } else if (coerced !== undefined) {\n result[key] = coerced;\n }\n }\n\n return {\n success: Object.keys(allErrors).length === 0,\n data: result,\n errors: allErrors,\n };\n}\n\n/** Validates schema definition at compile time. */\nfunction validateSchemaDefinition(schema: SchemaDefinition, path: string = ''): void {\n for (const [key, field] of Object.entries(schema)) {\n const fieldPath = path ? `${path}.${key}` : key;\n\n if (isNestedSchema(field)) {\n validateSchemaDefinition(field, fieldPath);\n } else {\n const typeStr = field as string;\n const isOptional = typeStr.endsWith('?');\n let baseType = isOptional ? typeStr.slice(0, -1) : typeStr;\n baseType = baseType.replace('[]', '');\n\n if (!PRIMITIVE_TYPES.includes(baseType)) {\n throw new Error(`Unknown type '${typeStr}' at '${fieldPath}'`);\n }\n }\n }\n}\n\n/**\n * Compiles a schema definition into a validator.\n *\n * Used internally by the router to validate query parameters and request bodies.\n * Provides a Zod-compatible `safeParse` API for consistency.\n *\n * @param schema - The schema definition object\n * @returns A compiled schema with `safeParse` method\n *\n * @example\n * ```typescript\n * const userSchema = compileSchema({\n * name: 'string',\n * age: 'number',\n * email: 'string?',\n * });\n *\n * const result = userSchema.safeParse({ name: 'Alice', age: 30 });\n * if (result.success) {\n * console.log(result.data.name); // 'Alice'\n * } else {\n * console.log(result.error.flatten().fieldErrors);\n * }\n * ```\n */\nexport function compileSchema<T extends SchemaDefinition>(\n schema: T,\n): CompiledSchema<InferSchema<T>> {\n // Validate schema structure at compile time.\n validateSchemaDefinition(schema);\n\n return {\n safeParse(data: object): ValidationResult<InferSchema<T>> {\n const {\n success,\n data: result,\n errors,\n } = validateSchema(schema, data as Record<string, unknown>);\n\n if (!success) {\n return {\n success: false,\n error: { flatten: () => ({ fieldErrors: errors }) },\n };\n }\n\n return { success: true, data: result as InferSchema<T> };\n },\n };\n}\n","/**\n * @fileoverview Core type definitions for the routing library.\n *\n * **Path Convention:**\n * - Property names = URL path segments\n * - `$param` prefix = dynamic segment (`:param`)\n * - `get`, `post`, `put`, `patch`, `delete` = HTTP method handlers\n *\n * **Structure:**\n * - `router({ ... })` - groups paths, can have middleware\n * - `route({ query?, body?, handler })` - single method with schemas\n * - Bare functions as shorthand for handlers without schemas\n */\n\nimport type { Middleware } from './middleware.js';\nimport type { InferSchema, SchemaDefinition } from './schema.js';\n\n/** Standard HTTP methods. */\nexport type Method = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'options' | 'head';\n\n/** HTTP method property names. */\nexport const HTTP_METHODS = new Set<string>([\n 'get',\n 'post',\n 'put',\n 'patch',\n 'delete',\n 'options',\n 'head',\n]);\n\n/** Symbol to mark an object as a route definition. */\nexport const ROUTE_MARKER = Symbol.for('@fresho/router:route');\n\n/** Symbol to mark an object as a router. */\nexport const ROUTER_MARKER = Symbol.for('@fresho/router:router');\n\n/**\n * Brand symbol for Router type identification across module boundaries.\n * This enables TypeScript to recognize Router types even when generic\n * type information is lost during cross-package imports.\n */\ndeclare const ROUTER_BRAND: unique symbol;\n\n/** Brand type for Router identification in conditional types. */\nexport type RouterBrand = { readonly [ROUTER_BRAND]: true };\n\n/**\n * Extracts the parameter name from a `$param` property name.\n */\nexport type ExtractParamFromProperty<T extends string> = T extends `$${infer Param}`\n ? Param\n : never;\n\n/**\n * Checks if a property name is a dynamic parameter (starts with $).\n */\nexport type IsParamProperty<T extends string> = T extends `$${string}` ? true : false;\n\n/**\n * Collects path params from a property path array.\n */\nexport type CollectPathParams<Path extends string[]> = Path extends [\n infer Head extends string,\n ...infer Rest extends string[],\n]\n ? (Head extends `$${infer Param}` ? { [K in Param]: string } : {}) & CollectPathParams<Rest>\n : {};\n\n/** Execution context for background tasks (Cloudflare Workers compatible). */\nexport interface ExecutionContext {\n waitUntil(promise: Promise<unknown>): void;\n passThroughOnException(): void;\n}\n\n/**\n * Unified context object passed to handlers.\n */\nexport type Context<\n Q = unknown,\n B = unknown,\n P extends Record<string, string> = Record<string, string>,\n Ctx = {},\n> = {\n /** The original Request. */\n request: Request;\n /** URL path parameters from `$param` segments. */\n path: P;\n /** Query string parameters. */\n query: Q;\n /** Request body. */\n body: B;\n /** Execution context for background tasks. */\n executionCtx?: ExecutionContext;\n /** Environment bindings. */\n env: Ctx extends { env: infer E } ? E : unknown;\n} & Omit<Ctx, 'env'>;\n\n/**\n * Typed response wrapper that carries the response body type.\n */\nexport type TypedResponse<T> = Response & { __responseType?: T };\n\n/**\n * Handler function type.\n */\nexport type TypedHandler<\n Q = unknown,\n B = unknown,\n P extends Record<string, string> = Record<string, string>,\n R = unknown,\n Ctx = {},\n> = (\n context: Context<Q, B, P, Ctx>,\n) => R | Response | TypedResponse<R> | Promise<R | Response | TypedResponse<R>>;\n\n/**\n * Route definition - a single HTTP method handler with optional schemas.\n *\n * Use `route()` when you need query or body validation schemas.\n * For simple handlers without schemas, use bare functions in the router.\n *\n * @example\n * ```typescript\n * // With schemas\n * get: route({\n * query: { limit: 'number?' },\n * handler: async (c) => ({ items: [], limit: c.query.limit }),\n * })\n *\n * // Without schemas (bare function shorthand)\n * get: async (c) => ({ items: [] })\n * ```\n */\nexport interface RouteDefinition<\n Q extends SchemaDefinition = {},\n B extends SchemaDefinition = {},\n R = unknown,\n P extends Record<string, string> = Record<string, string>,\n Ctx = {},\n> {\n /** Query parameter schema for validation. */\n query?: Q;\n /** Request body schema for validation. */\n body?: B;\n /** Optional description for documentation. */\n description?: string;\n /** The request handler function. */\n handler: TypedHandler<InferSchema<Q>, InferSchema<B>, P, R, Ctx>;\n}\n\n/** A bare handler function (shorthand for route without schemas). */\nexport type BareHandler = (\n context: Context<unknown, unknown, Record<string, string>, unknown>,\n) => unknown;\n\n/** Checks if a value is a function. */\nexport function isFunction(value: unknown): value is BareHandler {\n return typeof value === 'function';\n}\n\n/** Checks if entry is a Route (marked with ROUTE_MARKER). */\nexport function isRoute(entry: unknown): entry is RouteDefinition {\n return typeof entry === 'object' && entry !== null && ROUTE_MARKER in entry;\n}\n\n/** Checks if entry is a Router (marked with ROUTER_MARKER). */\nexport function isRouter(entry: unknown): entry is Router<RouterRoutes> {\n return typeof entry === 'object' && entry !== null && ROUTER_MARKER in entry;\n}\n\n/**\n * A method handler - either a route with schemas or a bare function.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type MethodEntry = RouteDefinition<any, any, any, any, any> | BareHandler;\n\n/**\n * Router routes record.\n *\n * Can contain:\n * - `get`, `post`, `put`, `patch`, `delete` - method handlers (route or function)\n * - Other property names - nested routers (path segments)\n * - `$param` properties - dynamic path segments\n */\nexport type RouterRoutes = {\n get?: MethodEntry;\n post?: MethodEntry;\n put?: MethodEntry;\n patch?: MethodEntry;\n delete?: MethodEntry;\n options?: MethodEntry;\n head?: MethodEntry;\n} & {\n [key: string]: MethodEntry | Router<RouterRoutes>;\n};\n\n/** Handler function signature for the standalone router. */\nexport type FetchHandler = (\n request: Request,\n env?: unknown,\n ctx?: ExecutionContext,\n) => Response | Promise<Response>;\n\n/**\n * Router - groups paths and can have middleware.\n *\n * @example\n * ```typescript\n * const api = router({\n * // Method handlers for this path\n * get: async (c) => [...],\n * post: route({ body: { name: 'string' }, handler: async (c) => c.body }),\n *\n * // Nested path segments\n * users: router({\n * get: async (c) => [...],\n * $id: router({\n * get: async (c) => ({ id: c.path.id }),\n * }),\n * }),\n * });\n * ```\n */\nexport interface Router<T extends RouterRoutes> extends RouterBrand {\n /** Route definitions and nested routers. */\n readonly routes: T;\n /** Middleware applied to all routes in this router. */\n readonly middleware?: Middleware[];\n /**\n * Creates a fetch handler for use with Cloudflare Workers, Deno, Bun, etc.\n */\n handler(): FetchHandler;\n}\n","/**\n * @fileoverview HTTP request handler for @fresho/router.\n *\n * Builds paths from property names:\n * - Regular properties = path segments\n * - `$param` properties = dynamic segments (`:param`)\n * - `get`, `post`, etc. = method handlers\n */\n\nimport type { Middleware, MiddlewareContext } from './middleware.js';\nimport { runMiddleware } from './middleware.js';\nimport type { CompiledSchema } from './schema.js';\nimport { compileSchema } from './schema.js';\nimport type {\n ExecutionContext,\n FetchHandler,\n RouteDefinition,\n Router,\n RouterRoutes,\n} from './types.js';\nimport { HTTP_METHODS, isFunction, isRoute, isRouter } from './types.js';\n\n/** Compiled route with pattern matcher and handler. */\ninterface CompiledRoute {\n method: string;\n pattern: RegExp;\n paramNames: string[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (context: any) => unknown;\n middleware: Middleware[];\n querySchema?: CompiledSchema<unknown>;\n bodySchema?: CompiledSchema<unknown>;\n}\n\n/**\n * Escapes special regex characters in a string.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Converts a path with :param segments to a regex pattern.\n */\nfunction pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {\n const paramNames: string[] = [];\n const parts = path.split(/:(\\w+)/g);\n let regexStr = '';\n\n for (let i = 0; i < parts.length; i++) {\n if (i % 2 === 0) {\n regexStr += escapeRegex(parts[i]);\n } else {\n paramNames.push(parts[i]);\n regexStr += '([^/]+)';\n }\n }\n\n return {\n pattern: new RegExp(`^${regexStr}$`),\n paramNames,\n };\n}\n\n/**\n * Converts a property name to a path segment.\n * - `$param` becomes `:param`\n * - Other names become literal segments\n */\nfunction propertyToSegment(prop: string): string {\n if (prop.startsWith('$')) {\n return `:${prop.slice(1)}`;\n }\n return prop;\n}\n\n/** Collects and compiles all routes from a router tree. */\nfunction compileRoutes(\n routerDef: Router<RouterRoutes>,\n parentPath = '',\n parentMiddleware: Middleware[] = [],\n): CompiledRoute[] {\n const currentMiddleware = [...parentMiddleware, ...(routerDef.middleware || [])];\n const compiled: CompiledRoute[] = [];\n\n for (const [prop, entry] of Object.entries(routerDef.routes)) {\n // Check if entry is a nested router first (takes precedence over method name check).\n // This allows path segments named 'get', 'post', etc. when they contain routers.\n if (isRouter(entry)) {\n // Nested router - recurse with updated path.\n const segment = propertyToSegment(prop);\n const newPath = parentPath ? `${parentPath}/${segment}` : `/${segment}`;\n compiled.push(...compileRoutes(entry, newPath, currentMiddleware));\n } else if (HTTP_METHODS.has(prop)) {\n // Method handler (get, post, etc.) - only when entry is a route or function.\n const method = prop.toUpperCase();\n const path = parentPath || '/';\n const { pattern, paramNames } = pathToRegex(path);\n\n if (isFunction(entry)) {\n // Bare function handler.\n compiled.push({\n method,\n pattern,\n paramNames,\n handler: entry,\n middleware: currentMiddleware,\n });\n } else if (isRoute(entry)) {\n // Route with schemas.\n const routeDef = entry as RouteDefinition;\n compiled.push({\n method,\n pattern,\n paramNames,\n handler: routeDef.handler,\n middleware: currentMiddleware,\n querySchema: routeDef.query ? compileSchema(routeDef.query) : undefined,\n bodySchema: routeDef.body ? compileSchema(routeDef.body) : undefined,\n });\n }\n }\n }\n\n return compiled;\n}\n\n/**\n * Strips the body from a response for HEAD requests.\n */\nfunction stripBodyForHead(response: Response, isHead: boolean): Response {\n if (!isHead) return response;\n return new Response(null, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n}\n\n/**\n * Creates a fetch handler from a router definition.\n *\n * @example\n * ```typescript\n * const api = router({\n * health: router({\n * get: async () => ({ status: 'ok' }),\n * }),\n * users: router({\n * get: async () => [...],\n * $id: router({\n * get: async (c) => ({ id: c.path.id }),\n * }),\n * }),\n * });\n *\n * export default { fetch: api.handler() };\n * ```\n */\nexport function createHandler<T extends RouterRoutes>(routerDef: Router<T>): FetchHandler {\n const compiledRoutes = compileRoutes(routerDef);\n\n return async (request: Request, env?: unknown, ctx?: ExecutionContext): Promise<Response> => {\n const url = new URL(request.url);\n const method = request.method.toUpperCase();\n const isHead = method === 'HEAD';\n\n // Find matching route.\n for (const compiled of compiledRoutes) {\n const methodMatches =\n compiled.method === method ||\n method === 'OPTIONS' ||\n (method === 'HEAD' && compiled.method === 'GET');\n if (!methodMatches) continue;\n\n const match = compiled.pattern.exec(url.pathname);\n if (!match) continue;\n\n // Extract path parameters.\n const path: Record<string, string> = {};\n for (let i = 0; i < compiled.paramNames.length; i++) {\n path[compiled.paramNames[i]] = decodeURIComponent(match[i + 1]);\n }\n\n // Create middleware context.\n const context: MiddlewareContext = {\n request,\n path,\n query: {},\n body: {},\n env,\n executionCtx: ctx,\n };\n\n // Run middleware and handler.\n if (\n compiled.middleware.length > 0 ||\n compiled.handler ||\n compiled.querySchema ||\n compiled.bodySchema\n ) {\n const response = await runMiddleware(compiled.middleware, context, async () => {\n const routeId = `${compiled.method} ${url.pathname}`;\n\n // Validate query params.\n let query: unknown = {};\n if (compiled.querySchema) {\n const raw = Object.fromEntries(url.searchParams);\n const result = compiled.querySchema.safeParse(raw);\n if (!result.success) {\n return Response.json(\n {\n error: 'Invalid query parameters',\n route: routeId,\n details: result.error.flatten(),\n },\n { status: 400 },\n );\n }\n query = result.data;\n }\n\n // Validate body.\n let body: unknown = {};\n if (compiled.bodySchema && ['POST', 'PUT', 'PATCH'].includes(method)) {\n const raw = await request.json().catch(() => ({}));\n const result = compiled.bodySchema.safeParse(raw);\n if (!result.success) {\n return Response.json(\n { error: 'Invalid request body', route: routeId, details: result.error.flatten() },\n { status: 400 },\n );\n }\n body = result.data;\n }\n\n context.query = query;\n context.body = body;\n\n if (compiled.handler) {\n // Build handler context with middleware extensions.\n const handlerContext = {\n request,\n path,\n query,\n body,\n env,\n executionCtx: ctx,\n };\n\n // Copy middleware extensions.\n for (const key of Object.keys(context)) {\n if (!['request', 'path', 'query', 'body', 'env', 'executionCtx'].includes(key)) {\n (handlerContext as Record<string, unknown>)[key] = (\n context as Record<string, unknown>\n )[key];\n }\n }\n\n const result = await compiled.handler(handlerContext);\n if (result instanceof Response) {\n return result;\n }\n return Response.json(result);\n }\n\n return Response.json({});\n });\n return stripBodyForHead(response, isHead);\n }\n\n return stripBodyForHead(Response.json({}), isHead);\n }\n\n return Response.json({ error: 'Not Found' }, { status: 404 });\n };\n}\n","/**\n * @fileoverview Core route and router creation functions.\n *\n * ## Path Convention\n * - Property names = URL path segments\n * - `$param` prefix = dynamic segment (`:param`)\n * - `get`, `post`, `put`, `patch`, `delete` = HTTP method handlers\n *\n * ## Typing Best Practices\n *\n * **Schemas** (`query`/`body`) provide runtime validation AND type inference.\n * Use them for any input that needs validation:\n * ```typescript\n * get: route({\n * query: { limit: 'number?' },\n * handler: async (c) => c.query.limit, // limit: number | undefined\n * })\n * ```\n *\n * **Context** (`route.ctx<T>()`) provides types only (no validation).\n * Use it for path params, env bindings, and middleware-injected values:\n * ```typescript\n * interface MyContext {\n * path: { id: string };\n * env: { DB: Database };\n * user: { name: string }; // from auth middleware\n * }\n * get: route.ctx<MyContext>()({\n * handler: async (c) => ({ id: c.path.id, user: c.user.name }),\n * })\n * ```\n *\n * **Important:** Don't add explicit type annotations to handler parameters.\n * Let types flow from schemas and context:\n * ```typescript\n * // GOOD: types inferred from schema\n * handler: async (c) => c.query.limit\n *\n * // BAD: redundant type annotation\n * handler: async (c: { query: { limit?: number } }) => c.query.limit\n * ```\n */\n\nimport { createHandler } from './handler.js';\nimport type { Middleware } from './middleware.js';\nimport type { SchemaDefinition } from './schema.js';\nimport type { RouteDefinition, Router, RouterRoutes } from './types.js';\nimport { ROUTE_MARKER, ROUTER_MARKER } from './types.js';\n\n/**\n * Creates a route with query/body validation schemas.\n *\n * Use `route()` when you need request validation. For simple handlers\n * without validation, you can use bare functions directly in the router.\n *\n * @example\n * ```typescript\n * // Route with query validation\n * get: route({\n * query: { limit: 'number?', offset: 'number?' },\n * handler: async (c) => {\n * const items = await db.list(c.query.limit, c.query.offset);\n * return { items };\n * },\n * })\n *\n * // Route with body validation\n * post: route({\n * body: { name: 'string', email: 'string' },\n * handler: async (c) => {\n * const user = await db.create(c.body);\n * return user;\n * },\n * })\n *\n * // Simple handler without validation (bare function)\n * get: async () => ({ status: 'ok' })\n * ```\n */\nexport function route<\n const Q extends SchemaDefinition = {},\n const B extends SchemaDefinition = {},\n R = unknown,\n>(definition: RouteDefinition<Q, B, R, {}, {}>): RouteDefinition<Q, B, R, {}, {}> {\n return { ...definition, [ROUTE_MARKER]: true } as RouteDefinition<Q, B, R, {}, {}>;\n}\n\n/** Return type of route.ctx<T>() - callable and chainable. */\ninterface CtxBuilder<Ctx> {\n <const Q extends SchemaDefinition = {}, const B extends SchemaDefinition = {}, R = unknown>(\n definition: RouteDefinition<Q, B, R, {}, Ctx>,\n ): RouteDefinition<Q, B, R, {}, Ctx>;\n\n ctx<AdditionalCtx>(): CtxBuilder<Ctx & AdditionalCtx>;\n}\n\n/** Creates a chainable context builder. */\nfunction createCtxBuilder<Ctx>(): CtxBuilder<Ctx> {\n const builder = <\n const Q extends SchemaDefinition = {},\n const B extends SchemaDefinition = {},\n R = unknown,\n >(\n definition: RouteDefinition<Q, B, R, {}, Ctx>,\n ): RouteDefinition<Q, B, R, {}, Ctx> =>\n ({ ...definition, [ROUTE_MARKER]: true }) as RouteDefinition<Q, B, R, {}, Ctx>;\n\n builder.ctx = <AdditionalCtx>() => createCtxBuilder<Ctx & AdditionalCtx>();\n\n return builder as CtxBuilder<Ctx>;\n}\n\n/**\n * Creates a route with typed context for values not covered by schemas.\n *\n * Use `route.ctx<T>()` to type:\n * - **Path params**: `{ path: { id: string } }` for `$id` segments\n * - **Env bindings**: `{ env: { DB: Database } }` for runtime environment\n * - **Middleware values**: `{ user: User }` for auth middleware, etc.\n *\n * Context provides types only, not runtime validation. For validated inputs,\n * use `query` and `body` schemas instead.\n *\n * @example\n * ```typescript\n * interface AppContext {\n * path: { id: string };\n * env: { DB: D1Database };\n * user: { id: string }; // injected by auth middleware\n * }\n *\n * $id: router({\n * get: route.ctx<AppContext>()({\n * query: { include: 'string?' }, // schema for validation\n * handler: async (c) => {\n * // c.path.id, c.env.DB, c.user.id - from context\n * // c.query.include - from schema\n * return c.env.DB.get(c.path.id);\n * },\n * }),\n * })\n * ```\n */\nroute.ctx = <Ctx>(): CtxBuilder<Ctx> => createCtxBuilder<Ctx>();\n\n/**\n * Creates a router that groups paths and applies middleware.\n *\n * @example\n * ```typescript\n * const api = router({\n * health: router({\n * get: async () => ({ status: 'ok' }),\n * }),\n *\n * users: router({\n * // GET /users - list users\n * get: route({\n * query: { limit: 'number?' },\n * handler: async (c) => db.users.list(c.query.limit),\n * }),\n *\n * // POST /users - create user\n * post: route({\n * body: { name: 'string', email: 'string' },\n * handler: async (c) => db.users.create(c.body),\n * }),\n *\n * // /users/:id\n * $id: router({\n * get: async (c) => db.users.get(c.path.id),\n * delete: async (c) => db.users.delete(c.path.id),\n * }),\n * }),\n * });\n *\n * export default { fetch: api.handler() };\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function router<T extends RouterRoutes, M extends Middleware<any>[]>(\n routes: T,\n ...middleware: M\n): Router<T> {\n // Cast needed because RouterBrand uses a type-only symbol declaration.\n const self = {\n routes,\n middleware: middleware.length > 0 ? middleware : undefined,\n handler() {\n return createHandler(self);\n },\n } as unknown as Router<T>;\n // Add marker for runtime type checking.\n (self as unknown as Record<symbol, boolean>)[ROUTER_MARKER] = true;\n return self;\n}\n","/**\n * @fileoverview OpenAPI documentation generation.\n *\n * Generates OpenAPI 3.0 documentation from router definitions.\n *\n * **Path Convention:**\n * - Property names = URL path segments\n * - `$param` prefix = dynamic segment (`{param}` in OpenAPI)\n * - `get`, `post`, `put`, `patch`, `delete` = HTTP method handlers\n */\n\nimport type { SchemaDefinition } from './schema.js';\nimport type { RouteDefinition, Router, RouterRoutes } from './types.js';\nimport { HTTP_METHODS, isFunction, isRoute, isRouter } from './types.js';\n\n/** Collected route info for documentation. */\ninterface CollectedRoute {\n path: string;\n method: string;\n route: RouteDefinition | null;\n}\n\n/** Converts property name to path segment, handling $param convention. */\nfunction propertyToSegment(prop: string): string {\n if (prop.startsWith('$')) {\n return `{${prop.slice(1)}}`;\n }\n return prop;\n}\n\n/** Collects all routes from a router tree. */\nfunction collectRoutes(routerDef: Router<RouterRoutes>, parentPath = ''): CollectedRoute[] {\n const collectedRoutes: CollectedRoute[] = [];\n\n for (const [prop, entry] of Object.entries(routerDef.routes)) {\n // Check if this is a method handler.\n if (HTTP_METHODS.has(prop)) {\n const path = parentPath || '/';\n if (isFunction(entry)) {\n collectedRoutes.push({ path, method: prop, route: null });\n } else if (isRoute(entry)) {\n collectedRoutes.push({ path, method: prop, route: entry });\n }\n } else if (isRouter(entry)) {\n // Nested router - recurse with updated path.\n const segment = propertyToSegment(prop);\n const newPath = parentPath ? `${parentPath}/${segment}` : `/${segment}`;\n collectedRoutes.push(...collectRoutes(entry, newPath));\n }\n }\n\n return collectedRoutes;\n}\n\n/** Generates OpenAPI documentation from a router. */\nexport function generateDocs(config: {\n title: string;\n version: string;\n description?: string;\n router: Router<RouterRoutes>;\n}): object {\n const paths: Record<string, Record<string, object>> = {};\n const allRoutes = collectRoutes(config.router);\n\n for (const { path, method, route } of allRoutes) {\n if (!paths[path]) {\n paths[path] = {};\n }\n\n const operation: Record<string, unknown> = {\n description: route?.description,\n responses: {\n 200: { description: 'Successful response' },\n 400: { description: 'Validation error' },\n },\n };\n\n // Add query parameters (only primitive types supported in query strings).\n if (route?.query) {\n const querySchema = route.query as SchemaDefinition;\n operation.parameters = Object.entries(querySchema)\n .filter(([, type]) => typeof type === 'string')\n .map(([name, type]) => ({\n name,\n in: 'query',\n required: !(type as string).endsWith('?'),\n schema: { type: (type as string).replace('?', '').replace('[]', '') },\n }));\n }\n\n // Add request body.\n if (route?.body && ['post', 'put', 'patch'].includes(method)) {\n const bodySchema = route.body as SchemaDefinition;\n operation.requestBody = {\n required: true,\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: Object.fromEntries(\n Object.entries(bodySchema).map(([name, type]) => {\n if (typeof type === 'string') {\n return [name, { type: type.replace('?', '').replace('[]', '') }];\n }\n // Nested object - simplified representation.\n return [name, { type: 'object' }];\n }),\n ),\n required: Object.entries(bodySchema)\n .filter(([, type]) => typeof type === 'string' && !type.endsWith('?'))\n .map(([name]) => name),\n },\n },\n },\n };\n }\n\n paths[path][method] = operation;\n }\n\n return {\n openapi: '3.0.0',\n info: {\n title: config.title,\n version: config.version,\n description: config.description,\n },\n paths,\n };\n}\n","import type { Method } from './types.js';\n\n// =============================================================================\n// Proxy Types\n// =============================================================================\n\nconst PREFIXED_HTTP_METHODS = new Set([\n '$get',\n '$post',\n '$put',\n '$patch',\n '$delete',\n '$options',\n '$head',\n]);\n\n/**\n * Configuration for the recursive proxy.\n */\nexport interface RecursiveProxyOptions {\n /**\n * Callback to execute when a method is invoked.\n * @param segments - The path segments accumulated so far.\n * @param method - The HTTP method to execute (e.g. 'get', 'post').\n * @param options - The arguments passed to the method execution (e.g. { query, body }).\n */\n onRequest: (segments: string[], method: Method, options?: unknown) => Promise<unknown>;\n /**\n * Callback to build a URL string without executing a request, invoked on `$url`.\n * @param segments - The path segments accumulated so far.\n * @param options - The arguments passed to `$url` (e.g. { path, query }).\n */\n onBuildUrl?: (segments: string[], options?: unknown) => string;\n}\n\n/**\n * Creates a recursive proxy that accumulates path segments and traps method calls.\n *\n * Use cases:\n * 1. HTTP Client: Accumulates path segments and executes HTTP requests on method calls.\n * 2. Local Client: Accumulates path segments and invokes local handlers on method calls.\n *\n * @param config - The proxy configuration.\n * @param segments - The initial path segments (default: []).\n */\nexport function createRecursiveProxy(\n config: RecursiveProxyOptions,\n segments: string[] = [],\n): unknown {\n // The proxy target is a function so that 'apply' can be intercepted (for the root call or recursive calls).\n // For the root client, this allows `client()` to be called (implicit GET /).\n // For nested paths, it allows `client.some.path()` (implicit GET /some/path).\n const callable = (options?: unknown) => {\n return config.onRequest(segments, 'get', options);\n };\n\n return new Proxy(callable, {\n get(_target, prop) {\n if (typeof prop !== 'string') return undefined;\n\n // Handle standard function methods and toJSON\n // This prevents 'apply', 'call', etc. from being treated as path segments.\n if (\n prop === 'apply' ||\n prop === 'call' ||\n prop === 'bind' ||\n prop === 'toString' ||\n prop === 'toJSON'\n ) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (_target as any)[prop];\n }\n\n // The `$url` trap builds a URL string synchronously instead of firing a request.\n if (prop === '$url' && config.onBuildUrl) {\n return (options?: unknown) => config.onBuildUrl!(segments, options);\n }\n\n // Check if this property is a method trap.\n if (PREFIXED_HTTP_METHODS.has(prop)) {\n const method = prop.slice(1) as Method;\n return (options?: unknown) => {\n return config.onRequest(segments, method, options);\n };\n }\n\n // Otherwise = nested path segment.\n return createRecursiveProxy(config, [...segments, prop]);\n },\n\n // Trap direct function calls (e.g. client() or client.path())\n apply(_target, _thisArg, args) {\n return callable(args[0]);\n },\n });\n}\n","/**\n * @fileoverview HTTP client for @fresho/router.\n *\n * Provides a typed HTTP client using type-only imports.\n * URLs are built from property access chains:\n * - Property names = path segments (including `get`, `post`, etc.)\n * - `$param` = dynamic segment (substituted from path options)\n * - `$get()`, `$post()`, etc. = HTTP method execution\n * - Direct call `()` = implicit GET\n *\n * The `$` prefix distinguishes HTTP method execution from path navigation,\n * allowing routes with path segments named after HTTP methods (e.g., `/api/get`).\n *\n * @example\n * ```typescript\n * // Type-only import - no server code in bundle!\n * import type { api } from './server/api.js';\n * import { createHttpClient } from '@fresho/router';\n *\n * const client = createHttpClient<typeof api>({\n * baseUrl: 'https://api.example.com',\n * });\n *\n * // URLs from property chains:\n * await client.health(); // GET /health (implicit)\n * await client.users.$get(); // GET /users (explicit)\n * await client.users.$post({ body: {...} }); // POST /users\n * await client.users.$id({ path: { id: '123' } }); // GET /users/123\n *\n * // Navigate to path segments named after HTTP methods:\n * await client.api.get.$get(); // GET /api/get\n * await client.resources.delete.$get(); // GET /resources/delete\n * ```\n */\n\nimport { createRecursiveProxy } from './client-proxy.js';\nimport type { HeaderValue, RequestOptions, RouterClient } from './client-types.js';\nimport type { Method, Router, RouterRoutes } from './types.js';\n\n// =============================================================================\n// Configuration Types\n// =============================================================================\n\n/** HTTP client configuration. */\nexport interface HttpClientConfig {\n baseUrl?: string;\n headers?: Record<string, HeaderValue>;\n credentials?: RequestCredentials;\n /** Custom fetch implementation for interception or environment-specific behavior. */\n fetch?: typeof fetch;\n}\n\n/** Extra options for HTTP requests (headers). */\nexport interface HttpExtraOptions {\n headers?: HeadersInit;\n /** If true, returns the raw Response object instead of parsing as JSON. */\n raw?: boolean;\n}\n\n/** Options for an HTTP request. */\nexport type HttpRequestOptions = RequestOptions<HttpExtraOptions>;\n\n// =============================================================================\n// Client Type Construction\n// =============================================================================\n\n/** Top-level HTTP client type. */\nexport type HttpClient<T extends Router<RouterRoutes>> = {\n configure(config: HttpClientConfig): void;\n} & RouterClient<T['routes'], HttpExtraOptions>;\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\ninterface SharedConfig {\n current: HttpClientConfig;\n}\n\nconst BODY_METHODS = new Set(['post', 'put', 'patch']);\n\n/**\n * Creates a typed HTTP client.\n *\n * HTTP methods are prefixed with `$` to distinguish them from path segments:\n * - `.$get()`, `.$post()`, `.$put()`, `.$patch()`, `.$delete()` - execute HTTP methods\n * - `.propertyName` - navigate to path segment (works for any name including `get`, `post`, etc.)\n * - Direct call `()` - implicit GET request\n *\n * @example\n * ```typescript\n * import type { api } from './server';\n * import { createHttpClient } from '@fresho/router';\n *\n * const client = createHttpClient<typeof api>({\n * baseUrl: 'https://api.example.com',\n * headers: { Authorization: () => `Bearer ${getToken()}` },\n * });\n *\n * // GET /health (implicit)\n * await client.health();\n *\n * // GET /users (explicit)\n * await client.users.$get();\n *\n * // POST /users\n * await client.users.$post({ body: { name: 'Alice' } });\n *\n * // GET /users/123\n * await client.users.$id({ path: { id: '123' } });\n *\n * // Navigate through path segments named after HTTP methods:\n * // GET /api/get\n * await client.api.get.$get();\n * ```\n */\nexport function createHttpClient<T extends Router<RouterRoutes>>(\n config: HttpClientConfig = {},\n): HttpClient<T> {\n const sharedConfig: SharedConfig = { current: { ...config } };\n\n const client = {\n configure(newConfig: HttpClientConfig) {\n Object.assign(sharedConfig.current, newConfig);\n },\n } as HttpClient<T>;\n\n const proxy = createRecursiveProxy({\n onRequest: (segments, method, options) => {\n return executeRequest(\n sharedConfig,\n segments,\n method as Method,\n options as HttpRequestOptions,\n );\n },\n onBuildUrl: (segments, options) =>\n buildUrl(sharedConfig.current, segments, options as HttpRequestOptions | undefined),\n });\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === 'configure') return target.configure;\n // Delegate to recursive proxy for path building\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (proxy as any)[prop];\n },\n apply(_target, _thisArg, args) {\n // Direct call on root = GET /\n return executeRequest(sharedConfig, [], 'get', args[0] as HttpRequestOptions | undefined);\n },\n }) as HttpClient<T>;\n}\n\n/** Builds a URL path from segments, substituting $param with values. */\nfunction buildPath(segments: string[], pathParams?: Record<string, string>): string {\n const parts: string[] = [];\n\n for (const segment of segments) {\n if (segment.startsWith('$')) {\n const paramName = segment.slice(1);\n const value = pathParams?.[paramName];\n if (!value) {\n throw new Error(`Missing path parameter: ${paramName}`);\n }\n parts.push(encodeURIComponent(value));\n } else {\n parts.push(segment);\n }\n }\n\n return `/${parts.join('/')}`;\n}\n\n/**\n * Builds a URL string from path segments and options without executing a request.\n *\n * Returns an absolute URL when a `baseUrl` is configured, otherwise a relative\n * `pathname + search` string suitable for same-origin fetches.\n */\nfunction buildUrl(\n config: HttpClientConfig,\n segments: string[],\n options?: HttpRequestOptions,\n): string {\n const path = buildPath(segments, options?.path);\n\n // Resolve the base, falling back to the browser origin when available.\n const baseUrl = config.baseUrl || (typeof window !== 'undefined' ? window.location?.origin : '');\n const url = new URL(path, baseUrl || 'http://localhost');\n\n // Add query params, skipping undefined values.\n if (options?.query) {\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n // Use an absolute URL only when a baseUrl was configured.\n return config.baseUrl ? url.toString() : url.pathname + url.search;\n}\n\n/** Executes an HTTP request. */\nasync function executeRequest(\n sharedConfig: SharedConfig,\n segments: string[],\n method: Method,\n options?: HttpRequestOptions,\n): Promise<unknown> {\n const config = sharedConfig.current;\n\n // Build headers.\n const headers = new Headers();\n if (config.headers) {\n for (const [key, value] of Object.entries(config.headers)) {\n const resolved = typeof value === 'function' ? await value() : value;\n if (resolved != null) {\n headers.set(key, resolved);\n }\n }\n }\n if (options?.headers) {\n new Headers(options.headers).forEach((v, k) => headers.set(k, v));\n }\n\n // Build request.\n const init: RequestInit = {\n method: method.toUpperCase(),\n headers,\n credentials: config.credentials,\n };\n\n // Add body.\n if (options?.body && BODY_METHODS.has(method)) {\n headers.set('Content-Type', 'application/json');\n init.body = JSON.stringify(options.body);\n }\n\n // Execute.\n const fetchUrl = buildUrl(config, segments, options);\n const fetchFn = config.fetch || fetch;\n const response = await fetchFn(fetchUrl, init);\n\n if (!response.ok) {\n throw new Error((await response.text()) || response.statusText);\n }\n\n if (options?.raw) {\n return response;\n }\n\n return response.json();\n}\n","/**\n * @fileoverview Local client for @fresho/router.\n *\n * Provides a typed client that invokes route handlers directly without HTTP.\n * Uses the same API as createHttpClient for consistency.\n *\n * @example\n * ```typescript\n * import { createLocalClient } from '@fresho/router';\n * import { api } from './api.js';\n *\n * const client = createLocalClient(api);\n *\n * // Same API as HTTP client\n * await client.health();\n * await client.users();\n * await client.users.post({ body: { name: 'Alice' } });\n * await client.users.$id({ path: { id: '123' } });\n * ```\n */\n\nimport { createRecursiveProxy } from './client-proxy.js';\nimport type { RequestOptions, RouterClient } from './client-types.js';\nimport { compileSchema } from './schema.js';\nimport type { ExecutionContext, RouteDefinition, Router, RouterRoutes } from './types.js';\nimport { isFunction, isRoute, isRouter } from './types.js';\n\n// =============================================================================\n// Configuration Types\n// =============================================================================\n\n/** Local client configuration. */\nexport interface LocalClientConfig {\n env?: unknown;\n ctx?: ExecutionContext;\n}\n\n/** Options for a local client request. */\nexport type LocalRequestOptions = RequestOptions<LocalClientConfig>;\n\n// =============================================================================\n// Client Type Construction (mirrors http-client types)\n// =============================================================================\n\nexport type LocalClient<T extends Router<RouterRoutes>> = {\n configure(config: LocalClientConfig): void;\n} & RouterClient<T['routes'], LocalClientConfig>;\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\ninterface SharedConfig {\n current: LocalClientConfig;\n}\n\ninterface RouteInfo {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (context: any) => unknown;\n querySchema?: ReturnType<typeof compileSchema>;\n bodySchema?: ReturnType<typeof compileSchema>;\n}\n\n/**\n * Finds a method handler in a router tree.\n */\nfunction findHandler(\n routerDef: Router<RouterRoutes>,\n segments: string[],\n method: string,\n): RouteInfo | null {\n let current: Router<RouterRoutes> | undefined = routerDef;\n\n // Navigate through path segments.\n for (const segment of segments) {\n if (!current) return null;\n\n const entry: unknown = current.routes[segment];\n if (isRouter(entry)) {\n current = entry;\n } else {\n return null;\n }\n }\n\n if (!current) return null;\n\n // Find the method handler.\n const entry = current.routes[method];\n if (!entry) return null;\n\n if (isFunction(entry)) {\n return { handler: entry };\n }\n\n if (isRoute(entry)) {\n const routeDef = entry as RouteDefinition;\n return {\n handler: routeDef.handler,\n querySchema: routeDef.query ? compileSchema(routeDef.query) : undefined,\n bodySchema: routeDef.body ? compileSchema(routeDef.body) : undefined,\n };\n }\n\n return null;\n}\n\n/**\n * Collects path parameters from segments.\n */\nfunction collectPathParams(\n segments: string[],\n pathValues?: Record<string, string>,\n): Record<string, string> {\n const params: Record<string, string> = {};\n\n for (const segment of segments) {\n if (segment.startsWith('$')) {\n const paramName = segment.slice(1);\n const value = pathValues?.[paramName];\n if (!value) {\n throw new Error(`Missing path parameter: ${paramName}`);\n }\n params[paramName] = value;\n }\n }\n\n return params;\n}\n\n/**\n * Creates a typed local client from a router definition.\n *\n * @example\n * ```typescript\n * import { createLocalClient } from '@fresho/router';\n * import { api } from './api.js';\n *\n * const client = createLocalClient(api);\n * client.configure({ env: { DB: mockDatabase } });\n *\n * await client.health();\n * await client.users();\n * await client.users.post({ body: { name: 'Alice' } });\n * await client.users.$id({ path: { id: '123' } });\n * ```\n */\nexport function createLocalClient<T extends Router<RouterRoutes>>(routerDef: T): LocalClient<T> {\n const sharedConfig: SharedConfig = { current: {} };\n\n const client = {\n configure(config: LocalClientConfig) {\n Object.assign(sharedConfig.current, config);\n },\n } as LocalClient<T>;\n\n const proxy = createRecursiveProxy({\n onRequest: (segments, method, options) => {\n return invokeHandler(\n sharedConfig,\n routerDef,\n segments,\n method,\n options as LocalRequestOptions | undefined,\n );\n },\n onBuildUrl: (segments, options) =>\n buildLocalUrl(segments, options as LocalRequestOptions | undefined),\n });\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === 'configure') return target.configure;\n // Delegate to recursive proxy for path building\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (proxy as any)[prop];\n },\n apply(_target, _thisArg, args) {\n return invokeHandler(\n sharedConfig,\n routerDef,\n [],\n 'get',\n args[0] as LocalRequestOptions | undefined,\n );\n },\n }) as LocalClient<T>;\n}\n\n/**\n * Builds a relative URL string from path segments and options.\n *\n * The local client has no baseUrl, so it returns a relative `pathname + search`\n * string. Mirrors the synthetic URL construction used in {@link invokeHandler}.\n */\nfunction buildLocalUrl(segments: string[], options?: LocalRequestOptions): string {\n const pathParams = collectPathParams(segments, options?.path);\n\n // Substitute $param segments with their encoded values.\n const pathParts = segments.map((s) =>\n s.startsWith('$') ? encodeURIComponent(pathParams[s.slice(1)]) : s,\n );\n const url = new URL(`/${pathParts.join('/')}`, 'http://localhost');\n\n // Add query params, skipping undefined values.\n if (options?.query) {\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n return url.pathname + url.search;\n}\n\n/** Invokes a handler directly. */\nasync function invokeHandler(\n sharedConfig: SharedConfig,\n routerDef: Router<RouterRoutes>,\n segments: string[],\n method: string,\n options?: LocalRequestOptions,\n): Promise<unknown> {\n const config = sharedConfig.current;\n const routeInfo = findHandler(routerDef, segments, method);\n\n if (!routeInfo) {\n throw new Error(`No handler found for ${method.toUpperCase()} /${segments.join('/')}`);\n }\n\n // Collect path params.\n const pathParams = collectPathParams(segments, options?.path);\n\n // Validate query.\n let query: unknown = {};\n if (routeInfo.querySchema) {\n const result = routeInfo.querySchema.safeParse(options?.query ?? {});\n if (!result.success) {\n throw new Error(`Invalid query parameters: ${JSON.stringify(result.error.flatten())}`);\n }\n query = result.data;\n }\n\n // Validate body.\n let body: unknown = {};\n if (routeInfo.bodySchema) {\n const result = routeInfo.bodySchema.safeParse((options?.body ?? {}) as object);\n if (!result.success) {\n throw new Error(`Invalid request body: ${JSON.stringify(result.error.flatten())}`);\n }\n body = result.data;\n }\n\n // Build path for synthetic request URL.\n const pathParts = segments.map((s) =>\n s.startsWith('$') ? encodeURIComponent(pathParams[s.slice(1)] || '') : s,\n );\n const urlPath = `/${pathParts.join('/')}`;\n const url = new URL(urlPath, 'http://localhost');\n if (options?.query) {\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n\n // Build request.\n const requestInit: RequestInit = {\n method: method.toUpperCase(),\n };\n if (options?.body && ['post', 'put', 'patch'].includes(method)) {\n requestInit.headers = { 'Content-Type': 'application/json' };\n requestInit.body = JSON.stringify(options.body);\n }\n\n const request = new Request(url.toString(), requestInit);\n\n // Build context.\n const context = {\n request,\n path: pathParams,\n query,\n body,\n env: options?.env ?? config.env,\n executionCtx: options?.ctx ?? config.ctx,\n };\n\n // Call handler.\n const result = await routeInfo.handler(context);\n\n // Parse response if needed.\n if (result instanceof Response) {\n return result.json();\n }\n\n return result;\n}\n","/**\n * @fileoverview Streaming response utilities for @fresho/router.\n *\n * Provides helpers for Server-Sent Events (SSE) and newline-delimited JSON (NDJSON)\n * streaming responses. These are useful for real-time updates, progress indicators,\n * and streaming large datasets.\n *\n * Available utilities:\n * - {@link sseResponse} - Server-Sent Events for real-time browser updates\n * - {@link streamJsonLines} - NDJSON for streaming JSON objects line by line\n *\n * @example\n * ```typescript\n * import { router, sseResponse, streamJsonLines } from '@fresho/router';\n *\n * const api = router({\n * // SSE for real-time updates\n * events: router({\n * get: async () => sseResponse(async (send, close) => {\n * for (let i = 0; i < 10; i++) {\n * send({ event: 'tick', data: { count: i } });\n * await sleep(1000);\n * }\n * close();\n * }),\n * }),\n *\n * // NDJSON for streaming data\n * export: router({\n * get: async () => streamJsonLines(async (send, close) => {\n * for await (const row of database.stream()) {\n * send(row);\n * }\n * close();\n * }),\n * }),\n * });\n * ```\n */\n\n/**\n * Options for SSE response.\n */\nexport interface SSEOptions {\n /** Custom headers to include in the response. */\n headers?: HeadersInit;\n /** Interval in milliseconds to send keep-alive comments (default: 30000). */\n keepAliveInterval?: number;\n}\n\n/**\n * Server-Sent Event message structure.\n */\nexport interface SSEMessage {\n /** Event type (optional). */\n event?: string;\n /** Event data. */\n data: string | object;\n /** Event ID (optional). */\n id?: string;\n /** Retry interval hint for client (optional). */\n retry?: number;\n}\n\n/**\n * Formats an SSE message for transmission.\n */\nfunction formatSSEMessage(message: SSEMessage): string {\n const lines: string[] = [];\n\n if (message.event) {\n lines.push(`event: ${message.event}`);\n }\n\n if (message.id) {\n lines.push(`id: ${message.id}`);\n }\n\n if (message.retry !== undefined) {\n lines.push(`retry: ${message.retry}`);\n }\n\n // Data can be a string or object (will be JSON stringified).\n const data = typeof message.data === 'string' ? message.data : JSON.stringify(message.data);\n\n // Split data by newlines and prefix each line with 'data: '.\n for (const line of data.split('\\n')) {\n lines.push(`data: ${line}`);\n }\n\n return `${lines.join('\\n')}\\n\\n`;\n}\n\n/**\n * Creates a Server-Sent Events (SSE) response.\n *\n * @example\n * ```typescript\n * const handler = async (c) => {\n * return sseResponse(async (send, close) => {\n * for (let i = 0; i < 5; i++) {\n * await send({ data: { count: i } });\n * await new Promise(r => setTimeout(r, 1000));\n * }\n * close();\n * });\n * };\n * ```\n */\nexport function sseResponse(\n handler: (send: (message: SSEMessage) => void, close: () => void) => void | Promise<void>,\n options: SSEOptions = {},\n): Response {\n const encoder = new TextEncoder();\n let isClosed = false;\n\n const stream = new ReadableStream({\n async start(controller) {\n // Send function for the handler.\n const send = (message: SSEMessage) => {\n if (!isClosed) {\n controller.enqueue(encoder.encode(formatSSEMessage(message)));\n }\n };\n\n // Close function for the handler.\n const close = () => {\n if (!isClosed) {\n isClosed = true;\n controller.close();\n }\n };\n\n // Optional keep-alive interval.\n const keepAliveInterval = options.keepAliveInterval ?? 30_000;\n let keepAliveTimer: ReturnType<typeof setInterval> | undefined;\n\n if (keepAliveInterval > 0) {\n keepAliveTimer = setInterval(() => {\n if (!isClosed) {\n // Send a comment as keep-alive.\n controller.enqueue(encoder.encode(': keep-alive\\n\\n'));\n }\n }, keepAliveInterval);\n }\n\n try {\n await handler(send, close);\n } finally {\n if (keepAliveTimer) {\n clearInterval(keepAliveTimer);\n }\n if (!isClosed) {\n controller.close();\n }\n }\n },\n });\n\n const headers = new Headers(options.headers);\n headers.set('Content-Type', 'text/event-stream');\n headers.set('Cache-Control', 'no-cache');\n headers.set('Connection', 'keep-alive');\n\n return new Response(stream, { headers });\n}\n\n/**\n * Creates a streaming JSON response where each line is a JSON object.\n *\n * Useful for streaming large datasets or real-time data where each item\n * can be processed as it arrives (NDJSON format).\n *\n * @example\n * ```typescript\n * const handler = async (c) => {\n * return streamJsonLines(async (send, close) => {\n * for (const item of largeDataset) {\n * await send(item);\n * }\n * close();\n * });\n * };\n * ```\n */\nexport function streamJsonLines(\n handler: (send: (data: object) => void, close: () => void) => void | Promise<void>,\n options: { headers?: HeadersInit } = {},\n): Response {\n const encoder = new TextEncoder();\n let isClosed = false;\n\n const stream = new ReadableStream({\n async start(controller) {\n const send = (data: object) => {\n if (!isClosed) {\n controller.enqueue(encoder.encode(`${JSON.stringify(data)}\\n`));\n }\n };\n\n const close = () => {\n if (!isClosed) {\n isClosed = true;\n controller.close();\n }\n };\n\n try {\n await handler(send, close);\n } finally {\n if (!isClosed) {\n controller.close();\n }\n }\n },\n });\n\n const headers = new Headers(options.headers);\n headers.set('Content-Type', 'application/x-ndjson');\n\n return new Response(stream, { headers });\n}\n"],"mappings":"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"}
|
|
@@ -267,6 +267,55 @@ interface ContentTypeOptions {
|
|
|
267
267
|
*/
|
|
268
268
|
declare function contentType(options: ContentTypeOptions): Middleware;
|
|
269
269
|
//#endregion
|
|
270
|
+
//#region src/middleware/cookies.d.ts
|
|
271
|
+
/** Options for serializing a cookie into a `Set-Cookie` header value. */
|
|
272
|
+
interface CookieOptions {
|
|
273
|
+
/** Lifetime in seconds, serialized as `Max-Age`. */
|
|
274
|
+
maxAge?: number;
|
|
275
|
+
/** Absolute expiry, serialized as `Expires`. */
|
|
276
|
+
expires?: Date;
|
|
277
|
+
/** Restricts the cookie to a path prefix. */
|
|
278
|
+
path?: string;
|
|
279
|
+
/** Restricts the cookie to a domain. */
|
|
280
|
+
domain?: string;
|
|
281
|
+
/** Only send the cookie over HTTPS. */
|
|
282
|
+
secure?: boolean;
|
|
283
|
+
/** Hide the cookie from client-side JavaScript. */
|
|
284
|
+
httpOnly?: boolean;
|
|
285
|
+
/** Cross-site sending policy. */
|
|
286
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
287
|
+
}
|
|
288
|
+
/** Helper installed on the context to queue a cookie for the response. */
|
|
289
|
+
type SetCookie = (name: string, value: string, options?: CookieOptions) => void;
|
|
290
|
+
/**
|
|
291
|
+
* Parses a `Cookie` request header into a plain map of name to value.
|
|
292
|
+
*
|
|
293
|
+
* Returns an empty object for a missing or empty header. When a name appears
|
|
294
|
+
* more than once the first value wins, matching the common `cookie` package.
|
|
295
|
+
*/
|
|
296
|
+
declare function parseCookies(header: string | null): Record<string, string>;
|
|
297
|
+
/**
|
|
298
|
+
* Serializes a cookie into a `Set-Cookie` header value per RFC 6265.
|
|
299
|
+
*
|
|
300
|
+
* The value is URL-encoded; attributes are appended in a stable order.
|
|
301
|
+
*/
|
|
302
|
+
declare function serializeCookie(name: string, value: string, options?: CookieOptions): string;
|
|
303
|
+
/**
|
|
304
|
+
* Creates a middleware that parses request cookies and enables setting them.
|
|
305
|
+
*
|
|
306
|
+
* Merges the parsed cookies into `context.cookies` and installs a
|
|
307
|
+
* `context.setCookie(name, value, options)` helper. Each `setCookie` call adds
|
|
308
|
+
* a separate `Set-Cookie` header to the response (append, not overwrite), so a
|
|
309
|
+
* handler may set multiple cookies.
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```typescript
|
|
313
|
+
* const api = router('/api', routes, cookies());
|
|
314
|
+
* // in a handler: c.cookies['session']; c.setCookie('session', id, { httpOnly: true });
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
declare function cookies(): Middleware;
|
|
318
|
+
//#endregion
|
|
270
319
|
//#region src/middleware/cors.d.ts
|
|
271
320
|
/** CORS middleware configuration options. */
|
|
272
321
|
interface CorsOptions {
|
|
@@ -295,5 +344,5 @@ interface CorsOptions {
|
|
|
295
344
|
*/
|
|
296
345
|
declare function cors(options?: CorsOptions): Middleware;
|
|
297
346
|
//#endregion
|
|
298
|
-
export { type BasicAuthOptions, type BearerAuthOptions, type ContentTypeOptions, type CorsOptions, type ErrorHandlerOptions, HttpError, type JwtAlgorithm, type JwtAuthOptions, type JwtPayload, type LogInfo, type LoggerOptions, MemoryRateLimitStore, type RateLimitOptions, type RateLimitStore, type RequestIdOptions, type SignJwtOptions, type TimeoutOptions, type VerifyJwtOptions, basicAuth, bearerAuth, compose, contentType, cors, errorHandler, jwtAuth, jwtSign, jwtVerify, logger, rateLimit, requestId, timeout };
|
|
347
|
+
export { type BasicAuthOptions, type BearerAuthOptions, type ContentTypeOptions, type CookieOptions, type CorsOptions, type ErrorHandlerOptions, HttpError, type JwtAlgorithm, type JwtAuthOptions, type JwtPayload, type LogInfo, type LoggerOptions, MemoryRateLimitStore, type RateLimitOptions, type RateLimitStore, type RequestIdOptions, type SetCookie, type SignJwtOptions, type TimeoutOptions, type VerifyJwtOptions, basicAuth, bearerAuth, compose, contentType, cookies, cors, errorHandler, jwtAuth, jwtSign, jwtVerify, logger, parseCookies, rateLimit, requestId, serializeCookie, timeout };
|
|
299
348
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/middleware/auth.ts","../../src/middleware/common.ts","../../src/middleware/cors.ts"],"sourcesContent":[],"mappings":";;;;;AAwDA;AA6CiB,UAzEA,gBAAA,CAyEc;EAErB;EAAyC,KAAA,CAAA,EAAA,MAAA;EAAlB;EAA2B,MAAA,EAAA,CAAA,QAAA,EAAA,MAAA,EAAA,QAAA,EAAA,MAAA,EAAA,OAAA,EApE/C,iBAoE+C,EAAA,GAnErD,OAmEqD,CAnE7C,MAmE6C,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA,IAAA,CAAA,GAnEX,MAmEW,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA,IAAA;;;;;;AAqC5D;;;;;;AA6DA;;;;;;AAyBA;iBA1KgB,SAAA,UAAmB,mBAAmB;;UA6CrC;ECpEJ;EA8BI,MAAA,EDwCP,SCxCO,GAAA,CAAA,CAAmB,OAAA,EDwCH,iBCxCG,CDwCe,GCxCf,CAAA,EAAA,GDwCwB,SCxCxB,CAAA;EAEpB;;;;EAIsB,MAAA,EAAA,CAAA,OAAA,EDuClB,UCvCkB,EAAA,GDuCH,MCvCG,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA,IAAA;EAAsB;EAAmB,UAAA,CAAA,EDyChE,YCzCgE,EAAA;EAAR;EAAO,QAAA,CAAA,EAAA,CAAA,OAAA,ED2CvD,OC3CuD,EAAA,GAAA,MAAA,GAAA,IAAA;AAW9E;AA4CA;AAYA;AAkBA;AA2EA;;;;;AAOA;;;;;;AA8DA;;;;;;;;AAyBA;AAiEA;AAeA;AAkCiB,iBDzSD,OCySe,CAAA,MAAA,CAAA,CAAA,CAAA,CAAA,OAAA,EDzSY,cCySZ,CDzS2B,GCyS3B,CAAA,CAAA,EDzSkC,UCySlC,CDzS6C,GCyS7C,CAAA;AAe/B;AAiCiB,UD5RA,iBAAA,CC4RkB;EAiBnB;mCDzSH,sBACN,QAAQ,kCAAkC;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/middleware/auth.ts","../../src/middleware/common.ts","../../src/middleware/cookies.ts","../../src/middleware/cors.ts"],"sourcesContent":[],"mappings":"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"}
|
|
@@ -437,6 +437,87 @@ function contentType(options) {
|
|
|
437
437
|
};
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
//#endregion
|
|
441
|
+
//#region src/middleware/cookies.ts
|
|
442
|
+
/**
|
|
443
|
+
* Decodes a cookie value, falling back to the raw value if it is malformed.
|
|
444
|
+
*
|
|
445
|
+
* Cookies are URL-encoded on write (see {@link serializeCookie}); decoding here
|
|
446
|
+
* keeps round-trips lossless without throwing on hand-crafted values.
|
|
447
|
+
*/
|
|
448
|
+
function tryDecode(value) {
|
|
449
|
+
try {
|
|
450
|
+
return decodeURIComponent(value);
|
|
451
|
+
} catch {
|
|
452
|
+
return value;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Parses a `Cookie` request header into a plain map of name to value.
|
|
457
|
+
*
|
|
458
|
+
* Returns an empty object for a missing or empty header. When a name appears
|
|
459
|
+
* more than once the first value wins, matching the common `cookie` package.
|
|
460
|
+
*/
|
|
461
|
+
function parseCookies(header) {
|
|
462
|
+
const cookies$1 = {};
|
|
463
|
+
if (!header) return cookies$1;
|
|
464
|
+
for (const pair of header.split(";")) {
|
|
465
|
+
const separator = pair.indexOf("=");
|
|
466
|
+
if (separator < 0) continue;
|
|
467
|
+
const name = pair.slice(0, separator).trim();
|
|
468
|
+
if (name && !(name in cookies$1)) cookies$1[name] = tryDecode(pair.slice(separator + 1).trim());
|
|
469
|
+
}
|
|
470
|
+
return cookies$1;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Serializes a cookie into a `Set-Cookie` header value per RFC 6265.
|
|
474
|
+
*
|
|
475
|
+
* The value is URL-encoded; attributes are appended in a stable order.
|
|
476
|
+
*/
|
|
477
|
+
function serializeCookie(name, value, options = {}) {
|
|
478
|
+
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
479
|
+
if (options.maxAge !== void 0) parts.push(`Max-Age=${Math.trunc(options.maxAge)}`);
|
|
480
|
+
if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
481
|
+
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
482
|
+
if (options.path) parts.push(`Path=${options.path}`);
|
|
483
|
+
if (options.secure) parts.push("Secure");
|
|
484
|
+
if (options.httpOnly) parts.push("HttpOnly");
|
|
485
|
+
if (options.sameSite) parts.push(`SameSite=${options.sameSite[0].toUpperCase()}${options.sameSite.slice(1)}`);
|
|
486
|
+
return parts.join("; ");
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Creates a middleware that parses request cookies and enables setting them.
|
|
490
|
+
*
|
|
491
|
+
* Merges the parsed cookies into `context.cookies` and installs a
|
|
492
|
+
* `context.setCookie(name, value, options)` helper. Each `setCookie` call adds
|
|
493
|
+
* a separate `Set-Cookie` header to the response (append, not overwrite), so a
|
|
494
|
+
* handler may set multiple cookies.
|
|
495
|
+
*
|
|
496
|
+
* @example
|
|
497
|
+
* ```typescript
|
|
498
|
+
* const api = router('/api', routes, cookies());
|
|
499
|
+
* // in a handler: c.cookies['session']; c.setCookie('session', id, { httpOnly: true });
|
|
500
|
+
* ```
|
|
501
|
+
*/
|
|
502
|
+
function cookies() {
|
|
503
|
+
return async (context, next) => {
|
|
504
|
+
context.cookies = parseCookies(context.request.headers.get("Cookie"));
|
|
505
|
+
const pending = [];
|
|
506
|
+
context.setCookie = ((name, value, options) => {
|
|
507
|
+
pending.push(serializeCookie(name, value, options));
|
|
508
|
+
});
|
|
509
|
+
const response = await next();
|
|
510
|
+
if (pending.length === 0) return response;
|
|
511
|
+
const headers = new Headers(response.headers);
|
|
512
|
+
for (const cookie of pending) headers.append("Set-Cookie", cookie);
|
|
513
|
+
return new Response(response.body, {
|
|
514
|
+
status: response.status,
|
|
515
|
+
statusText: response.statusText,
|
|
516
|
+
headers
|
|
517
|
+
});
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
440
521
|
//#endregion
|
|
441
522
|
//#region src/middleware/cors.ts
|
|
442
523
|
/**
|
|
@@ -532,5 +613,5 @@ function cors(options = {}) {
|
|
|
532
613
|
}
|
|
533
614
|
|
|
534
615
|
//#endregion
|
|
535
|
-
export { HttpError, MemoryRateLimitStore, basicAuth, bearerAuth, compose, contentType, cors, errorHandler, jwtAuth, jwtSign, jwtVerify, logger, rateLimit, requestId, timeout };
|
|
616
|
+
export { HttpError, MemoryRateLimitStore, basicAuth, bearerAuth, compose, contentType, cookies, cors, errorHandler, jwtAuth, jwtSign, jwtVerify, logger, parseCookies, rateLimit, requestId, serializeCookie, timeout };
|
|
536
617
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["status: number","info: LogInfo","windowMs: number","contentType","config: Required<CorsOptions>","headers: Record<string, string>","response","headers"],"sources":["../../src/middleware/auth.ts","../../src/middleware/common.ts","../../src/middleware/cors.ts"],"sourcesContent":["/**\n * @fileoverview Authentication middleware for @fresho/router.\n *\n * Provides middleware wrappers for Basic HTTP, JWT, and Bearer token authentication.\n * The underlying authentication utilities are in `@fresho/router/auth`.\n */\n\nimport { parseBasicAuth } from '../auth/basic.js';\nimport { type JwtAlgorithm, type JwtPayload, type JwtSecret, jwtVerify } from '../auth/jwt.js';\nimport type { Middleware, MiddlewareContext } from '../middleware.js';\n\n// Re-export types that middleware users need\nexport type {\n JwtAlgorithm,\n JwtPayload,\n JwtSecret,\n SignJwtOptions,\n VerifyJwtOptions,\n} from '../auth/jwt.js';\n\n// Re-export utilities that are commonly used alongside middleware\nexport { jwtSign, jwtVerify } from '../auth/jwt.js';\n\n// =============================================================================\n// Basic Auth Middleware\n// =============================================================================\n\n/** Basic authentication middleware configuration. */\nexport interface BasicAuthOptions {\n /** Realm name for the authentication challenge. */\n realm?: string;\n /** Function to validate credentials. Returns claims to merge into context, or null to reject. */\n verify: (\n username: string,\n password: string,\n context: MiddlewareContext,\n ) => Promise<Record<string, unknown> | null> | Record<string, unknown> | null;\n}\n\n/**\n * Creates a Basic HTTP authentication middleware.\n *\n * @example\n * ```typescript\n * const adminRouter = router('/admin', routes,\n * basicAuth({\n * verify: async (username, password) => {\n * if (username === 'admin' && password === 'secret') {\n * return { user: { name: username } };\n * }\n * return null;\n * }\n * })\n * );\n * ```\n */\nexport function basicAuth(options: BasicAuthOptions): Middleware {\n const realm = options.realm || 'Secure Area';\n\n return async (context, next) => {\n const { request } = context;\n\n const credentials = parseBasicAuth(request.headers.get('Authorization'));\n if (!credentials) {\n return new Response('Unauthorized', {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Basic realm=\"${realm}\", charset=\"UTF-8\"`,\n },\n });\n }\n\n try {\n const claims = await options.verify(credentials.username, credentials.password, context);\n if (claims === null) {\n return new Response('Invalid credentials', {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Basic realm=\"${realm}\", charset=\"UTF-8\"`,\n },\n });\n }\n\n Object.assign(context, claims);\n return next();\n } catch (_error) {\n return new Response('Invalid credentials', {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Basic realm=\"${realm}\", charset=\"UTF-8\"`,\n },\n });\n }\n };\n}\n\n// =============================================================================\n// JWT Auth Middleware\n// =============================================================================\n\n/** JWT authentication middleware configuration. */\nexport interface JwtAuthOptions<Ctx = {}> {\n /** Secret key or function to get the secret from context. */\n secret: JwtSecret | ((context: MiddlewareContext<Ctx>) => JwtSecret);\n /**\n * Map JWT payload to context properties. Return null to reject the token.\n * The returned object is merged into the middleware context.\n */\n claims: (payload: JwtPayload) => Record<string, unknown> | null;\n /** Allowed algorithms (default: ['HS256']). */\n algorithms?: JwtAlgorithm[];\n /** Function to extract token from request (default: Authorization header or 'token' cookie). */\n getToken?: (request: Request) => string | null;\n}\n\n/**\n * Creates a JWT authentication middleware.\n *\n * @example\n * ```typescript\n * interface AppContext {\n * env: { JWT_SECRET: string };\n * user: { id: string; email: string };\n * }\n *\n * jwtAuth<AppContext>({\n * secret: (ctx) => ctx.env.JWT_SECRET, // ctx.env is typed\n * claims: (payload) => ({\n * user: { id: payload.sub, email: payload.email },\n * }),\n * })\n *\n * // In your route handler:\n * route.ctx<AppContext>()({\n * handler: async (c) => {\n * return { email: c.user.email }; // Fully typed\n * },\n * })\n * ```\n */\nexport function jwtAuth<Ctx = {}>(options: JwtAuthOptions<Ctx>): Middleware<Ctx> {\n const getToken =\n options.getToken ||\n ((req) => {\n const auth = req.headers.get('Authorization');\n if (auth?.startsWith('Bearer ')) {\n return auth.slice(7);\n }\n // Also check for token in cookie.\n const cookie = req.headers.get('Cookie');\n if (cookie) {\n const match = cookie.match(/(?:^|;\\s*)token=([^;]*)/);\n if (match) {\n return match[1];\n }\n }\n return null;\n });\n\n return async (context, next) => {\n const { request } = context;\n\n const token = getToken(request);\n if (!token) {\n return new Response('Missing token', {\n status: 401,\n headers: { 'WWW-Authenticate': 'Bearer' },\n });\n }\n\n try {\n const secret =\n typeof options.secret === 'function' ? options.secret(context) : options.secret;\n\n const payload = await jwtVerify(token, secret, { algorithms: options.algorithms });\n\n // Map payload to context properties.\n const claims = options.claims(payload);\n if (claims === null) {\n throw new Error('Token validation failed');\n }\n Object.assign(context, claims);\n\n return next();\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Invalid token';\n return new Response(message, {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Bearer error=\"invalid_token\", error_description=\"${message}\"`,\n },\n });\n }\n };\n}\n\n// =============================================================================\n// Bearer Token Auth Middleware (Simple)\n// =============================================================================\n\n/** Bearer token authentication options. */\nexport interface BearerAuthOptions {\n /** Function to validate the token. Returns claims to merge into context, or null to reject. */\n verify: (\n token: string,\n context: MiddlewareContext,\n ) => Promise<Record<string, unknown> | null> | Record<string, unknown> | null;\n}\n\n/**\n * Creates a simple bearer token authentication middleware.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes,\n * bearerAuth({\n * verify: async (token) => {\n * if (token === process.env.API_TOKEN) {\n * return { apiClient: 'trusted' };\n * }\n * return null;\n * }\n * })\n * );\n * ```\n */\nexport function bearerAuth(options: BearerAuthOptions): Middleware {\n return async (context, next) => {\n const { request } = context;\n\n const authHeader = request.headers.get('Authorization');\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Missing token', {\n status: 401,\n headers: { 'WWW-Authenticate': 'Bearer' },\n });\n }\n\n const token = authHeader.slice(7);\n const claims = await options.verify(token, context);\n\n if (claims === null) {\n return new Response('Invalid token', {\n status: 401,\n headers: { 'WWW-Authenticate': 'Bearer error=\"invalid_token\"' },\n });\n }\n\n Object.assign(context, claims);\n return next();\n };\n}\n","/**\n * @fileoverview Common middleware utilities for @fresho/router.\n *\n * Provides error handling, logging, rate limiting, and other utilities.\n */\n\nimport type { Middleware, MiddlewareContext } from '../middleware.js';\n\n// =============================================================================\n// Error Handler\n// =============================================================================\n\n/**\n * HTTP error with status code for use with errorHandler middleware.\n *\n * Throw this in route handlers to return a specific HTTP status code.\n * The errorHandler middleware will catch it and format the response.\n *\n * @example\n * ```typescript\n * import { HttpError } from '@fresho/router/middleware';\n *\n * const getUser = route({\n * method: 'get',\n * path: '/users/:id',\n * handler: async (c) => {\n * const user = await db.findUser(c.path.id);\n * if (!user) throw new HttpError('User not found', 404);\n * return user;\n * },\n * });\n * ```\n */\nexport class HttpError extends Error {\n constructor(\n message: string,\n public status: number,\n ) {\n super(message);\n this.name = 'HttpError';\n }\n}\n\n/**\n * Extracts HTTP status code from an error object.\n *\n * Supports errors with `status` or `statusCode` properties (common patterns\n * in frameworks like Express, Koa, Hono, and custom HTTP error classes).\n */\nfunction getErrorStatus(error: unknown): number {\n if (typeof error !== 'object' || error === null) {\n return 500;\n }\n if ('status' in error && typeof error.status === 'number') {\n return error.status;\n }\n if ('statusCode' in error && typeof error.statusCode === 'number') {\n return error.statusCode;\n }\n return 500;\n}\n\n/** Error handler middleware configuration. */\nexport interface ErrorHandlerOptions {\n /** Custom error logger. */\n log?: (error: Error, context: MiddlewareContext) => void | Promise<void>;\n /** Expose error details in response (dangerous in production). */\n expose?: boolean;\n /** Custom error response formatter. */\n formatter?: (error: Error, context: MiddlewareContext) => Response | Promise<Response>;\n}\n\n/**\n * Creates an error handling middleware that catches and formats errors.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, errorHandler({ expose: false }));\n * ```\n */\nexport function errorHandler(options: ErrorHandlerOptions = {}): Middleware {\n return async (context, next) => {\n try {\n return await next();\n } catch (error) {\n const err = error as Error;\n\n // Log error if logger provided\n if (options.log) {\n await options.log(err, context);\n } else {\n console.error(`Error in ${context.request.method} ${context.request.url}:`, err);\n }\n\n // Use custom formatter if provided\n if (options.formatter) {\n return await options.formatter(err, context);\n }\n\n // Default error response\n const message = options.expose ? err.message : 'Internal Server Error';\n const status = getErrorStatus(error);\n\n const errorBody = {\n error: message,\n ...(options.expose && {\n stack: err.stack,\n name: err.name,\n }),\n };\n\n return new Response(JSON.stringify(errorBody), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n };\n}\n\n// =============================================================================\n// Logger\n// =============================================================================\n\n/** Logger middleware configuration. */\nexport interface LoggerOptions {\n /** Custom logger function. */\n log?: (message: string) => void;\n /** Include request headers in log. */\n includeHeaders?: boolean;\n /** Include request body in log. */\n includeBody?: boolean;\n /** Custom log formatter. */\n formatter?: (info: LogInfo) => string;\n}\n\n/** Information logged for each request. */\nexport interface LogInfo {\n method: string;\n url: string;\n status: number;\n duration: number;\n headers?: Record<string, string>;\n body?: unknown;\n error?: Error;\n}\n\n/**\n * Creates a request/response logging middleware.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, logger({ includeHeaders: true }));\n * ```\n */\nexport function logger(options: LoggerOptions = {}): Middleware {\n const log = options.log || console.log;\n\n return async (context, next) => {\n const { request } = context;\n const start = Date.now();\n const url = new URL(request.url);\n\n // Collect request info\n const info: LogInfo = {\n method: request.method,\n url: `${url.pathname}${url.search}`,\n status: 0,\n duration: 0,\n };\n\n if (options.includeHeaders) {\n info.headers = Object.fromEntries(request.headers.entries());\n }\n\n if (options.includeBody && request.body) {\n try {\n const clonedRequest = request.clone();\n const text = await clonedRequest.text();\n try {\n info.body = JSON.parse(text);\n } catch {\n info.body = text;\n }\n } catch {\n info.body = '[Body not readable]';\n }\n }\n\n // Log request\n const requestMessage = options.formatter\n ? options.formatter({ ...info, status: 0, duration: 0 })\n : `→ ${info.method} ${info.url}`;\n log(requestMessage);\n\n try {\n // Process request\n const response = await next();\n\n // Log response\n info.status = response.status;\n info.duration = Date.now() - start;\n\n const responseMessage = options.formatter\n ? options.formatter(info)\n : `← ${info.status} ${response.statusText || 'OK'} (${info.duration}ms)`;\n log(responseMessage);\n\n return response;\n } catch (error) {\n // Log error\n info.status = 500;\n info.duration = Date.now() - start;\n info.error = error as Error;\n\n const errorMessage = options.formatter\n ? options.formatter(info)\n : `✗ ${info.status} Error: ${(error as Error).message} (${info.duration}ms)`;\n log(errorMessage);\n\n throw error;\n }\n };\n}\n\n// =============================================================================\n// Rate Limiter\n// =============================================================================\n\n/** Rate limit store interface. */\nexport interface RateLimitStore {\n increment(key: string): Promise<number>;\n decrement(key: string): Promise<void>;\n reset(key: string): Promise<void>;\n}\n\n/** In-memory rate limit store with periodic cleanup of expired entries. */\nexport class MemoryRateLimitStore implements RateLimitStore {\n private counts = new Map<string, { count: number; resetAt: number }>();\n private lastCleanup = Date.now();\n\n /** Interval between cleanups (default: 60 seconds). */\n private readonly cleanupIntervalMs: number;\n\n constructor(\n private windowMs: number,\n options?: { cleanupIntervalMs?: number },\n ) {\n // Default cleanup interval is the window duration or 60 seconds, whichever is larger.\n this.cleanupIntervalMs = options?.cleanupIntervalMs ?? Math.max(windowMs, 60_000);\n }\n\n async increment(key: string): Promise<number> {\n const now = Date.now();\n\n // Periodic cleanup to prevent memory leaks.\n if (now - this.lastCleanup > this.cleanupIntervalMs) {\n this.cleanup(now);\n }\n\n const record = this.counts.get(key);\n\n if (!record || now > record.resetAt) {\n this.counts.set(key, { count: 1, resetAt: now + this.windowMs });\n return 1;\n }\n\n record.count++;\n return record.count;\n }\n\n async decrement(key: string): Promise<void> {\n const record = this.counts.get(key);\n if (record && record.count > 0) {\n record.count--;\n }\n }\n\n async reset(key: string): Promise<void> {\n this.counts.delete(key);\n }\n\n /** Removes all expired entries from the store. */\n private cleanup(now: number): void {\n for (const [key, record] of this.counts) {\n if (now > record.resetAt) {\n this.counts.delete(key);\n }\n }\n this.lastCleanup = now;\n }\n\n /** Returns the current number of tracked keys (useful for testing). */\n get size(): number {\n return this.counts.size;\n }\n}\n\n/** Rate limiter middleware configuration. */\nexport interface RateLimitOptions {\n /** Time window in milliseconds. */\n windowMs?: number;\n /** Maximum requests per window. */\n max?: number;\n /** Function to generate a unique key for each client. */\n keyGenerator?: (context: MiddlewareContext) => string;\n /** Store for rate limit data. */\n store?: RateLimitStore;\n /** Skip successful requests from rate limit. */\n skipSuccessfulRequests?: boolean;\n /** Skip failed requests from rate limit. */\n skipFailedRequests?: boolean;\n /** Custom handler for rate limited requests. */\n handler?: (context: MiddlewareContext) => Response | Promise<Response>;\n}\n\n/**\n * Creates a rate limiting middleware.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, rateLimit({ max: 100, windowMs: 60000 }));\n * ```\n */\nexport function rateLimit(options: RateLimitOptions = {}): Middleware {\n const config = {\n windowMs: 60 * 1000, // 1 minute\n max: 100,\n keyGenerator: (ctx: MiddlewareContext) => {\n // Use IP address as default key\n const cfIp = ctx.request.headers.get('CF-Connecting-IP');\n const xForwardedFor = ctx.request.headers.get('X-Forwarded-For');\n const xRealIp = ctx.request.headers.get('X-Real-IP');\n return cfIp || xForwardedFor?.split(',')[0] || xRealIp || 'unknown';\n },\n ...options,\n };\n\n const store = config.store || new MemoryRateLimitStore(config.windowMs);\n\n return async (context, next) => {\n const key = config.keyGenerator(context);\n const current = await store.increment(key);\n\n if (current > config.max) {\n if (config.handler) {\n return await config.handler(context);\n }\n\n return new Response('Too many requests', {\n status: 429,\n headers: {\n 'Retry-After': String(Math.ceil(config.windowMs / 1000)),\n 'X-RateLimit-Limit': String(config.max),\n 'X-RateLimit-Remaining': '0',\n 'X-RateLimit-Reset': new Date(Date.now() + config.windowMs).toISOString(),\n },\n });\n }\n\n const response = await next();\n\n // Optionally don't count successful/failed requests\n if (\n (config.skipSuccessfulRequests && response.status < 400) ||\n (config.skipFailedRequests && response.status >= 400)\n ) {\n await store.decrement(key);\n }\n\n // Add rate limit headers\n const headers = new Headers(response.headers);\n headers.set('X-RateLimit-Limit', String(config.max));\n headers.set('X-RateLimit-Remaining', String(Math.max(0, config.max - current)));\n headers.set('X-RateLimit-Reset', new Date(Date.now() + config.windowMs).toISOString());\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n\n// =============================================================================\n// Request ID\n// =============================================================================\n\n/** Request ID middleware configuration. */\nexport interface RequestIdOptions {\n /** Header name for request ID. */\n headerName?: string;\n /** Function to generate request ID. */\n generator?: () => string;\n}\n\n/**\n * Creates a middleware that adds a unique request ID to each request.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, requestId({ headerName: 'X-Request-ID' }));\n * ```\n */\nexport function requestId(options: RequestIdOptions = {}): Middleware {\n const headerName = options.headerName || 'X-Request-ID';\n const generator = options.generator || (() => crypto.randomUUID());\n\n return async (context, next) => {\n // Check if request already has an ID\n let id = context.request.headers.get(headerName);\n if (!id) {\n id = generator();\n }\n\n // Add to context\n context.requestId = id;\n\n // Process request\n const response = await next();\n\n // Add request ID to response headers\n const headers = new Headers(response.headers);\n headers.set(headerName, id);\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n\n// =============================================================================\n// Timeout\n// =============================================================================\n\n/** Timeout middleware configuration. */\nexport interface TimeoutOptions {\n /** Timeout in milliseconds. */\n timeout: number;\n /** Custom timeout response. */\n message?: string;\n}\n\n/**\n * Creates a middleware that times out requests after a specified duration.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, timeout({ timeout: 5000 }));\n * ```\n */\nexport function timeout(options: TimeoutOptions): Middleware {\n return async (context, next) => {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n try {\n const responsePromise = next();\n const timeoutPromise = new Promise<Response>((_, reject) => {\n controller.signal.addEventListener('abort', () => {\n reject(new Error('Request timeout'));\n });\n });\n\n const response = await Promise.race([responsePromise, timeoutPromise]);\n clearTimeout(timeoutId);\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n if ((error as Error).message === 'Request timeout') {\n return new Response(options.message || 'Request timeout', {\n status: 408,\n });\n }\n throw error;\n }\n };\n}\n\n// =============================================================================\n// Content Type Validation\n// =============================================================================\n\n/** Content type validation options. */\nexport interface ContentTypeOptions {\n /** Expected content types. */\n types: string[];\n /** Skip validation for these methods. */\n skipMethods?: string[];\n /** Custom error message. */\n message?: string;\n}\n\n/**\n * Creates a middleware that validates request content type.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, contentType({ types: ['application/json'] }));\n * ```\n */\nexport function contentType(options: ContentTypeOptions): Middleware {\n const skipMethods = new Set(\n options.skipMethods?.map((m) => m.toUpperCase()) || ['GET', 'HEAD', 'OPTIONS'],\n );\n\n return async (context, next) => {\n if (skipMethods.has(context.request.method.toUpperCase())) {\n return next();\n }\n\n const contentType = context.request.headers.get('Content-Type');\n if (!contentType) {\n return new Response(options.message || 'Missing Content-Type header', {\n status: 415,\n });\n }\n\n const matches = options.types.some((type) => contentType.includes(type));\n if (!matches) {\n return new Response(\n options.message || `Unsupported Media Type. Expected: ${options.types.join(', ')}`,\n { status: 415 },\n );\n }\n\n return next();\n };\n}\n","/**\n * @fileoverview CORS middleware for @fresho/router.\n *\n * Provides Cross-Origin Resource Sharing support with configurable options.\n */\n\nimport type { Middleware } from '../middleware.js';\n\n/** CORS middleware configuration options. */\nexport interface CorsOptions {\n /** Allowed origins. Can be string, array, regex, or function. */\n origin?: string | string[] | RegExp | ((origin: string) => boolean);\n /** Allowed HTTP methods. */\n methods?: string[];\n /** Allowed request headers. */\n allowedHeaders?: string[];\n /** Headers to expose to the browser. */\n exposedHeaders?: string[];\n /** Allow credentials (cookies, authorization headers). */\n credentials?: boolean;\n /** Max age for preflight cache (seconds). */\n maxAge?: number;\n /** Pass the CORS preflight response to the next handler. */\n preflightContinue?: boolean;\n}\n\n/**\n * Determines if an origin is allowed based on the configuration.\n */\nfunction getAllowedOrigin(\n requestOrigin: string | null,\n configOrigin: CorsOptions['origin'],\n): string {\n if (!requestOrigin) {\n return '*';\n }\n\n if (!configOrigin || configOrigin === '*') {\n return '*';\n }\n\n if (typeof configOrigin === 'string') {\n return configOrigin === requestOrigin ? configOrigin : 'false';\n }\n\n if (Array.isArray(configOrigin)) {\n return configOrigin.includes(requestOrigin) ? requestOrigin : 'false';\n }\n\n if (configOrigin instanceof RegExp) {\n return configOrigin.test(requestOrigin) ? requestOrigin : 'false';\n }\n\n if (typeof configOrigin === 'function') {\n return configOrigin(requestOrigin) ? requestOrigin : 'false';\n }\n\n return 'false';\n}\n\n/**\n * Creates a CORS middleware with the specified options.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, cors({ origin: 'https://example.com', credentials: true }));\n * ```\n */\nexport function cors(options: CorsOptions = {}): Middleware {\n const config: Required<CorsOptions> = {\n origin: '*',\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n exposedHeaders: [],\n credentials: false,\n maxAge: 86400,\n preflightContinue: false,\n ...options,\n };\n\n return async (context, next) => {\n const { request } = context;\n const requestOrigin = request.headers.get('Origin');\n const allowedOrigin = getAllowedOrigin(requestOrigin, config.origin);\n\n // Handle preflight request\n if (request.method === 'OPTIONS') {\n const headers: Record<string, string> = {\n 'Access-Control-Allow-Origin': allowedOrigin,\n 'Access-Control-Allow-Methods': config.methods.join(', '),\n Vary: 'Origin',\n };\n\n if (config.allowedHeaders.length > 0) {\n const requestHeaders = request.headers.get('Access-Control-Request-Headers');\n if (requestHeaders) {\n // Allow requested headers if they're in the allowed list\n const requested = requestHeaders.split(',').map((h) => h.trim().toLowerCase());\n const allowed = config.allowedHeaders.map((h) => h.toLowerCase());\n const toAllow = requested.filter((h) => allowed.includes(h));\n if (toAllow.length > 0) {\n headers['Access-Control-Allow-Headers'] = toAllow.join(', ');\n }\n } else {\n headers['Access-Control-Allow-Headers'] = config.allowedHeaders.join(', ');\n }\n }\n\n if (config.exposedHeaders.length > 0) {\n headers['Access-Control-Expose-Headers'] = config.exposedHeaders.join(', ');\n }\n\n if (config.credentials) {\n headers['Access-Control-Allow-Credentials'] = 'true';\n }\n\n if (config.maxAge) {\n headers['Access-Control-Max-Age'] = String(config.maxAge);\n }\n\n const response = new Response(null, {\n status: 204,\n headers,\n });\n\n if (config.preflightContinue) {\n // Continue to next middleware/handler even for preflight\n const nextResponse = await next();\n // Merge headers from next response\n const mergedHeaders = new Headers(nextResponse.headers);\n Object.entries(headers).forEach(([key, value]) => {\n mergedHeaders.set(key, value);\n });\n return new Response(nextResponse.body, {\n status: nextResponse.status,\n statusText: nextResponse.statusText,\n headers: mergedHeaders,\n });\n }\n\n return response;\n }\n\n // Handle actual request\n const response = await next();\n\n // Add CORS headers to response\n const headers = new Headers(response.headers);\n headers.set('Access-Control-Allow-Origin', allowedOrigin);\n headers.set('Vary', 'Origin');\n\n if (config.credentials && allowedOrigin !== '*') {\n headers.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (config.exposedHeaders.length > 0) {\n headers.set('Access-Control-Expose-Headers', config.exposedHeaders.join(', '));\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["status: number","info: LogInfo","windowMs: number","contentType","cookies: Record<string, string>","cookies","pending: string[]","config: Required<CorsOptions>","headers: Record<string, string>","response","headers"],"sources":["../../src/middleware/auth.ts","../../src/middleware/common.ts","../../src/middleware/cookies.ts","../../src/middleware/cors.ts"],"sourcesContent":["/**\n * @fileoverview Authentication middleware for @fresho/router.\n *\n * Provides middleware wrappers for Basic HTTP, JWT, and Bearer token authentication.\n * The underlying authentication utilities are in `@fresho/router/auth`.\n */\n\nimport { parseBasicAuth } from '../auth/basic.js';\nimport { type JwtAlgorithm, type JwtPayload, type JwtSecret, jwtVerify } from '../auth/jwt.js';\nimport type { Middleware, MiddlewareContext } from '../middleware.js';\n\n// Re-export types that middleware users need\nexport type {\n JwtAlgorithm,\n JwtPayload,\n JwtSecret,\n SignJwtOptions,\n VerifyJwtOptions,\n} from '../auth/jwt.js';\n\n// Re-export utilities that are commonly used alongside middleware\nexport { jwtSign, jwtVerify } from '../auth/jwt.js';\n\n// =============================================================================\n// Basic Auth Middleware\n// =============================================================================\n\n/** Basic authentication middleware configuration. */\nexport interface BasicAuthOptions {\n /** Realm name for the authentication challenge. */\n realm?: string;\n /** Function to validate credentials. Returns claims to merge into context, or null to reject. */\n verify: (\n username: string,\n password: string,\n context: MiddlewareContext,\n ) => Promise<Record<string, unknown> | null> | Record<string, unknown> | null;\n}\n\n/**\n * Creates a Basic HTTP authentication middleware.\n *\n * @example\n * ```typescript\n * const adminRouter = router('/admin', routes,\n * basicAuth({\n * verify: async (username, password) => {\n * if (username === 'admin' && password === 'secret') {\n * return { user: { name: username } };\n * }\n * return null;\n * }\n * })\n * );\n * ```\n */\nexport function basicAuth(options: BasicAuthOptions): Middleware {\n const realm = options.realm || 'Secure Area';\n\n return async (context, next) => {\n const { request } = context;\n\n const credentials = parseBasicAuth(request.headers.get('Authorization'));\n if (!credentials) {\n return new Response('Unauthorized', {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Basic realm=\"${realm}\", charset=\"UTF-8\"`,\n },\n });\n }\n\n try {\n const claims = await options.verify(credentials.username, credentials.password, context);\n if (claims === null) {\n return new Response('Invalid credentials', {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Basic realm=\"${realm}\", charset=\"UTF-8\"`,\n },\n });\n }\n\n Object.assign(context, claims);\n return next();\n } catch (_error) {\n return new Response('Invalid credentials', {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Basic realm=\"${realm}\", charset=\"UTF-8\"`,\n },\n });\n }\n };\n}\n\n// =============================================================================\n// JWT Auth Middleware\n// =============================================================================\n\n/** JWT authentication middleware configuration. */\nexport interface JwtAuthOptions<Ctx = {}> {\n /** Secret key or function to get the secret from context. */\n secret: JwtSecret | ((context: MiddlewareContext<Ctx>) => JwtSecret);\n /**\n * Map JWT payload to context properties. Return null to reject the token.\n * The returned object is merged into the middleware context.\n */\n claims: (payload: JwtPayload) => Record<string, unknown> | null;\n /** Allowed algorithms (default: ['HS256']). */\n algorithms?: JwtAlgorithm[];\n /** Function to extract token from request (default: Authorization header or 'token' cookie). */\n getToken?: (request: Request) => string | null;\n}\n\n/**\n * Creates a JWT authentication middleware.\n *\n * @example\n * ```typescript\n * interface AppContext {\n * env: { JWT_SECRET: string };\n * user: { id: string; email: string };\n * }\n *\n * jwtAuth<AppContext>({\n * secret: (ctx) => ctx.env.JWT_SECRET, // ctx.env is typed\n * claims: (payload) => ({\n * user: { id: payload.sub, email: payload.email },\n * }),\n * })\n *\n * // In your route handler:\n * route.ctx<AppContext>()({\n * handler: async (c) => {\n * return { email: c.user.email }; // Fully typed\n * },\n * })\n * ```\n */\nexport function jwtAuth<Ctx = {}>(options: JwtAuthOptions<Ctx>): Middleware<Ctx> {\n const getToken =\n options.getToken ||\n ((req) => {\n const auth = req.headers.get('Authorization');\n if (auth?.startsWith('Bearer ')) {\n return auth.slice(7);\n }\n // Also check for token in cookie.\n const cookie = req.headers.get('Cookie');\n if (cookie) {\n const match = cookie.match(/(?:^|;\\s*)token=([^;]*)/);\n if (match) {\n return match[1];\n }\n }\n return null;\n });\n\n return async (context, next) => {\n const { request } = context;\n\n const token = getToken(request);\n if (!token) {\n return new Response('Missing token', {\n status: 401,\n headers: { 'WWW-Authenticate': 'Bearer' },\n });\n }\n\n try {\n const secret =\n typeof options.secret === 'function' ? options.secret(context) : options.secret;\n\n const payload = await jwtVerify(token, secret, { algorithms: options.algorithms });\n\n // Map payload to context properties.\n const claims = options.claims(payload);\n if (claims === null) {\n throw new Error('Token validation failed');\n }\n Object.assign(context, claims);\n\n return next();\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Invalid token';\n return new Response(message, {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Bearer error=\"invalid_token\", error_description=\"${message}\"`,\n },\n });\n }\n };\n}\n\n// =============================================================================\n// Bearer Token Auth Middleware (Simple)\n// =============================================================================\n\n/** Bearer token authentication options. */\nexport interface BearerAuthOptions {\n /** Function to validate the token. Returns claims to merge into context, or null to reject. */\n verify: (\n token: string,\n context: MiddlewareContext,\n ) => Promise<Record<string, unknown> | null> | Record<string, unknown> | null;\n}\n\n/**\n * Creates a simple bearer token authentication middleware.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes,\n * bearerAuth({\n * verify: async (token) => {\n * if (token === process.env.API_TOKEN) {\n * return { apiClient: 'trusted' };\n * }\n * return null;\n * }\n * })\n * );\n * ```\n */\nexport function bearerAuth(options: BearerAuthOptions): Middleware {\n return async (context, next) => {\n const { request } = context;\n\n const authHeader = request.headers.get('Authorization');\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Missing token', {\n status: 401,\n headers: { 'WWW-Authenticate': 'Bearer' },\n });\n }\n\n const token = authHeader.slice(7);\n const claims = await options.verify(token, context);\n\n if (claims === null) {\n return new Response('Invalid token', {\n status: 401,\n headers: { 'WWW-Authenticate': 'Bearer error=\"invalid_token\"' },\n });\n }\n\n Object.assign(context, claims);\n return next();\n };\n}\n","/**\n * @fileoverview Common middleware utilities for @fresho/router.\n *\n * Provides error handling, logging, rate limiting, and other utilities.\n */\n\nimport type { Middleware, MiddlewareContext } from '../middleware.js';\n\n// =============================================================================\n// Error Handler\n// =============================================================================\n\n/**\n * HTTP error with status code for use with errorHandler middleware.\n *\n * Throw this in route handlers to return a specific HTTP status code.\n * The errorHandler middleware will catch it and format the response.\n *\n * @example\n * ```typescript\n * import { HttpError } from '@fresho/router/middleware';\n *\n * const getUser = route({\n * method: 'get',\n * path: '/users/:id',\n * handler: async (c) => {\n * const user = await db.findUser(c.path.id);\n * if (!user) throw new HttpError('User not found', 404);\n * return user;\n * },\n * });\n * ```\n */\nexport class HttpError extends Error {\n constructor(\n message: string,\n public status: number,\n ) {\n super(message);\n this.name = 'HttpError';\n }\n}\n\n/**\n * Extracts HTTP status code from an error object.\n *\n * Supports errors with `status` or `statusCode` properties (common patterns\n * in frameworks like Express, Koa, Hono, and custom HTTP error classes).\n */\nfunction getErrorStatus(error: unknown): number {\n if (typeof error !== 'object' || error === null) {\n return 500;\n }\n if ('status' in error && typeof error.status === 'number') {\n return error.status;\n }\n if ('statusCode' in error && typeof error.statusCode === 'number') {\n return error.statusCode;\n }\n return 500;\n}\n\n/** Error handler middleware configuration. */\nexport interface ErrorHandlerOptions {\n /** Custom error logger. */\n log?: (error: Error, context: MiddlewareContext) => void | Promise<void>;\n /** Expose error details in response (dangerous in production). */\n expose?: boolean;\n /** Custom error response formatter. */\n formatter?: (error: Error, context: MiddlewareContext) => Response | Promise<Response>;\n}\n\n/**\n * Creates an error handling middleware that catches and formats errors.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, errorHandler({ expose: false }));\n * ```\n */\nexport function errorHandler(options: ErrorHandlerOptions = {}): Middleware {\n return async (context, next) => {\n try {\n return await next();\n } catch (error) {\n const err = error as Error;\n\n // Log error if logger provided\n if (options.log) {\n await options.log(err, context);\n } else {\n console.error(`Error in ${context.request.method} ${context.request.url}:`, err);\n }\n\n // Use custom formatter if provided\n if (options.formatter) {\n return await options.formatter(err, context);\n }\n\n // Default error response\n const message = options.expose ? err.message : 'Internal Server Error';\n const status = getErrorStatus(error);\n\n const errorBody = {\n error: message,\n ...(options.expose && {\n stack: err.stack,\n name: err.name,\n }),\n };\n\n return new Response(JSON.stringify(errorBody), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n };\n}\n\n// =============================================================================\n// Logger\n// =============================================================================\n\n/** Logger middleware configuration. */\nexport interface LoggerOptions {\n /** Custom logger function. */\n log?: (message: string) => void;\n /** Include request headers in log. */\n includeHeaders?: boolean;\n /** Include request body in log. */\n includeBody?: boolean;\n /** Custom log formatter. */\n formatter?: (info: LogInfo) => string;\n}\n\n/** Information logged for each request. */\nexport interface LogInfo {\n method: string;\n url: string;\n status: number;\n duration: number;\n headers?: Record<string, string>;\n body?: unknown;\n error?: Error;\n}\n\n/**\n * Creates a request/response logging middleware.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, logger({ includeHeaders: true }));\n * ```\n */\nexport function logger(options: LoggerOptions = {}): Middleware {\n const log = options.log || console.log;\n\n return async (context, next) => {\n const { request } = context;\n const start = Date.now();\n const url = new URL(request.url);\n\n // Collect request info\n const info: LogInfo = {\n method: request.method,\n url: `${url.pathname}${url.search}`,\n status: 0,\n duration: 0,\n };\n\n if (options.includeHeaders) {\n info.headers = Object.fromEntries(request.headers.entries());\n }\n\n if (options.includeBody && request.body) {\n try {\n const clonedRequest = request.clone();\n const text = await clonedRequest.text();\n try {\n info.body = JSON.parse(text);\n } catch {\n info.body = text;\n }\n } catch {\n info.body = '[Body not readable]';\n }\n }\n\n // Log request\n const requestMessage = options.formatter\n ? options.formatter({ ...info, status: 0, duration: 0 })\n : `→ ${info.method} ${info.url}`;\n log(requestMessage);\n\n try {\n // Process request\n const response = await next();\n\n // Log response\n info.status = response.status;\n info.duration = Date.now() - start;\n\n const responseMessage = options.formatter\n ? options.formatter(info)\n : `← ${info.status} ${response.statusText || 'OK'} (${info.duration}ms)`;\n log(responseMessage);\n\n return response;\n } catch (error) {\n // Log error\n info.status = 500;\n info.duration = Date.now() - start;\n info.error = error as Error;\n\n const errorMessage = options.formatter\n ? options.formatter(info)\n : `✗ ${info.status} Error: ${(error as Error).message} (${info.duration}ms)`;\n log(errorMessage);\n\n throw error;\n }\n };\n}\n\n// =============================================================================\n// Rate Limiter\n// =============================================================================\n\n/** Rate limit store interface. */\nexport interface RateLimitStore {\n increment(key: string): Promise<number>;\n decrement(key: string): Promise<void>;\n reset(key: string): Promise<void>;\n}\n\n/** In-memory rate limit store with periodic cleanup of expired entries. */\nexport class MemoryRateLimitStore implements RateLimitStore {\n private counts = new Map<string, { count: number; resetAt: number }>();\n private lastCleanup = Date.now();\n\n /** Interval between cleanups (default: 60 seconds). */\n private readonly cleanupIntervalMs: number;\n\n constructor(\n private windowMs: number,\n options?: { cleanupIntervalMs?: number },\n ) {\n // Default cleanup interval is the window duration or 60 seconds, whichever is larger.\n this.cleanupIntervalMs = options?.cleanupIntervalMs ?? Math.max(windowMs, 60_000);\n }\n\n async increment(key: string): Promise<number> {\n const now = Date.now();\n\n // Periodic cleanup to prevent memory leaks.\n if (now - this.lastCleanup > this.cleanupIntervalMs) {\n this.cleanup(now);\n }\n\n const record = this.counts.get(key);\n\n if (!record || now > record.resetAt) {\n this.counts.set(key, { count: 1, resetAt: now + this.windowMs });\n return 1;\n }\n\n record.count++;\n return record.count;\n }\n\n async decrement(key: string): Promise<void> {\n const record = this.counts.get(key);\n if (record && record.count > 0) {\n record.count--;\n }\n }\n\n async reset(key: string): Promise<void> {\n this.counts.delete(key);\n }\n\n /** Removes all expired entries from the store. */\n private cleanup(now: number): void {\n for (const [key, record] of this.counts) {\n if (now > record.resetAt) {\n this.counts.delete(key);\n }\n }\n this.lastCleanup = now;\n }\n\n /** Returns the current number of tracked keys (useful for testing). */\n get size(): number {\n return this.counts.size;\n }\n}\n\n/** Rate limiter middleware configuration. */\nexport interface RateLimitOptions {\n /** Time window in milliseconds. */\n windowMs?: number;\n /** Maximum requests per window. */\n max?: number;\n /** Function to generate a unique key for each client. */\n keyGenerator?: (context: MiddlewareContext) => string;\n /** Store for rate limit data. */\n store?: RateLimitStore;\n /** Skip successful requests from rate limit. */\n skipSuccessfulRequests?: boolean;\n /** Skip failed requests from rate limit. */\n skipFailedRequests?: boolean;\n /** Custom handler for rate limited requests. */\n handler?: (context: MiddlewareContext) => Response | Promise<Response>;\n}\n\n/**\n * Creates a rate limiting middleware.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, rateLimit({ max: 100, windowMs: 60000 }));\n * ```\n */\nexport function rateLimit(options: RateLimitOptions = {}): Middleware {\n const config = {\n windowMs: 60 * 1000, // 1 minute\n max: 100,\n keyGenerator: (ctx: MiddlewareContext) => {\n // Use IP address as default key\n const cfIp = ctx.request.headers.get('CF-Connecting-IP');\n const xForwardedFor = ctx.request.headers.get('X-Forwarded-For');\n const xRealIp = ctx.request.headers.get('X-Real-IP');\n return cfIp || xForwardedFor?.split(',')[0] || xRealIp || 'unknown';\n },\n ...options,\n };\n\n const store = config.store || new MemoryRateLimitStore(config.windowMs);\n\n return async (context, next) => {\n const key = config.keyGenerator(context);\n const current = await store.increment(key);\n\n if (current > config.max) {\n if (config.handler) {\n return await config.handler(context);\n }\n\n return new Response('Too many requests', {\n status: 429,\n headers: {\n 'Retry-After': String(Math.ceil(config.windowMs / 1000)),\n 'X-RateLimit-Limit': String(config.max),\n 'X-RateLimit-Remaining': '0',\n 'X-RateLimit-Reset': new Date(Date.now() + config.windowMs).toISOString(),\n },\n });\n }\n\n const response = await next();\n\n // Optionally don't count successful/failed requests\n if (\n (config.skipSuccessfulRequests && response.status < 400) ||\n (config.skipFailedRequests && response.status >= 400)\n ) {\n await store.decrement(key);\n }\n\n // Add rate limit headers\n const headers = new Headers(response.headers);\n headers.set('X-RateLimit-Limit', String(config.max));\n headers.set('X-RateLimit-Remaining', String(Math.max(0, config.max - current)));\n headers.set('X-RateLimit-Reset', new Date(Date.now() + config.windowMs).toISOString());\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n\n// =============================================================================\n// Request ID\n// =============================================================================\n\n/** Request ID middleware configuration. */\nexport interface RequestIdOptions {\n /** Header name for request ID. */\n headerName?: string;\n /** Function to generate request ID. */\n generator?: () => string;\n}\n\n/**\n * Creates a middleware that adds a unique request ID to each request.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, requestId({ headerName: 'X-Request-ID' }));\n * ```\n */\nexport function requestId(options: RequestIdOptions = {}): Middleware {\n const headerName = options.headerName || 'X-Request-ID';\n const generator = options.generator || (() => crypto.randomUUID());\n\n return async (context, next) => {\n // Check if request already has an ID\n let id = context.request.headers.get(headerName);\n if (!id) {\n id = generator();\n }\n\n // Add to context\n context.requestId = id;\n\n // Process request\n const response = await next();\n\n // Add request ID to response headers\n const headers = new Headers(response.headers);\n headers.set(headerName, id);\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n\n// =============================================================================\n// Timeout\n// =============================================================================\n\n/** Timeout middleware configuration. */\nexport interface TimeoutOptions {\n /** Timeout in milliseconds. */\n timeout: number;\n /** Custom timeout response. */\n message?: string;\n}\n\n/**\n * Creates a middleware that times out requests after a specified duration.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, timeout({ timeout: 5000 }));\n * ```\n */\nexport function timeout(options: TimeoutOptions): Middleware {\n return async (context, next) => {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n try {\n const responsePromise = next();\n const timeoutPromise = new Promise<Response>((_, reject) => {\n controller.signal.addEventListener('abort', () => {\n reject(new Error('Request timeout'));\n });\n });\n\n const response = await Promise.race([responsePromise, timeoutPromise]);\n clearTimeout(timeoutId);\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n if ((error as Error).message === 'Request timeout') {\n return new Response(options.message || 'Request timeout', {\n status: 408,\n });\n }\n throw error;\n }\n };\n}\n\n// =============================================================================\n// Content Type Validation\n// =============================================================================\n\n/** Content type validation options. */\nexport interface ContentTypeOptions {\n /** Expected content types. */\n types: string[];\n /** Skip validation for these methods. */\n skipMethods?: string[];\n /** Custom error message. */\n message?: string;\n}\n\n/**\n * Creates a middleware that validates request content type.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, contentType({ types: ['application/json'] }));\n * ```\n */\nexport function contentType(options: ContentTypeOptions): Middleware {\n const skipMethods = new Set(\n options.skipMethods?.map((m) => m.toUpperCase()) || ['GET', 'HEAD', 'OPTIONS'],\n );\n\n return async (context, next) => {\n if (skipMethods.has(context.request.method.toUpperCase())) {\n return next();\n }\n\n const contentType = context.request.headers.get('Content-Type');\n if (!contentType) {\n return new Response(options.message || 'Missing Content-Type header', {\n status: 415,\n });\n }\n\n const matches = options.types.some((type) => contentType.includes(type));\n if (!matches) {\n return new Response(\n options.message || `Unsupported Media Type. Expected: ${options.types.join(', ')}`,\n { status: 415 },\n );\n }\n\n return next();\n };\n}\n","/**\n * @fileoverview Cookie parsing/serialization and the cookies() middleware.\n *\n * Reads the request `Cookie` header into `context.cookies` and installs a\n * `context.setCookie` helper that serializes per RFC 6265 and appends\n * `Set-Cookie` headers to the outgoing response. Dependency-free, Web APIs only.\n */\n\nimport type { Middleware } from '../middleware.js';\n\n/** Options for serializing a cookie into a `Set-Cookie` header value. */\nexport interface CookieOptions {\n /** Lifetime in seconds, serialized as `Max-Age`. */\n maxAge?: number;\n /** Absolute expiry, serialized as `Expires`. */\n expires?: Date;\n /** Restricts the cookie to a path prefix. */\n path?: string;\n /** Restricts the cookie to a domain. */\n domain?: string;\n /** Only send the cookie over HTTPS. */\n secure?: boolean;\n /** Hide the cookie from client-side JavaScript. */\n httpOnly?: boolean;\n /** Cross-site sending policy. */\n sameSite?: 'strict' | 'lax' | 'none';\n}\n\n/** Helper installed on the context to queue a cookie for the response. */\nexport type SetCookie = (name: string, value: string, options?: CookieOptions) => void;\n\n/**\n * Decodes a cookie value, falling back to the raw value if it is malformed.\n *\n * Cookies are URL-encoded on write (see {@link serializeCookie}); decoding here\n * keeps round-trips lossless without throwing on hand-crafted values.\n */\nfunction tryDecode(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\n/**\n * Parses a `Cookie` request header into a plain map of name to value.\n *\n * Returns an empty object for a missing or empty header. When a name appears\n * more than once the first value wins, matching the common `cookie` package.\n */\nexport function parseCookies(header: string | null): Record<string, string> {\n const cookies: Record<string, string> = {};\n if (!header) {\n return cookies;\n }\n\n // Split on the standard \"; \" delimiter, tolerating arbitrary surrounding space.\n for (const pair of header.split(';')) {\n const separator = pair.indexOf('=');\n if (separator < 0) {\n continue;\n }\n\n // Split on the first \"=\" only so values may themselves contain \"=\".\n const name = pair.slice(0, separator).trim();\n if (name && !(name in cookies)) {\n cookies[name] = tryDecode(pair.slice(separator + 1).trim());\n }\n }\n\n return cookies;\n}\n\n/**\n * Serializes a cookie into a `Set-Cookie` header value per RFC 6265.\n *\n * The value is URL-encoded; attributes are appended in a stable order.\n */\nexport function serializeCookie(name: string, value: string, options: CookieOptions = {}): string {\n const parts = [`${name}=${encodeURIComponent(value)}`];\n\n // Attributes, in the conventional Set-Cookie order.\n if (options.maxAge !== undefined) {\n parts.push(`Max-Age=${Math.trunc(options.maxAge)}`);\n }\n if (options.expires) {\n parts.push(`Expires=${options.expires.toUTCString()}`);\n }\n if (options.domain) {\n parts.push(`Domain=${options.domain}`);\n }\n if (options.path) {\n parts.push(`Path=${options.path}`);\n }\n if (options.secure) {\n parts.push('Secure');\n }\n if (options.httpOnly) {\n parts.push('HttpOnly');\n }\n if (options.sameSite) {\n // Capitalize to match the spec's token casing (Strict / Lax / None).\n parts.push(`SameSite=${options.sameSite[0].toUpperCase()}${options.sameSite.slice(1)}`);\n }\n\n return parts.join('; ');\n}\n\n/**\n * Creates a middleware that parses request cookies and enables setting them.\n *\n * Merges the parsed cookies into `context.cookies` and installs a\n * `context.setCookie(name, value, options)` helper. Each `setCookie` call adds\n * a separate `Set-Cookie` header to the response (append, not overwrite), so a\n * handler may set multiple cookies.\n *\n * @example\n * ```typescript\n * const api = router('/api', routes, cookies());\n * // in a handler: c.cookies['session']; c.setCookie('session', id, { httpOnly: true });\n * ```\n */\nexport function cookies(): Middleware {\n return async (context, next) => {\n // Read side: parse the incoming Cookie header once.\n context.cookies = parseCookies(context.request.headers.get('Cookie'));\n\n // Write side: queue serialized cookies for the outgoing response.\n const pending: string[] = [];\n context.setCookie = ((name, value, options) => {\n pending.push(serializeCookie(name, value, options));\n }) satisfies SetCookie;\n\n const response = await next();\n\n // Leave the response untouched when no cookies were set.\n if (pending.length === 0) {\n return response;\n }\n\n // Append each cookie as its own Set-Cookie header.\n const headers = new Headers(response.headers);\n for (const cookie of pending) {\n headers.append('Set-Cookie', cookie);\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n","/**\n * @fileoverview CORS middleware for @fresho/router.\n *\n * Provides Cross-Origin Resource Sharing support with configurable options.\n */\n\nimport type { Middleware } from '../middleware.js';\n\n/** CORS middleware configuration options. */\nexport interface CorsOptions {\n /** Allowed origins. Can be string, array, regex, or function. */\n origin?: string | string[] | RegExp | ((origin: string) => boolean);\n /** Allowed HTTP methods. */\n methods?: string[];\n /** Allowed request headers. */\n allowedHeaders?: string[];\n /** Headers to expose to the browser. */\n exposedHeaders?: string[];\n /** Allow credentials (cookies, authorization headers). */\n credentials?: boolean;\n /** Max age for preflight cache (seconds). */\n maxAge?: number;\n /** Pass the CORS preflight response to the next handler. */\n preflightContinue?: boolean;\n}\n\n/**\n * Determines if an origin is allowed based on the configuration.\n */\nfunction getAllowedOrigin(\n requestOrigin: string | null,\n configOrigin: CorsOptions['origin'],\n): string {\n if (!requestOrigin) {\n return '*';\n }\n\n if (!configOrigin || configOrigin === '*') {\n return '*';\n }\n\n if (typeof configOrigin === 'string') {\n return configOrigin === requestOrigin ? configOrigin : 'false';\n }\n\n if (Array.isArray(configOrigin)) {\n return configOrigin.includes(requestOrigin) ? requestOrigin : 'false';\n }\n\n if (configOrigin instanceof RegExp) {\n return configOrigin.test(requestOrigin) ? requestOrigin : 'false';\n }\n\n if (typeof configOrigin === 'function') {\n return configOrigin(requestOrigin) ? requestOrigin : 'false';\n }\n\n return 'false';\n}\n\n/**\n * Creates a CORS middleware with the specified options.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, cors({ origin: 'https://example.com', credentials: true }));\n * ```\n */\nexport function cors(options: CorsOptions = {}): Middleware {\n const config: Required<CorsOptions> = {\n origin: '*',\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n exposedHeaders: [],\n credentials: false,\n maxAge: 86400,\n preflightContinue: false,\n ...options,\n };\n\n return async (context, next) => {\n const { request } = context;\n const requestOrigin = request.headers.get('Origin');\n const allowedOrigin = getAllowedOrigin(requestOrigin, config.origin);\n\n // Handle preflight request\n if (request.method === 'OPTIONS') {\n const headers: Record<string, string> = {\n 'Access-Control-Allow-Origin': allowedOrigin,\n 'Access-Control-Allow-Methods': config.methods.join(', '),\n Vary: 'Origin',\n };\n\n if (config.allowedHeaders.length > 0) {\n const requestHeaders = request.headers.get('Access-Control-Request-Headers');\n if (requestHeaders) {\n // Allow requested headers if they're in the allowed list\n const requested = requestHeaders.split(',').map((h) => h.trim().toLowerCase());\n const allowed = config.allowedHeaders.map((h) => h.toLowerCase());\n const toAllow = requested.filter((h) => allowed.includes(h));\n if (toAllow.length > 0) {\n headers['Access-Control-Allow-Headers'] = toAllow.join(', ');\n }\n } else {\n headers['Access-Control-Allow-Headers'] = config.allowedHeaders.join(', ');\n }\n }\n\n if (config.exposedHeaders.length > 0) {\n headers['Access-Control-Expose-Headers'] = config.exposedHeaders.join(', ');\n }\n\n if (config.credentials) {\n headers['Access-Control-Allow-Credentials'] = 'true';\n }\n\n if (config.maxAge) {\n headers['Access-Control-Max-Age'] = String(config.maxAge);\n }\n\n const response = new Response(null, {\n status: 204,\n headers,\n });\n\n if (config.preflightContinue) {\n // Continue to next middleware/handler even for preflight\n const nextResponse = await next();\n // Merge headers from next response\n const mergedHeaders = new Headers(nextResponse.headers);\n Object.entries(headers).forEach(([key, value]) => {\n mergedHeaders.set(key, value);\n });\n return new Response(nextResponse.body, {\n status: nextResponse.status,\n statusText: nextResponse.statusText,\n headers: mergedHeaders,\n });\n }\n\n return response;\n }\n\n // Handle actual request\n const response = await next();\n\n // Add CORS headers to response\n const headers = new Headers(response.headers);\n headers.set('Access-Control-Allow-Origin', allowedOrigin);\n headers.set('Vary', 'Origin');\n\n if (config.credentials && allowedOrigin !== '*') {\n headers.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (config.exposedHeaders.length > 0) {\n headers.set('Access-Control-Expose-Headers', config.exposedHeaders.join(', '));\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware-Bxq0LHLS.d.mts","names":[],"sources":["../src/schema.ts","../src/types.ts","../src/middleware.ts"],"sourcesContent":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"middleware-Bxq0LHLS.d.mts","names":[],"sources":["../src/schema.ts","../src/types.ts","../src/middleware.ts"],"sourcesContent":[],"mappings":"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"}
|
package/docs/middleware.md
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
- [Request ID](#requestid)
|
|
19
19
|
- [Timeout](#timeout)
|
|
20
20
|
- [Content Type Validation](#contenttype)
|
|
21
|
+
- [Cookies](#cookies)
|
|
21
22
|
- [Middleware Utilities](#middleware-utilities)
|
|
22
23
|
- [compose](#compose)
|
|
23
24
|
|
|
@@ -422,6 +423,48 @@ Returns 415 Unsupported Media Type on mismatch.
|
|
|
422
423
|
|
|
423
424
|
---
|
|
424
425
|
|
|
426
|
+
### cookies
|
|
427
|
+
|
|
428
|
+
Parses request cookies into the context and lets handlers set response cookies.
|
|
429
|
+
Serialization follows RFC 6265 and uses only Web APIs (no third-party `cookie`
|
|
430
|
+
dependency).
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
cookies()
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Sets `ctx.cookies` (a `Record<string, string>`) from the request `Cookie`
|
|
437
|
+
header and installs `ctx.setCookie(name, value, options?)`. Each `setCookie`
|
|
438
|
+
call appends a separate `Set-Cookie` header, so a handler may set several
|
|
439
|
+
cookies.
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
import { cookies, type SetCookie } from '@fresho/router/middleware';
|
|
443
|
+
|
|
444
|
+
const api = router(
|
|
445
|
+
{
|
|
446
|
+
session: router({
|
|
447
|
+
get: route.ctx<{ cookies: Record<string, string>; setCookie: SetCookie }>()({
|
|
448
|
+
handler: async (c) => {
|
|
449
|
+
const userId = c.cookies['user'] ?? crypto.randomUUID();
|
|
450
|
+
c.setCookie('user', userId, { httpOnly: true, sameSite: 'lax', path: '/' });
|
|
451
|
+
return { userId };
|
|
452
|
+
},
|
|
453
|
+
}),
|
|
454
|
+
}),
|
|
455
|
+
},
|
|
456
|
+
cookies(),
|
|
457
|
+
);
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
`setCookie` options: `maxAge` (seconds → `Max-Age`), `expires` (`Date` →
|
|
461
|
+
`Expires`), `path`, `domain`, `secure`, `httpOnly`, and `sameSite`
|
|
462
|
+
(`'strict' | 'lax' | 'none'`). The standalone `parseCookies(header)` and
|
|
463
|
+
`serializeCookie(name, value, options?)` helpers are also exported for use
|
|
464
|
+
outside the middleware.
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
425
468
|
## Middleware Utilities
|
|
426
469
|
|
|
427
470
|
### compose
|