@fresho/router 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["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/client-proxy.ts","../src/client-response.ts","../src/http-client.ts","../src/local-client.ts","../src/streaming.ts"],"sourcesContent":["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","const STREAMING_CONTENT_TYPES = new Set([\n 'text/event-stream',\n 'application/x-ndjson',\n 'application/ndjson',\n]);\n\n/**\n * Parses ordinary client responses as JSON while preserving streaming bodies.\n */\nexport async function parseClientResponse(response: Response, raw = false): Promise<unknown> {\n const contentType = response.headers.get('Content-Type')?.split(';', 1)[0].trim().toLowerCase();\n\n if (raw || (contentType && STREAMING_CONTENT_TYPES.has(contentType))) {\n return response;\n }\n\n return response.json();\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 { parseClientResponse } from './client-response.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 *\n * @example Router built by a dependency-injected factory:\n * ```typescript\n * import type { createServer } from './server';\n * import { createHttpClient } from '@fresho/router';\n *\n * type Api = ReturnType<typeof createServer>['api'];\n *\n * const client = createHttpClient<Api>({\n * baseUrl: 'https://api.example.com',\n * });\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 return parseClientResponse(response, options?.raw);\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 { parseClientResponse } from './client-response.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 parseClientResponse(result);\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 * interface Events {\n * tick: { count: number };\n * }\n *\n * const api = router({\n * // SSE for real-time updates\n * events: router({\n * get: async () => sseResponse<Events>(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 * Data supported by an SSE message.\n */\nexport type SSEData = string | object;\n\n/**\n * Untyped map of SSE event names to their payloads.\n *\n * Define a narrower map and pass it to {@link sseResponse} to type-check event\n * names and their corresponding data.\n */\nexport type SSEEventMap = Record<string, SSEData>;\n\ntype ValidSSEEventMap<Events> = {\n [Name in keyof Events]: SSEData;\n};\n\ninterface SSEMessageMetadata {\n /** Event ID (optional). */\n id?: string;\n /** Retry interval hint for client (optional). */\n retry?: number;\n}\n\n/**\n * Server-Sent Event message structure without a typed event map.\n */\nexport interface SSEMessage extends SSEMessageMetadata {\n /** Event type (optional). */\n event?: string;\n /** Event data. */\n data: SSEData;\n}\n\ntype NamedSSEMessage<Events> = {\n [Name in Exclude<keyof Events & string, 'message'>]: {\n event: Name;\n data: Events[Name];\n };\n}[Exclude<keyof Events & string, 'message'>];\n\ntype DefaultSSEMessage<Events> = 'message' extends keyof Events\n ? {\n /** Omit the event name to dispatch the default `message` event. */\n event?: 'message';\n data: Events['message'];\n }\n : never;\n\n/**\n * Server-Sent Event message structure.\n *\n * For a typed event map, each event name is coupled to its payload type. Use\n * the reserved `message` key to type messages that omit the `event` field.\n */\nexport type SSEEventMessage<Events extends ValidSSEEventMap<Events>> = (string extends keyof Events\n ? SSEMessage\n : NamedSSEMessage<Events> | DefaultSSEMessage<Events>) &\n SSEMessageMetadata;\n\ndeclare const SSE_EVENTS: unique symbol;\n\n/**\n * Response carrying its SSE event map at the type level.\n */\nexport type SSEResponse<Events extends ValidSSEEventMap<Events> = SSEEventMap> = Response & {\n readonly [SSE_EVENTS]: Events;\n};\n\n/**\n * Extracts the event map carried by an SSE response or typed client method.\n *\n * @example\n * ```typescript\n * type Events = SSEEventsOf<ReturnType<typeof client.events>>;\n * ```\n */\nexport type SSEEventsOf<T> = Awaited<T> extends SSEResponse<infer Events> ? Events : never;\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 * interface CounterEvents {\n * tick: { count: number };\n * }\n *\n * const handler = async (c) => {\n * return sseResponse<CounterEvents>(async (send, close) => {\n * for (let i = 0; i < 5; i++) {\n * send({ event: 'tick', data: { count: i } });\n * await new Promise(r => setTimeout(r, 1000));\n * }\n * close();\n * });\n * };\n * ```\n */\nexport function sseResponse<Events extends ValidSSEEventMap<Events> = SSEEventMap>(\n handler: (\n send: (message: SSEEventMessage<Events>) => void,\n close: () => void,\n ) => void | Promise<void>,\n options: SSEOptions = {},\n): SSEResponse<Events> {\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: SSEEventMessage<Events>) => {\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 }) as SSEResponse<Events>;\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":["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/client-proxy.ts","../src/client-response.ts","../src/http-client.ts","../src/local-client.ts","../src/streaming.ts"],"sourcesContent":["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","const STREAMING_CONTENT_TYPES = new Set([\n 'text/event-stream',\n 'application/x-ndjson',\n 'application/ndjson',\n]);\n\n/**\n * Parses ordinary client responses as JSON while preserving streaming bodies.\n */\nexport async function parseClientResponse(response: Response, raw = false): Promise<unknown> {\n const contentType = response.headers.get('Content-Type')?.split(';', 1)[0].trim().toLowerCase();\n\n if (raw || (contentType && STREAMING_CONTENT_TYPES.has(contentType))) {\n return response;\n }\n\n return response.json();\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 { parseClientResponse } from './client-response.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 *\n * @example Router built by a dependency-injected factory:\n * ```typescript\n * import type { createServer } from './server';\n * import { createHttpClient } from '@fresho/router';\n *\n * type Api = ReturnType<typeof createServer>['api'];\n *\n * const client = createHttpClient<Api>({\n * baseUrl: 'https://api.example.com',\n * });\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 return parseClientResponse(response, options?.raw);\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 { parseClientResponse } from './client-response.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 parseClientResponse(result);\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 * interface Events {\n * tick: { count: number };\n * }\n *\n * const api = router({\n * // SSE for real-time updates\n * events: router({\n * get: async () => sseResponse<Events>(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 * Data supported by an SSE message.\n */\nexport type SSEData = string | object;\n\n/**\n * Untyped map of SSE event names to their payloads.\n *\n * Define a narrower map and pass it to {@link sseResponse} to type-check event\n * names and their corresponding data.\n */\nexport type SSEEventMap = Record<string, SSEData>;\n\ntype ValidSSEEventMap<Events> = {\n [Name in keyof Events]: SSEData;\n};\n\ninterface SSEMessageMetadata {\n /** Event ID (optional). */\n id?: string;\n /** Retry interval hint for client (optional). */\n retry?: number;\n}\n\n/**\n * Server-Sent Event message structure without a typed event map.\n */\nexport interface SSEMessage extends SSEMessageMetadata {\n /** Event type (optional). */\n event?: string;\n /** Event data. */\n data: SSEData;\n}\n\ntype NamedSSEMessage<Events> = {\n [Name in Exclude<keyof Events & string, 'message'>]: {\n event: Name;\n data: Events[Name];\n };\n}[Exclude<keyof Events & string, 'message'>];\n\ntype DefaultSSEMessage<Events> = 'message' extends keyof Events\n ? {\n /** Omit the event name to dispatch the default `message` event. */\n event?: 'message';\n data: Events['message'];\n }\n : never;\n\n/**\n * Server-Sent Event message structure.\n *\n * For a typed event map, each event name is coupled to its payload type. Use\n * the reserved `message` key to type messages that omit the `event` field.\n */\nexport type SSEEventMessage<Events extends ValidSSEEventMap<Events>> = (string extends keyof Events\n ? SSEMessage\n : NamedSSEMessage<Events> | DefaultSSEMessage<Events>) &\n SSEMessageMetadata;\n\ndeclare const SSE_EVENTS: unique symbol;\n\n/**\n * Response carrying its SSE event map at the type level.\n */\nexport type SSEResponse<Events extends ValidSSEEventMap<Events> = SSEEventMap> = Response & {\n readonly [SSE_EVENTS]: Events;\n};\n\n/**\n * Extracts the event map carried by an SSE response or typed client method.\n *\n * @example\n * ```typescript\n * type Events = SSEEventsOf<ReturnType<typeof client.events>>;\n * ```\n */\nexport type SSEEventsOf<T> = Awaited<T> extends SSEResponse<infer Events> ? Events : never;\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.replace(/[\\r\\n]/g, '')}`);\n }\n\n if (message.id) {\n lines.push(`id: ${message.id.replace(/[\\r\\n]/g, '')}`);\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 * interface CounterEvents {\n * tick: { count: number };\n * }\n *\n * const handler = async (c) => {\n * return sseResponse<CounterEvents>(async (send, close) => {\n * for (let i = 0; i < 5; i++) {\n * send({ event: 'tick', data: { count: i } });\n * await new Promise(r => setTimeout(r, 1000));\n * }\n * close();\n * });\n * };\n * ```\n */\nexport function sseResponse<Events extends ValidSSEEventMap<Events> = SSEEventMap>(\n handler: (\n send: (message: SSEEventMessage<Events>) => void,\n close: () => void,\n ) => void | Promise<void>,\n options: SSEOptions = {},\n): SSEResponse<Events> {\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: SSEEventMessage<Events>) => {\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 }) as SSEResponse<Events>;\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,4 +1,5 @@
1
- import { h as RouterRoutes, p as Router, t as Middleware } from "../middleware-zRMBpTLe.mjs";
1
+ import { h as RouterRoutes, p as Router, t as Middleware } from "../middleware-Cj1yHSJF.mjs";
2
+ import { Icon } from "@modelcontextprotocol/sdk/types.js";
2
3
 
3
4
  //#region src/mcp/index.d.ts
4
5
 
@@ -15,6 +16,12 @@ interface McpOptions {
15
16
  name: string;
16
17
  /** Server version reported to clients. */
17
18
  version: string;
19
+ /** Display name for clients' UI. */
20
+ title?: string;
21
+ /** Icons for clients' UI. */
22
+ icons?: Icon[];
23
+ /** Home page of the server or its product. */
24
+ websiteUrl?: string;
18
25
  /** Instructions for agents on how to use the tools. */
19
26
  instructions?: string;
20
27
  /** Advertise OAuth: 401s gain a `resource_metadata` challenge and metadata is served. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/mcp/index.ts"],"sourcesContent":[],"mappings":";;;;;UA4CiB,eAAA;;;;;;;UAQA,UAAA;;;;;;;;UAQP;;;;;;;;;;;;;;;;;;;;;;;;;iBAgRM,GAAA,SACN,OAAO,gBAAgB,eAAe,OAAO,yBAC5C,2BACM,eACd,OAAO;;;;;;;;;;;;;;iBAyDM,sBAAA,QAA8B,kBAAkB,OAAO"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/mcp/index.ts"],"sourcesContent":[],"mappings":";;;;;;UAmDiB,eAAA;;;;;;;UAQA,UAAA;;;;;;;;UAQP;;;;;;UAMA;;;;;;;;;;;;;;;;;;;;;;;;;iBAwRM,GAAA,SACN,OAAO,gBAAgB,eAAe,OAAO,yBAC5C,2BACM,eACd,OAAO;;;;;;;;;;;;;;iBAyDM,sBAAA,QAA8B,kBAAkB,OAAO"}
@@ -1,5 +1,5 @@
1
1
  import { n as runMiddleware } from "../middleware-B-_0PLA6.mjs";
2
- import { i as router, l as toJsonSchema, s as isRouter, t as collectRoutes } from "../docs-B1IFBS_w.mjs";
2
+ import { d as unpackField, i as router, l as isNestedSchema, s as isRouter, t as collectRoutes, u as toJsonSchema } from "../docs-BLjbxLe-.mjs";
3
3
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
4
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
5
5
  import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
@@ -97,7 +97,7 @@ function collectTools(root) {
97
97
  const required = [];
98
98
  const sources = /* @__PURE__ */ new Map();
99
99
  const add = (source, schema) => {
100
- const json = toJsonSchema(schema, route?.descriptions);
100
+ const json = toJsonSchema(schema);
101
101
  for (const [key, property] of Object.entries(json.properties)) {
102
102
  if (sources.has(key)) throw new Error(`Duplicate argument ${key} in tool ${name}`);
103
103
  sources.set(key, source);
@@ -108,7 +108,11 @@ function collectTools(root) {
108
108
  const params = [...path.matchAll(/\{(\w+)\}/g)].map((m) => m[1]);
109
109
  add("path", Object.fromEntries(params.map((p) => [p, "string"])));
110
110
  if (route?.query) {
111
- const scalars = Object.entries(route.query).filter(([, type]) => typeof type === "string" && !type.includes("[]"));
111
+ const scalars = Object.entries(route.query).filter(([, field]) => {
112
+ if (isNestedSchema(field)) return false;
113
+ const [type] = unpackField(field);
114
+ return typeof type === "string" && !type.includes("[]");
115
+ });
112
116
  add("query", Object.fromEntries(scalars));
113
117
  }
114
118
  const hasBodySchema = !!route?.body && METHODS_WITH_BODY.includes(method);
@@ -206,7 +210,10 @@ async function callTool(dispatch, target, args, c) {
206
210
  async function serve(tools, dispatch, options, c) {
207
211
  const server = new Server({
208
212
  name: options.name,
209
- version: options.version
213
+ version: options.version,
214
+ title: options.title,
215
+ icons: options.icons,
216
+ websiteUrl: options.websiteUrl
210
217
  }, {
211
218
  capabilities: { tools: {} },
212
219
  instructions: options.instructions
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["DEFAULT_ANNOTATIONS: Record<string, ToolAnnotations>","properties: Record<string, object>","required: string[]","pathArgs: Record<string, unknown>","queryArgs: Record<string, unknown>","bodyArgs: Record<string, unknown>","missing: string | undefined","invalid: string | undefined","response: Response","result: CallToolResult","inherit: Middleware","dispatch: Dispatch"],"sources":["../../src/mcp/index.ts"],"sourcesContent":["/**\n * @fileoverview MCP server adapter for @fresho/router.\n *\n * Exposes routes as Model Context Protocol tools over stateless Streamable HTTP. Each tool call\n * is dispatched as a regular HTTP request through the router, so route middleware (auth, rate\n * limits, logging) applies to agents exactly as it does to other clients.\n *\n * Requires the optional peer dependency `@modelcontextprotocol/sdk`.\n *\n * @example\n * ```typescript\n * import { router } from '@fresho/router';\n * import { mcp } from '@fresho/router/mcp';\n * import { bearerAuth } from '@fresho/router/middleware';\n *\n * const api = router({ users, billing });\n *\n * export default {\n * fetch: router({\n * ...api.routes,\n * mcp: mcp({ users }, { name: 'my-api', version: '1.0.0' }, bearerAuth({ verify })),\n * }).handler(),\n * };\n * ```\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';\nimport {\n CallToolRequestSchema,\n type CallToolResult,\n ErrorCode,\n ListToolsRequestSchema,\n McpError,\n type Tool,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { router } from '../core.js';\nimport { collectRoutes } from '../docs.js';\nimport { type Middleware, type MiddlewareContext, runMiddleware } from '../middleware.js';\nimport { type SchemaDefinition, toJsonSchema } from '../schema.js';\nimport type { Context, Router, RouterRoutes, ToolAnnotations } from '../types.js';\nimport { isRouter } from '../types.js';\n\n/** OAuth 2.0 protected resource settings (RFC 9728). */\nexport interface McpOAuthOptions {\n /** Issuer URLs of the authorization servers that issue tokens for this server. */\n authorizationServers: string[];\n /** Scopes advertised to clients. */\n scopes?: string[];\n}\n\n/** MCP server options. */\nexport interface McpOptions {\n /** Server name reported to clients. */\n name: string;\n /** Server version reported to clients. */\n version: string;\n /** Instructions for agents on how to use the tools. */\n instructions?: string;\n /** Advertise OAuth: 401s gain a `resource_metadata` challenge and metadata is served. */\n oauth?: McpOAuthOptions;\n /** Allow an endpoint without middleware: it is open, or already guarded by parent routers. */\n public?: boolean;\n}\n\n/** Where a flat tool argument goes in the HTTP request. */\ntype ArgumentSource = 'path' | 'query' | 'body';\n\n/** A tool and the route it calls. */\ninterface ToolRoute {\n tool: Tool;\n method: string;\n path: string;\n sources: Map<string, ArgumentSource>;\n /** Without a body schema, arguments that aren't path or query fields form the body. */\n hasBodySchema: boolean;\n}\n\nconst METHODS_WITH_BODY = ['post', 'put', 'patch'];\nconst CONTEXT_KEYS = new Set(['request', 'path', 'query', 'body', 'env', 'executionCtx']);\n// Describe the outer MCP request or connection, not the dispatched tool call.\nconst DROPPED_HEADERS = [\n 'Connection',\n 'Content-Encoding',\n 'Content-Length',\n 'Content-Type',\n 'Keep-Alive',\n 'Mcp-Protocol-Version',\n 'Mcp-Session-Id',\n 'Proxy-Connection',\n 'TE',\n 'Trailer',\n 'Transfer-Encoding',\n 'Upgrade',\n];\nconst STREAMING_TYPES = /^(text\\/event-stream|application\\/x-ndjson)/i;\nconst TOOL_NAME = /^[A-Za-z0-9_-]{1,128}$/;\nconst DEFAULT_ANNOTATIONS: Record<string, ToolAnnotations> = {\n get: { readOnlyHint: true },\n put: { idempotentHint: true },\n delete: { destructiveHint: true, idempotentHint: true },\n};\nconst TOOL_METHODS = new Set(['get', 'delete', ...METHODS_WITH_BODY]);\nconst METADATA_PATH = '/.well-known/oauth-protected-resource';\n\n/** Parses JSON, returning undefined for invalid input. */\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Builds a tool name such as `get_users_by_id` from a method and `{param}` path. */\nfunction toolName(method: string, path: string): string {\n const segments = path\n .split('/')\n .filter(Boolean)\n .map((s) => (s.startsWith('{') ? `by_${s.slice(1, -1)}` : s));\n return [method, ...segments].join('_').replace(/[^A-Za-z0-9_-]/g, '_');\n}\n\n/** Derives one tool per route in the tree, with path, query and body fields as flat arguments. */\nfunction collectTools(root: Router<RouterRoutes>): Map<string, ToolRoute> {\n const tools = new Map<string, ToolRoute>();\n for (const { path, method, route } of collectRoutes(root)) {\n if (!TOOL_METHODS.has(method)) continue;\n const name = route?.operationId ?? toolName(method, path);\n // MCP allows up to 128 characters; some clients reject other characters.\n if (!TOOL_NAME.test(name)) throw new Error(`Invalid tool name: ${name}`);\n if (tools.has(name)) throw new Error(`Duplicate tool name: ${name}`);\n\n const properties: Record<string, object> = {};\n const required: string[] = [];\n const sources = new Map<string, ArgumentSource>();\n const add = (source: ArgumentSource, schema: SchemaDefinition) => {\n const json = toJsonSchema(schema, route?.descriptions);\n for (const [key, property] of Object.entries(json.properties)) {\n if (sources.has(key)) throw new Error(`Duplicate argument ${key} in tool ${name}`);\n sources.set(key, source);\n properties[key] = property as object;\n }\n required.push(...(json.required ?? []));\n };\n const params = [...path.matchAll(/\\{(\\w+)\\}/g)].map((m) => m[1]);\n add('path', Object.fromEntries(params.map((p) => [p, 'string'])));\n if (route?.query) {\n // Query strings only carry scalars.\n const scalars = Object.entries(route.query as SchemaDefinition).filter(\n ([, type]) => typeof type === 'string' && !type.includes('[]'),\n );\n add('query', Object.fromEntries(scalars));\n }\n const hasBodySchema = !!route?.body && METHODS_WITH_BODY.includes(method);\n if (hasBodySchema) add('body', route?.body as SchemaDefinition);\n\n tools.set(name, {\n tool: {\n name,\n description: route?.description ?? `${method.toUpperCase()} ${path}`,\n inputSchema: { type: 'object', properties, ...(required.length > 0 && { required }) },\n annotations: { ...DEFAULT_ANNOTATIONS[method], ...route?.mcp?.annotations },\n },\n method,\n path,\n sources,\n hasBodySchema,\n });\n }\n return tools;\n}\n\n/** Sends a tool's request through the routes on behalf of the MCP request context `c`. */\ntype Dispatch = (request: Request, c: Context) => Response | Promise<Response>;\n\n/** Runs a tool as an HTTP request through the router, so its middleware applies. */\nasync function callTool(\n dispatch: Dispatch,\n target: ToolRoute,\n args: Record<string, unknown>,\n c: Context,\n): Promise<CallToolResult> {\n const fail = (text: string): CallToolResult => ({\n content: [{ type: 'text', text }],\n isError: true,\n });\n const pathArgs: Record<string, unknown> = {};\n const queryArgs: Record<string, unknown> = {};\n const bodyArgs: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(args)) {\n const source = target.sources.get(key) ?? (target.hasBodySchema ? undefined : 'body');\n if (source === 'path') pathArgs[key] = value;\n else if (source === 'query') queryArgs[key] = value;\n else if (source === 'body') bodyArgs[key] = value;\n }\n let missing: string | undefined;\n let invalid: string | undefined;\n const path = target.path.replace(/\\{(\\w+)\\}/g, (_, name: string) => {\n const value = pathArgs[name];\n if (value === undefined || value === null || value === '') missing ??= name;\n // URL parsing resolves dot segments, which would reach a different route.\n if (value === '.' || value === '..') invalid ??= name;\n return encodeURIComponent(String(value ?? ''));\n });\n if (missing) return fail(`Missing path parameter: ${missing}`);\n if (invalid) return fail(`Invalid path parameter: ${invalid}`);\n\n const url = new URL(path, c.request.url);\n for (const [key, value] of Object.entries(queryArgs)) {\n if (typeof value === 'object' && value !== null) return fail(`Invalid query parameter: ${key}`);\n if (value !== undefined && value !== null) url.searchParams.set(key, String(value));\n }\n\n // Forward the caller's headers (Authorization, cookies, ...) but describe the new request.\n const headers = new Headers(c.request.headers);\n for (const name of DROPPED_HEADERS) headers.delete(name);\n headers.set('Accept', 'application/json');\n const hasBody = METHODS_WITH_BODY.includes(target.method);\n if (hasBody) headers.set('Content-Type', 'application/json');\n\n let response: Response;\n try {\n response = await dispatch(\n new Request(url, {\n method: target.method.toUpperCase(),\n headers,\n body: hasBody ? JSON.stringify(bodyArgs) : undefined,\n }),\n c,\n );\n } catch (error) {\n console.error(`Error in MCP tool ${target.tool.name}:`, error);\n return fail('Internal error');\n }\n if (STREAMING_TYPES.test(response.headers.get('Content-Type') ?? '')) {\n await response.body?.cancel();\n return fail('Streaming responses are not supported');\n }\n const text = await response.text();\n const result: CallToolResult = { content: [{ type: 'text', text }], isError: !response.ok };\n if (/^application\\/json/i.test(response.headers.get('Content-Type') ?? '')) {\n const value = parseJson(text);\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n result.structuredContent = value as Record<string, unknown>;\n }\n }\n return result;\n}\n\n/** Handles one MCP request with a fresh stateless server. */\nasync function serve(\n tools: Map<string, ToolRoute>,\n dispatch: Dispatch,\n options: McpOptions,\n c: Context,\n): Promise<Response> {\n const server = new Server(\n { name: options.name, version: options.version },\n { capabilities: { tools: {} }, instructions: options.instructions },\n );\n server.setRequestHandler(ListToolsRequestSchema, () => ({\n tools: [...tools.values()].map((t) => t.tool),\n }));\n server.setRequestHandler(CallToolRequestSchema, (request) => {\n const target = tools.get(request.params.name);\n if (!target) {\n throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`);\n }\n return callTool(dispatch, target, request.params.arguments ?? {}, c);\n });\n const transport = new WebStandardStreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: true,\n });\n await server.connect(transport);\n return transport.handleRequest(c.request);\n}\n\n/** Adds the RFC 9728 `resource_metadata` parameter to a 401's Bearer challenge. */\nfunction challenge(oauth: McpOAuthOptions): Middleware {\n return async (c, next) => {\n const response = await next();\n if (response.status !== 401) return response;\n const url = new URL(c.request.url);\n const params = [`resource_metadata=\"${url.origin}${url.pathname}${METADATA_PATH}\"`];\n if (oauth.scopes?.length) params.push(`scope=\"${oauth.scopes.join(' ')}\"`);\n const existing = response.headers.get('WWW-Authenticate');\n const value = existing?.startsWith('Bearer ')\n ? `${existing}, ${params.join(', ')}`\n : `Bearer ${params.join(', ')}`;\n const challenged = new Response(response.body, response);\n challenged.headers.set('WWW-Authenticate', value);\n return challenged;\n };\n}\n\n/** Serves protected resource metadata for the resource named by the request path. */\nfunction metadata(oauth: McpOAuthOptions) {\n return (c: Context) => {\n const url = new URL(c.request.url);\n const resource = url.pathname.replace(METADATA_PATH, '');\n return Response.json({\n resource: `${url.origin}${resource}`,\n authorization_servers: oauth.authorizationServers,\n ...(oauth.scopes && { scopes_supported: oauth.scopes }),\n bearer_methods_supported: ['header'],\n });\n };\n}\n\n/**\n * Creates an MCP endpoint exposing routes as tools.\n *\n * Mount the result in a router (e.g. `mcp: mcp({ users }, options, auth)`). Tool names come from\n * `route.operationId` or the method and path (`get_users`, `delete_users_by_id`), descriptions\n * from `route.description`, and flat inputs from the route's path params and query/body schemas.\n *\n * Middleware passed after `options` guards the endpoint itself, and is required unless\n * `options.public` is set; put auth there so unauthenticated\n * clients get an HTTP 401 (with an OAuth challenge when `oauth` is set). Context it adds reaches\n * handlers. Middleware on the given routers runs on every tool call, with the caller's headers\n * forwarded; middleware on their parents elsewhere in the app does not.\n *\n * With `oauth`, metadata is served at `<endpoint>/.well-known/oauth-protected-resource` and\n * referenced from the 401 challenge. Mount {@link oauthProtectedResource} at the origin root as\n * well for clients that probe `/.well-known/oauth-protected-resource/<endpoint>` first.\n *\n * @param routes - A router, or a record of routers mounted under their keys\n * @param options - Server info and optional OAuth settings\n * @param middleware - Middleware for the MCP endpoint\n */\nexport function mcp(\n routes: Router<RouterRoutes> | Record<string, Router<RouterRoutes>>,\n options: McpOptions,\n ...middleware: Middleware[]\n): Router<RouterRoutes> {\n if (middleware.length === 0 && options.public !== true) {\n throw new Error('mcp() needs endpoint middleware such as auth, or public: true');\n }\n const base = isRouter(routes) ? routes : router(routes as RouterRoutes);\n const tools = collectTools(base);\n\n // Context set by middleware on the endpoint and its parents, keyed by dispatched request.\n const inherited = new WeakMap<Request, Record<string, unknown>>();\n const inherit: Middleware = (c, next) => {\n Object.assign(c, inherited.get(c.request));\n return next();\n };\n const fetchRoute = router(base.routes, inherit, ...(base.middleware ?? [])).handler();\n const dispatch: Dispatch = (request, c) => {\n inherited.set(\n request,\n Object.fromEntries(Object.entries(c).filter(([key]) => !CONTEXT_KEYS.has(key))),\n );\n return fetchRoute(request, c.env, c.executionCtx);\n };\n\n const chain = options.oauth ? [challenge(options.oauth), ...middleware] : middleware;\n const notAllowed = () => new Response(null, { status: 405, headers: { Allow: 'POST' } });\n\n return router({\n post: (c) => {\n // The MCP spec requires Origin validation against DNS rebinding.\n const origin = c.request.headers.get('Origin');\n if (origin && origin !== new URL(c.request.url).origin) {\n return new Response('Forbidden origin', { status: 403 });\n }\n return runMiddleware(chain, c as MiddlewareContext, () => serve(tools, dispatch, options, c));\n },\n get: notAllowed,\n delete: notAllowed,\n ...(options.oauth && {\n '.well-known': router({\n 'oauth-protected-resource': router({ get: metadata(options.oauth) }),\n }),\n }),\n });\n}\n\n/**\n * Creates RFC 9728 protected resource metadata routes to mount as `'.well-known'` at the origin\n * root, serving `/.well-known/oauth-protected-resource` and\n * `/.well-known/oauth-protected-resource/<resource>` for single-segment resource paths like `/mcp`.\n *\n * @example\n * ```typescript\n * router({\n * mcp: mcp({ users }, { name: 'api', version: '1.0.0', oauth }, jwtAuth({ ... })),\n * '.well-known': oauthProtectedResource(oauth),\n * });\n * ```\n */\nexport function oauthProtectedResource(oauth: McpOAuthOptions): Router<RouterRoutes> {\n const get = metadata(oauth);\n return router({ 'oauth-protected-resource': router({ get, $resource: router({ get }) }) });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["DEFAULT_ANNOTATIONS: Record<string, ToolAnnotations>","properties: Record<string, object>","required: string[]","pathArgs: Record<string, unknown>","queryArgs: Record<string, unknown>","bodyArgs: Record<string, unknown>","missing: string | undefined","invalid: string | undefined","response: Response","result: CallToolResult","inherit: Middleware","dispatch: Dispatch"],"sources":["../../src/mcp/index.ts"],"sourcesContent":["/**\n * @fileoverview MCP server adapter for @fresho/router.\n *\n * Exposes routes as Model Context Protocol tools over stateless Streamable HTTP. Each tool call\n * is dispatched as a regular HTTP request through the router, so route middleware (auth, rate\n * limits, logging) applies to agents exactly as it does to other clients.\n *\n * Requires the optional peer dependency `@modelcontextprotocol/sdk`.\n *\n * @example\n * ```typescript\n * import { router } from '@fresho/router';\n * import { mcp } from '@fresho/router/mcp';\n * import { bearerAuth } from '@fresho/router/middleware';\n *\n * const api = router({ users, billing });\n *\n * export default {\n * fetch: router({\n * ...api.routes,\n * mcp: mcp({ users }, { name: 'my-api', version: '1.0.0' }, bearerAuth({ verify })),\n * }).handler(),\n * };\n * ```\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';\nimport {\n CallToolRequestSchema,\n type CallToolResult,\n ErrorCode,\n type Icon,\n ListToolsRequestSchema,\n McpError,\n type Tool,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { router } from '../core.js';\nimport { collectRoutes } from '../docs.js';\nimport { type Middleware, type MiddlewareContext, runMiddleware } from '../middleware.js';\nimport {\n isNestedSchema,\n type SchemaDefinition,\n type SchemaField,\n toJsonSchema,\n unpackField,\n} from '../schema.js';\nimport type { Context, Router, RouterRoutes, ToolAnnotations } from '../types.js';\nimport { isRouter } from '../types.js';\n\n/** OAuth 2.0 protected resource settings (RFC 9728). */\nexport interface McpOAuthOptions {\n /** Issuer URLs of the authorization servers that issue tokens for this server. */\n authorizationServers: string[];\n /** Scopes advertised to clients. */\n scopes?: string[];\n}\n\n/** MCP server options. */\nexport interface McpOptions {\n /** Server name reported to clients. */\n name: string;\n /** Server version reported to clients. */\n version: string;\n /** Display name for clients' UI. */\n title?: string;\n /** Icons for clients' UI. */\n icons?: Icon[];\n /** Home page of the server or its product. */\n websiteUrl?: string;\n /** Instructions for agents on how to use the tools. */\n instructions?: string;\n /** Advertise OAuth: 401s gain a `resource_metadata` challenge and metadata is served. */\n oauth?: McpOAuthOptions;\n /** Allow an endpoint without middleware: it is open, or already guarded by parent routers. */\n public?: boolean;\n}\n\n/** Where a flat tool argument goes in the HTTP request. */\ntype ArgumentSource = 'path' | 'query' | 'body';\n\n/** A tool and the route it calls. */\ninterface ToolRoute {\n tool: Tool;\n method: string;\n path: string;\n sources: Map<string, ArgumentSource>;\n /** Without a body schema, arguments that aren't path or query fields form the body. */\n hasBodySchema: boolean;\n}\n\nconst METHODS_WITH_BODY = ['post', 'put', 'patch'];\nconst CONTEXT_KEYS = new Set(['request', 'path', 'query', 'body', 'env', 'executionCtx']);\n// Describe the outer MCP request or connection, not the dispatched tool call.\nconst DROPPED_HEADERS = [\n 'Connection',\n 'Content-Encoding',\n 'Content-Length',\n 'Content-Type',\n 'Keep-Alive',\n 'Mcp-Protocol-Version',\n 'Mcp-Session-Id',\n 'Proxy-Connection',\n 'TE',\n 'Trailer',\n 'Transfer-Encoding',\n 'Upgrade',\n];\nconst STREAMING_TYPES = /^(text\\/event-stream|application\\/x-ndjson)/i;\nconst TOOL_NAME = /^[A-Za-z0-9_-]{1,128}$/;\nconst DEFAULT_ANNOTATIONS: Record<string, ToolAnnotations> = {\n get: { readOnlyHint: true },\n put: { idempotentHint: true },\n delete: { destructiveHint: true, idempotentHint: true },\n};\nconst TOOL_METHODS = new Set(['get', 'delete', ...METHODS_WITH_BODY]);\nconst METADATA_PATH = '/.well-known/oauth-protected-resource';\n\n/** Parses JSON, returning undefined for invalid input. */\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Builds a tool name such as `get_users_by_id` from a method and `{param}` path. */\nfunction toolName(method: string, path: string): string {\n const segments = path\n .split('/')\n .filter(Boolean)\n .map((s) => (s.startsWith('{') ? `by_${s.slice(1, -1)}` : s));\n return [method, ...segments].join('_').replace(/[^A-Za-z0-9_-]/g, '_');\n}\n\n/** Derives one tool per route in the tree, with path, query and body fields as flat arguments. */\nfunction collectTools(root: Router<RouterRoutes>): Map<string, ToolRoute> {\n const tools = new Map<string, ToolRoute>();\n for (const { path, method, route } of collectRoutes(root)) {\n if (!TOOL_METHODS.has(method)) continue;\n const name = route?.operationId ?? toolName(method, path);\n // MCP allows up to 128 characters; some clients reject other characters.\n if (!TOOL_NAME.test(name)) throw new Error(`Invalid tool name: ${name}`);\n if (tools.has(name)) throw new Error(`Duplicate tool name: ${name}`);\n\n const properties: Record<string, object> = {};\n const required: string[] = [];\n const sources = new Map<string, ArgumentSource>();\n const add = (source: ArgumentSource, schema: SchemaDefinition) => {\n const json = toJsonSchema(schema);\n for (const [key, property] of Object.entries(json.properties)) {\n if (sources.has(key)) throw new Error(`Duplicate argument ${key} in tool ${name}`);\n sources.set(key, source);\n properties[key] = property as object;\n }\n required.push(...(json.required ?? []));\n };\n const params = [...path.matchAll(/\\{(\\w+)\\}/g)].map((m) => m[1]);\n add('path', Object.fromEntries(params.map((p) => [p, 'string'])));\n if (route?.query) {\n // Query strings only carry scalars.\n const scalars = Object.entries(route.query as SchemaDefinition).filter(([, field]) => {\n if (isNestedSchema(field)) return false;\n const [type] = unpackField(field as SchemaField);\n return typeof type === 'string' && !type.includes('[]');\n });\n add('query', Object.fromEntries(scalars));\n }\n const hasBodySchema = !!route?.body && METHODS_WITH_BODY.includes(method);\n if (hasBodySchema) add('body', route?.body as SchemaDefinition);\n\n tools.set(name, {\n tool: {\n name,\n description: route?.description ?? `${method.toUpperCase()} ${path}`,\n inputSchema: { type: 'object', properties, ...(required.length > 0 && { required }) },\n annotations: { ...DEFAULT_ANNOTATIONS[method], ...route?.mcp?.annotations },\n },\n method,\n path,\n sources,\n hasBodySchema,\n });\n }\n return tools;\n}\n\n/** Sends a tool's request through the routes on behalf of the MCP request context `c`. */\ntype Dispatch = (request: Request, c: Context) => Response | Promise<Response>;\n\n/** Runs a tool as an HTTP request through the router, so its middleware applies. */\nasync function callTool(\n dispatch: Dispatch,\n target: ToolRoute,\n args: Record<string, unknown>,\n c: Context,\n): Promise<CallToolResult> {\n const fail = (text: string): CallToolResult => ({\n content: [{ type: 'text', text }],\n isError: true,\n });\n const pathArgs: Record<string, unknown> = {};\n const queryArgs: Record<string, unknown> = {};\n const bodyArgs: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(args)) {\n const source = target.sources.get(key) ?? (target.hasBodySchema ? undefined : 'body');\n if (source === 'path') pathArgs[key] = value;\n else if (source === 'query') queryArgs[key] = value;\n else if (source === 'body') bodyArgs[key] = value;\n }\n let missing: string | undefined;\n let invalid: string | undefined;\n const path = target.path.replace(/\\{(\\w+)\\}/g, (_, name: string) => {\n const value = pathArgs[name];\n if (value === undefined || value === null || value === '') missing ??= name;\n // URL parsing resolves dot segments, which would reach a different route.\n if (value === '.' || value === '..') invalid ??= name;\n return encodeURIComponent(String(value ?? ''));\n });\n if (missing) return fail(`Missing path parameter: ${missing}`);\n if (invalid) return fail(`Invalid path parameter: ${invalid}`);\n\n const url = new URL(path, c.request.url);\n for (const [key, value] of Object.entries(queryArgs)) {\n if (typeof value === 'object' && value !== null) return fail(`Invalid query parameter: ${key}`);\n if (value !== undefined && value !== null) url.searchParams.set(key, String(value));\n }\n\n // Forward the caller's headers (Authorization, cookies, ...) but describe the new request.\n const headers = new Headers(c.request.headers);\n for (const name of DROPPED_HEADERS) headers.delete(name);\n headers.set('Accept', 'application/json');\n const hasBody = METHODS_WITH_BODY.includes(target.method);\n if (hasBody) headers.set('Content-Type', 'application/json');\n\n let response: Response;\n try {\n response = await dispatch(\n new Request(url, {\n method: target.method.toUpperCase(),\n headers,\n body: hasBody ? JSON.stringify(bodyArgs) : undefined,\n }),\n c,\n );\n } catch (error) {\n console.error(`Error in MCP tool ${target.tool.name}:`, error);\n return fail('Internal error');\n }\n if (STREAMING_TYPES.test(response.headers.get('Content-Type') ?? '')) {\n await response.body?.cancel();\n return fail('Streaming responses are not supported');\n }\n const text = await response.text();\n const result: CallToolResult = { content: [{ type: 'text', text }], isError: !response.ok };\n if (/^application\\/json/i.test(response.headers.get('Content-Type') ?? '')) {\n const value = parseJson(text);\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n result.structuredContent = value as Record<string, unknown>;\n }\n }\n return result;\n}\n\n/** Handles one MCP request with a fresh stateless server. */\nasync function serve(\n tools: Map<string, ToolRoute>,\n dispatch: Dispatch,\n options: McpOptions,\n c: Context,\n): Promise<Response> {\n const server = new Server(\n {\n name: options.name,\n version: options.version,\n title: options.title,\n icons: options.icons,\n websiteUrl: options.websiteUrl,\n },\n { capabilities: { tools: {} }, instructions: options.instructions },\n );\n server.setRequestHandler(ListToolsRequestSchema, () => ({\n tools: [...tools.values()].map((t) => t.tool),\n }));\n server.setRequestHandler(CallToolRequestSchema, (request) => {\n const target = tools.get(request.params.name);\n if (!target) {\n throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`);\n }\n return callTool(dispatch, target, request.params.arguments ?? {}, c);\n });\n const transport = new WebStandardStreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: true,\n });\n await server.connect(transport);\n return transport.handleRequest(c.request);\n}\n\n/** Adds the RFC 9728 `resource_metadata` parameter to a 401's Bearer challenge. */\nfunction challenge(oauth: McpOAuthOptions): Middleware {\n return async (c, next) => {\n const response = await next();\n if (response.status !== 401) return response;\n const url = new URL(c.request.url);\n const params = [`resource_metadata=\"${url.origin}${url.pathname}${METADATA_PATH}\"`];\n if (oauth.scopes?.length) params.push(`scope=\"${oauth.scopes.join(' ')}\"`);\n const existing = response.headers.get('WWW-Authenticate');\n const value = existing?.startsWith('Bearer ')\n ? `${existing}, ${params.join(', ')}`\n : `Bearer ${params.join(', ')}`;\n const challenged = new Response(response.body, response);\n challenged.headers.set('WWW-Authenticate', value);\n return challenged;\n };\n}\n\n/** Serves protected resource metadata for the resource named by the request path. */\nfunction metadata(oauth: McpOAuthOptions) {\n return (c: Context) => {\n const url = new URL(c.request.url);\n const resource = url.pathname.replace(METADATA_PATH, '');\n return Response.json({\n resource: `${url.origin}${resource}`,\n authorization_servers: oauth.authorizationServers,\n ...(oauth.scopes && { scopes_supported: oauth.scopes }),\n bearer_methods_supported: ['header'],\n });\n };\n}\n\n/**\n * Creates an MCP endpoint exposing routes as tools.\n *\n * Mount the result in a router (e.g. `mcp: mcp({ users }, options, auth)`). Tool names come from\n * `route.operationId` or the method and path (`get_users`, `delete_users_by_id`), descriptions\n * from `route.description`, and flat inputs from the route's path params and query/body schemas.\n *\n * Middleware passed after `options` guards the endpoint itself, and is required unless\n * `options.public` is set; put auth there so unauthenticated\n * clients get an HTTP 401 (with an OAuth challenge when `oauth` is set). Context it adds reaches\n * handlers. Middleware on the given routers runs on every tool call, with the caller's headers\n * forwarded; middleware on their parents elsewhere in the app does not.\n *\n * With `oauth`, metadata is served at `<endpoint>/.well-known/oauth-protected-resource` and\n * referenced from the 401 challenge. Mount {@link oauthProtectedResource} at the origin root as\n * well for clients that probe `/.well-known/oauth-protected-resource/<endpoint>` first.\n *\n * @param routes - A router, or a record of routers mounted under their keys\n * @param options - Server info and optional OAuth settings\n * @param middleware - Middleware for the MCP endpoint\n */\nexport function mcp(\n routes: Router<RouterRoutes> | Record<string, Router<RouterRoutes>>,\n options: McpOptions,\n ...middleware: Middleware[]\n): Router<RouterRoutes> {\n if (middleware.length === 0 && options.public !== true) {\n throw new Error('mcp() needs endpoint middleware such as auth, or public: true');\n }\n const base = isRouter(routes) ? routes : router(routes as RouterRoutes);\n const tools = collectTools(base);\n\n // Context set by middleware on the endpoint and its parents, keyed by dispatched request.\n const inherited = new WeakMap<Request, Record<string, unknown>>();\n const inherit: Middleware = (c, next) => {\n Object.assign(c, inherited.get(c.request));\n return next();\n };\n const fetchRoute = router(base.routes, inherit, ...(base.middleware ?? [])).handler();\n const dispatch: Dispatch = (request, c) => {\n inherited.set(\n request,\n Object.fromEntries(Object.entries(c).filter(([key]) => !CONTEXT_KEYS.has(key))),\n );\n return fetchRoute(request, c.env, c.executionCtx);\n };\n\n const chain = options.oauth ? [challenge(options.oauth), ...middleware] : middleware;\n const notAllowed = () => new Response(null, { status: 405, headers: { Allow: 'POST' } });\n\n return router({\n post: (c) => {\n // The MCP spec requires Origin validation against DNS rebinding.\n const origin = c.request.headers.get('Origin');\n if (origin && origin !== new URL(c.request.url).origin) {\n return new Response('Forbidden origin', { status: 403 });\n }\n return runMiddleware(chain, c as MiddlewareContext, () => serve(tools, dispatch, options, c));\n },\n get: notAllowed,\n delete: notAllowed,\n ...(options.oauth && {\n '.well-known': router({\n 'oauth-protected-resource': router({ get: metadata(options.oauth) }),\n }),\n }),\n });\n}\n\n/**\n * Creates RFC 9728 protected resource metadata routes to mount as `'.well-known'` at the origin\n * root, serving `/.well-known/oauth-protected-resource` and\n * `/.well-known/oauth-protected-resource/<resource>` for single-segment resource paths like `/mcp`.\n *\n * @example\n * ```typescript\n * router({\n * mcp: mcp({ users }, { name: 'api', version: '1.0.0', oauth }, jwtAuth({ ... })),\n * '.well-known': oauthProtectedResource(oauth),\n * });\n * ```\n */\nexport function oauthProtectedResource(oauth: McpOAuthOptions): Router<RouterRoutes> {\n const get = metadata(oauth);\n return router({ 'oauth-protected-resource': router({ get, $resource: router({ get }) }) });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2FA,MAAM,oBAAoB;CAAC;CAAQ;CAAO;CAAQ;AAClD,MAAM,eAAe,IAAI,IAAI;CAAC;CAAW;CAAQ;CAAS;CAAQ;CAAO;CAAe,CAAC;AAEzF,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AACD,MAAM,kBAAkB;AACxB,MAAM,YAAY;AAClB,MAAMA,sBAAuD;CAC3D,KAAK,EAAE,cAAc,MAAM;CAC3B,KAAK,EAAE,gBAAgB,MAAM;CAC7B,QAAQ;EAAE,iBAAiB;EAAM,gBAAgB;EAAM;CACxD;AACD,MAAM,eAAe,IAAI,IAAI;CAAC;CAAO;CAAU,GAAG;CAAkB,CAAC;AACrE,MAAM,gBAAgB;;AAGtB,SAAS,UAAU,MAAuB;AACxC,KAAI;AACF,SAAO,KAAK,MAAM,KAAK;SACjB;AACN;;;;AAKJ,SAAS,SAAS,QAAgB,MAAsB;AAKtD,QAAO,CAAC,QAAQ,GAJC,KACd,MAAM,IAAI,CACV,OAAO,QAAQ,CACf,KAAK,MAAO,EAAE,WAAW,IAAI,GAAG,MAAM,EAAE,MAAM,GAAG,GAAG,KAAK,EAAG,CACnC,CAAC,KAAK,IAAI,CAAC,QAAQ,mBAAmB,IAAI;;;AAIxE,SAAS,aAAa,MAAoD;CACxE,MAAM,wBAAQ,IAAI,KAAwB;AAC1C,MAAK,MAAM,EAAE,MAAM,QAAQ,WAAW,cAAc,KAAK,EAAE;AACzD,MAAI,CAAC,aAAa,IAAI,OAAO,CAAE;EAC/B,MAAM,OAAO,OAAO,eAAe,SAAS,QAAQ,KAAK;AAEzD,MAAI,CAAC,UAAU,KAAK,KAAK,CAAE,OAAM,IAAI,MAAM,sBAAsB,OAAO;AACxE,MAAI,MAAM,IAAI,KAAK,CAAE,OAAM,IAAI,MAAM,wBAAwB,OAAO;EAEpE,MAAMC,aAAqC,EAAE;EAC7C,MAAMC,WAAqB,EAAE;EAC7B,MAAM,0BAAU,IAAI,KAA6B;EACjD,MAAM,OAAO,QAAwB,WAA6B;GAChE,MAAM,OAAO,aAAa,OAAO;AACjC,QAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,KAAK,WAAW,EAAE;AAC7D,QAAI,QAAQ,IAAI,IAAI,CAAE,OAAM,IAAI,MAAM,sBAAsB,IAAI,WAAW,OAAO;AAClF,YAAQ,IAAI,KAAK,OAAO;AACxB,eAAW,OAAO;;AAEpB,YAAS,KAAK,GAAI,KAAK,YAAY,EAAE,CAAE;;EAEzC,MAAM,SAAS,CAAC,GAAG,KAAK,SAAS,aAAa,CAAC,CAAC,KAAK,MAAM,EAAE,GAAG;AAChE,MAAI,QAAQ,OAAO,YAAY,OAAO,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;AACjE,MAAI,OAAO,OAAO;GAEhB,MAAM,UAAU,OAAO,QAAQ,MAAM,MAA0B,CAAC,QAAQ,GAAG,WAAW;AACpF,QAAI,eAAe,MAAM,CAAE,QAAO;IAClC,MAAM,CAAC,QAAQ,YAAY,MAAqB;AAChD,WAAO,OAAO,SAAS,YAAY,CAAC,KAAK,SAAS,KAAK;KACvD;AACF,OAAI,SAAS,OAAO,YAAY,QAAQ,CAAC;;EAE3C,MAAM,gBAAgB,CAAC,CAAC,OAAO,QAAQ,kBAAkB,SAAS,OAAO;AACzE,MAAI,cAAe,KAAI,QAAQ,OAAO,KAAyB;AAE/D,QAAM,IAAI,MAAM;GACd,MAAM;IACJ;IACA,aAAa,OAAO,eAAe,GAAG,OAAO,aAAa,CAAC,GAAG;IAC9D,aAAa;KAAE,MAAM;KAAU;KAAY,GAAI,SAAS,SAAS,KAAK,EAAE,UAAU;KAAG;IACrF,aAAa;KAAE,GAAG,oBAAoB;KAAS,GAAG,OAAO,KAAK;KAAa;IAC5E;GACD;GACA;GACA;GACA;GACD,CAAC;;AAEJ,QAAO;;;AAOT,eAAe,SACb,UACA,QACA,MACA,GACyB;CACzB,MAAM,QAAQ,YAAkC;EAC9C,SAAS,CAAC;GAAE,MAAM;GAAQ;GAAM,CAAC;EACjC,SAAS;EACV;CACD,MAAMC,WAAoC,EAAE;CAC5C,MAAMC,YAAqC,EAAE;CAC7C,MAAMC,WAAoC,EAAE;AAC5C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,EAAE;EAC/C,MAAM,SAAS,OAAO,QAAQ,IAAI,IAAI,KAAK,OAAO,gBAAgB,SAAY;AAC9E,MAAI,WAAW,OAAQ,UAAS,OAAO;WAC9B,WAAW,QAAS,WAAU,OAAO;WACrC,WAAW,OAAQ,UAAS,OAAO;;CAE9C,IAAIC;CACJ,IAAIC;CACJ,MAAM,OAAO,OAAO,KAAK,QAAQ,eAAe,GAAG,SAAiB;EAClE,MAAM,QAAQ,SAAS;AACvB,MAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,GAAI,aAAY;AAEvE,MAAI,UAAU,OAAO,UAAU,KAAM,aAAY;AACjD,SAAO,mBAAmB,OAAO,SAAS,GAAG,CAAC;GAC9C;AACF,KAAI,QAAS,QAAO,KAAK,2BAA2B,UAAU;AAC9D,KAAI,QAAS,QAAO,KAAK,2BAA2B,UAAU;CAE9D,MAAM,MAAM,IAAI,IAAI,MAAM,EAAE,QAAQ,IAAI;AACxC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,EAAE;AACpD,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO,KAAK,4BAA4B,MAAM;AAC/F,MAAI,UAAU,UAAa,UAAU,KAAM,KAAI,aAAa,IAAI,KAAK,OAAO,MAAM,CAAC;;CAIrF,MAAM,UAAU,IAAI,QAAQ,EAAE,QAAQ,QAAQ;AAC9C,MAAK,MAAM,QAAQ,gBAAiB,SAAQ,OAAO,KAAK;AACxD,SAAQ,IAAI,UAAU,mBAAmB;CACzC,MAAM,UAAU,kBAAkB,SAAS,OAAO,OAAO;AACzD,KAAI,QAAS,SAAQ,IAAI,gBAAgB,mBAAmB;CAE5D,IAAIC;AACJ,KAAI;AACF,aAAW,MAAM,SACf,IAAI,QAAQ,KAAK;GACf,QAAQ,OAAO,OAAO,aAAa;GACnC;GACA,MAAM,UAAU,KAAK,UAAU,SAAS,GAAG;GAC5C,CAAC,EACF,EACD;UACM,OAAO;AACd,UAAQ,MAAM,qBAAqB,OAAO,KAAK,KAAK,IAAI,MAAM;AAC9D,SAAO,KAAK,iBAAiB;;AAE/B,KAAI,gBAAgB,KAAK,SAAS,QAAQ,IAAI,eAAe,IAAI,GAAG,EAAE;AACpE,QAAM,SAAS,MAAM,QAAQ;AAC7B,SAAO,KAAK,wCAAwC;;CAEtD,MAAM,OAAO,MAAM,SAAS,MAAM;CAClC,MAAMC,SAAyB;EAAE,SAAS,CAAC;GAAE,MAAM;GAAQ;GAAM,CAAC;EAAE,SAAS,CAAC,SAAS;EAAI;AAC3F,KAAI,sBAAsB,KAAK,SAAS,QAAQ,IAAI,eAAe,IAAI,GAAG,EAAE;EAC1E,MAAM,QAAQ,UAAU,KAAK;AAC7B,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM,CACtE,QAAO,oBAAoB;;AAG/B,QAAO;;;AAIT,eAAe,MACb,OACA,UACA,SACA,GACmB;CACnB,MAAM,SAAS,IAAI,OACjB;EACE,MAAM,QAAQ;EACd,SAAS,QAAQ;EACjB,OAAO,QAAQ;EACf,OAAO,QAAQ;EACf,YAAY,QAAQ;EACrB,EACD;EAAE,cAAc,EAAE,OAAO,EAAE,EAAE;EAAE,cAAc,QAAQ;EAAc,CACpE;AACD,QAAO,kBAAkB,+BAA+B,EACtD,OAAO,CAAC,GAAG,MAAM,QAAQ,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK,EAC9C,EAAE;AACH,QAAO,kBAAkB,wBAAwB,YAAY;EAC3D,MAAM,SAAS,MAAM,IAAI,QAAQ,OAAO,KAAK;AAC7C,MAAI,CAAC,OACH,OAAM,IAAI,SAAS,UAAU,eAAe,iBAAiB,QAAQ,OAAO,OAAO;AAErF,SAAO,SAAS,UAAU,QAAQ,QAAQ,OAAO,aAAa,EAAE,EAAE,EAAE;GACpE;CACF,MAAM,YAAY,IAAI,yCAAyC;EAC7D,oBAAoB;EACpB,oBAAoB;EACrB,CAAC;AACF,OAAM,OAAO,QAAQ,UAAU;AAC/B,QAAO,UAAU,cAAc,EAAE,QAAQ;;;AAI3C,SAAS,UAAU,OAAoC;AACrD,QAAO,OAAO,GAAG,SAAS;EACxB,MAAM,WAAW,MAAM,MAAM;AAC7B,MAAI,SAAS,WAAW,IAAK,QAAO;EACpC,MAAM,MAAM,IAAI,IAAI,EAAE,QAAQ,IAAI;EAClC,MAAM,SAAS,CAAC,sBAAsB,IAAI,SAAS,IAAI,WAAW,cAAc,GAAG;AACnF,MAAI,MAAM,QAAQ,OAAQ,QAAO,KAAK,UAAU,MAAM,OAAO,KAAK,IAAI,CAAC,GAAG;EAC1E,MAAM,WAAW,SAAS,QAAQ,IAAI,mBAAmB;EACzD,MAAM,QAAQ,UAAU,WAAW,UAAU,GACzC,GAAG,SAAS,IAAI,OAAO,KAAK,KAAK,KACjC,UAAU,OAAO,KAAK,KAAK;EAC/B,MAAM,aAAa,IAAI,SAAS,SAAS,MAAM,SAAS;AACxD,aAAW,QAAQ,IAAI,oBAAoB,MAAM;AACjD,SAAO;;;;AAKX,SAAS,SAAS,OAAwB;AACxC,SAAQ,MAAe;EACrB,MAAM,MAAM,IAAI,IAAI,EAAE,QAAQ,IAAI;EAClC,MAAM,WAAW,IAAI,SAAS,QAAQ,eAAe,GAAG;AACxD,SAAO,SAAS,KAAK;GACnB,UAAU,GAAG,IAAI,SAAS;GAC1B,uBAAuB,MAAM;GAC7B,GAAI,MAAM,UAAU,EAAE,kBAAkB,MAAM,QAAQ;GACtD,0BAA0B,CAAC,SAAS;GACrC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBN,SAAgB,IACd,QACA,SACA,GAAG,YACmB;AACtB,KAAI,WAAW,WAAW,KAAK,QAAQ,WAAW,KAChD,OAAM,IAAI,MAAM,gEAAgE;CAElF,MAAM,OAAO,SAAS,OAAO,GAAG,SAAS,OAAO,OAAuB;CACvE,MAAM,QAAQ,aAAa,KAAK;CAGhC,MAAM,4BAAY,IAAI,SAA2C;CACjE,MAAMC,WAAuB,GAAG,SAAS;AACvC,SAAO,OAAO,GAAG,UAAU,IAAI,EAAE,QAAQ,CAAC;AAC1C,SAAO,MAAM;;CAEf,MAAM,aAAa,OAAO,KAAK,QAAQ,SAAS,GAAI,KAAK,cAAc,EAAE,CAAE,CAAC,SAAS;CACrF,MAAMC,YAAsB,SAAS,MAAM;AACzC,YAAU,IACR,SACA,OAAO,YAAY,OAAO,QAAQ,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,aAAa,IAAI,IAAI,CAAC,CAAC,CAChF;AACD,SAAO,WAAW,SAAS,EAAE,KAAK,EAAE,aAAa;;CAGnD,MAAM,QAAQ,QAAQ,QAAQ,CAAC,UAAU,QAAQ,MAAM,EAAE,GAAG,WAAW,GAAG;CAC1E,MAAM,mBAAmB,IAAI,SAAS,MAAM;EAAE,QAAQ;EAAK,SAAS,EAAE,OAAO,QAAQ;EAAE,CAAC;AAExF,QAAO,OAAO;EACZ,OAAO,MAAM;GAEX,MAAM,SAAS,EAAE,QAAQ,QAAQ,IAAI,SAAS;AAC9C,OAAI,UAAU,WAAW,IAAI,IAAI,EAAE,QAAQ,IAAI,CAAC,OAC9C,QAAO,IAAI,SAAS,oBAAoB,EAAE,QAAQ,KAAK,CAAC;AAE1D,UAAO,cAAc,OAAO,SAA8B,MAAM,OAAO,UAAU,SAAS,EAAE,CAAC;;EAE/F,KAAK;EACL,QAAQ;EACR,GAAI,QAAQ,SAAS,EACnB,eAAe,OAAO,EACpB,4BAA4B,OAAO,EAAE,KAAK,SAAS,QAAQ,MAAM,EAAE,CAAC,EACrE,CAAC,EACH;EACF,CAAC;;;;;;;;;;;;;;;AAgBJ,SAAgB,uBAAuB,OAA8C;CACnF,MAAM,MAAM,SAAS,MAAM;AAC3B,QAAO,OAAO,EAAE,4BAA4B,OAAO;EAAE;EAAK,WAAW,OAAO,EAAE,KAAK,CAAC;EAAE,CAAC,EAAE,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { a as SignJwtOptions, c as jwtSign, i as JwtSecret, l as jwtVerify, n as JwtAlgorithm, o as VerifyJwtOptions, r as JwtPayload, s as createJwks, t as Jwks } from "../jwt-B-Fqwu4E.mjs";
2
- import { i as compose, n as MiddlewareContext, t as Middleware } from "../middleware-zRMBpTLe.mjs";
2
+ import { i as compose, n as MiddlewareContext, t as Middleware } from "../middleware-Cj1yHSJF.mjs";
3
3
 
4
4
  //#region src/middleware/auth.d.ts
5
5
 
@@ -1 +1 @@
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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"}
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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"}
@@ -480,11 +480,11 @@ function parseCookies(header) {
480
480
  * The value is URL-encoded; attributes are appended in a stable order.
481
481
  */
482
482
  function serializeCookie(name, value, options = {}) {
483
- const parts = [`${name}=${encodeURIComponent(value)}`];
483
+ const parts = [`${name.replace(/[\r\n;]/g, "")}=${encodeURIComponent(value)}`];
484
484
  if (options.maxAge !== void 0) parts.push(`Max-Age=${Math.trunc(options.maxAge)}`);
485
485
  if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
486
- if (options.domain) parts.push(`Domain=${options.domain}`);
487
- if (options.path) parts.push(`Path=${options.path}`);
486
+ if (options.domain) parts.push(`Domain=${options.domain.replace(/[\r\n;]/g, "")}`);
487
+ if (options.path) parts.push(`Path=${options.path.replace(/[\r\n;]/g, "")}`);
488
488
  if (options.secure) parts.push("Secure");
489
489
  if (options.httpOnly) parts.push("HttpOnly");
490
490
  if (options.sameSite) parts.push(`SameSite=${options.sameSite[0].toUpperCase()}${options.sameSite.slice(1)}`);
@@ -529,13 +529,13 @@ function cookies() {
529
529
  * Determines if an origin is allowed based on the configuration.
530
530
  */
531
531
  function getAllowedOrigin(requestOrigin, configOrigin) {
532
- if (!requestOrigin) return "*";
533
532
  if (!configOrigin || configOrigin === "*") return "*";
534
- if (typeof configOrigin === "string") return configOrigin === requestOrigin ? configOrigin : "false";
535
- if (Array.isArray(configOrigin)) return configOrigin.includes(requestOrigin) ? requestOrigin : "false";
536
- if (configOrigin instanceof RegExp) return configOrigin.test(requestOrigin) ? requestOrigin : "false";
537
- if (typeof configOrigin === "function") return configOrigin(requestOrigin) ? requestOrigin : "false";
538
- return "false";
533
+ if (!requestOrigin) return false;
534
+ if (typeof configOrigin === "string") return configOrigin === requestOrigin ? configOrigin : false;
535
+ if (Array.isArray(configOrigin)) return configOrigin.includes(requestOrigin) ? requestOrigin : false;
536
+ if (configOrigin instanceof RegExp) return configOrigin.test(requestOrigin) ? requestOrigin : false;
537
+ if (typeof configOrigin === "function") return configOrigin(requestOrigin) ? requestOrigin : false;
538
+ return false;
539
539
  }
540
540
  /**
541
541
  * Creates a CORS middleware with the specified options.
@@ -569,10 +569,10 @@ function cors(options = {}) {
569
569
  const allowedOrigin = getAllowedOrigin(request.headers.get("Origin"), config.origin);
570
570
  if (request.method === "OPTIONS") {
571
571
  const headers$1 = {
572
- "Access-Control-Allow-Origin": allowedOrigin,
573
572
  "Access-Control-Allow-Methods": config.methods.join(", "),
574
573
  Vary: "Origin"
575
574
  };
575
+ if (allowedOrigin) headers$1["Access-Control-Allow-Origin"] = allowedOrigin;
576
576
  if (config.allowedHeaders.length > 0) {
577
577
  const requestHeaders = request.headers.get("Access-Control-Request-Headers");
578
578
  if (requestHeaders) {
@@ -583,7 +583,7 @@ function cors(options = {}) {
583
583
  } else headers$1["Access-Control-Allow-Headers"] = config.allowedHeaders.join(", ");
584
584
  }
585
585
  if (config.exposedHeaders.length > 0) headers$1["Access-Control-Expose-Headers"] = config.exposedHeaders.join(", ");
586
- if (config.credentials) headers$1["Access-Control-Allow-Credentials"] = "true";
586
+ if (config.credentials && allowedOrigin && allowedOrigin !== "*") headers$1["Access-Control-Allow-Credentials"] = "true";
587
587
  if (config.maxAge) headers$1["Access-Control-Max-Age"] = String(config.maxAge);
588
588
  const response$1 = new Response(null, {
589
589
  status: 204,
@@ -605,9 +605,9 @@ function cors(options = {}) {
605
605
  }
606
606
  const response = await next();
607
607
  const headers = new Headers(response.headers);
608
- headers.set("Access-Control-Allow-Origin", allowedOrigin);
608
+ if (allowedOrigin) headers.set("Access-Control-Allow-Origin", allowedOrigin);
609
609
  headers.set("Vary", "Origin");
610
- if (config.credentials && allowedOrigin !== "*") headers.set("Access-Control-Allow-Credentials", "true");
610
+ if (config.credentials && allowedOrigin && allowedOrigin !== "*") headers.set("Access-Control-Allow-Credentials", "true");
611
611
  if (config.exposedHeaders.length > 0) headers.set("Access-Control-Expose-Headers", config.exposedHeaders.join(", "));
612
612
  return new Response(response.body, {
613
613
  status: response.status,