@mateosuarezdev/brpc 1.0.6 โ†’ 1.0.7

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/dist/index.js.map CHANGED
@@ -7,7 +7,7 @@
7
7
  "export type BRPCErrorCode =\r\n | \"BAD_REQUEST\"\r\n | \"UNAUTHORIZED\"\r\n | \"FORBIDDEN\"\r\n | \"NOT_FOUND\"\r\n | \"METHOD_NOT_SUPPORTED\"\r\n | \"TIMEOUT\"\r\n | \"CONFLICT\"\r\n | \"PRECONDITION_FAILED\"\r\n | \"PAYLOAD_TOO_LARGE\"\r\n | \"UNPROCESSABLE_CONTENT\"\r\n | \"TOO_MANY_REQUESTS\"\r\n | \"CLIENT_CLOSED_REQUEST\"\r\n | \"INTERNAL_SERVER_ERROR\"\r\n | \"NOT_IMPLEMENTED\"\r\n | \"BAD_GATEWAY\"\r\n | \"SERVICE_UNAVAILABLE\"\r\n | \"GATEWAY_TIMEOUT\";\r\n\r\nexport interface BRPCErrorOptions {\r\n code: BRPCErrorCode;\r\n message: string;\r\n clientCode?: string; // Custom code for client-side detection\r\n cause?: Error;\r\n data?: Record<string, any>;\r\n}\r\n\r\nexport class BRPCError extends Error {\r\n public readonly code: BRPCErrorCode;\r\n public readonly clientCode?: string;\r\n public readonly httpStatus: number;\r\n public readonly data?: Record<string, any>;\r\n public readonly cause?: Error;\r\n\r\n private static readonly STATUS_MAP: Record<BRPCErrorCode, number> = {\r\n BAD_REQUEST: 400,\r\n UNAUTHORIZED: 401,\r\n FORBIDDEN: 403,\r\n NOT_FOUND: 404,\r\n METHOD_NOT_SUPPORTED: 405,\r\n TIMEOUT: 408,\r\n CONFLICT: 409,\r\n PRECONDITION_FAILED: 412,\r\n PAYLOAD_TOO_LARGE: 413,\r\n UNPROCESSABLE_CONTENT: 422,\r\n TOO_MANY_REQUESTS: 429,\r\n CLIENT_CLOSED_REQUEST: 499,\r\n INTERNAL_SERVER_ERROR: 500,\r\n NOT_IMPLEMENTED: 501,\r\n BAD_GATEWAY: 502,\r\n SERVICE_UNAVAILABLE: 503,\r\n GATEWAY_TIMEOUT: 504,\r\n };\r\n\r\n constructor(options: BRPCErrorOptions) {\r\n super(options.message);\r\n\r\n this.name = \"BRPCError\";\r\n this.code = options.code;\r\n this.clientCode = options.clientCode;\r\n this.httpStatus = BRPCError.STATUS_MAP[options.code];\r\n this.data = options.data;\r\n this.cause = options.cause;\r\n\r\n // Maintains proper stack trace for where our error was thrown (only available on V8)\r\n if (Error.captureStackTrace) {\r\n Error.captureStackTrace(this, BRPCError);\r\n }\r\n }\r\n\r\n /**\r\n * Create a serializable object for sending to frontend\r\n */\r\n toJSON() {\r\n return {\r\n name: this.name,\r\n code: this.code,\r\n clientCode: this.clientCode,\r\n message: this.message,\r\n data: this.data,\r\n httpStatus: this.httpStatus,\r\n };\r\n }\r\n\r\n /**\r\n * Static factory methods for common errors\r\n */\r\n static badRequest(\r\n message: string,\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"BAD_REQUEST\", message, clientCode, data });\r\n }\r\n\r\n static unauthorized(\r\n message: string = \"Unauthorized\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"UNAUTHORIZED\", message, clientCode, data });\r\n }\r\n\r\n static forbidden(\r\n message: string = \"Forbidden\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"FORBIDDEN\", message, clientCode, data });\r\n }\r\n\r\n static notFound(\r\n message: string = \"Not Found\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"NOT_FOUND\", message, clientCode, data });\r\n }\r\n\r\n static preconditionFailed(\r\n message: string = \"Precondition failed\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"NOT_FOUND\", message, clientCode, data });\r\n }\r\n\r\n static conflict(\r\n message: string,\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"CONFLICT\", message, clientCode, data });\r\n }\r\n\r\n static unprocessableContent(\r\n message: string,\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({\r\n code: \"UNPROCESSABLE_CONTENT\",\r\n message,\r\n clientCode,\r\n data,\r\n });\r\n }\r\n\r\n static tooManyRequests(\r\n message: string = \"Too many requests\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({\r\n code: \"TOO_MANY_REQUESTS\",\r\n message,\r\n clientCode,\r\n data,\r\n });\r\n }\r\n\r\n static internalServerError(\r\n message: string = \"Internal Server Error\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({\r\n code: \"INTERNAL_SERVER_ERROR\",\r\n message,\r\n clientCode,\r\n data,\r\n });\r\n }\r\n\r\n static timeout(\r\n message: string = \"Request timeout\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"TIMEOUT\", message, clientCode, data });\r\n }\r\n}\r\n",
8
8
  "// Optimized route finding for BRPC\r\n\r\nimport type { Routes, Procedure, ProcedureType } from \"../types\";\r\n\r\ninterface RouteMatch {\r\n params: Record<string, string>;\r\n route: Procedure<any, any, any, any>;\r\n}\r\n\r\ninterface CompiledRoute {\r\n handler: Procedure<any, any, any, any>;\r\n paramNames: string[];\r\n hasWildcard: boolean;\r\n wildcardIndex?: number;\r\n}\r\n\r\ninterface RouteNode {\r\n // Static routes (exact match) - O(1) lookup with Map\r\n static: Map<string, RouteNode>;\r\n // Parameter route (single dynamic segment)\r\n param?: { name: string; node: RouteNode };\r\n // Wildcard route (catches everything)\r\n wildcard?: CompiledRoute;\r\n // Handler if this node is a terminal route\r\n handler?: CompiledRoute;\r\n // Index handler\r\n index?: CompiledRoute;\r\n}\r\n\r\ninterface RouteMatcherOptions {\r\n enableCaching?: boolean;\r\n maxCacheSize?: number;\r\n debug?: boolean;\r\n}\r\n\r\ninterface RouteStats {\r\n totalRoutes: number;\r\n staticRoutes: number;\r\n paramRoutes: number;\r\n wildcardRoutes: number;\r\n indexRoutes: number;\r\n maxDepth: number;\r\n procedureTypes: Record<ProcedureType, number>;\r\n}\r\n\r\nexport class RouteMatcher {\r\n private routeTree: RouteNode;\r\n private routeCache: Map<string, RouteMatch | null>;\r\n private readonly options: Required<RouteMatcherOptions>;\r\n private routeStats: RouteStats;\r\n\r\n constructor(routes: Routes, options: RouteMatcherOptions = {}) {\r\n this.options = {\r\n enableCaching: true,\r\n maxCacheSize: 1000,\r\n debug: false,\r\n ...options,\r\n };\r\n\r\n this.routeCache = new Map();\r\n this.routeStats = this.analyzeRoutes(routes);\r\n this.routeTree = this.compileRoutes(routes);\r\n\r\n if (this.options.debug) {\r\n console.log(\"[ROUTEMATCHER]\", \"initialized with stats:\", this.routeStats);\r\n }\r\n }\r\n\r\n /**\r\n * Main route matching method\r\n */\r\n public match(pathParts: string[]): RouteMatch | null {\r\n if (this.options.enableCaching) {\r\n return this.matchWithCache(pathParts);\r\n }\r\n return this.matchDirect(pathParts);\r\n }\r\n\r\n /**\r\n * Route matching with caching\r\n */\r\n private matchWithCache(pathParts: string[]): RouteMatch | null {\r\n const cacheKey = pathParts.join(\"/\");\r\n\r\n // Check cache first\r\n if (this.routeCache.has(cacheKey)) {\r\n const cached = this.routeCache.get(cacheKey)!;\r\n if (this.options.debug && cached) {\r\n console.log(\"[ROUTEMATCHER]\", `๐ŸŽฏ Cache hit for route: /${cacheKey}`);\r\n }\r\n return cached;\r\n }\r\n\r\n // Find route\r\n const result = this.matchDirect(pathParts);\r\n\r\n // Cache the result (including null results to avoid repeated failed lookups)\r\n this.cacheResult(cacheKey, result);\r\n\r\n if (this.options.debug) {\r\n console.log(\r\n \"[ROUTEMATCHER]\",\r\n `๐Ÿ’พ Cached route: /${cacheKey} -> ${result ? \"found\" : \"not found\"}`\r\n );\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Direct route matching without caching\r\n */\r\n private matchDirect(pathParts: string[]): RouteMatch | null {\r\n const startTime = this.options.debug ? performance.now() : 0;\r\n\r\n const result = this.traverseTree(this.routeTree, pathParts, 0, {});\r\n\r\n if (this.options.debug) {\r\n const duration = performance.now() - startTime;\r\n const routeType = result?.route._type || \"none\";\r\n console.log(\r\n \"[ROUTEMATCHER]\",\r\n `โšก Route match: /${pathParts.join(\r\n \"/\"\r\n )} (${routeType}) took ${duration.toFixed(3)}ms`\r\n );\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Traverse the compiled route tree\r\n */\r\n private traverseTree(\r\n node: RouteNode,\r\n pathParts: string[],\r\n index: number,\r\n params: Record<string, string>\r\n ): RouteMatch | null {\r\n // If we've consumed all path parts\r\n if (index >= pathParts.length) {\r\n // Try handler first, then index\r\n if (node.handler) {\r\n return { params, route: node.handler.handler };\r\n }\r\n if (node.index) {\r\n return { params, route: node.index.handler };\r\n }\r\n return null;\r\n }\r\n\r\n const segment = pathParts[index];\r\n const decodedSegment = decodeURIComponent(segment);\r\n\r\n // 1. Try static route first (O(1) lookup with Map)\r\n const staticChild = node.static.get(decodedSegment);\r\n if (staticChild) {\r\n const result = this.traverseTree(\r\n staticChild,\r\n pathParts,\r\n index + 1,\r\n params\r\n );\r\n if (result) return result;\r\n }\r\n\r\n // 2. Try parameter route\r\n if (node.param) {\r\n const newParams = { ...params, [node.param.name]: decodedSegment };\r\n const result = this.traverseTree(\r\n node.param.node,\r\n pathParts,\r\n index + 1,\r\n newParams\r\n );\r\n if (result) return result;\r\n }\r\n\r\n // 3. Try wildcard route (catches all remaining segments)\r\n if (node.wildcard) {\r\n const wildcardPath = pathParts.slice(index).join(\"/\");\r\n return {\r\n params: { ...params, wildcardPath },\r\n route: node.wildcard.handler,\r\n };\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Compile the routes object into an optimized tree structure\r\n */\r\n private compileRoutes(routes: Routes, paramNames: string[] = []): RouteNode {\r\n const node: RouteNode = {\r\n static: new Map(),\r\n };\r\n\r\n for (const [key, value] of Object.entries(routes)) {\r\n if (key === \"index\" && this.isProcedure(value)) {\r\n // Index route\r\n node.index = {\r\n handler: value,\r\n paramNames: [...paramNames],\r\n hasWildcard: false,\r\n };\r\n continue;\r\n }\r\n\r\n if (key === \"*\") {\r\n // Wildcard route\r\n if (this.isProcedure(value)) {\r\n node.wildcard = {\r\n handler: value,\r\n paramNames: [...paramNames, \"wildcardPath\"],\r\n hasWildcard: true,\r\n wildcardIndex: paramNames.length,\r\n };\r\n }\r\n continue;\r\n }\r\n\r\n if (this.isProcedure(value)) {\r\n // Terminal route with handler\r\n const compiledRoute: CompiledRoute = {\r\n handler: value,\r\n paramNames: [...paramNames],\r\n hasWildcard: false,\r\n };\r\n\r\n if (key.startsWith(\":\")) {\r\n // Parameter route\r\n const paramName = key.slice(1);\r\n const terminalNode: RouteNode = {\r\n static: new Map(),\r\n handler: compiledRoute,\r\n };\r\n node.param = { name: paramName, node: terminalNode };\r\n } else {\r\n // Static route\r\n const terminalNode: RouteNode = {\r\n static: new Map(),\r\n handler: compiledRoute,\r\n };\r\n node.static.set(key, terminalNode);\r\n }\r\n } else if (typeof value === \"object\" && value !== null) {\r\n // Nested routes\r\n if (key.startsWith(\":\")) {\r\n // Parameter segment with nested routes\r\n const paramName = key.slice(1);\r\n const childNode = this.compileRoutes(value as Routes, [\r\n ...paramNames,\r\n paramName,\r\n ]);\r\n node.param = { name: paramName, node: childNode };\r\n } else {\r\n // Static segment with nested routes\r\n const childNode = this.compileRoutes(value as Routes, paramNames);\r\n node.static.set(key, childNode);\r\n }\r\n }\r\n }\r\n\r\n return node;\r\n }\r\n\r\n /**\r\n * Type guard to check if value is a Procedure\r\n */\r\n private isProcedure(value: any): value is Procedure<any, any, any, any> {\r\n return (\r\n value &&\r\n typeof value === \"object\" &&\r\n \"_type\" in value &&\r\n \"_input\" in value\r\n );\r\n }\r\n\r\n /**\r\n * Cache management with LRU behavior\r\n */\r\n private cacheResult(key: string, result: RouteMatch | null): void {\r\n // Implement LRU: remove oldest entry if cache is full\r\n if (this.routeCache.size >= this.options.maxCacheSize) {\r\n const firstKey = this.routeCache.keys().next().value;\r\n if (firstKey !== undefined) {\r\n this.routeCache.delete(firstKey);\r\n\r\n if (this.options.debug) {\r\n console.log(`๐Ÿ—‘๏ธ Evicted oldest cache entry: ${firstKey}`);\r\n }\r\n }\r\n }\r\n\r\n this.routeCache.set(key, result);\r\n }\r\n\r\n /**\r\n * Analyze route structure for optimization insights and debugging\r\n */\r\n private analyzeRoutes(routes: Routes): RouteStats {\r\n const stats: RouteStats = {\r\n totalRoutes: 0,\r\n staticRoutes: 0,\r\n paramRoutes: 0,\r\n wildcardRoutes: 0,\r\n indexRoutes: 0,\r\n maxDepth: 0,\r\n procedureTypes: {\r\n query: 0,\r\n mutation: 0,\r\n subscription: 0,\r\n file: 0,\r\n fileStream: 0,\r\n streamQuery: 0,\r\n streamMutation: 0,\r\n formMutation: 0,\r\n html: 0,\r\n },\r\n };\r\n\r\n const traverse = (obj: any, depth = 0): void => {\r\n stats.maxDepth = Math.max(stats.maxDepth, depth);\r\n\r\n for (const [key, value] of Object.entries(obj)) {\r\n if (this.isProcedure(value)) {\r\n stats.totalRoutes++;\r\n\r\n // Count procedure types\r\n if (value._type in stats.procedureTypes) {\r\n stats.procedureTypes[value._type as ProcedureType]++;\r\n }\r\n\r\n // Count route patterns\r\n if (key === \"index\") {\r\n stats.indexRoutes++;\r\n } else if (key.startsWith(\":\")) {\r\n stats.paramRoutes++;\r\n } else if (key === \"*\") {\r\n stats.wildcardRoutes++;\r\n } else {\r\n stats.staticRoutes++;\r\n }\r\n } else if (typeof value === \"object\" && value !== null) {\r\n traverse(value, depth + 1);\r\n }\r\n }\r\n };\r\n\r\n traverse(routes);\r\n return stats;\r\n }\r\n\r\n /**\r\n * Utility methods\r\n */\r\n public clearCache(): void {\r\n this.routeCache.clear();\r\n if (this.options.debug) {\r\n console.log(\"[ROUTEMATCHER]\", \"๐Ÿงน Route cache cleared\");\r\n }\r\n }\r\n\r\n public getCacheStats(): {\r\n size: number;\r\n maxSize: number;\r\n hitRatio?: number;\r\n routeStats: RouteStats;\r\n } {\r\n return {\r\n size: this.routeCache.size,\r\n maxSize: this.options.maxCacheSize,\r\n routeStats: this.routeStats,\r\n };\r\n }\r\n\r\n /**\r\n * Get suggestions for route optimization\r\n */\r\n public getOptimizationSuggestions(): string[] {\r\n const suggestions: string[] = [];\r\n const { routeStats } = this;\r\n\r\n if (routeStats.paramRoutes > routeStats.staticRoutes * 2) {\r\n suggestions.push(\r\n \"๐Ÿš€ Consider grouping routes to reduce parameter route overhead\"\r\n );\r\n }\r\n\r\n if (routeStats.maxDepth > 6) {\r\n suggestions.push(\r\n \"๐Ÿ“Š Deep route nesting detected - consider flattening some routes\"\r\n );\r\n }\r\n\r\n if (routeStats.totalRoutes > 200) {\r\n suggestions.push(\r\n \"โšก Large route table - consider route splitting or lazy loading\"\r\n );\r\n }\r\n\r\n if (!this.options.enableCaching && routeStats.totalRoutes > 50) {\r\n suggestions.push(\r\n \"๐Ÿ’พ Enable caching for better performance with many routes\"\r\n );\r\n }\r\n\r\n if (routeStats.procedureTypes.file > 20) {\r\n suggestions.push(\r\n \"๐Ÿ“ Many file routes detected - consider static file serving\"\r\n );\r\n }\r\n\r\n if (routeStats.procedureTypes.subscription > 10) {\r\n suggestions.push(\r\n \"๐Ÿ”„ Many subscription routes - ensure WebSocket optimization\"\r\n );\r\n }\r\n\r\n return suggestions;\r\n }\r\n\r\n /**\r\n * Find all routes matching a pattern (useful for debugging)\r\n */\r\n public findRoutesByPattern(_pattern: string): string[] {\r\n const matchingRoutes: string[] = [];\r\n // const regex = new RegExp(pattern);\r\n\r\n // This would require storing all route paths during compilation\r\n // For now, return empty array - can be implemented if needed\r\n return matchingRoutes;\r\n }\r\n\r\n /**\r\n * Get route information for debugging\r\n */\r\n public getRouteInfo(path: string): {\r\n found: boolean;\r\n route?: Procedure<any, any, any, any>;\r\n params?: Record<string, string>;\r\n cached: boolean;\r\n matchTime?: number;\r\n } {\r\n const pathParts = path.split(\"/\").filter(Boolean);\r\n const cacheKey = pathParts.join(\"/\");\r\n const cached = this.routeCache.has(cacheKey);\r\n\r\n const startTime = performance.now();\r\n const result = this.matchDirect(pathParts);\r\n const matchTime = performance.now() - startTime;\r\n\r\n return {\r\n found: !!result,\r\n route: result?.route,\r\n params: result?.params,\r\n cached,\r\n matchTime,\r\n };\r\n }\r\n\r\n /**\r\n * Benchmark route matching performance\r\n */\r\n public benchmark(\r\n testPaths: string[],\r\n iterations = 1000\r\n ): {\r\n averageTime: number;\r\n totalTime: number;\r\n routesPerSecond: number;\r\n cacheHitRatio?: number;\r\n } {\r\n const pathSegments = testPaths.map((path) =>\r\n path.split(\"/\").filter(Boolean)\r\n );\r\n\r\n // Clear cache for accurate benchmarking\r\n const originalCache = new Map(this.routeCache);\r\n this.routeCache.clear();\r\n\r\n const startTime = performance.now();\r\n\r\n for (let i = 0; i < iterations; i++) {\r\n for (const segments of pathSegments) {\r\n this.matchDirect(segments); // Use direct matching for pure performance\r\n }\r\n }\r\n\r\n const endTime = performance.now();\r\n const totalTime = endTime - startTime;\r\n const totalMatches = iterations * testPaths.length;\r\n const averageTime = totalTime / totalMatches;\r\n const routesPerSecond = (totalMatches / totalTime) * 1000;\r\n\r\n // Restore cache\r\n this.routeCache = originalCache;\r\n\r\n return {\r\n averageTime,\r\n totalTime,\r\n routesPerSecond,\r\n };\r\n }\r\n\r\n /**\r\n * Warm up the cache with common routes\r\n */\r\n public warmupCache(commonPaths: string[]): void {\r\n if (!this.options.enableCaching) return;\r\n\r\n const startTime = performance.now();\r\n let warmedRoutes = 0;\r\n\r\n for (const path of commonPaths) {\r\n const pathParts = path.split(\"/\").filter(Boolean);\r\n const result = this.matchDirect(pathParts);\r\n if (result) {\r\n this.cacheResult(pathParts.join(\"/\"), result);\r\n warmedRoutes++;\r\n }\r\n }\r\n\r\n const endTime = performance.now();\r\n\r\n if (this.options.debug) {\r\n console.log(\r\n \"[ROUTEMATCHER]\",\r\n `๐Ÿ”ฅ Cache warmed up: ${warmedRoutes} routes in ${(\r\n endTime - startTime\r\n ).toFixed(2)}ms`\r\n );\r\n }\r\n }\r\n}\r\n\r\n// Export types for use in Router\r\nexport type { RouteMatch, RouteMatcherOptions, RouteStats };\r\n",
9
9
  "// @bun\nvar L=import.meta.require;var W={NO_CACHE:{cacheControl:\"no-cache, no-store, must-revalidate\",expires:new Date(0).toUTCString()},ONE_YEAR:{cacheControl:\"public, max-age=31536000\",expires:new Date(Date.now()+31536000000).toUTCString()},IMMUTABLE:{cacheControl:\"public, max-age=31536000, immutable\",expires:new Date(Date.now()+31536000000).toUTCString()},ONE_MONTH:{cacheControl:\"public, max-age=2592000\"},ONE_WEEK:{cacheControl:\"public, max-age=604800\"},ONE_DAY:{cacheControl:\"public, max-age=86400, must-revalidate\"},ONE_HOUR:{cacheControl:\"public, max-age=3600, must-revalidate\"},FIVE_MINUTES:{cacheControl:\"public, max-age=300\"},API:{cacheControl:\"public, max-age=300, s-maxage=3600\"},STYLESHEET:{cacheControl:\"public, max-age=2592000, must-revalidate\"},JAVASCRIPT:{cacheControl:\"public, max-age=2592000, must-revalidate\"},HASHED_ASSET:{cacheControl:\"public, max-age=31536000, immutable\",expires:new Date(Date.now()+31536000000).toUTCString()},FONT:{cacheControl:\"public, max-age=31536000\",expires:new Date(Date.now()+31536000000).toUTCString()},IMAGE:{cacheControl:\"public, max-age=2592000\"},FAVICON:{cacheControl:\"public, max-age=31536000\",expires:new Date(Date.now()+31536000000).toUTCString()},HTML_PAGE:{cacheControl:\"public, max-age=3600, must-revalidate\"},API_RESPONSE:{cacheControl:\"public, max-age=300, s-maxage=900\"},FEED:{cacheControl:\"public, max-age=3600\"},SITEMAP:{cacheControl:\"public, max-age=86400\"},MANIFEST:{cacheControl:\"public, max-age=86400, must-revalidate\"},SERVICE_WORKER:{cacheControl:\"no-cache, no-store, must-revalidate\",expires:new Date(0).toUTCString()},MEDIA_STREAM:{cacheControl:\"no-cache, no-store\"},DOCUMENT:{cacheControl:\"public, max-age=86400\"},ARCHIVE:{cacheControl:\"public, max-age=604800\"}};function J(k){return W[k]}var Y={html:\"text/html; charset=utf-8\",css:\"text/css\",javascript:\"application/javascript\",json:\"application/json\",xml:\"application/xml\",text:\"text/plain; charset=utf-8\",csv:\"text/csv\",markdown:\"text/markdown; charset=utf-8\",png:\"image/png\",jpeg:\"image/jpeg\",gif:\"image/gif\",svg:\"image/svg+xml\",webp:\"image/webp\",icon:\"image/x-icon\",bmp:\"image/bmp\",woff:\"font/woff\",woff2:\"font/woff2\",truetype:\"font/ttf\",\"embedded-opentype\":\"application/vnd.ms-fontobject\",opentype:\"font/otf\",mp4:\"video/mp4\",webm:\"video/webm\",ogg:\"video/ogg\",mpeg:\"audio/mpeg\",wav:\"audio/wav\",flac:\"audio/flac\",pdf:\"application/pdf\",doc:\"application/msword\",docx:\"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",zip:\"application/zip\",tar:\"application/x-tar\",gzip:\"application/gzip\",\"7z\":\"application/x-7z-compressed\",js:\"application/javascript\",txt:\"text/plain; charset=utf-8\",jpg:\"image/jpeg\",ico:\"image/x-icon\",ttf:\"font/ttf\",eot:\"application/vnd.ms-fontobject\",otf:\"font/otf\",mp3:\"audio/mpeg\",gz:\"application/gzip\"};function V(k){return Y[k]||\"application/octet-stream\"}function Z(k){if(typeof Bun!==\"undefined\"&&Bun.hash)return Bun.hash(k).toString(16);try{let z=L(\"crypto\"),K;if(typeof k===\"string\")K=Buffer.from(k);else if(k instanceof ArrayBuffer)K=Buffer.from(new Uint8Array(k));else K=Buffer.from(k);return z.createHash(\"md5\").update(K).digest(\"hex\")}catch{let z=typeof k===\"string\"?new TextEncoder().encode(k):k instanceof ArrayBuffer?new Uint8Array(k):k,K=0;for(let U=0;U<z.length;U++)K=(K<<5)-K+z[U],K=K&K;return Math.abs(K).toString(16).padStart(16,\"0\")}}function B(k,z,K){let U=k.headers.get(\"Origin\"),D=!0;if((K?.allowAll??!0)&&U)return U;if(z.length===0)return U||\"*\";if(!U)return z[0];if(z.includes(U))return U;return z[0]}class ${headers={};contentType(k){return this.headers[\"Content-Type\"]=V(k),this}filePath(k){let z=k.split(\".\").pop()?.toLowerCase()||\"\";return this.headers[\"Content-Type\"]=V(z),this}mimeType(k){return this.headers[\"Content-Type\"]=k,this}cache(k){let{cacheControl:z,expires:K}=J(k);if(this.headers[\"Cache-Control\"]=z,K)this.headers.Expires=K;return this}eTag(k){if(!k)return this;let z;if(typeof k===\"string\"&&k.startsWith('\"'))z=k;else z=`\"${Z(k)}\"`;return this.headers.ETag=z,this}lastModified(k){return this.headers[\"Last-Modified\"]=k.toUTCString(),this}contentLength(k){return this.headers[\"Content-Length\"]=k.toString(),this}redirect(k,z=!1){return this.headers.Location=k,this.headers[\"X-Redirect-Type\"]=z?\"permanent\":\"temporary\",this}cors({origin:k=\"*\",methods:z=[\"GET\",\"POST\",\"PUT\",\"DELETE\",\"OPTIONS\"],headers:K=[\"Content-Type\",\"Authorization\"],maxAge:U=86400,credentials:D}={}){let X=D??k!==\"*\";if(X&&k===\"*\")throw new Error('CORS: Cannot use credentials with wildcard origin \"*\". Specify an exact origin or set credentials to false.');if(this.headers[\"Access-Control-Allow-Origin\"]=k,this.headers[\"Access-Control-Allow-Methods\"]=z.join(\", \"),this.headers[\"Access-Control-Allow-Headers\"]=K.join(\", \"),X)this.headers[\"Access-Control-Allow-Credentials\"]=\"true\";return this.headers[\"Access-Control-Max-Age\"]=U.toString(),this}security(k){let z={hsts:!0,noSniff:!0,frameOptions:\"SAMEORIGIN\",xssProtection:!0,...k};if(z.csp)this.headers[\"Content-Security-Policy\"]=z.csp;if(z.hsts){let K=typeof z.hsts===\"number\"?z.hsts:31536000;this.headers[\"Strict-Transport-Security\"]=`max-age=${K}; includeSubDomains`}if(z.noSniff)this.headers[\"X-Content-Type-Options\"]=\"nosniff\";if(z.frameOptions)this.headers[\"X-Frame-Options\"]=z.frameOptions;if(z.xssProtection)this.headers[\"X-XSS-Protection\"]=\"1; mode=block\";return this}custom(k,z){return this.headers[k]=z,this}customHeaders(k){return Object.assign(this.headers,k),this}vary(...k){let z=this.headers.Vary,K=z?`${z}, ${k.join(\", \")}`:k.join(\", \");return this.headers.Vary=K,this}compress(k){if(k)this.headers[\"Content-Encoding\"]=k;return this}build(k){let z={...this.headers};if(k&&!z[\"Content-Length\"]){let K=typeof k===\"string\"?new TextEncoder().encode(k).length:k.byteLength;z[\"Content-Length\"]=K.toString()}return z}buildHeaders(k){return new Headers(this.build(k))}}function Q(){return new $}function F(k,z){let K=Q().contentType(k);if(z)K.cache(z);return K.build()}var G={css:(k)=>Q().contentType(\"css\").cache(\"STYLESHEET\").eTag(k),javascript:(k)=>Q().contentType(\"javascript\").cache(\"JAVASCRIPT\").eTag(k),hashedAsset:(k,z)=>Q().filePath(k).cache(\"HASHED_ASSET\").eTag(z),font:(k)=>Q().filePath(k).cache(\"FONT\").cors({origin:\"*\"}),image:(k)=>Q().filePath(k).cache(\"IMAGE\"),favicon:()=>Q().contentType(\"icon\").cache(\"FAVICON\"),api:()=>Q().contentType(\"json\").cache(\"API_RESPONSE\").cors(),expensiveApi:(k)=>Q().contentType(\"json\").cache(\"API_RESPONSE\").cors().eTag(k),realtime:()=>Q().contentType(\"json\").cache(\"NO_CACHE\").cors(),html:()=>Q().contentType(\"html\").cache(\"HTML_PAGE\").security(),expensiveHtml:(k)=>Q().contentType(\"html\").cache(\"HTML_PAGE\").security().eTag(k),manifest:(k)=>Q().contentType(\"json\").cache(\"MANIFEST\").eTag(k),serviceWorker:()=>Q().contentType(\"javascript\").cache(\"SERVICE_WORKER\"),sitemap:(k)=>Q().contentType(\"xml\").cache(\"SITEMAP\").eTag(k),feed:(k,z=\"rss\")=>Q().contentType(\"xml\").cache(\"FEED\").eTag(k).mimeType(z===\"rss\"?\"application/rss+xml\":\"application/atom+xml\"),video:(k)=>Q().filePath(k).cache(\"MEDIA_STREAM\").custom(\"Accept-Ranges\",\"bytes\"),audio:(k)=>Q().filePath(k).cache(\"MEDIA_STREAM\").custom(\"Accept-Ranges\",\"bytes\"),pdf:(k)=>Q().contentType(\"pdf\").cache(\"DOCUMENT\").custom(\"Content-Disposition\",\"inline\").eTag(k),download:(k,z,K)=>Q().filePath(z).cache(\"ARCHIVE\").custom(\"Content-Disposition\",`attachment; filename=\"${k}\"`).eTag(K),secure:()=>Q().security({csp:\"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'\",hsts:31536000}).custom(\"X-Powered-By\",\"Bun\"),permanentRedirect:(k)=>Q().redirect(k,!0),temporaryRedirect:(k)=>Q().redirect(k,!1)};export{F as quickHeaders,Z as hashContent,V as getMimeType,B as getCorsOrigin,G as commonHeaders,Q as buildHeaders};\n\n//# debugId=4EE16D0787D156E064756E2164756E21\n",
10
- "import type { BunFile } from \"bun\";\r\nimport { buildHeaders, getCorsOrigin } from \"@mateosuarezdev/headers-builder\"; // Adjust import path as needed\r\n\r\ninterface StreamMediaOptions {\r\n maxChunkSize?: number; // Default: 2MB\r\n cacheMaxAge?: number; // Default: 3600 (1 hour)\r\n acceptedExtensions?: string[]; // Optional whitelist\r\n}\r\n\r\n/**\r\n * Metadata for deferred media streaming\r\n * Used with router's streamFile procedure\r\n */\r\nexport interface StreamableMediaResponse {\r\n type: \"stream\";\r\n file: BunFile;\r\n request: Request;\r\n options?: StreamMediaOptions;\r\n}\r\n\r\n/**\r\n * Handle media streaming with automatic Range request support\r\n * Supports video, audio, and other streamable media formats\r\n * @internal - Used by router to process StreamableMediaResponse\r\n */\r\nexport async function streamMediaInternal(\r\n file: BunFile,\r\n request: Request,\r\n allowedOrigins: string[],\r\n options: StreamMediaOptions = {}\r\n): Promise<Response> {\r\n const {\r\n maxChunkSize = 2 * 1024 * 1024,\r\n cacheMaxAge = 3600,\r\n acceptedExtensions,\r\n } = options;\r\n\r\n // Check if file exists\r\n const fileExists = await file.exists();\r\n if (!fileExists) {\r\n return new Response(\"Media file not found\", {\r\n status: 404,\r\n headers: buildHeaders()\r\n .contentType(\"text\")\r\n .cors({ origin: getCorsOrigin(request, allowedOrigins) })\r\n .build(),\r\n });\r\n }\r\n\r\n // Validate file extension if acceptedExtensions provided\r\n if (acceptedExtensions && acceptedExtensions.length > 0) {\r\n const ext = file.name?.toLowerCase().split(\".\").pop();\r\n if (!ext || !acceptedExtensions.includes(ext)) {\r\n return new Response(\"File type not supported\", {\r\n status: 415, // Unsupported Media Type\r\n headers: buildHeaders()\r\n .contentType(\"text\")\r\n .cors({ origin: getCorsOrigin(request, allowedOrigins) })\r\n .build(),\r\n });\r\n }\r\n }\r\n\r\n const fileSize = file.size;\r\n const range = request.headers.get(\"Range\");\r\n\r\n // No range request - send full file\r\n if (!range) {\r\n return new Response(file, {\r\n headers: buildHeaders()\r\n .filePath(file.name || \"media\")\r\n .custom(\"Accept-Ranges\", \"bytes\")\r\n .custom(\"Cache-Control\", `public, max-age=${cacheMaxAge}`)\r\n .cors({ origin: getCorsOrigin(request, allowedOrigins) })\r\n .build(),\r\n });\r\n }\r\n\r\n // Parse range header\r\n const rangeMatch = range.match(/bytes=(\\d*)-(\\d*)/);\r\n if (!rangeMatch) {\r\n return new Response(\"Invalid Range header\", {\r\n status: 416,\r\n headers: buildHeaders()\r\n .contentType(\"text\")\r\n .cors({ origin: getCorsOrigin(request, allowedOrigins) })\r\n .custom(\"Content-Range\", `bytes */${fileSize}`)\r\n .build(),\r\n });\r\n }\r\n\r\n // Calculate start and end positions\r\n const start = rangeMatch[1] ? parseInt(rangeMatch[1]) : 0;\r\n const requestedEnd = rangeMatch[2] ? parseInt(rangeMatch[2]) : fileSize - 1;\r\n\r\n // Apply maxChunkSize limit\r\n const end = Math.min(requestedEnd, start + maxChunkSize - 1, fileSize - 1);\r\n\r\n // Validate range\r\n if (start >= fileSize || end >= fileSize || start > end) {\r\n return new Response(\"Range not satisfiable\", {\r\n status: 416,\r\n headers: buildHeaders()\r\n .contentType(\"text\")\r\n .cors({ origin: getCorsOrigin(request, allowedOrigins) })\r\n .custom(\"Content-Range\", `bytes */${fileSize}`)\r\n .build(),\r\n });\r\n }\r\n\r\n // Bun automatically sets Content-Range and Content-Length for slices\r\n return new Response(file.slice(start, end + 1), {\r\n status: 206,\r\n headers: buildHeaders()\r\n .filePath(file.name || \"media\")\r\n .custom(\"Accept-Ranges\", \"bytes\")\r\n .custom(\"Cache-Control\", `public, max-age=${cacheMaxAge}`)\r\n .cors({ origin: getCorsOrigin(request, allowedOrigins) })\r\n .build(),\r\n });\r\n}\r\n\r\n/**\r\n * Helper function to create a streamable media response\r\n * Returns metadata that the router will process with streamMediaInternal()\r\n *\r\n * @example\r\n * ```typescript\r\n * // Stream any media\r\n * return streamMedia(file, ctx.request);\r\n *\r\n * // Stream with validation\r\n * return streamMedia(file, ctx.request, {\r\n * acceptedExtensions: [\"mp4\", \"webm\", \"mov\"],\r\n * maxChunkSize: 5 * 1024 * 1024,\r\n * });\r\n * ```\r\n */\r\nexport function streamMedia(\r\n file: BunFile,\r\n request: Request,\r\n options?: StreamMediaOptions\r\n): StreamableMediaResponse {\r\n return {\r\n type: \"stream\",\r\n file,\r\n request,\r\n options,\r\n };\r\n}\r\n\r\n//? I don't need to bundle them, users can create their owns\r\n// /**\r\n// * Common media extension presets\r\n// */\r\n// export const MEDIA_EXTENSIONS = {\r\n// video: [\"mp4\", \"webm\", \"mov\", \"avi\", \"mkv\", \"ogg\", \"ogv\"],\r\n// audio: [\"mp3\", \"wav\", \"ogg\", \"m4a\", \"flac\", \"aac\", \"opus\"],\r\n// all: [\r\n// \"mp4\",\r\n// \"webm\",\r\n// \"mov\",\r\n// \"avi\",\r\n// \"mkv\",\r\n// \"ogg\",\r\n// \"ogv\",\r\n// \"mp3\",\r\n// \"wav\",\r\n// \"m4a\",\r\n// \"flac\",\r\n// \"aac\",\r\n// \"opus\",\r\n// ],\r\n// } as const;\r\n",
10
+ "import type { BunFile } from \"bun\";\r\nimport { buildHeaders, getCorsOrigin } from \"@mateosuarezdev/headers-builder\"; // Adjust import path as needed\r\n\r\ninterface StreamMediaOptions {\r\n maxChunkSize?: number; // Default: 2MB\r\n cacheMaxAge?: number; // Default: 3600 (1 hour)\r\n acceptedExtensions?: string[]; // Optional whitelist\r\n}\r\n\r\n/**\r\n * Metadata for deferred media streaming\r\n * Used with router's streamFile procedure\r\n */\r\nexport interface StreamableMediaResponse {\r\n type: \"stream\";\r\n file: BunFile;\r\n request: Request;\r\n options?: StreamMediaOptions;\r\n}\r\n\r\n/**\r\n * Handle media streaming with automatic Range request support\r\n * Supports video, audio, and other streamable media formats\r\n * @internal - Used by router to process StreamableMediaResponse\r\n */\r\nexport async function streamMediaInternal(\r\n file: BunFile,\r\n request: Request,\r\n allowedOrigins: string[],\r\n options: StreamMediaOptions = {}\r\n): Promise<Response> {\r\n const {\r\n maxChunkSize = 2 * 1024 * 1024,\r\n cacheMaxAge = 3600,\r\n acceptedExtensions,\r\n } = options;\r\n\r\n // Check if file exists\r\n const fileExists = await file.exists();\r\n if (!fileExists) {\r\n return new Response(\"Media file not found\", {\r\n status: 404,\r\n headers: buildHeaders()\r\n .contentType(\"text\")\r\n .cors({ origin: getCorsOrigin(request, allowedOrigins) })\r\n .build(),\r\n });\r\n }\r\n\r\n // Validate file extension if acceptedExtensions provided\r\n if (acceptedExtensions && acceptedExtensions.length > 0) {\r\n const ext = file.name?.toLowerCase().split(\".\").pop();\r\n if (!ext || !acceptedExtensions.includes(ext)) {\r\n return new Response(\"File type not supported\", {\r\n status: 415, // Unsupported Media Type\r\n headers: buildHeaders()\r\n .contentType(\"text\")\r\n .cors({ origin: getCorsOrigin(request, allowedOrigins) })\r\n .build(),\r\n });\r\n }\r\n }\r\n\r\n const fileSize = file.size;\r\n const range = request.headers.get(\"Range\");\r\n\r\n // No range request - send full file\r\n if (!range) {\r\n return new Response(file, {\r\n headers: buildHeaders()\r\n .filePath(file.name || \"media\")\r\n .custom(\"Accept-Ranges\", \"bytes\")\r\n .custom(\"Cache-Control\", `public, max-age=${cacheMaxAge}`)\r\n .cors({ origin: getCorsOrigin(request, allowedOrigins) })\r\n .build(),\r\n });\r\n }\r\n\r\n // Parse range header\r\n const rangeMatch = range.match(/bytes=(\\d*)-(\\d*)/);\r\n if (!rangeMatch) {\r\n return new Response(\"Invalid Range header\", {\r\n status: 416,\r\n headers: buildHeaders()\r\n .contentType(\"text\")\r\n .cors({ origin: getCorsOrigin(request, allowedOrigins) })\r\n .custom(\"Content-Range\", `bytes */${fileSize}`)\r\n .build(),\r\n });\r\n }\r\n\r\n // Calculate start and end positions\r\n const start = rangeMatch[1] ? parseInt(rangeMatch[1]) : 0;\r\n const requestedEnd = rangeMatch[2] ? parseInt(rangeMatch[2]) : fileSize - 1;\r\n\r\n // Apply maxChunkSize limit\r\n const end = Math.min(requestedEnd, start + maxChunkSize - 1, fileSize - 1);\r\n\r\n // Validate range\r\n if (start >= fileSize || end >= fileSize || start > end) {\r\n return new Response(\"Range not satisfiable\", {\r\n status: 416,\r\n headers: buildHeaders()\r\n .contentType(\"text\")\r\n .cors({ origin: getCorsOrigin(request, allowedOrigins) })\r\n .custom(\"Content-Range\", `bytes */${fileSize}`)\r\n .build(),\r\n });\r\n }\r\n\r\n // Bun automatically sets Content-Range and Content-Length for slices\r\n return new Response(file.slice(start, end + 1), {\r\n status: 206,\r\n headers: buildHeaders()\r\n .filePath(file.name || \"media\")\r\n .custom(\"Accept-Ranges\", \"bytes\")\r\n .custom(\"Cache-Control\", `public, max-age=${cacheMaxAge}`)\r\n .cors({ origin: getCorsOrigin(request, allowedOrigins) })\r\n .build(),\r\n });\r\n}\r\n\r\n/**\r\n * Helper function to create a streamable media response\r\n * Returns metadata that the router will process with streamMediaInternal()\r\n *\r\n * @example\r\n * ```typescript\r\n * // Stream any media\r\n * return streamMedia(file, ctx.request);\r\n *\r\n * // Stream with validation\r\n * return streamMedia(file, ctx.request, {\r\n * acceptedExtensions: [\"mp4\", \"webm\", \"mov\"],\r\n * maxChunkSize: 5 * 1024 * 1024,\r\n * });\r\n * ```\r\n */\r\nexport function streamMedia(\r\n file: BunFile,\r\n request: Request,\r\n options?: StreamMediaOptions\r\n): StreamableMediaResponse {\r\n return {\r\n type: \"stream\",\r\n file,\r\n request,\r\n options,\r\n };\r\n}\r\n\r\n//? I don't need to bundle them, users can create their owns\r\n// but just in case or if I want to add to docs\r\n// /**\r\n// * Common media extension presets\r\n// */\r\n// export const MEDIA_EXTENSIONS = {\r\n// video: [\"mp4\", \"webm\", \"mov\", \"avi\", \"mkv\", \"ogg\", \"ogv\"],\r\n// audio: [\"mp3\", \"wav\", \"ogg\", \"m4a\", \"flac\", \"aac\", \"opus\"],\r\n// all: [\r\n// \"mp4\",\r\n// \"webm\",\r\n// \"mov\",\r\n// \"avi\",\r\n// \"mkv\",\r\n// \"ogg\",\r\n// \"ogv\",\r\n// \"mp3\",\r\n// \"wav\",\r\n// \"m4a\",\r\n// \"flac\",\r\n// \"aac\",\r\n// \"opus\",\r\n// ],\r\n// } as const;\r\n",
11
11
  "import type { BaseContext } from \"./types\";\r\n\r\nexport function createContext<C extends BaseContext>(\r\n contextCreator: (req: Request) => Promise<C>\r\n) {\r\n return contextCreator;\r\n}\r\n",
12
12
  "import { z } from \"zod\";\r\n\r\n// Common MIME type groups\r\nconst MIME_TYPES = {\r\n image: [\r\n \"image/jpeg\",\r\n \"image/png\",\r\n \"image/gif\",\r\n \"image/webp\",\r\n \"image/svg+xml\",\r\n ],\r\n video: [\"video/mp4\", \"video/webm\", \"video/ogg\"],\r\n audio: [\"audio/mpeg\", \"audio/ogg\", \"audio/wav\"],\r\n document: [\r\n \"application/pdf\",\r\n \"application/msword\",\r\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\r\n \"application/vnd.ms-excel\",\r\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\r\n ],\r\n} as const;\r\n\r\ntype MimeTypeGroup = keyof typeof MIME_TYPES;\r\ntype MimeTypeValues<T extends MimeTypeGroup> = (typeof MIME_TYPES)[T][number];\r\n\r\n// Type for accepting either specific types or wildcard per group\r\ntype MimeTypeConfig = {\r\n [K in MimeTypeGroup]?: \"*\" | MimeTypeValues<K>[];\r\n};\r\n\r\n/** Options for creating a file validation schema */\r\ntype FileSchemaOptions = {\r\n /** Accepted MIME types configuration. Use \"*\" for all types in a category or specify an array of types\r\n * @example\r\n * {\r\n * image: \"*\", // All image types\r\n * audio: [\"audio/mpeg\"], // Only MP3s\r\n * document: [\"application/pdf\", \"application/msword\"] // PDFs and DOCs\r\n * }\r\n */\r\n acceptedTypes?: MimeTypeConfig;\r\n /** Maximum file size in megabytes */\r\n maxSize?: number;\r\n /** Minimum file size in megabytes */\r\n minSize?: number;\r\n /** Whether the file field is required */\r\n // required?: boolean;\r\n /** Custom error messages */\r\n messages?: {\r\n /** Custom message for type validation failure */\r\n type?: string;\r\n /** Custom message for maximum size validation failure */\r\n maxSize?: string;\r\n /** Custom message for minimum size validation failure */\r\n minSize?: string;\r\n /** Custom message for required field validation failure */\r\n required?: string;\r\n };\r\n};\r\n\r\nconst convertToBytes = (\r\n size: number,\r\n unit: \"MB\" | \"KB\" | \"B\" = \"MB\"\r\n): number => {\r\n switch (unit) {\r\n case \"MB\":\r\n return size * 1024 * 1024;\r\n case \"KB\":\r\n return size * 1024;\r\n case \"B\":\r\n return size;\r\n }\r\n};\r\n\r\n/**\r\n * Creates a Zod schema for file validation with support for MIME types and size constraints\r\n * you can extend it as any other zod schema after calling it\r\n * @param {Object} options - The configuration options for the file schema\r\n * @returns Zod schema for file validation\r\n */\r\nexport const createFileSchema = ({\r\n acceptedTypes,\r\n maxSize = Infinity,\r\n minSize,\r\n // required = true,\r\n messages = {},\r\n}: FileSchemaOptions = {}) => {\r\n // Convert MB to bytes\r\n const maxBytes = maxSize ? convertToBytes(maxSize) : Infinity;\r\n const minBytes = minSize ? convertToBytes(minSize) : 0;\r\n\r\n // Process accepted types\r\n const validMimeTypes = acceptedTypes\r\n ? Object.entries(acceptedTypes).flatMap(([category, types]) => {\r\n const cat = category as MimeTypeGroup;\r\n\r\n // Handle wildcard\r\n if (types === \"*\") {\r\n return MIME_TYPES[cat];\r\n }\r\n\r\n // Handle array of specific types\r\n if (Array.isArray(types)) {\r\n return types;\r\n }\r\n\r\n return [];\r\n })\r\n : undefined;\r\n\r\n // Base schema\r\n let schema = z.instanceof(File, {\r\n message: messages.required ?? \"File is required\",\r\n });\r\n\r\n // Add size refinement\r\n schema = schema\r\n .refine(\r\n (file) => file.size <= maxBytes,\r\n messages.maxSize ?? `File size must be less than ${maxSize}MB`\r\n )\r\n .refine(\r\n (file) => file.size >= minBytes,\r\n messages.minSize ?? `File size must be at least ${minSize}MB`\r\n );\r\n\r\n // Add type refinement if acceptedTypes is provided\r\n if (validMimeTypes?.length) {\r\n schema = schema.refine(\r\n (file) =>\r\n validMimeTypes.includes(file.type as MimeTypeValues<MimeTypeGroup>),\r\n messages.type ?? `File must be of type: ${validMimeTypes.join(\", \")}`\r\n );\r\n }\r\n\r\n return schema;\r\n};\r\n",
13
13
  "import type { BaseContext } from \"../..\";\r\n\r\n// blockPaths.ts\r\nexport type PathBlockerConfig = {\r\n paths: string[]; // Array of paths/patterns to block\r\n};\r\n\r\nexport const createPathBlocker = (config: PathBlockerConfig) => {\r\n const patterns = config.paths.map((path) => new RegExp(path));\r\n const message = \"Not Found\";\r\n\r\n return async <C extends BaseContext>(ctx: C): Promise<void> => {\r\n const url = new URL(ctx.req.url);\r\n const path = url.pathname;\r\n\r\n // Check if the path matches any blocked pattern\r\n if (patterns.some((pattern) => pattern.test(path))) {\r\n throw new Error(message);\r\n }\r\n };\r\n};\r\n",
@@ -0,0 +1,15 @@
1
+ import type { GetOptimizedImageOptions } from "./types";
2
+ /**
3
+ * Returns an existing optimized image from cache or
4
+ * creates one on the fly
5
+ * @example
6
+ * ```ts
7
+ * const optimizedImage = await getImage({
8
+ * key: "originalFileKey",
9
+ * width: 800
10
+ * })
11
+ * if (!optimizedImage) throw BRPCError.notFound()
12
+ * return optimizedImage
13
+ * ```
14
+ */
15
+ export declare function getOptimizedImage(options: GetOptimizedImageOptions): Promise<Bun.BunFile | null>;
@@ -0,0 +1,3 @@
1
+ export * from "./optimize";
2
+ export * from "./get-optimized";
3
+ export * from "./types";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import type { ResolvedFileType } from "../types";
2
+ export type OptimizedImageBuffer = {
3
+ buffer: Buffer;
4
+ metadata: {
5
+ name: string;
6
+ type: string;
7
+ extension: string;
8
+ resolvedType: ResolvedFileType;
9
+ };
10
+ };
11
+ export type GetOptimizedImageOptions = {
12
+ key: string;
13
+ width: number;
14
+ quality?: number;
15
+ provider?: "local" | "s3" | "external";
16
+ };
@@ -1,4 +1,4 @@
1
1
  // @bun
2
- var J=(x)=>x.startsWith("image/"),K=(x)=>x.startsWith("video/"),M=(x)=>x.startsWith("audio/"),Q=(x)=>x.startsWith("text/")||x.includes("pdf")||x.includes("word")||x.includes("sheet")||x.includes("presentation")||x.includes("opendocument")||x==="application/rtf",S=(x)=>x.includes("zip")||x.includes("rar")||x.includes("tar")||x.includes("7z")||x.includes("gzip"),U=(x)=>x==="application/javascript"||x==="application/json"||x==="text/html"||x==="text/css"||x==="application/xml"||x.includes("javascript"),X=(x)=>x.startsWith("font/")||x.includes("font"),Y=(x)=>{if(!x)return"other";let L=x.toLowerCase();if(J(L))return"image";if(K(L))return"video";if(M(L))return"audio";if(Q(L))return"document";if(S(L))return"archive";if(U(L))return"code";if(X(L))return"font";return"other"},Z={isImage:J,isVideo:K,isAudio:M,isDocument:Q,isArchive:S,isCode:U,isFont:X};function b(){let L=Buffer.from("Hello, this is a test file for S3 upload!","utf-8");return new File([L],"test.txt",{type:"text/plain"})}import A from"sharp";class _ extends Error{code;clientCode;httpStatus;data;cause;static STATUS_MAP={BAD_REQUEST:400,UNAUTHORIZED:401,FORBIDDEN:403,NOT_FOUND:404,METHOD_NOT_SUPPORTED:405,TIMEOUT:408,CONFLICT:409,PRECONDITION_FAILED:412,PAYLOAD_TOO_LARGE:413,UNPROCESSABLE_CONTENT:422,TOO_MANY_REQUESTS:429,CLIENT_CLOSED_REQUEST:499,INTERNAL_SERVER_ERROR:500,NOT_IMPLEMENTED:501,BAD_GATEWAY:502,SERVICE_UNAVAILABLE:503,GATEWAY_TIMEOUT:504};constructor(x){super(x.message);if(this.name="BRPCError",this.code=x.code,this.clientCode=x.clientCode,this.httpStatus=_.STATUS_MAP[x.code],this.data=x.data,this.cause=x.cause,Error.captureStackTrace)Error.captureStackTrace(this,_)}toJSON(){return{name:this.name,code:this.code,clientCode:this.clientCode,message:this.message,data:this.data,httpStatus:this.httpStatus}}static badRequest(x,L,W){return new _({code:"BAD_REQUEST",message:x,clientCode:L,data:W})}static unauthorized(x="Unauthorized",L,W){return new _({code:"UNAUTHORIZED",message:x,clientCode:L,data:W})}static forbidden(x="Forbidden",L,W){return new _({code:"FORBIDDEN",message:x,clientCode:L,data:W})}static notFound(x="Not Found",L,W){return new _({code:"NOT_FOUND",message:x,clientCode:L,data:W})}static preconditionFailed(x="Precondition failed",L,W){return new _({code:"NOT_FOUND",message:x,clientCode:L,data:W})}static conflict(x,L,W){return new _({code:"CONFLICT",message:x,clientCode:L,data:W})}static unprocessableContent(x,L,W){return new _({code:"UNPROCESSABLE_CONTENT",message:x,clientCode:L,data:W})}static tooManyRequests(x="Too many requests",L,W){return new _({code:"TOO_MANY_REQUESTS",message:x,clientCode:L,data:W})}static internalServerError(x="Internal Server Error",L,W){return new _({code:"INTERNAL_SERVER_ERROR",message:x,clientCode:L,data:W})}static timeout(x="Request timeout",L,W){return new _({code:"TIMEOUT",message:x,clientCode:L,data:W})}}async function E(x,L={}){let W=L.width??1200,O=L.quality??75;if(!x)throw _.conflict("A file must be provided in order to optimize");if(!Z.isImage(x.type))throw _.conflict("The file must be an image to be optimized");let D=await x.arrayBuffer();return{buffer:await A(D).resize(W,null,{withoutEnlargement:!0}).webp({quality:O}).toBuffer(),metadata:{name:x.name?x.name.replace(/\.[^/.]+$/,".webp"):"unknown.webp",type:"image/webp",extension:".webp",resolvedType:"image"}}}import V from"path";var j=process.env.AWS_FOLDER,q=j?`${j}/`:"";var{s3:F}=globalThis.Bun;async function d(x,L,W){let O="buffer"in L?{buffer:L.buffer,metadata:L.metadata}:{buffer:L,metadata:{name:L.name,type:L.type,extension:V.extname(L.name),resolvedType:Y(L.type)}};if("buffer"in L&&!L.metadata)throw _.badRequest("Metadata required when uploading buffer");let D=x.length>1?`${x.replace(/^\/+|\/+$/g,"")}/`:"",G=`${crypto.randomUUID()}${O.metadata.extension}`,H=`${q}${D}${G}`,z=F.file(H),$=W?.acl??"public-read",N=$!=="public-read"&&$!=="public-read-write",w=await z.write(O.buffer,{acl:$});return{uploadedBy:W?.uploadedBy??null,key:H,url:null,name:O.metadata.name,thumbnail:null,resolvedType:O.metadata.resolvedType,provider:"s3",isPrivate:N,metadata:{size:w,type:O.metadata.type,extension:O.metadata.extension,acl:$},isActive:!0}}var{s3:k}=globalThis.Bun;async function l(x,L={debug:!0}){try{return{exists:await k.exists(x),error:null}}catch(W){if(L?.debug)console.error("There was an error checking for file existance",W);return{exists:null,error:W}}}export{d as uploadOne,E as optimizeImage,Y as mimeTypeToResolvedType,b as generateTestFile,Z as fileTypePredicates,l as checkFileExistance};
2
+ var D=(_)=>_.startsWith("image/"),K=(_)=>_.startsWith("video/"),M=(_)=>_.startsWith("audio/"),q=(_)=>_.startsWith("text/")||_.includes("pdf")||_.includes("word")||_.includes("sheet")||_.includes("presentation")||_.includes("opendocument")||_==="application/rtf",z=(_)=>_.includes("zip")||_.includes("rar")||_.includes("tar")||_.includes("7z")||_.includes("gzip"),A=(_)=>_==="application/javascript"||_==="application/json"||_==="text/html"||_==="text/css"||_==="application/xml"||_.includes("javascript"),G=(_)=>_.startsWith("font/")||_.includes("font"),S=(_)=>{if(!_)return"other";let L=_.toLowerCase();if(D(L))return"image";if(K(L))return"video";if(M(L))return"audio";if(q(L))return"document";if(z(L))return"archive";if(A(L))return"code";if(G(L))return"font";return"other"},$={isImage:D,isVideo:K,isAudio:M,isDocument:q,isArchive:z,isCode:A,isFont:G};function h(){let L=Buffer.from("Hello, this is a test file for S3 upload!","utf-8");return new File([L],"test.txt",{type:"text/plain"})}class x extends Error{code;clientCode;httpStatus;data;cause;static STATUS_MAP={BAD_REQUEST:400,UNAUTHORIZED:401,FORBIDDEN:403,NOT_FOUND:404,METHOD_NOT_SUPPORTED:405,TIMEOUT:408,CONFLICT:409,PRECONDITION_FAILED:412,PAYLOAD_TOO_LARGE:413,UNPROCESSABLE_CONTENT:422,TOO_MANY_REQUESTS:429,CLIENT_CLOSED_REQUEST:499,INTERNAL_SERVER_ERROR:500,NOT_IMPLEMENTED:501,BAD_GATEWAY:502,SERVICE_UNAVAILABLE:503,GATEWAY_TIMEOUT:504};constructor(_){super(_.message);if(this.name="BRPCError",this.code=_.code,this.clientCode=_.clientCode,this.httpStatus=x.STATUS_MAP[_.code],this.data=_.data,this.cause=_.cause,Error.captureStackTrace)Error.captureStackTrace(this,x)}toJSON(){return{name:this.name,code:this.code,clientCode:this.clientCode,message:this.message,data:this.data,httpStatus:this.httpStatus}}static badRequest(_,L,W){return new x({code:"BAD_REQUEST",message:_,clientCode:L,data:W})}static unauthorized(_="Unauthorized",L,W){return new x({code:"UNAUTHORIZED",message:_,clientCode:L,data:W})}static forbidden(_="Forbidden",L,W){return new x({code:"FORBIDDEN",message:_,clientCode:L,data:W})}static notFound(_="Not Found",L,W){return new x({code:"NOT_FOUND",message:_,clientCode:L,data:W})}static preconditionFailed(_="Precondition failed",L,W){return new x({code:"NOT_FOUND",message:_,clientCode:L,data:W})}static conflict(_,L,W){return new x({code:"CONFLICT",message:_,clientCode:L,data:W})}static unprocessableContent(_,L,W){return new x({code:"UNPROCESSABLE_CONTENT",message:_,clientCode:L,data:W})}static tooManyRequests(_="Too many requests",L,W){return new x({code:"TOO_MANY_REQUESTS",message:_,clientCode:L,data:W})}static internalServerError(_="Internal Server Error",L,W){return new x({code:"INTERNAL_SERVER_ERROR",message:_,clientCode:L,data:W})}static timeout(_="Request timeout",L,W){return new x({code:"TIMEOUT",message:_,clientCode:L,data:W})}}import T from"sharp";import{randomUUID as v}from"crypto";import{extname as I}from"path";async function N(_,L,W){if(!L)return{data:null,error:Error("File not found")};let H=_.replace(/^\/+|\/+$/g,""),Q=I(L.name),J=`${v()}${Q}`,X=`${H}/${J}`,Z=`./buckets/${H}/${J}`;try{let Y=await Bun.write(Z,L);return{data:{key:X,file:L,bytesWritten:Y},error:null}}catch(Y){return{data:null,error:Error(`Failed to upload ${L.name} to local filesystem`)}}}async function k(_,L,W){if(!L||L.length===0)return[];let H=L.map((J)=>{let X=W?.(J)??J.name;return N(_,J,X)});return await Promise.all(H)}async function b(_){let L=Bun.file(`./buckets/${_}`);if(!await L.exists())return null;return L}async function F(_){let L=Bun.file(`./buckets/${_}`);if(!await L.exists())return!1;return await L.delete(),!0}var j={uploadOne:N,uploadMany:k,getOne:b,deleteOne:F};async function n(_){let L=`./cache/images/${_.key}_${_.width}_${_.quality}.webp`,W=await j.getOne(L);if(W){if(!$.isImage(W.type))return null;return W}let H=_.key,Q=await j.getOne(H);if(!Q)return null;if(!$.isImage(Q.type))return null;let J=await Q.arrayBuffer(),X=await T(J).resize(_.width,null,{withoutEnlargement:!0}).webp({quality:_.quality??75}).toBuffer();await Bun.write(L,X);let Z=Bun.file(L);if(!await Z.exists())return null;return Z}import E from"path";var V=process.env.AWS_FOLDER,O=V?`${V}/`:"";var{s3:R}=globalThis.Bun;async function t(_,L,W){let H="buffer"in L?{buffer:L.buffer,metadata:L.metadata}:{buffer:L,metadata:{name:L.name,type:L.type,extension:E.extname(L.name),resolvedType:S(L.type)}};if("buffer"in L&&!L.metadata)throw x.badRequest("Metadata required when uploading buffer");let Q=_.length>1?`${_.replace(/^\/+|\/+$/g,"")}/`:"",J=`${crypto.randomUUID()}${H.metadata.extension}`,X=`${O}${Q}${J}`,Z=R.file(X),Y=W?.acl??"public-read",w=Y!=="public-read"&&Y!=="public-read-write",U=await Z.write(H.buffer,{acl:Y});return{uploadedBy:W?.uploadedBy??null,key:X,url:null,name:H.metadata.name,thumbnail:null,resolvedType:H.metadata.resolvedType,provider:"s3",isPrivate:w,metadata:{size:U,type:H.metadata.type,extension:H.metadata.extension,acl:Y},isActive:!0}}var{s3:B}=globalThis.Bun;async function L_(_,L={debug:!0}){try{return{exists:await B.exists(_),error:null}}catch(W){if(L?.debug)console.error("There was an error checking for file existance",W);return{exists:null,error:W}}}export{t as uploadOne,S as mimeTypeToResolvedType,n as getOptimizedImage,h as generateTestFile,$ as fileTypePredicates,L_ as checkFileExistance};
3
3
 
4
- //# debugId=8AB7792AECD8AEAA64756E2164756E21
4
+ //# debugId=DE5A1B3485300E8464756E2164756E21
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["..\\src\\storage\\utils\\files.ts", "..\\src\\storage\\images.ts", "..\\src\\errors\\BRPCError.ts", "..\\src\\storage\\s3\\upload.ts", "..\\src\\storage\\s3\\constants.ts", "..\\src\\storage\\s3\\helpers.ts"],
3
+ "sources": ["..\\src\\storage\\utils\\files.ts", "..\\src\\errors\\BRPCError.ts", "..\\src\\storage\\images\\get-optimized.ts", "..\\src\\storage\\local\\index.ts", "..\\src\\storage\\s3\\upload.ts", "..\\src\\storage\\s3\\constants.ts", "..\\src\\storage\\s3\\helpers.ts"],
4
4
  "sourcesContent": [
5
5
  "import type { ResolvedFileType } from \"../types\";\r\n\r\nconst isImage = (type: string): boolean => type.startsWith(\"image/\");\r\n\r\nconst isVideo = (type: string): boolean => type.startsWith(\"video/\");\r\n\r\nconst isAudio = (type: string): boolean => type.startsWith(\"audio/\");\r\n\r\nconst isDocument = (type: string): boolean =>\r\n type.startsWith(\"text/\") ||\r\n type.includes(\"pdf\") ||\r\n type.includes(\"word\") ||\r\n type.includes(\"sheet\") ||\r\n type.includes(\"presentation\") ||\r\n type.includes(\"opendocument\") ||\r\n type === \"application/rtf\";\r\n\r\nconst isArchive = (type: string): boolean =>\r\n type.includes(\"zip\") ||\r\n type.includes(\"rar\") ||\r\n type.includes(\"tar\") ||\r\n type.includes(\"7z\") ||\r\n type.includes(\"gzip\");\r\n\r\nconst isCode = (type: string): boolean =>\r\n type === \"application/javascript\" ||\r\n type === \"application/json\" ||\r\n type === \"text/html\" ||\r\n type === \"text/css\" ||\r\n type === \"application/xml\" ||\r\n type.includes(\"javascript\");\r\n\r\nconst isFont = (type: string): boolean =>\r\n type.startsWith(\"font/\") || type.includes(\"font\");\r\n\r\n/**\r\n * Maps MIME types to file categories\r\n * @param mimeType - The MIME type string\r\n * @returns FileType category\r\n */\r\nconst mimeTypeToResolvedType = (mimeType: string): ResolvedFileType => {\r\n if (!mimeType) return \"other\";\r\n\r\n const type = mimeType.toLowerCase();\r\n\r\n if (isImage(type)) return \"image\";\r\n if (isVideo(type)) return \"video\";\r\n if (isAudio(type)) return \"audio\";\r\n if (isDocument(type)) return \"document\";\r\n if (isArchive(type)) return \"archive\";\r\n if (isCode(type)) return \"code\";\r\n if (isFont(type)) return \"font\";\r\n\r\n return \"other\";\r\n};\r\n\r\n// Optional: Export the predicate functions if you need them elsewhere\r\nconst fileTypePredicates = {\r\n isImage,\r\n isVideo,\r\n isAudio,\r\n isDocument,\r\n isArchive,\r\n isCode,\r\n isFont,\r\n};\r\n\r\n/**\r\n * Generates a text.txt file so you can test file uploading is working\r\n */\r\nfunction generateTestFile() {\r\n // Create test content\r\n const textContent = \"Hello, this is a test file for S3 upload!\";\r\n const buffer = Buffer.from(textContent, \"utf-8\");\r\n\r\n // Create a File object (assuming you have File constructor available in Bun)\r\n const testFile = new File([buffer], \"test.txt\", { type: \"text/plain\" });\r\n\r\n return testFile;\r\n}\r\n\r\nexport { mimeTypeToResolvedType, fileTypePredicates, generateTestFile };\r\n",
6
- "import type { BunFile } from \"bun\";\r\nimport sharp from \"sharp\";\r\nimport type { ResolvedFileType } from \"./types\";\r\nimport { BRPCError } from \"../errors/BRPCError\";\r\nimport { fileTypePredicates } from \"./utils\";\r\n\r\ntype OptimizedImageBuffer = {\r\n // buffer: Buffer<ArrayBufferLike>;\r\n buffer: Buffer;\r\n metadata: {\r\n name: string;\r\n type: string;\r\n extension: string;\r\n resolvedType: ResolvedFileType;\r\n };\r\n};\r\n\r\nasync function optimizeImage(\r\n file: File | BunFile,\r\n opts: {\r\n width?: number;\r\n quality?: number;\r\n } = {}\r\n): Promise<OptimizedImageBuffer> {\r\n // doing like this because zod can\r\n // directly pass undefined, overriding\r\n // the defaults that are only for not provided\r\n // not for strict undefined\r\n const width = opts.width ?? 1200;\r\n const quality = opts.quality ?? 75;\r\n\r\n if (!file) {\r\n throw BRPCError.conflict(\"A file must be provided in order to optimize\");\r\n }\r\n\r\n if (!fileTypePredicates.isImage(file.type)) {\r\n throw BRPCError.conflict(\"The file must be an image to be optimized\");\r\n }\r\n\r\n const buffer = await file.arrayBuffer();\r\n\r\n const optimizedBuffer = await sharp(buffer)\r\n .resize(width, null, { withoutEnlargement: true })\r\n .webp({ quality: quality })\r\n .toBuffer();\r\n\r\n return {\r\n buffer: optimizedBuffer,\r\n metadata: {\r\n name: file.name\r\n ? file.name.replace(/\\.[^/.]+$/, \".webp\")\r\n : \"unknown.webp\",\r\n type: \"image/webp\",\r\n extension: \".webp\",\r\n resolvedType: \"image\",\r\n },\r\n };\r\n}\r\n\r\nexport { optimizeImage };\r\nexport type { OptimizedImageBuffer };\r\n",
7
6
  "export type BRPCErrorCode =\r\n | \"BAD_REQUEST\"\r\n | \"UNAUTHORIZED\"\r\n | \"FORBIDDEN\"\r\n | \"NOT_FOUND\"\r\n | \"METHOD_NOT_SUPPORTED\"\r\n | \"TIMEOUT\"\r\n | \"CONFLICT\"\r\n | \"PRECONDITION_FAILED\"\r\n | \"PAYLOAD_TOO_LARGE\"\r\n | \"UNPROCESSABLE_CONTENT\"\r\n | \"TOO_MANY_REQUESTS\"\r\n | \"CLIENT_CLOSED_REQUEST\"\r\n | \"INTERNAL_SERVER_ERROR\"\r\n | \"NOT_IMPLEMENTED\"\r\n | \"BAD_GATEWAY\"\r\n | \"SERVICE_UNAVAILABLE\"\r\n | \"GATEWAY_TIMEOUT\";\r\n\r\nexport interface BRPCErrorOptions {\r\n code: BRPCErrorCode;\r\n message: string;\r\n clientCode?: string; // Custom code for client-side detection\r\n cause?: Error;\r\n data?: Record<string, any>;\r\n}\r\n\r\nexport class BRPCError extends Error {\r\n public readonly code: BRPCErrorCode;\r\n public readonly clientCode?: string;\r\n public readonly httpStatus: number;\r\n public readonly data?: Record<string, any>;\r\n public readonly cause?: Error;\r\n\r\n private static readonly STATUS_MAP: Record<BRPCErrorCode, number> = {\r\n BAD_REQUEST: 400,\r\n UNAUTHORIZED: 401,\r\n FORBIDDEN: 403,\r\n NOT_FOUND: 404,\r\n METHOD_NOT_SUPPORTED: 405,\r\n TIMEOUT: 408,\r\n CONFLICT: 409,\r\n PRECONDITION_FAILED: 412,\r\n PAYLOAD_TOO_LARGE: 413,\r\n UNPROCESSABLE_CONTENT: 422,\r\n TOO_MANY_REQUESTS: 429,\r\n CLIENT_CLOSED_REQUEST: 499,\r\n INTERNAL_SERVER_ERROR: 500,\r\n NOT_IMPLEMENTED: 501,\r\n BAD_GATEWAY: 502,\r\n SERVICE_UNAVAILABLE: 503,\r\n GATEWAY_TIMEOUT: 504,\r\n };\r\n\r\n constructor(options: BRPCErrorOptions) {\r\n super(options.message);\r\n\r\n this.name = \"BRPCError\";\r\n this.code = options.code;\r\n this.clientCode = options.clientCode;\r\n this.httpStatus = BRPCError.STATUS_MAP[options.code];\r\n this.data = options.data;\r\n this.cause = options.cause;\r\n\r\n // Maintains proper stack trace for where our error was thrown (only available on V8)\r\n if (Error.captureStackTrace) {\r\n Error.captureStackTrace(this, BRPCError);\r\n }\r\n }\r\n\r\n /**\r\n * Create a serializable object for sending to frontend\r\n */\r\n toJSON() {\r\n return {\r\n name: this.name,\r\n code: this.code,\r\n clientCode: this.clientCode,\r\n message: this.message,\r\n data: this.data,\r\n httpStatus: this.httpStatus,\r\n };\r\n }\r\n\r\n /**\r\n * Static factory methods for common errors\r\n */\r\n static badRequest(\r\n message: string,\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"BAD_REQUEST\", message, clientCode, data });\r\n }\r\n\r\n static unauthorized(\r\n message: string = \"Unauthorized\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"UNAUTHORIZED\", message, clientCode, data });\r\n }\r\n\r\n static forbidden(\r\n message: string = \"Forbidden\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"FORBIDDEN\", message, clientCode, data });\r\n }\r\n\r\n static notFound(\r\n message: string = \"Not Found\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"NOT_FOUND\", message, clientCode, data });\r\n }\r\n\r\n static preconditionFailed(\r\n message: string = \"Precondition failed\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"NOT_FOUND\", message, clientCode, data });\r\n }\r\n\r\n static conflict(\r\n message: string,\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"CONFLICT\", message, clientCode, data });\r\n }\r\n\r\n static unprocessableContent(\r\n message: string,\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({\r\n code: \"UNPROCESSABLE_CONTENT\",\r\n message,\r\n clientCode,\r\n data,\r\n });\r\n }\r\n\r\n static tooManyRequests(\r\n message: string = \"Too many requests\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({\r\n code: \"TOO_MANY_REQUESTS\",\r\n message,\r\n clientCode,\r\n data,\r\n });\r\n }\r\n\r\n static internalServerError(\r\n message: string = \"Internal Server Error\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({\r\n code: \"INTERNAL_SERVER_ERROR\",\r\n message,\r\n clientCode,\r\n data,\r\n });\r\n }\r\n\r\n static timeout(\r\n message: string = \"Request timeout\",\r\n clientCode?: string,\r\n data?: Record<string, any>\r\n ) {\r\n return new BRPCError({ code: \"TIMEOUT\", message, clientCode, data });\r\n }\r\n}\r\n",
7
+ "import sharp from \"sharp\";\r\nimport { local } from \"../local\";\r\nimport { fileTypePredicates } from \"../utils\";\r\nimport type { GetOptimizedImageOptions } from \"./types\";\r\n\r\n/**\r\n * Returns an existing optimized image from cache or\r\n * creates one on the fly\r\n * @example\r\n * ```ts\r\n * const optimizedImage = await getImage({\r\n * key: \"originalFileKey\",\r\n * width: 800\r\n * })\r\n * if (!optimizedImage) throw BRPCError.notFound()\r\n * return optimizedImage\r\n * ```\r\n */\r\nexport async function getOptimizedImage(options: GetOptimizedImageOptions) {\r\n // const url = new URL(req.url);\r\n // const imagePath = url.searchParams.get(\"src\");\r\n // const width = parseInt(url.searchParams.get(\"w\") || \"800\");\r\n // const quality = parseInt(url.searchParams.get(\"q\") || \"75\");\r\n\r\n const localCache = `./cache/images/${options.key}_${options.width}_${options.quality}.webp`;\r\n\r\n // 1. Check local cache\r\n const localFile = await local.getOne(localCache);\r\n\r\n if (localFile) {\r\n if (!fileTypePredicates.isImage(localFile.type)) {\r\n return null;\r\n }\r\n return localFile;\r\n }\r\n\r\n // 2. Check Spaces cache - using Bun's global s3 instance\r\n // const cachedFile = s3.file(`appS3Folder/cache/${cacheKey}`);\r\n // if (await cachedFile.exists()) {\r\n // const cached = await cachedFile.arrayBuffer();\r\n // await Bun.write(localCache, cached);\r\n // return new Response(cached, {\r\n // headers: { 'Content-Type': 'image/webp' }\r\n // });\r\n // }\r\n\r\n // 3. Fetch from Spaces, optimize, cache\r\n // const originalFile = s3.file(`originals/${imagePath}`);\r\n // const originalBuffer = await originalFile.arrayBuffer();\r\n\r\n // 3.2 Fetch from local\r\n // const originalPath = `./buckets/cache/images/originals/${options.path}`;\r\n const originalKey = options.key;\r\n const originalFile = await local.getOne(originalKey);\r\n\r\n if (!originalFile) return null;\r\n\r\n if (!fileTypePredicates.isImage(originalFile.type)) {\r\n return null;\r\n }\r\n\r\n const buffer = await originalFile.arrayBuffer();\r\n\r\n const optimizedBuffer = await sharp(buffer)\r\n .resize(options.width, null, { withoutEnlargement: true })\r\n .webp({ quality: options.quality ?? 75 })\r\n .toBuffer();\r\n\r\n // Cache everywhere\r\n // await Promise.all([\r\n // Bun.write(localCache, optimizedBuffer),\r\n // cachedFile.write(optimizedBuffer, { type: 'image/webp' })\r\n // ]);\r\n\r\n // Cache to local\r\n await Bun.write(localCache, optimizedBuffer);\r\n\r\n const cachedFile = Bun.file(localCache);\r\n\r\n const exists = await cachedFile.exists();\r\n\r\n if (!exists) return null;\r\n\r\n return cachedFile;\r\n}\r\n",
8
+ "import { randomUUID } from \"crypto\";\r\nimport { extname } from \"path\";\r\nimport type { SafeResult } from \"../../types\";\r\n\r\ntype UploadSuccess = {\r\n key: string;\r\n file: File;\r\n bytesWritten: number;\r\n};\r\n\r\n/**\r\n * Uploads a file to local filesystem\r\n * @param path - The folder path (e.g. \"public/images\")\r\n * @param file - The file to upload\r\n * @param filename - Optional custom filename to use instead of original file.name\r\n * @returns Promise<SafeResult<number>> - Success: {data: bytesWritten, error: null}\r\n * Failure: {data: null, error: Error}\r\n *\r\n * Example:\r\n * ```ts\r\n * const {data, error} = await uploadToLocal(\"public/avatars\", userPhoto);\r\n * if (error) {\r\n * console.error(\"Upload failed:\", error);\r\n * return;\r\n * }\r\n * console.log(`Uploaded ${data} bytes`);\r\n * ```\r\n */\r\nasync function uploadOne(\r\n path: string,\r\n file: File,\r\n _filename?: string\r\n): Promise<SafeResult<UploadSuccess>> {\r\n if (!file)\r\n return {\r\n data: null,\r\n error: new Error(`File not found`),\r\n };\r\n\r\n const cleanPath = path.replace(/^\\/+|\\/+$/g, \"\");\r\n const ext = extname(file.name);\r\n const uniqueName = `${randomUUID()}${ext}`;\r\n const filePath = `${cleanPath}/${uniqueName}`;\r\n const fullPath = `./buckets/${cleanPath}/${uniqueName}`;\r\n\r\n try {\r\n const writtenBytes = await Bun.write(fullPath, file);\r\n return {\r\n data: {\r\n key: filePath,\r\n file: file,\r\n bytesWritten: writtenBytes,\r\n },\r\n error: null,\r\n };\r\n } catch (err) {\r\n return {\r\n data: null,\r\n error: new Error(`Failed to upload ${file.name} to local filesystem`),\r\n };\r\n }\r\n}\r\n\r\nasync function uploadMany(\r\n path: string,\r\n files: File[],\r\n nameGetter?: (file: File) => string\r\n): Promise<SafeResult<UploadSuccess>[]> {\r\n if (!files || files.length === 0) {\r\n return [];\r\n }\r\n\r\n const uploadPromises = files.map((file) => {\r\n const filename = nameGetter?.(file) ?? file.name;\r\n return uploadOne(path, file, filename);\r\n });\r\n\r\n const results = await Promise.all(uploadPromises);\r\n\r\n return results;\r\n}\r\n\r\nasync function getOne(key: string) {\r\n const file = Bun.file(`./buckets/${key}`);\r\n const exists = await file.exists();\r\n\r\n if (!exists) return null;\r\n\r\n return file;\r\n}\r\n\r\nasync function deleteOne(key: string) {\r\n const file = Bun.file(`./buckets/${key}`);\r\n const exists = await file.exists();\r\n\r\n if (!exists) return false;\r\n\r\n await file.delete();\r\n\r\n return true;\r\n}\r\n\r\nexport const local = {\r\n uploadOne,\r\n uploadMany,\r\n getOne,\r\n deleteOne,\r\n};\r\n",
8
9
  "import type { Acl, InsertStorageObjet } from \"../types\";\r\nimport bunPath from \"path\";\r\nimport { mimeTypeToResolvedType } from \"../utils\";\r\nimport { BRPCError } from \"../../errors/BRPCError\";\r\nimport { CONSTRUCTED_AWS_FOLDER } from \"./constants\";\r\nimport { s3 as s3Bun } from \"bun\";\r\nimport type { OptimizedImageBuffer } from \"../images\";\r\n\r\n/**\r\n * Uploads a file or optimized buffer to S3 with UUID-based naming\r\n *\r\n * @param path - S3 folder path (e.g. \"images/avatars\")\r\n * @param file - File object or OptimizedBuffer from optimize()\r\n * @returns SafeResult with S3 key, bytes written, isPrivate and metadata for database storage\r\n *\r\n * @throws {Error} When buffer is provided without required metadata\r\n *\r\n * @example\r\n * ```typescript\r\n * // regular files\r\n * const result = await uploadOne('images', userFile);\r\n *\r\n * // optimized images\r\n * const optimized = await optimize(bunFile);\r\n * const result2 = await uploadOne('images/optimized', optimized);\r\n *\r\n * if (result.error) throw BRPCError.conlict(\"Error uploading \")\r\n *\r\n * if (result.data) {\r\n * console.log(`Uploaded: ${result.data.key}`);\r\n * // Save metadata to DB: result.data.metadata\r\n * }\r\n * ```\r\n */\r\nasync function uploadOne(\r\n path: string,\r\n file: File | OptimizedImageBuffer,\r\n opts?: { acl?: Acl; externalReference?: string; uploadedBy?: string }\r\n): Promise<InsertStorageObjet> {\r\n // Extract file info based on type\r\n const fileInfo =\r\n \"buffer\" in file\r\n ? {\r\n buffer: file.buffer,\r\n metadata: file.metadata,\r\n }\r\n : {\r\n buffer: file,\r\n metadata: {\r\n name: file.name,\r\n type: file.type,\r\n extension: bunPath.extname(file.name),\r\n resolvedType: mimeTypeToResolvedType(file.type),\r\n },\r\n };\r\n\r\n // Validate metadata for buffer uploads\r\n if (\"buffer\" in file && !file.metadata) {\r\n throw BRPCError.badRequest(\"Metadata required when uploading buffer\");\r\n }\r\n\r\n // Common upload logic\r\n const cleanPath = path.length > 1 ? `${path.replace(/^\\/+|\\/+$/g, \"\")}/` : \"\";\r\n const fileName = `${crypto.randomUUID()}${fileInfo.metadata.extension}`;\r\n const key = `${CONSTRUCTED_AWS_FOLDER}${cleanPath}${fileName}`;\r\n\r\n const s3file = s3Bun.file(key);\r\n\r\n const acl = opts?.acl ?? \"public-read\";\r\n\r\n const isPrivate = acl !== \"public-read\" && acl !== \"public-read-write\";\r\n\r\n const writtenBytes = await s3file.write(fileInfo.buffer, {\r\n acl: acl,\r\n });\r\n\r\n const digestedObject: InsertStorageObjet = {\r\n uploadedBy: opts?.uploadedBy ?? null,\r\n key: key,\r\n url: null,\r\n name: fileInfo.metadata.name,\r\n thumbnail: null,\r\n resolvedType: fileInfo.metadata.resolvedType,\r\n provider: \"s3\",\r\n isPrivate: isPrivate,\r\n metadata: {\r\n size: writtenBytes,\r\n type: fileInfo.metadata.type,\r\n extension: fileInfo.metadata.extension,\r\n acl: acl,\r\n },\r\n isActive: true,\r\n };\r\n\r\n return digestedObject;\r\n}\r\n\r\nexport { uploadOne };\r\n",
9
10
  "const AWS_FOLDER = process.env.AWS_FOLDER;\r\nconst CONSTRUCTED_AWS_FOLDER = AWS_FOLDER ? `${AWS_FOLDER}/` : \"\";\r\n\r\nexport { CONSTRUCTED_AWS_FOLDER };\r\n",
10
11
  "import { s3 as s3Bun } from \"bun\";\r\n\r\n/**\r\n * Checks a file existance in S3\r\n */\r\nasync function checkFileExistance(\r\n key: string,\r\n opts: { debug?: boolean } = { debug: process.env.NODE_ENV !== \"production\" }\r\n): Promise<{ exists: boolean; error: null } | { exists: null; error: Error }> {\r\n try {\r\n const exists = await s3Bun.exists(key);\r\n return { exists, error: null };\r\n } catch (e) {\r\n if (opts?.debug) {\r\n console.error(\"There was an error checking for file existance\", e);\r\n }\r\n return { exists: null, error: e as Error };\r\n }\r\n}\r\n\r\nexport { checkFileExistance };\r\n"
11
12
  ],
12
- "mappings": "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",
13
- "debugId": "8AB7792AECD8AEAA64756E2164756E21",
13
+ "mappings": "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",
14
+ "debugId": "DE5A1B3485300E8464756E2164756E21",
14
15
  "names": []
15
16
  }
@@ -0,0 +1,35 @@
1
+ import type { SafeResult } from "../../types";
2
+ type UploadSuccess = {
3
+ key: string;
4
+ file: File;
5
+ bytesWritten: number;
6
+ };
7
+ /**
8
+ * Uploads a file to local filesystem
9
+ * @param path - The folder path (e.g. "public/images")
10
+ * @param file - The file to upload
11
+ * @param filename - Optional custom filename to use instead of original file.name
12
+ * @returns Promise<SafeResult<number>> - Success: {data: bytesWritten, error: null}
13
+ * Failure: {data: null, error: Error}
14
+ *
15
+ * Example:
16
+ * ```ts
17
+ * const {data, error} = await uploadToLocal("public/avatars", userPhoto);
18
+ * if (error) {
19
+ * console.error("Upload failed:", error);
20
+ * return;
21
+ * }
22
+ * console.log(`Uploaded ${data} bytes`);
23
+ * ```
24
+ */
25
+ declare function uploadOne(path: string, file: File, _filename?: string): Promise<SafeResult<UploadSuccess>>;
26
+ declare function uploadMany(path: string, files: File[], nameGetter?: (file: File) => string): Promise<SafeResult<UploadSuccess>[]>;
27
+ declare function getOne(key: string): Promise<Bun.BunFile | null>;
28
+ declare function deleteOne(key: string): Promise<boolean>;
29
+ export declare const local: {
30
+ uploadOne: typeof uploadOne;
31
+ uploadMany: typeof uploadMany;
32
+ getOne: typeof getOne;
33
+ deleteOne: typeof deleteOne;
34
+ };
35
+ export {};
package/dist/types.d.ts CHANGED
@@ -89,3 +89,10 @@ export type InferProcedureType<T> = T extends Procedure<any, any, any, infer P>
89
89
  export type ExtractRouterStructure<T extends Routes> = {
90
90
  [K in keyof T]: T[K] extends Routes ? ExtractRouterStructure<T[K]> : T[K] extends Procedure<any, any, any, any> ? T[K] : never;
91
91
  };
92
+ export type SafeResult<T> = {
93
+ data: null;
94
+ error: Error;
95
+ } | {
96
+ data: T;
97
+ error: null;
98
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mateosuarezdev/brpc",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "A Type-Safe, Flexible Web application framework for Bun",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -1,17 +0,0 @@
1
- import type { BunFile } from "bun";
2
- import type { ResolvedFileType } from "./types";
3
- type OptimizedImageBuffer = {
4
- buffer: Buffer;
5
- metadata: {
6
- name: string;
7
- type: string;
8
- extension: string;
9
- resolvedType: ResolvedFileType;
10
- };
11
- };
12
- declare function optimizeImage(file: File | BunFile, opts?: {
13
- width?: number;
14
- quality?: number;
15
- }): Promise<OptimizedImageBuffer>;
16
- export { optimizeImage };
17
- export type { OptimizedImageBuffer };