@fresho/router 0.1.0 → 0.3.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.
- package/README.md +24 -0
- package/dist/auth/index.d.mts +327 -0
- package/dist/auth/index.d.mts.map +1 -0
- package/dist/auth/index.mjs +279 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/index.d.mts +57 -99
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +112 -54
- package/dist/index.mjs.map +1 -1
- package/dist/jwt-DHM_nSwL.d.mts +124 -0
- package/dist/jwt-DHM_nSwL.d.mts.map +1 -0
- package/dist/jwt-Dab9kOC2.mjs +208 -0
- package/dist/jwt-Dab9kOC2.mjs.map +1 -0
- package/dist/middleware/index.d.mts +2 -99
- package/dist/middleware/index.d.mts.map +1 -1
- package/dist/middleware/index.mjs +11 -150
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/{middleware-CqtYOc6D.d.mts → middleware-Bxq0LHLS.d.mts} +1 -1
- package/dist/{middleware-CqtYOc6D.d.mts.map → middleware-Bxq0LHLS.d.mts.map} +1 -1
- package/dist/{middleware-BpFh0XrR.mjs → middleware-XMbIevSv.mjs} +1 -1
- package/dist/{middleware-BpFh0XrR.mjs.map → middleware-XMbIevSv.mjs.map} +1 -1
- package/docs/authentication.md +29 -2
- package/docs/middleware.md +10 -1
- package/package.json +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["ALGORITHM_MAP: Record<JwtAlgorithm, string>","key: CryptoKey","finalPayload: JwtPayload","status: number","info: LogInfo","windowMs: number","contentType","config: Required<CorsOptions>","headers: Record<string, string>","response","headers"],"sources":["../../src/middleware/auth.ts","../../src/middleware/common.ts","../../src/middleware/cors.ts"],"sourcesContent":["/**\n * @fileoverview Authentication middleware for @fresho/router.\n *\n * Provides Basic HTTP and JWT authentication middleware.\n */\n\nimport type { Middleware, MiddlewareContext } from '../middleware.js';\n\n// =============================================================================\n// Basic Auth\n// =============================================================================\n\n/** Basic authentication middleware configuration. */\nexport interface BasicAuthOptions {\n /** Realm name for the authentication challenge. */\n realm?: string;\n /** Function to validate credentials. Returns claims to merge into context, or null to reject. */\n verify: (\n username: string,\n password: string,\n context: MiddlewareContext,\n ) => Promise<Record<string, unknown> | null> | Record<string, unknown> | null;\n}\n\n/**\n * Creates a Basic HTTP authentication middleware.\n *\n * @example\n * ```typescript\n * const adminRouter = router('/admin', routes,\n * basicAuth({\n * verify: async (username, password) => {\n * if (username === 'admin' && password === 'secret') {\n * return { user: { name: username } };\n * }\n * return null;\n * }\n * })\n * );\n * ```\n */\nexport function basicAuth(options: BasicAuthOptions): Middleware {\n const realm = options.realm || 'Secure Area';\n\n return async (context, next) => {\n const { request } = context;\n\n const authHeader = request.headers.get('Authorization');\n if (!authHeader?.startsWith('Basic ')) {\n return new Response('Unauthorized', {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Basic realm=\"${realm}\", charset=\"UTF-8\"`,\n },\n });\n }\n\n try {\n const credentials = atob(authHeader.slice(6));\n const colonIndex = credentials.indexOf(':');\n if (colonIndex === -1) {\n throw new Error('Invalid credentials format');\n }\n const username = credentials.substring(0, colonIndex);\n const password = credentials.substring(colonIndex + 1);\n\n const claims = await options.verify(username, password, context);\n if (claims === null) {\n return new Response('Invalid credentials', {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Basic realm=\"${realm}\", charset=\"UTF-8\"`,\n },\n });\n }\n\n Object.assign(context, claims);\n return next();\n } catch (_error) {\n return new Response('Invalid credentials', {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Basic realm=\"${realm}\", charset=\"UTF-8\"`,\n },\n });\n }\n };\n}\n\n// =============================================================================\n// JWT Auth\n// =============================================================================\n\n/** JWT payload type. */\nexport interface JwtPayload {\n /** Subject (user ID). */\n sub?: string;\n /** Issued at timestamp. */\n iat?: number;\n /** Expiration timestamp. */\n exp?: number;\n /** Not before timestamp. */\n nbf?: number;\n /** Issuer. */\n iss?: string;\n /** Audience. */\n aud?: string | string[];\n /** Additional claims. */\n [key: string]: unknown;\n}\n\n/** Supported HMAC algorithms for JWT signing/verification. */\nexport type JwtAlgorithm = 'HS256' | 'HS384' | 'HS512';\n\n/** Maps JWT algorithm names to Web Crypto hash names. */\nconst ALGORITHM_MAP: Record<JwtAlgorithm, string> = {\n HS256: 'SHA-256',\n HS384: 'SHA-384',\n HS512: 'SHA-512',\n};\n\n/** Options for signing a JWT. */\nexport interface SignJwtOptions {\n /** Signing algorithm (default: 'HS256'). */\n algorithm?: JwtAlgorithm;\n /** Expiration time (e.g., '1h', '7d', '30m', or seconds as number). */\n expiresIn?: string | number;\n /** Not before time (e.g., '5m', or seconds as number). */\n notBefore?: string | number;\n /** Issuer claim. */\n issuer?: string;\n /** Audience claim. */\n audience?: string | string[];\n /** Subject claim. */\n subject?: string;\n /** Custom issued-at timestamp (defaults to now). */\n issuedAt?: number | Date;\n}\n\n/** JWT secret value. */\ntype JwtSecret = string | ArrayBuffer | CryptoKey;\n\n/** Options for verifying a JWT. */\nexport interface VerifyJwtOptions {\n /** Allowed algorithms (default: ['HS256']). */\n algorithms?: JwtAlgorithm[];\n}\n\n/** JWT authentication middleware configuration. */\nexport interface JwtAuthOptions<Ctx = {}> {\n /** Secret key or function to get the secret from context. */\n secret: JwtSecret | ((context: MiddlewareContext<Ctx>) => JwtSecret);\n /**\n * Map JWT payload to context properties. Return null to reject the token.\n * The returned object is merged into the middleware context.\n */\n claims: (payload: JwtPayload) => Record<string, unknown> | null;\n /** Allowed algorithms (default: ['HS256']). */\n algorithms?: JwtAlgorithm[];\n /** Function to extract token from request (default: Authorization header or 'token' cookie). */\n getToken?: (request: Request) => string | null;\n}\n\n/**\n * Verifies a JWT token using Web Crypto API.\n *\n * Uses only Web Crypto API, compatible with Cloudflare Workers, Deno, and browsers.\n *\n * @param token - The JWT token string to verify\n * @param secret - The secret used to sign the token (string, ArrayBuffer, or CryptoKey)\n * @param options - Optional verification options\n * @returns The decoded JWT payload\n * @throws Error if the token is invalid, expired, or not yet valid\n *\n * @example\n * ```typescript\n * import { jwtVerify } from '@fresho/router/middleware';\n *\n * try {\n * const payload = await jwtVerify(token, 'your-secret-key');\n * console.log(payload.sub); // user ID\n * } catch (error) {\n * console.error('Invalid token:', error.message);\n * }\n *\n * // With specific algorithms\n * const payload = await jwtVerify(token, secret, { algorithms: ['HS512'] });\n * ```\n */\nexport async function jwtVerify(\n token: string,\n secret: string | ArrayBuffer | CryptoKey,\n options: VerifyJwtOptions = {},\n): Promise<JwtPayload> {\n const algorithms = options.algorithms || ['HS256'];\n\n const parts = token.split('.');\n if (parts.length !== 3) {\n throw new Error('Invalid JWT format');\n }\n\n const [headerB64, payloadB64, signatureB64] = parts;\n\n // Decode header.\n const header = JSON.parse(atob(headerB64.replace(/-/g, '+').replace(/_/g, '/')));\n if (!algorithms.includes(header.alg)) {\n throw new Error(`Unsupported algorithm: ${header.alg}`);\n }\n\n // Get the hash algorithm for the JWT algorithm.\n const hashAlg = ALGORITHM_MAP[header.alg as JwtAlgorithm];\n if (!hashAlg) {\n throw new Error(`Unknown algorithm: ${header.alg}`);\n }\n\n // Decode payload.\n const payload = JSON.parse(atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/')));\n\n // Verify signature using Web Crypto API.\n const data = `${headerB64}.${payloadB64}`;\n const signature = Uint8Array.from(atob(signatureB64.replace(/-/g, '+').replace(/_/g, '/')), (c) =>\n c.charCodeAt(0),\n );\n\n let key: CryptoKey;\n if (secret instanceof CryptoKey) {\n key = secret;\n } else {\n const keyData = typeof secret === 'string' ? new TextEncoder().encode(secret) : secret;\n key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: hashAlg }, false, [\n 'verify',\n ]);\n }\n\n const valid = await crypto.subtle.verify('HMAC', key, signature, new TextEncoder().encode(data));\n\n if (!valid) {\n throw new Error('Invalid signature');\n }\n\n // Check expiration.\n if (payload.exp && payload.exp < Date.now() / 1000) {\n throw new Error('Token expired');\n }\n\n // Check not before.\n if (payload.nbf && payload.nbf > Date.now() / 1000) {\n throw new Error('Token not yet valid');\n }\n\n return payload as JwtPayload;\n}\n\n/**\n * Parses a duration string (e.g., '1h', '7d', '30m') to seconds.\n */\nfunction parseDuration(duration: string | number): number {\n if (typeof duration === 'number') return duration;\n\n const match = duration.match(/^(\\d+)\\s*(s|m|h|d|w)$/i);\n if (!match) {\n throw new Error(\n `Invalid duration format: ${duration}. Use formats like '1h', '7d', '30m', '60s'.`,\n );\n }\n\n const value = parseInt(match[1], 10);\n const unit = match[2].toLowerCase();\n\n switch (unit) {\n case 's':\n return value;\n case 'm':\n return value * 60;\n case 'h':\n return value * 60 * 60;\n case 'd':\n return value * 60 * 60 * 24;\n case 'w':\n return value * 60 * 60 * 24 * 7;\n default:\n throw new Error(`Unknown duration unit: ${unit}`);\n }\n}\n\n/**\n * Base64url encodes a string or Uint8Array.\n */\nfunction base64urlEncode(data: string | Uint8Array): string {\n const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;\n const binary = String.fromCharCode(...bytes);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Signs a JWT token using Web Crypto API.\n *\n * Uses only Web Crypto API, compatible with Cloudflare Workers, Deno, and browsers.\n *\n * @param payload - The JWT payload (custom claims)\n * @param secret - The signing secret (string, ArrayBuffer, or CryptoKey)\n * @param options - Optional signing options (algorithm, expiresIn, issuer, etc.)\n * @returns The signed JWT token string\n *\n * @example\n * ```typescript\n * // Basic usage (HS256 by default)\n * const token = await jwtSign(\n * { uid: 'user-123', role: 'admin' },\n * 'your-secret-key',\n * { expiresIn: '1h' }\n * );\n *\n * // With HS512 algorithm\n * const token = await jwtSign(\n * { uid: 'user-123' },\n * process.env.JWT_SECRET,\n * { algorithm: 'HS512', expiresIn: '7d' }\n * );\n * ```\n */\nexport async function jwtSign(\n payload: Record<string, unknown>,\n secret: JwtSecret,\n options: SignJwtOptions = {},\n): Promise<string> {\n const algorithm = options.algorithm || 'HS256';\n const hashAlg = ALGORITHM_MAP[algorithm];\n\n const now = options.issuedAt\n ? options.issuedAt instanceof Date\n ? Math.floor(options.issuedAt.getTime() / 1000)\n : options.issuedAt\n : Math.floor(Date.now() / 1000);\n\n // Build the final payload with registered claims.\n const finalPayload: JwtPayload = {\n ...payload,\n iat: now,\n };\n\n if (options.expiresIn !== undefined) {\n finalPayload.exp = now + parseDuration(options.expiresIn);\n }\n\n if (options.notBefore !== undefined) {\n finalPayload.nbf = now + parseDuration(options.notBefore);\n }\n\n if (options.issuer !== undefined) {\n finalPayload.iss = options.issuer;\n }\n\n if (options.audience !== undefined) {\n finalPayload.aud = options.audience;\n }\n\n if (options.subject !== undefined) {\n finalPayload.sub = options.subject;\n }\n\n // Create header.\n const header = { alg: algorithm, typ: 'JWT' };\n\n // Encode header and payload.\n const headerB64 = base64urlEncode(JSON.stringify(header));\n const payloadB64 = base64urlEncode(JSON.stringify(finalPayload));\n const data = `${headerB64}.${payloadB64}`;\n\n // Import key for signing.\n let key: CryptoKey;\n if (secret instanceof CryptoKey) {\n key = secret;\n } else {\n const keyData = typeof secret === 'string' ? new TextEncoder().encode(secret) : secret;\n key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: hashAlg }, false, [\n 'sign',\n ]);\n }\n\n // Sign the data.\n const signatureBuffer = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(data));\n\n const signatureB64 = base64urlEncode(new Uint8Array(signatureBuffer));\n\n return `${data}.${signatureB64}`;\n}\n\n/**\n * Creates a JWT authentication middleware.\n *\n * @example\n * ```typescript\n * interface AppContext {\n * env: { JWT_SECRET: string };\n * user: { id: string; email: string };\n * }\n *\n * jwtAuth<AppContext>({\n * secret: (ctx) => ctx.env.JWT_SECRET, // ctx.env is typed\n * claims: (payload) => ({\n * user: { id: payload.sub, email: payload.email },\n * }),\n * })\n *\n * // In your route handler:\n * route.ctx<AppContext>()({\n * handler: async (c) => {\n * return { email: c.user.email }; // Fully typed\n * },\n * })\n * ```\n */\nexport function jwtAuth<Ctx = {}>(options: JwtAuthOptions<Ctx>): Middleware<Ctx> {\n const getToken =\n options.getToken ||\n ((req) => {\n const auth = req.headers.get('Authorization');\n if (auth?.startsWith('Bearer ')) {\n return auth.slice(7);\n }\n // Also check for token in cookie\n const cookie = req.headers.get('Cookie');\n if (cookie) {\n const match = cookie.match(/(?:^|;\\s*)token=([^;]*)/);\n if (match) {\n return match[1];\n }\n }\n return null;\n });\n\n return async (context, next) => {\n const { request } = context;\n\n const token = getToken(request);\n if (!token) {\n return new Response('Missing token', {\n status: 401,\n headers: { 'WWW-Authenticate': 'Bearer' },\n });\n }\n\n try {\n const secret =\n typeof options.secret === 'function' ? options.secret(context) : options.secret;\n\n const payload = await jwtVerify(token, secret, { algorithms: options.algorithms });\n\n // Map payload to context properties\n const claims = options.claims(payload);\n if (claims === null) {\n throw new Error('Token validation failed');\n }\n Object.assign(context, claims);\n\n return next();\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Invalid token';\n return new Response(message, {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Bearer error=\"invalid_token\", error_description=\"${message}\"`,\n },\n });\n }\n };\n}\n\n// =============================================================================\n// Bearer Token Auth (Simple)\n// =============================================================================\n\n/** Bearer token authentication options. */\nexport interface BearerAuthOptions {\n /** Function to validate the token. Returns claims to merge into context, or null to reject. */\n verify: (\n token: string,\n context: MiddlewareContext,\n ) => Promise<Record<string, unknown> | null> | Record<string, unknown> | null;\n}\n\n/**\n * Creates a simple bearer token authentication middleware.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes,\n * bearerAuth({\n * verify: async (token) => {\n * if (token === process.env.API_TOKEN) {\n * return { apiClient: 'trusted' };\n * }\n * return null;\n * }\n * })\n * );\n * ```\n */\nexport function bearerAuth(options: BearerAuthOptions): Middleware {\n return async (context, next) => {\n const { request } = context;\n\n const authHeader = request.headers.get('Authorization');\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Missing token', {\n status: 401,\n headers: { 'WWW-Authenticate': 'Bearer' },\n });\n }\n\n const token = authHeader.slice(7);\n const claims = await options.verify(token, context);\n\n if (claims === null) {\n return new Response('Invalid token', {\n status: 401,\n headers: { 'WWW-Authenticate': 'Bearer error=\"invalid_token\"' },\n });\n }\n\n Object.assign(context, claims);\n return next();\n };\n}\n","/**\n * @fileoverview Common middleware utilities for @fresho/router.\n *\n * Provides error handling, logging, rate limiting, and other utilities.\n */\n\nimport type { Middleware, MiddlewareContext } from '../middleware.js';\n\n// =============================================================================\n// Error Handler\n// =============================================================================\n\n/**\n * HTTP error with status code for use with errorHandler middleware.\n *\n * Throw this in route handlers to return a specific HTTP status code.\n * The errorHandler middleware will catch it and format the response.\n *\n * @example\n * ```typescript\n * import { HttpError } from '@fresho/router/middleware';\n *\n * const getUser = route({\n * method: 'get',\n * path: '/users/:id',\n * handler: async (c) => {\n * const user = await db.findUser(c.path.id);\n * if (!user) throw new HttpError('User not found', 404);\n * return user;\n * },\n * });\n * ```\n */\nexport class HttpError extends Error {\n constructor(\n message: string,\n public status: number,\n ) {\n super(message);\n this.name = 'HttpError';\n }\n}\n\n/**\n * Extracts HTTP status code from an error object.\n *\n * Supports errors with `status` or `statusCode` properties (common patterns\n * in frameworks like Express, Koa, Hono, and custom HTTP error classes).\n */\nfunction getErrorStatus(error: unknown): number {\n if (typeof error !== 'object' || error === null) {\n return 500;\n }\n if ('status' in error && typeof error.status === 'number') {\n return error.status;\n }\n if ('statusCode' in error && typeof error.statusCode === 'number') {\n return error.statusCode;\n }\n return 500;\n}\n\n/** Error handler middleware configuration. */\nexport interface ErrorHandlerOptions {\n /** Custom error logger. */\n log?: (error: Error, context: MiddlewareContext) => void | Promise<void>;\n /** Expose error details in response (dangerous in production). */\n expose?: boolean;\n /** Custom error response formatter. */\n formatter?: (error: Error, context: MiddlewareContext) => Response | Promise<Response>;\n}\n\n/**\n * Creates an error handling middleware that catches and formats errors.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, errorHandler({ expose: false }));\n * ```\n */\nexport function errorHandler(options: ErrorHandlerOptions = {}): Middleware {\n return async (context, next) => {\n try {\n return await next();\n } catch (error) {\n const err = error as Error;\n\n // Log error if logger provided\n if (options.log) {\n await options.log(err, context);\n } else {\n console.error(`Error in ${context.request.method} ${context.request.url}:`, err);\n }\n\n // Use custom formatter if provided\n if (options.formatter) {\n return await options.formatter(err, context);\n }\n\n // Default error response\n const message = options.expose ? err.message : 'Internal Server Error';\n const status = getErrorStatus(error);\n\n const errorBody = {\n error: message,\n ...(options.expose && {\n stack: err.stack,\n name: err.name,\n }),\n };\n\n return new Response(JSON.stringify(errorBody), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n };\n}\n\n// =============================================================================\n// Logger\n// =============================================================================\n\n/** Logger middleware configuration. */\nexport interface LoggerOptions {\n /** Custom logger function. */\n log?: (message: string) => void;\n /** Include request headers in log. */\n includeHeaders?: boolean;\n /** Include request body in log. */\n includeBody?: boolean;\n /** Custom log formatter. */\n formatter?: (info: LogInfo) => string;\n}\n\n/** Information logged for each request. */\nexport interface LogInfo {\n method: string;\n url: string;\n status: number;\n duration: number;\n headers?: Record<string, string>;\n body?: unknown;\n error?: Error;\n}\n\n/**\n * Creates a request/response logging middleware.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, logger({ includeHeaders: true }));\n * ```\n */\nexport function logger(options: LoggerOptions = {}): Middleware {\n const log = options.log || console.log;\n\n return async (context, next) => {\n const { request } = context;\n const start = Date.now();\n const url = new URL(request.url);\n\n // Collect request info\n const info: LogInfo = {\n method: request.method,\n url: `${url.pathname}${url.search}`,\n status: 0,\n duration: 0,\n };\n\n if (options.includeHeaders) {\n info.headers = Object.fromEntries(request.headers.entries());\n }\n\n if (options.includeBody && request.body) {\n try {\n const clonedRequest = request.clone();\n const text = await clonedRequest.text();\n try {\n info.body = JSON.parse(text);\n } catch {\n info.body = text;\n }\n } catch {\n info.body = '[Body not readable]';\n }\n }\n\n // Log request\n const requestMessage = options.formatter\n ? options.formatter({ ...info, status: 0, duration: 0 })\n : `→ ${info.method} ${info.url}`;\n log(requestMessage);\n\n try {\n // Process request\n const response = await next();\n\n // Log response\n info.status = response.status;\n info.duration = Date.now() - start;\n\n const responseMessage = options.formatter\n ? options.formatter(info)\n : `← ${info.status} ${response.statusText || 'OK'} (${info.duration}ms)`;\n log(responseMessage);\n\n return response;\n } catch (error) {\n // Log error\n info.status = 500;\n info.duration = Date.now() - start;\n info.error = error as Error;\n\n const errorMessage = options.formatter\n ? options.formatter(info)\n : `✗ ${info.status} Error: ${(error as Error).message} (${info.duration}ms)`;\n log(errorMessage);\n\n throw error;\n }\n };\n}\n\n// =============================================================================\n// Rate Limiter\n// =============================================================================\n\n/** Rate limit store interface. */\nexport interface RateLimitStore {\n increment(key: string): Promise<number>;\n decrement(key: string): Promise<void>;\n reset(key: string): Promise<void>;\n}\n\n/** In-memory rate limit store with periodic cleanup of expired entries. */\nexport class MemoryRateLimitStore implements RateLimitStore {\n private counts = new Map<string, { count: number; resetAt: number }>();\n private lastCleanup = Date.now();\n\n /** Interval between cleanups (default: 60 seconds). */\n private readonly cleanupIntervalMs: number;\n\n constructor(\n private windowMs: number,\n options?: { cleanupIntervalMs?: number },\n ) {\n // Default cleanup interval is the window duration or 60 seconds, whichever is larger.\n this.cleanupIntervalMs = options?.cleanupIntervalMs ?? Math.max(windowMs, 60_000);\n }\n\n async increment(key: string): Promise<number> {\n const now = Date.now();\n\n // Periodic cleanup to prevent memory leaks.\n if (now - this.lastCleanup > this.cleanupIntervalMs) {\n this.cleanup(now);\n }\n\n const record = this.counts.get(key);\n\n if (!record || now > record.resetAt) {\n this.counts.set(key, { count: 1, resetAt: now + this.windowMs });\n return 1;\n }\n\n record.count++;\n return record.count;\n }\n\n async decrement(key: string): Promise<void> {\n const record = this.counts.get(key);\n if (record && record.count > 0) {\n record.count--;\n }\n }\n\n async reset(key: string): Promise<void> {\n this.counts.delete(key);\n }\n\n /** Removes all expired entries from the store. */\n private cleanup(now: number): void {\n for (const [key, record] of this.counts) {\n if (now > record.resetAt) {\n this.counts.delete(key);\n }\n }\n this.lastCleanup = now;\n }\n\n /** Returns the current number of tracked keys (useful for testing). */\n get size(): number {\n return this.counts.size;\n }\n}\n\n/** Rate limiter middleware configuration. */\nexport interface RateLimitOptions {\n /** Time window in milliseconds. */\n windowMs?: number;\n /** Maximum requests per window. */\n max?: number;\n /** Function to generate a unique key for each client. */\n keyGenerator?: (context: MiddlewareContext) => string;\n /** Store for rate limit data. */\n store?: RateLimitStore;\n /** Skip successful requests from rate limit. */\n skipSuccessfulRequests?: boolean;\n /** Skip failed requests from rate limit. */\n skipFailedRequests?: boolean;\n /** Custom handler for rate limited requests. */\n handler?: (context: MiddlewareContext) => Response | Promise<Response>;\n}\n\n/**\n * Creates a rate limiting middleware.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, rateLimit({ max: 100, windowMs: 60000 }));\n * ```\n */\nexport function rateLimit(options: RateLimitOptions = {}): Middleware {\n const config = {\n windowMs: 60 * 1000, // 1 minute\n max: 100,\n keyGenerator: (ctx: MiddlewareContext) => {\n // Use IP address as default key\n const cfIp = ctx.request.headers.get('CF-Connecting-IP');\n const xForwardedFor = ctx.request.headers.get('X-Forwarded-For');\n const xRealIp = ctx.request.headers.get('X-Real-IP');\n return cfIp || xForwardedFor?.split(',')[0] || xRealIp || 'unknown';\n },\n ...options,\n };\n\n const store = config.store || new MemoryRateLimitStore(config.windowMs);\n\n return async (context, next) => {\n const key = config.keyGenerator(context);\n const current = await store.increment(key);\n\n if (current > config.max) {\n if (config.handler) {\n return await config.handler(context);\n }\n\n return new Response('Too many requests', {\n status: 429,\n headers: {\n 'Retry-After': String(Math.ceil(config.windowMs / 1000)),\n 'X-RateLimit-Limit': String(config.max),\n 'X-RateLimit-Remaining': '0',\n 'X-RateLimit-Reset': new Date(Date.now() + config.windowMs).toISOString(),\n },\n });\n }\n\n const response = await next();\n\n // Optionally don't count successful/failed requests\n if (\n (config.skipSuccessfulRequests && response.status < 400) ||\n (config.skipFailedRequests && response.status >= 400)\n ) {\n await store.decrement(key);\n }\n\n // Add rate limit headers\n const headers = new Headers(response.headers);\n headers.set('X-RateLimit-Limit', String(config.max));\n headers.set('X-RateLimit-Remaining', String(Math.max(0, config.max - current)));\n headers.set('X-RateLimit-Reset', new Date(Date.now() + config.windowMs).toISOString());\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n\n// =============================================================================\n// Request ID\n// =============================================================================\n\n/** Request ID middleware configuration. */\nexport interface RequestIdOptions {\n /** Header name for request ID. */\n headerName?: string;\n /** Function to generate request ID. */\n generator?: () => string;\n}\n\n/**\n * Creates a middleware that adds a unique request ID to each request.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, requestId({ headerName: 'X-Request-ID' }));\n * ```\n */\nexport function requestId(options: RequestIdOptions = {}): Middleware {\n const headerName = options.headerName || 'X-Request-ID';\n const generator = options.generator || (() => crypto.randomUUID());\n\n return async (context, next) => {\n // Check if request already has an ID\n let id = context.request.headers.get(headerName);\n if (!id) {\n id = generator();\n }\n\n // Add to context\n context.requestId = id;\n\n // Process request\n const response = await next();\n\n // Add request ID to response headers\n const headers = new Headers(response.headers);\n headers.set(headerName, id);\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n\n// =============================================================================\n// Timeout\n// =============================================================================\n\n/** Timeout middleware configuration. */\nexport interface TimeoutOptions {\n /** Timeout in milliseconds. */\n timeout: number;\n /** Custom timeout response. */\n message?: string;\n}\n\n/**\n * Creates a middleware that times out requests after a specified duration.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, timeout({ timeout: 5000 }));\n * ```\n */\nexport function timeout(options: TimeoutOptions): Middleware {\n return async (context, next) => {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n try {\n const responsePromise = next();\n const timeoutPromise = new Promise<Response>((_, reject) => {\n controller.signal.addEventListener('abort', () => {\n reject(new Error('Request timeout'));\n });\n });\n\n const response = await Promise.race([responsePromise, timeoutPromise]);\n clearTimeout(timeoutId);\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n if ((error as Error).message === 'Request timeout') {\n return new Response(options.message || 'Request timeout', {\n status: 408,\n });\n }\n throw error;\n }\n };\n}\n\n// =============================================================================\n// Content Type Validation\n// =============================================================================\n\n/** Content type validation options. */\nexport interface ContentTypeOptions {\n /** Expected content types. */\n types: string[];\n /** Skip validation for these methods. */\n skipMethods?: string[];\n /** Custom error message. */\n message?: string;\n}\n\n/**\n * Creates a middleware that validates request content type.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, contentType({ types: ['application/json'] }));\n * ```\n */\nexport function contentType(options: ContentTypeOptions): Middleware {\n const skipMethods = new Set(\n options.skipMethods?.map((m) => m.toUpperCase()) || ['GET', 'HEAD', 'OPTIONS'],\n );\n\n return async (context, next) => {\n if (skipMethods.has(context.request.method.toUpperCase())) {\n return next();\n }\n\n const contentType = context.request.headers.get('Content-Type');\n if (!contentType) {\n return new Response(options.message || 'Missing Content-Type header', {\n status: 415,\n });\n }\n\n const matches = options.types.some((type) => contentType.includes(type));\n if (!matches) {\n return new Response(\n options.message || `Unsupported Media Type. Expected: ${options.types.join(', ')}`,\n { status: 415 },\n );\n }\n\n return next();\n };\n}\n","/**\n * @fileoverview CORS middleware for @fresho/router.\n *\n * Provides Cross-Origin Resource Sharing support with configurable options.\n */\n\nimport type { Middleware } from '../middleware.js';\n\n/** CORS middleware configuration options. */\nexport interface CorsOptions {\n /** Allowed origins. Can be string, array, regex, or function. */\n origin?: string | string[] | RegExp | ((origin: string) => boolean);\n /** Allowed HTTP methods. */\n methods?: string[];\n /** Allowed request headers. */\n allowedHeaders?: string[];\n /** Headers to expose to the browser. */\n exposedHeaders?: string[];\n /** Allow credentials (cookies, authorization headers). */\n credentials?: boolean;\n /** Max age for preflight cache (seconds). */\n maxAge?: number;\n /** Pass the CORS preflight response to the next handler. */\n preflightContinue?: boolean;\n}\n\n/**\n * Determines if an origin is allowed based on the configuration.\n */\nfunction getAllowedOrigin(\n requestOrigin: string | null,\n configOrigin: CorsOptions['origin'],\n): string {\n if (!requestOrigin) {\n return '*';\n }\n\n if (!configOrigin || configOrigin === '*') {\n return '*';\n }\n\n if (typeof configOrigin === 'string') {\n return configOrigin === requestOrigin ? configOrigin : 'false';\n }\n\n if (Array.isArray(configOrigin)) {\n return configOrigin.includes(requestOrigin) ? requestOrigin : 'false';\n }\n\n if (configOrigin instanceof RegExp) {\n return configOrigin.test(requestOrigin) ? requestOrigin : 'false';\n }\n\n if (typeof configOrigin === 'function') {\n return configOrigin(requestOrigin) ? requestOrigin : 'false';\n }\n\n return 'false';\n}\n\n/**\n * Creates a CORS middleware with the specified options.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, cors({ origin: 'https://example.com', credentials: true }));\n * ```\n */\nexport function cors(options: CorsOptions = {}): Middleware {\n const config: Required<CorsOptions> = {\n origin: '*',\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n exposedHeaders: [],\n credentials: false,\n maxAge: 86400,\n preflightContinue: false,\n ...options,\n };\n\n return async (context, next) => {\n const { request } = context;\n const requestOrigin = request.headers.get('Origin');\n const allowedOrigin = getAllowedOrigin(requestOrigin, config.origin);\n\n // Handle preflight request\n if (request.method === 'OPTIONS') {\n const headers: Record<string, string> = {\n 'Access-Control-Allow-Origin': allowedOrigin,\n 'Access-Control-Allow-Methods': config.methods.join(', '),\n Vary: 'Origin',\n };\n\n if (config.allowedHeaders.length > 0) {\n const requestHeaders = request.headers.get('Access-Control-Request-Headers');\n if (requestHeaders) {\n // Allow requested headers if they're in the allowed list\n const requested = requestHeaders.split(',').map((h) => h.trim().toLowerCase());\n const allowed = config.allowedHeaders.map((h) => h.toLowerCase());\n const toAllow = requested.filter((h) => allowed.includes(h));\n if (toAllow.length > 0) {\n headers['Access-Control-Allow-Headers'] = toAllow.join(', ');\n }\n } else {\n headers['Access-Control-Allow-Headers'] = config.allowedHeaders.join(', ');\n }\n }\n\n if (config.exposedHeaders.length > 0) {\n headers['Access-Control-Expose-Headers'] = config.exposedHeaders.join(', ');\n }\n\n if (config.credentials) {\n headers['Access-Control-Allow-Credentials'] = 'true';\n }\n\n if (config.maxAge) {\n headers['Access-Control-Max-Age'] = String(config.maxAge);\n }\n\n const response = new Response(null, {\n status: 204,\n headers,\n });\n\n if (config.preflightContinue) {\n // Continue to next middleware/handler even for preflight\n const nextResponse = await next();\n // Merge headers from next response\n const mergedHeaders = new Headers(nextResponse.headers);\n Object.entries(headers).forEach(([key, value]) => {\n mergedHeaders.set(key, value);\n });\n return new Response(nextResponse.body, {\n status: nextResponse.status,\n statusText: nextResponse.statusText,\n headers: mergedHeaders,\n });\n }\n\n return response;\n }\n\n // Handle actual request\n const response = await next();\n\n // Add CORS headers to response\n const headers = new Headers(response.headers);\n headers.set('Access-Control-Allow-Origin', allowedOrigin);\n headers.set('Vary', 'Origin');\n\n if (config.credentials && allowedOrigin !== '*') {\n headers.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (config.exposedHeaders.length > 0) {\n headers.set('Access-Control-Expose-Headers', config.exposedHeaders.join(', '));\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["status: number","info: LogInfo","windowMs: number","contentType","config: Required<CorsOptions>","headers: Record<string, string>","response","headers"],"sources":["../../src/middleware/auth.ts","../../src/middleware/common.ts","../../src/middleware/cors.ts"],"sourcesContent":["/**\n * @fileoverview Authentication middleware for @fresho/router.\n *\n * Provides middleware wrappers for Basic HTTP, JWT, and Bearer token authentication.\n * The underlying authentication utilities are in `@fresho/router/auth`.\n */\n\nimport { parseBasicAuth } from '../auth/basic.js';\nimport { type JwtAlgorithm, type JwtPayload, type JwtSecret, jwtVerify } from '../auth/jwt.js';\nimport type { Middleware, MiddlewareContext } from '../middleware.js';\n\n// Re-export types that middleware users need\nexport type {\n JwtAlgorithm,\n JwtPayload,\n JwtSecret,\n SignJwtOptions,\n VerifyJwtOptions,\n} from '../auth/jwt.js';\n\n// Re-export utilities that are commonly used alongside middleware\nexport { jwtSign, jwtVerify } from '../auth/jwt.js';\n\n// =============================================================================\n// Basic Auth Middleware\n// =============================================================================\n\n/** Basic authentication middleware configuration. */\nexport interface BasicAuthOptions {\n /** Realm name for the authentication challenge. */\n realm?: string;\n /** Function to validate credentials. Returns claims to merge into context, or null to reject. */\n verify: (\n username: string,\n password: string,\n context: MiddlewareContext,\n ) => Promise<Record<string, unknown> | null> | Record<string, unknown> | null;\n}\n\n/**\n * Creates a Basic HTTP authentication middleware.\n *\n * @example\n * ```typescript\n * const adminRouter = router('/admin', routes,\n * basicAuth({\n * verify: async (username, password) => {\n * if (username === 'admin' && password === 'secret') {\n * return { user: { name: username } };\n * }\n * return null;\n * }\n * })\n * );\n * ```\n */\nexport function basicAuth(options: BasicAuthOptions): Middleware {\n const realm = options.realm || 'Secure Area';\n\n return async (context, next) => {\n const { request } = context;\n\n const credentials = parseBasicAuth(request.headers.get('Authorization'));\n if (!credentials) {\n return new Response('Unauthorized', {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Basic realm=\"${realm}\", charset=\"UTF-8\"`,\n },\n });\n }\n\n try {\n const claims = await options.verify(credentials.username, credentials.password, context);\n if (claims === null) {\n return new Response('Invalid credentials', {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Basic realm=\"${realm}\", charset=\"UTF-8\"`,\n },\n });\n }\n\n Object.assign(context, claims);\n return next();\n } catch (_error) {\n return new Response('Invalid credentials', {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Basic realm=\"${realm}\", charset=\"UTF-8\"`,\n },\n });\n }\n };\n}\n\n// =============================================================================\n// JWT Auth Middleware\n// =============================================================================\n\n/** JWT authentication middleware configuration. */\nexport interface JwtAuthOptions<Ctx = {}> {\n /** Secret key or function to get the secret from context. */\n secret: JwtSecret | ((context: MiddlewareContext<Ctx>) => JwtSecret);\n /**\n * Map JWT payload to context properties. Return null to reject the token.\n * The returned object is merged into the middleware context.\n */\n claims: (payload: JwtPayload) => Record<string, unknown> | null;\n /** Allowed algorithms (default: ['HS256']). */\n algorithms?: JwtAlgorithm[];\n /** Function to extract token from request (default: Authorization header or 'token' cookie). */\n getToken?: (request: Request) => string | null;\n}\n\n/**\n * Creates a JWT authentication middleware.\n *\n * @example\n * ```typescript\n * interface AppContext {\n * env: { JWT_SECRET: string };\n * user: { id: string; email: string };\n * }\n *\n * jwtAuth<AppContext>({\n * secret: (ctx) => ctx.env.JWT_SECRET, // ctx.env is typed\n * claims: (payload) => ({\n * user: { id: payload.sub, email: payload.email },\n * }),\n * })\n *\n * // In your route handler:\n * route.ctx<AppContext>()({\n * handler: async (c) => {\n * return { email: c.user.email }; // Fully typed\n * },\n * })\n * ```\n */\nexport function jwtAuth<Ctx = {}>(options: JwtAuthOptions<Ctx>): Middleware<Ctx> {\n const getToken =\n options.getToken ||\n ((req) => {\n const auth = req.headers.get('Authorization');\n if (auth?.startsWith('Bearer ')) {\n return auth.slice(7);\n }\n // Also check for token in cookie.\n const cookie = req.headers.get('Cookie');\n if (cookie) {\n const match = cookie.match(/(?:^|;\\s*)token=([^;]*)/);\n if (match) {\n return match[1];\n }\n }\n return null;\n });\n\n return async (context, next) => {\n const { request } = context;\n\n const token = getToken(request);\n if (!token) {\n return new Response('Missing token', {\n status: 401,\n headers: { 'WWW-Authenticate': 'Bearer' },\n });\n }\n\n try {\n const secret =\n typeof options.secret === 'function' ? options.secret(context) : options.secret;\n\n const payload = await jwtVerify(token, secret, { algorithms: options.algorithms });\n\n // Map payload to context properties.\n const claims = options.claims(payload);\n if (claims === null) {\n throw new Error('Token validation failed');\n }\n Object.assign(context, claims);\n\n return next();\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Invalid token';\n return new Response(message, {\n status: 401,\n headers: {\n 'WWW-Authenticate': `Bearer error=\"invalid_token\", error_description=\"${message}\"`,\n },\n });\n }\n };\n}\n\n// =============================================================================\n// Bearer Token Auth Middleware (Simple)\n// =============================================================================\n\n/** Bearer token authentication options. */\nexport interface BearerAuthOptions {\n /** Function to validate the token. Returns claims to merge into context, or null to reject. */\n verify: (\n token: string,\n context: MiddlewareContext,\n ) => Promise<Record<string, unknown> | null> | Record<string, unknown> | null;\n}\n\n/**\n * Creates a simple bearer token authentication middleware.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes,\n * bearerAuth({\n * verify: async (token) => {\n * if (token === process.env.API_TOKEN) {\n * return { apiClient: 'trusted' };\n * }\n * return null;\n * }\n * })\n * );\n * ```\n */\nexport function bearerAuth(options: BearerAuthOptions): Middleware {\n return async (context, next) => {\n const { request } = context;\n\n const authHeader = request.headers.get('Authorization');\n if (!authHeader?.startsWith('Bearer ')) {\n return new Response('Missing token', {\n status: 401,\n headers: { 'WWW-Authenticate': 'Bearer' },\n });\n }\n\n const token = authHeader.slice(7);\n const claims = await options.verify(token, context);\n\n if (claims === null) {\n return new Response('Invalid token', {\n status: 401,\n headers: { 'WWW-Authenticate': 'Bearer error=\"invalid_token\"' },\n });\n }\n\n Object.assign(context, claims);\n return next();\n };\n}\n","/**\n * @fileoverview Common middleware utilities for @fresho/router.\n *\n * Provides error handling, logging, rate limiting, and other utilities.\n */\n\nimport type { Middleware, MiddlewareContext } from '../middleware.js';\n\n// =============================================================================\n// Error Handler\n// =============================================================================\n\n/**\n * HTTP error with status code for use with errorHandler middleware.\n *\n * Throw this in route handlers to return a specific HTTP status code.\n * The errorHandler middleware will catch it and format the response.\n *\n * @example\n * ```typescript\n * import { HttpError } from '@fresho/router/middleware';\n *\n * const getUser = route({\n * method: 'get',\n * path: '/users/:id',\n * handler: async (c) => {\n * const user = await db.findUser(c.path.id);\n * if (!user) throw new HttpError('User not found', 404);\n * return user;\n * },\n * });\n * ```\n */\nexport class HttpError extends Error {\n constructor(\n message: string,\n public status: number,\n ) {\n super(message);\n this.name = 'HttpError';\n }\n}\n\n/**\n * Extracts HTTP status code from an error object.\n *\n * Supports errors with `status` or `statusCode` properties (common patterns\n * in frameworks like Express, Koa, Hono, and custom HTTP error classes).\n */\nfunction getErrorStatus(error: unknown): number {\n if (typeof error !== 'object' || error === null) {\n return 500;\n }\n if ('status' in error && typeof error.status === 'number') {\n return error.status;\n }\n if ('statusCode' in error && typeof error.statusCode === 'number') {\n return error.statusCode;\n }\n return 500;\n}\n\n/** Error handler middleware configuration. */\nexport interface ErrorHandlerOptions {\n /** Custom error logger. */\n log?: (error: Error, context: MiddlewareContext) => void | Promise<void>;\n /** Expose error details in response (dangerous in production). */\n expose?: boolean;\n /** Custom error response formatter. */\n formatter?: (error: Error, context: MiddlewareContext) => Response | Promise<Response>;\n}\n\n/**\n * Creates an error handling middleware that catches and formats errors.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, errorHandler({ expose: false }));\n * ```\n */\nexport function errorHandler(options: ErrorHandlerOptions = {}): Middleware {\n return async (context, next) => {\n try {\n return await next();\n } catch (error) {\n const err = error as Error;\n\n // Log error if logger provided\n if (options.log) {\n await options.log(err, context);\n } else {\n console.error(`Error in ${context.request.method} ${context.request.url}:`, err);\n }\n\n // Use custom formatter if provided\n if (options.formatter) {\n return await options.formatter(err, context);\n }\n\n // Default error response\n const message = options.expose ? err.message : 'Internal Server Error';\n const status = getErrorStatus(error);\n\n const errorBody = {\n error: message,\n ...(options.expose && {\n stack: err.stack,\n name: err.name,\n }),\n };\n\n return new Response(JSON.stringify(errorBody), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n }\n };\n}\n\n// =============================================================================\n// Logger\n// =============================================================================\n\n/** Logger middleware configuration. */\nexport interface LoggerOptions {\n /** Custom logger function. */\n log?: (message: string) => void;\n /** Include request headers in log. */\n includeHeaders?: boolean;\n /** Include request body in log. */\n includeBody?: boolean;\n /** Custom log formatter. */\n formatter?: (info: LogInfo) => string;\n}\n\n/** Information logged for each request. */\nexport interface LogInfo {\n method: string;\n url: string;\n status: number;\n duration: number;\n headers?: Record<string, string>;\n body?: unknown;\n error?: Error;\n}\n\n/**\n * Creates a request/response logging middleware.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, logger({ includeHeaders: true }));\n * ```\n */\nexport function logger(options: LoggerOptions = {}): Middleware {\n const log = options.log || console.log;\n\n return async (context, next) => {\n const { request } = context;\n const start = Date.now();\n const url = new URL(request.url);\n\n // Collect request info\n const info: LogInfo = {\n method: request.method,\n url: `${url.pathname}${url.search}`,\n status: 0,\n duration: 0,\n };\n\n if (options.includeHeaders) {\n info.headers = Object.fromEntries(request.headers.entries());\n }\n\n if (options.includeBody && request.body) {\n try {\n const clonedRequest = request.clone();\n const text = await clonedRequest.text();\n try {\n info.body = JSON.parse(text);\n } catch {\n info.body = text;\n }\n } catch {\n info.body = '[Body not readable]';\n }\n }\n\n // Log request\n const requestMessage = options.formatter\n ? options.formatter({ ...info, status: 0, duration: 0 })\n : `→ ${info.method} ${info.url}`;\n log(requestMessage);\n\n try {\n // Process request\n const response = await next();\n\n // Log response\n info.status = response.status;\n info.duration = Date.now() - start;\n\n const responseMessage = options.formatter\n ? options.formatter(info)\n : `← ${info.status} ${response.statusText || 'OK'} (${info.duration}ms)`;\n log(responseMessage);\n\n return response;\n } catch (error) {\n // Log error\n info.status = 500;\n info.duration = Date.now() - start;\n info.error = error as Error;\n\n const errorMessage = options.formatter\n ? options.formatter(info)\n : `✗ ${info.status} Error: ${(error as Error).message} (${info.duration}ms)`;\n log(errorMessage);\n\n throw error;\n }\n };\n}\n\n// =============================================================================\n// Rate Limiter\n// =============================================================================\n\n/** Rate limit store interface. */\nexport interface RateLimitStore {\n increment(key: string): Promise<number>;\n decrement(key: string): Promise<void>;\n reset(key: string): Promise<void>;\n}\n\n/** In-memory rate limit store with periodic cleanup of expired entries. */\nexport class MemoryRateLimitStore implements RateLimitStore {\n private counts = new Map<string, { count: number; resetAt: number }>();\n private lastCleanup = Date.now();\n\n /** Interval between cleanups (default: 60 seconds). */\n private readonly cleanupIntervalMs: number;\n\n constructor(\n private windowMs: number,\n options?: { cleanupIntervalMs?: number },\n ) {\n // Default cleanup interval is the window duration or 60 seconds, whichever is larger.\n this.cleanupIntervalMs = options?.cleanupIntervalMs ?? Math.max(windowMs, 60_000);\n }\n\n async increment(key: string): Promise<number> {\n const now = Date.now();\n\n // Periodic cleanup to prevent memory leaks.\n if (now - this.lastCleanup > this.cleanupIntervalMs) {\n this.cleanup(now);\n }\n\n const record = this.counts.get(key);\n\n if (!record || now > record.resetAt) {\n this.counts.set(key, { count: 1, resetAt: now + this.windowMs });\n return 1;\n }\n\n record.count++;\n return record.count;\n }\n\n async decrement(key: string): Promise<void> {\n const record = this.counts.get(key);\n if (record && record.count > 0) {\n record.count--;\n }\n }\n\n async reset(key: string): Promise<void> {\n this.counts.delete(key);\n }\n\n /** Removes all expired entries from the store. */\n private cleanup(now: number): void {\n for (const [key, record] of this.counts) {\n if (now > record.resetAt) {\n this.counts.delete(key);\n }\n }\n this.lastCleanup = now;\n }\n\n /** Returns the current number of tracked keys (useful for testing). */\n get size(): number {\n return this.counts.size;\n }\n}\n\n/** Rate limiter middleware configuration. */\nexport interface RateLimitOptions {\n /** Time window in milliseconds. */\n windowMs?: number;\n /** Maximum requests per window. */\n max?: number;\n /** Function to generate a unique key for each client. */\n keyGenerator?: (context: MiddlewareContext) => string;\n /** Store for rate limit data. */\n store?: RateLimitStore;\n /** Skip successful requests from rate limit. */\n skipSuccessfulRequests?: boolean;\n /** Skip failed requests from rate limit. */\n skipFailedRequests?: boolean;\n /** Custom handler for rate limited requests. */\n handler?: (context: MiddlewareContext) => Response | Promise<Response>;\n}\n\n/**\n * Creates a rate limiting middleware.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, rateLimit({ max: 100, windowMs: 60000 }));\n * ```\n */\nexport function rateLimit(options: RateLimitOptions = {}): Middleware {\n const config = {\n windowMs: 60 * 1000, // 1 minute\n max: 100,\n keyGenerator: (ctx: MiddlewareContext) => {\n // Use IP address as default key\n const cfIp = ctx.request.headers.get('CF-Connecting-IP');\n const xForwardedFor = ctx.request.headers.get('X-Forwarded-For');\n const xRealIp = ctx.request.headers.get('X-Real-IP');\n return cfIp || xForwardedFor?.split(',')[0] || xRealIp || 'unknown';\n },\n ...options,\n };\n\n const store = config.store || new MemoryRateLimitStore(config.windowMs);\n\n return async (context, next) => {\n const key = config.keyGenerator(context);\n const current = await store.increment(key);\n\n if (current > config.max) {\n if (config.handler) {\n return await config.handler(context);\n }\n\n return new Response('Too many requests', {\n status: 429,\n headers: {\n 'Retry-After': String(Math.ceil(config.windowMs / 1000)),\n 'X-RateLimit-Limit': String(config.max),\n 'X-RateLimit-Remaining': '0',\n 'X-RateLimit-Reset': new Date(Date.now() + config.windowMs).toISOString(),\n },\n });\n }\n\n const response = await next();\n\n // Optionally don't count successful/failed requests\n if (\n (config.skipSuccessfulRequests && response.status < 400) ||\n (config.skipFailedRequests && response.status >= 400)\n ) {\n await store.decrement(key);\n }\n\n // Add rate limit headers\n const headers = new Headers(response.headers);\n headers.set('X-RateLimit-Limit', String(config.max));\n headers.set('X-RateLimit-Remaining', String(Math.max(0, config.max - current)));\n headers.set('X-RateLimit-Reset', new Date(Date.now() + config.windowMs).toISOString());\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n\n// =============================================================================\n// Request ID\n// =============================================================================\n\n/** Request ID middleware configuration. */\nexport interface RequestIdOptions {\n /** Header name for request ID. */\n headerName?: string;\n /** Function to generate request ID. */\n generator?: () => string;\n}\n\n/**\n * Creates a middleware that adds a unique request ID to each request.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, requestId({ headerName: 'X-Request-ID' }));\n * ```\n */\nexport function requestId(options: RequestIdOptions = {}): Middleware {\n const headerName = options.headerName || 'X-Request-ID';\n const generator = options.generator || (() => crypto.randomUUID());\n\n return async (context, next) => {\n // Check if request already has an ID\n let id = context.request.headers.get(headerName);\n if (!id) {\n id = generator();\n }\n\n // Add to context\n context.requestId = id;\n\n // Process request\n const response = await next();\n\n // Add request ID to response headers\n const headers = new Headers(response.headers);\n headers.set(headerName, id);\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n\n// =============================================================================\n// Timeout\n// =============================================================================\n\n/** Timeout middleware configuration. */\nexport interface TimeoutOptions {\n /** Timeout in milliseconds. */\n timeout: number;\n /** Custom timeout response. */\n message?: string;\n}\n\n/**\n * Creates a middleware that times out requests after a specified duration.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, timeout({ timeout: 5000 }));\n * ```\n */\nexport function timeout(options: TimeoutOptions): Middleware {\n return async (context, next) => {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n try {\n const responsePromise = next();\n const timeoutPromise = new Promise<Response>((_, reject) => {\n controller.signal.addEventListener('abort', () => {\n reject(new Error('Request timeout'));\n });\n });\n\n const response = await Promise.race([responsePromise, timeoutPromise]);\n clearTimeout(timeoutId);\n return response;\n } catch (error) {\n clearTimeout(timeoutId);\n if ((error as Error).message === 'Request timeout') {\n return new Response(options.message || 'Request timeout', {\n status: 408,\n });\n }\n throw error;\n }\n };\n}\n\n// =============================================================================\n// Content Type Validation\n// =============================================================================\n\n/** Content type validation options. */\nexport interface ContentTypeOptions {\n /** Expected content types. */\n types: string[];\n /** Skip validation for these methods. */\n skipMethods?: string[];\n /** Custom error message. */\n message?: string;\n}\n\n/**\n * Creates a middleware that validates request content type.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, contentType({ types: ['application/json'] }));\n * ```\n */\nexport function contentType(options: ContentTypeOptions): Middleware {\n const skipMethods = new Set(\n options.skipMethods?.map((m) => m.toUpperCase()) || ['GET', 'HEAD', 'OPTIONS'],\n );\n\n return async (context, next) => {\n if (skipMethods.has(context.request.method.toUpperCase())) {\n return next();\n }\n\n const contentType = context.request.headers.get('Content-Type');\n if (!contentType) {\n return new Response(options.message || 'Missing Content-Type header', {\n status: 415,\n });\n }\n\n const matches = options.types.some((type) => contentType.includes(type));\n if (!matches) {\n return new Response(\n options.message || `Unsupported Media Type. Expected: ${options.types.join(', ')}`,\n { status: 415 },\n );\n }\n\n return next();\n };\n}\n","/**\n * @fileoverview CORS middleware for @fresho/router.\n *\n * Provides Cross-Origin Resource Sharing support with configurable options.\n */\n\nimport type { Middleware } from '../middleware.js';\n\n/** CORS middleware configuration options. */\nexport interface CorsOptions {\n /** Allowed origins. Can be string, array, regex, or function. */\n origin?: string | string[] | RegExp | ((origin: string) => boolean);\n /** Allowed HTTP methods. */\n methods?: string[];\n /** Allowed request headers. */\n allowedHeaders?: string[];\n /** Headers to expose to the browser. */\n exposedHeaders?: string[];\n /** Allow credentials (cookies, authorization headers). */\n credentials?: boolean;\n /** Max age for preflight cache (seconds). */\n maxAge?: number;\n /** Pass the CORS preflight response to the next handler. */\n preflightContinue?: boolean;\n}\n\n/**\n * Determines if an origin is allowed based on the configuration.\n */\nfunction getAllowedOrigin(\n requestOrigin: string | null,\n configOrigin: CorsOptions['origin'],\n): string {\n if (!requestOrigin) {\n return '*';\n }\n\n if (!configOrigin || configOrigin === '*') {\n return '*';\n }\n\n if (typeof configOrigin === 'string') {\n return configOrigin === requestOrigin ? configOrigin : 'false';\n }\n\n if (Array.isArray(configOrigin)) {\n return configOrigin.includes(requestOrigin) ? requestOrigin : 'false';\n }\n\n if (configOrigin instanceof RegExp) {\n return configOrigin.test(requestOrigin) ? requestOrigin : 'false';\n }\n\n if (typeof configOrigin === 'function') {\n return configOrigin(requestOrigin) ? requestOrigin : 'false';\n }\n\n return 'false';\n}\n\n/**\n * Creates a CORS middleware with the specified options.\n *\n * @example\n * ```typescript\n * const apiRouter = router('/api', routes, cors({ origin: 'https://example.com', credentials: true }));\n * ```\n */\nexport function cors(options: CorsOptions = {}): Middleware {\n const config: Required<CorsOptions> = {\n origin: '*',\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n exposedHeaders: [],\n credentials: false,\n maxAge: 86400,\n preflightContinue: false,\n ...options,\n };\n\n return async (context, next) => {\n const { request } = context;\n const requestOrigin = request.headers.get('Origin');\n const allowedOrigin = getAllowedOrigin(requestOrigin, config.origin);\n\n // Handle preflight request\n if (request.method === 'OPTIONS') {\n const headers: Record<string, string> = {\n 'Access-Control-Allow-Origin': allowedOrigin,\n 'Access-Control-Allow-Methods': config.methods.join(', '),\n Vary: 'Origin',\n };\n\n if (config.allowedHeaders.length > 0) {\n const requestHeaders = request.headers.get('Access-Control-Request-Headers');\n if (requestHeaders) {\n // Allow requested headers if they're in the allowed list\n const requested = requestHeaders.split(',').map((h) => h.trim().toLowerCase());\n const allowed = config.allowedHeaders.map((h) => h.toLowerCase());\n const toAllow = requested.filter((h) => allowed.includes(h));\n if (toAllow.length > 0) {\n headers['Access-Control-Allow-Headers'] = toAllow.join(', ');\n }\n } else {\n headers['Access-Control-Allow-Headers'] = config.allowedHeaders.join(', ');\n }\n }\n\n if (config.exposedHeaders.length > 0) {\n headers['Access-Control-Expose-Headers'] = config.exposedHeaders.join(', ');\n }\n\n if (config.credentials) {\n headers['Access-Control-Allow-Credentials'] = 'true';\n }\n\n if (config.maxAge) {\n headers['Access-Control-Max-Age'] = String(config.maxAge);\n }\n\n const response = new Response(null, {\n status: 204,\n headers,\n });\n\n if (config.preflightContinue) {\n // Continue to next middleware/handler even for preflight\n const nextResponse = await next();\n // Merge headers from next response\n const mergedHeaders = new Headers(nextResponse.headers);\n Object.entries(headers).forEach(([key, value]) => {\n mergedHeaders.set(key, value);\n });\n return new Response(nextResponse.body, {\n status: nextResponse.status,\n statusText: nextResponse.statusText,\n headers: mergedHeaders,\n });\n }\n\n return response;\n }\n\n // Handle actual request\n const response = await next();\n\n // Add CORS headers to response\n const headers = new Headers(response.headers);\n headers.set('Access-Control-Allow-Origin', allowedOrigin);\n headers.set('Vary', 'Origin');\n\n if (config.credentials && allowedOrigin !== '*') {\n headers.set('Access-Control-Allow-Credentials', 'true');\n }\n\n if (config.exposedHeaders.length > 0) {\n headers.set('Access-Control-Expose-Headers', config.exposedHeaders.join(', '));\n }\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n"],"mappings":"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"}
|
|
@@ -379,4 +379,4 @@ declare function runMiddleware(middleware: Middleware[], context: MiddlewareCont
|
|
|
379
379
|
declare function compose(...middleware: Middleware[]): Middleware;
|
|
380
380
|
//#endregion
|
|
381
381
|
export { SchemaDefinition as C, compileSchema as D, ValidationResult as E, InferSchema as S, SchemaTypeMap as T, TypedResponse as _, runMiddleware as a, isRouter as b, ExecutionContext as c, MethodEntry as d, RouteDefinition as f, TypedHandler as g, RouterRoutes as h, compose as i, FetchHandler as l, RouterBrand as m, MiddlewareContext as n, CollectPathParams as o, Router as p, MiddlewareNext as r, Context as s, Middleware as t, Method as u, isFunction as v, SchemaType as w, CompiledSchema as x, isRoute as y };
|
|
382
|
-
//# sourceMappingURL=middleware-
|
|
382
|
+
//# sourceMappingURL=middleware-Bxq0LHLS.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware-
|
|
1
|
+
{"version":3,"file":"middleware-Bxq0LHLS.d.mts","names":[],"sources":["../src/schema.ts","../src/types.ts","../src/middleware.ts"],"sourcesContent":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware-
|
|
1
|
+
{"version":3,"file":"middleware-XMbIevSv.mjs","names":["next: MiddlewareNext"],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * @fileoverview Core middleware types and utilities.\n *\n * Provides middleware support for @fresho/router with a simple, composable API.\n *\n * @example Type-hinting context properties\n * ```typescript\n * // Define your context type (env is required, other properties are optional in middleware)\n * interface AppContext {\n * env: { JWT_SECRET: string; DB: D1Database };\n * user: { id: string; name: string };\n * }\n *\n * // Middleware receives env as required, other properties as optional\n * const authMiddleware: Middleware<AppContext> = async (context, next) => {\n * context.env.JWT_SECRET; // typed, required\n * context.user; // typed as optional (may not exist yet)\n * context.user = await validateToken(context.request);\n * return next();\n * };\n * ```\n */\n\nimport type { ExecutionContext } from './types.js';\n\n/**\n * Base context properties always available to middleware.\n */\ninterface BaseMiddlewareContext {\n request: Request;\n path: Record<string, string>;\n query: unknown;\n body: unknown;\n executionCtx?: ExecutionContext;\n}\n\n/**\n * Context passed to middleware functions.\n *\n * The `env` property is always required (extracted from Ctx if provided).\n * Other properties from Ctx are optional since middleware builds them up.\n *\n * @typeParam Ctx - Context type including env and middleware-added properties\n *\n * @example\n * ```typescript\n * interface AppContext {\n * env: { JWT_SECRET: string };\n * user: { id: string };\n * }\n *\n * const middleware: Middleware<AppContext> = async (context, next) => {\n * context.env.JWT_SECRET; // typed, required\n * context.user; // typed as { id: string } | undefined\n * context.user = { id: '123' };\n * return next();\n * };\n * ```\n */\nexport type MiddlewareContext<Ctx = {}> = BaseMiddlewareContext & {\n env: Ctx extends { env: infer E } ? E : unknown;\n} & Partial<Omit<Ctx, 'env'>> & {\n [key: string]: unknown;\n };\n\n/**\n * Function to continue to the next middleware or handler.\n */\nexport type MiddlewareNext = () => Promise<Response>;\n\n/**\n * Middleware function signature.\n *\n * @typeParam Ctx - Context type (default: {})\n *\n * @example\n * ```typescript\n * // Simple middleware without typed context\n * const logger: Middleware = async (context, next) => {\n * console.log(context.request.url);\n * return next();\n * };\n *\n * // Middleware with typed context\n * interface AppContext {\n * env: { JWT_SECRET: string };\n * user: { id: string };\n * }\n * const auth: Middleware<AppContext> = async (context, next) => {\n * const secret = context.env.JWT_SECRET; // typed, required\n * context.user = { id: '123' }; // adds to context\n * return next();\n * };\n * ```\n */\nexport type Middleware<Ctx = {}> = (\n context: MiddlewareContext<Ctx>,\n next: MiddlewareNext,\n) => Promise<Response> | Response;\n\n/**\n * Runs a chain of middleware with a final handler.\n *\n * Middleware are executed in order. Each middleware receives the context and\n * a `next` function. Calling `next()` continues to the next middleware or the\n * final handler. Returning without calling `next()` short-circuits the chain.\n *\n * @param middleware - Array of middleware functions to run\n * @param context - The request context\n * @param finalHandler - Handler to call after all middleware complete\n * @returns The response from the middleware chain or final handler\n */\nexport async function runMiddleware(\n middleware: Middleware[],\n context: MiddlewareContext,\n finalHandler: () => Promise<Response>,\n): Promise<Response> {\n let index = 0;\n\n const next: MiddlewareNext = async () => {\n if (index >= middleware.length) {\n return finalHandler();\n }\n\n const currentMiddleware = middleware[index++];\n return await currentMiddleware(context, next);\n };\n\n return next();\n}\n\n/**\n * Composes multiple middleware into a single middleware.\n *\n * Useful for grouping related middleware together.\n *\n * @example\n * ```typescript\n * const security = compose(\n * cors(),\n * rateLimit({ max: 100 }),\n * errorHandler(),\n * );\n *\n * const api = router('/api', routes, security);\n * ```\n *\n * @param middleware - Middleware functions to compose\n * @returns A single middleware that runs all provided middleware in order\n */\nexport function compose(...middleware: Middleware[]): Middleware {\n return async (context, next) => {\n return runMiddleware(middleware, context, next);\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAgHA,eAAsB,cACpB,YACA,SACA,cACmB;CACnB,IAAI,QAAQ;CAEZ,MAAMA,OAAuB,YAAY;AACvC,MAAI,SAAS,WAAW,OACtB,QAAO,cAAc;EAGvB,MAAM,oBAAoB,WAAW;AACrC,SAAO,MAAM,kBAAkB,SAAS,KAAK;;AAG/C,QAAO,MAAM;;;;;;;;;;;;;;;;;;;;;AAsBf,SAAgB,QAAQ,GAAG,YAAsC;AAC/D,QAAO,OAAO,SAAS,SAAS;AAC9B,SAAO,cAAc,YAAY,SAAS,KAAK"}
|
package/docs/authentication.md
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
## Table of Contents
|
|
6
6
|
|
|
7
7
|
- [Overview](#overview)
|
|
8
|
+
- [Import Paths](#import-paths)
|
|
8
9
|
- [Cookie-Based Auth](#cookie-based-auth)
|
|
9
10
|
- [Token-Based Auth](#token-based-auth)
|
|
10
11
|
- [Token Refresh](#token-refresh)
|
|
11
12
|
- [API Reference](#api-reference)
|
|
12
13
|
- [jwtSign](#jwtsign)
|
|
14
|
+
- [jwtVerify](#jwtverify)
|
|
13
15
|
- [jwtAuth](#jwtauth)
|
|
14
16
|
|
|
15
17
|
---
|
|
@@ -27,6 +29,29 @@ Both approaches use the same server-side middleware (`jwtAuth`) - the difference
|
|
|
27
29
|
|
|
28
30
|
---
|
|
29
31
|
|
|
32
|
+
## Import Paths
|
|
33
|
+
|
|
34
|
+
Authentication utilities are available from two paths:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// Standalone utilities (JWT, Basic Auth, OAuth)
|
|
38
|
+
import { jwtSign, jwtVerify, parseBasicAuth } from '@fresho/router/auth';
|
|
39
|
+
|
|
40
|
+
// Middleware wrappers (also re-exports jwtSign/jwtVerify for convenience)
|
|
41
|
+
import { jwtAuth, basicAuth, bearerAuth, jwtSign, jwtVerify } from '@fresho/router/middleware';
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Use `@fresho/router/auth` when you need:
|
|
45
|
+
- JWT signing/verification without middleware
|
|
46
|
+
- Basic auth parsing utilities
|
|
47
|
+
- OAuth state encoding, token exchange, etc.
|
|
48
|
+
|
|
49
|
+
Use `@fresho/router/middleware` when you need:
|
|
50
|
+
- Authentication middleware (`jwtAuth`, `basicAuth`, `bearerAuth`)
|
|
51
|
+
- Convenience access to `jwtSign`/`jwtVerify` alongside middleware
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
30
55
|
## Cookie-Based Auth
|
|
31
56
|
|
|
32
57
|
The simplest approach for web applications. The server sets an httpOnly cookie, and the browser automatically sends it with every request.
|
|
@@ -269,7 +294,8 @@ refresh: router({
|
|
|
269
294
|
Signs a JWT token using Web Crypto API. Compatible with Cloudflare Workers, Deno, and browsers.
|
|
270
295
|
|
|
271
296
|
```typescript
|
|
272
|
-
import { jwtSign } from '@fresho/router/
|
|
297
|
+
import { jwtSign } from '@fresho/router/auth';
|
|
298
|
+
// Or: import { jwtSign } from '@fresho/router/middleware';
|
|
273
299
|
|
|
274
300
|
const token = await jwtSign(
|
|
275
301
|
{ email: 'user@example.com', role: 'admin' }, // Custom claims
|
|
@@ -303,7 +329,8 @@ const token = await jwtSign(
|
|
|
303
329
|
Verifies a JWT token and returns the payload. Use this when you need to verify tokens outside of the middleware context.
|
|
304
330
|
|
|
305
331
|
```typescript
|
|
306
|
-
import { jwtVerify } from '@fresho/router/
|
|
332
|
+
import { jwtVerify } from '@fresho/router/auth';
|
|
333
|
+
// Or: import { jwtVerify } from '@fresho/router/middleware';
|
|
307
334
|
|
|
308
335
|
try {
|
|
309
336
|
const payload = await jwtVerify(token, 'your-secret-key');
|
package/docs/middleware.md
CHANGED
|
@@ -154,7 +154,16 @@ import {
|
|
|
154
154
|
- **`basicAuth`** - HTTP Basic authentication
|
|
155
155
|
- **`bearerAuth`** - Simple bearer token authentication
|
|
156
156
|
- **`jwtAuth`** - JWT authentication with signature verification
|
|
157
|
-
|
|
157
|
+
|
|
158
|
+
The middleware module also re-exports `jwtSign` and `jwtVerify` for convenience. For standalone auth utilities (without middleware), import from `@fresho/router/auth`:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// Standalone utilities
|
|
162
|
+
import { jwtSign, jwtVerify, parseBasicAuth } from '@fresho/router/auth';
|
|
163
|
+
|
|
164
|
+
// Middleware (also re-exports jwtSign/jwtVerify)
|
|
165
|
+
import { jwtAuth, basicAuth, jwtSign } from '@fresho/router/middleware';
|
|
166
|
+
```
|
|
158
167
|
|
|
159
168
|
For complete documentation, examples, and patterns (cookie-based auth, token refresh, etc.), see the **[Authentication Guide](authentication.md)**.
|
|
160
169
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"engines": {
|
|
4
4
|
"node": ">=22"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.3.2",
|
|
7
7
|
"description": "Type-safe route definitions with auto-generated clients and OpenAPI docs",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"author": "fresho.dev",
|
|
@@ -40,13 +40,17 @@
|
|
|
40
40
|
"types": "./dist/index.d.mts",
|
|
41
41
|
"import": "./dist/index.mjs"
|
|
42
42
|
},
|
|
43
|
+
"./auth": {
|
|
44
|
+
"types": "./dist/auth/index.d.mts",
|
|
45
|
+
"import": "./dist/auth/index.mjs"
|
|
46
|
+
},
|
|
43
47
|
"./middleware": {
|
|
44
48
|
"types": "./dist/middleware/index.d.mts",
|
|
45
49
|
"import": "./dist/middleware/index.mjs"
|
|
46
50
|
}
|
|
47
51
|
},
|
|
48
52
|
"scripts": {
|
|
49
|
-
"build": "tsdown src/index.ts src/middleware/index.ts",
|
|
53
|
+
"build": "tsdown src/index.ts src/auth/index.ts src/middleware/index.ts",
|
|
50
54
|
"clean": "rm -rf ./dist",
|
|
51
55
|
"typecheck": "tsc --noEmit",
|
|
52
56
|
"lint": "biome check . && npm run typecheck",
|