@warlock.js/core 5.11.0 → 5.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/CHANGELOG.md +92 -54
  2. package/esm/application/app.d.mts +3 -3
  3. package/esm/application/app.mjs +6 -6
  4. package/esm/application/app.mjs.map +1 -1
  5. package/esm/cli/cli-commands.utils.mjs +7 -3
  6. package/esm/cli/cli-commands.utils.mjs.map +1 -1
  7. package/esm/cli/commands/doctor/checks/health.check.mjs +1 -1
  8. package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
  9. package/esm/connectors/socket-connector.mjs +13 -16
  10. package/esm/connectors/socket-connector.mjs.map +1 -1
  11. package/esm/container/index.d.mts +20 -7
  12. package/esm/container/index.d.mts.map +1 -1
  13. package/esm/container/index.mjs +8 -5
  14. package/esm/container/index.mjs.map +1 -1
  15. package/esm/database/utils.d.mts +5 -1
  16. package/esm/database/utils.d.mts.map +1 -1
  17. package/esm/database/utils.mjs +7 -3
  18. package/esm/database/utils.mjs.map +1 -1
  19. package/esm/dev-server/dev-logger.mjs +12 -1
  20. package/esm/dev-server/dev-logger.mjs.map +1 -1
  21. package/esm/dev-server/development-server.mjs +1 -1
  22. package/esm/dev-server/development-server.mjs.map +1 -1
  23. package/esm/dev-server/file-event-handler.mjs +72 -13
  24. package/esm/dev-server/file-event-handler.mjs.map +1 -1
  25. package/esm/dev-server/file-manager.d.mts +8 -0
  26. package/esm/dev-server/file-manager.d.mts.map +1 -1
  27. package/esm/dev-server/file-manager.mjs +26 -4
  28. package/esm/dev-server/file-manager.mjs.map +1 -1
  29. package/esm/dev-server/files-orchestrator.mjs +3 -3
  30. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  31. package/esm/dev-server/files-watcher.mjs +16 -2
  32. package/esm/dev-server/files-watcher.mjs.map +1 -1
  33. package/esm/dev-server/flags.mjs +16 -1
  34. package/esm/dev-server/flags.mjs.map +1 -1
  35. package/esm/dev-server/layer-executor.mjs +19 -2
  36. package/esm/dev-server/layer-executor.mjs.map +1 -1
  37. package/esm/dev-server/translation-type-generator.mjs +28 -0
  38. package/esm/dev-server/translation-type-generator.mjs.map +1 -0
  39. package/esm/dev-server/tsconfig-manager.mjs +1 -0
  40. package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
  41. package/esm/dev-server/type-generator.mjs +41 -5
  42. package/esm/dev-server/type-generator.mjs.map +1 -1
  43. package/esm/encryption/index.mjs +1 -1
  44. package/esm/errors/container-key-missing-error.mjs +18 -6
  45. package/esm/errors/container-key-missing-error.mjs.map +1 -1
  46. package/esm/generations/features/auth-google.feature.mjs +18 -0
  47. package/esm/generations/features/auth-google.feature.mjs.map +1 -0
  48. package/esm/generations/features/auth-passkeys.feature.mjs +19 -0
  49. package/esm/generations/features/auth-passkeys.feature.mjs.map +1 -0
  50. package/esm/generations/features/index.mjs +6 -0
  51. package/esm/generations/features/index.mjs.map +1 -1
  52. package/esm/generations/features/queue.feature.mjs +67 -0
  53. package/esm/generations/features/queue.feature.mjs.map +1 -0
  54. package/esm/generations/stubs.mjs +10 -10
  55. package/esm/generations/stubs.mjs.map +1 -1
  56. package/esm/http/context/request-context.d.mts +14 -11
  57. package/esm/http/context/request-context.d.mts.map +1 -1
  58. package/esm/http/context/request-context.mjs +13 -9
  59. package/esm/http/context/request-context.mjs.map +1 -1
  60. package/esm/http/createHttpApplication.d.mts.map +1 -1
  61. package/esm/http/createHttpApplication.mjs +2 -0
  62. package/esm/http/createHttpApplication.mjs.map +1 -1
  63. package/esm/http/csp.d.mts +139 -0
  64. package/esm/http/csp.d.mts.map +1 -0
  65. package/esm/http/csp.mjs +134 -0
  66. package/esm/http/csp.mjs.map +1 -0
  67. package/esm/http/errors/errors.d.mts +17 -1
  68. package/esm/http/errors/errors.d.mts.map +1 -1
  69. package/esm/http/errors/errors.mjs +20 -1
  70. package/esm/http/errors/errors.mjs.map +1 -1
  71. package/esm/http/index.d.mts +8 -3
  72. package/esm/http/index.mjs +6 -1
  73. package/esm/http/middleware/concurrency-limit.middleware.d.mts +2 -2
  74. package/esm/http/middleware/concurrency-limit.middleware.mjs +1 -1
  75. package/esm/http/middleware/concurrency-limit.middleware.mjs.map +1 -1
  76. package/esm/http/middleware/inject-request-context.d.mts +1 -1
  77. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  78. package/esm/http/middleware/inject-request-context.mjs +10 -0
  79. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  80. package/esm/http/middleware/rate-limit.middleware.d.mts +1 -1
  81. package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
  82. package/esm/http/middleware/utils/idempotency-key.d.mts.map +1 -1
  83. package/esm/http/middleware/utils/idempotency-key.mjs +13 -13
  84. package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
  85. package/esm/http/request-controller.d.mts +1 -1
  86. package/esm/http/request.d.mts +32 -44
  87. package/esm/http/request.d.mts.map +1 -1
  88. package/esm/http/request.mjs +52 -44
  89. package/esm/http/request.mjs.map +1 -1
  90. package/esm/http/response.d.mts +18 -0
  91. package/esm/http/response.d.mts.map +1 -1
  92. package/esm/http/response.mjs +31 -0
  93. package/esm/http/response.mjs.map +1 -1
  94. package/esm/http/stream-react-response.d.mts +65 -0
  95. package/esm/http/stream-react-response.d.mts.map +1 -0
  96. package/esm/http/stream-react-response.mjs +46 -0
  97. package/esm/http/stream-react-response.mjs.map +1 -0
  98. package/esm/http/tracing/index.mjs +4 -0
  99. package/esm/http/tracing/trace-id.d.mts +14 -0
  100. package/esm/http/tracing/trace-id.d.mts.map +1 -0
  101. package/esm/http/tracing/trace-id.mjs +37 -0
  102. package/esm/http/tracing/trace-id.mjs.map +1 -0
  103. package/esm/http/tracing/tracing-dispatcher.d.mts +42 -0
  104. package/esm/http/tracing/tracing-dispatcher.d.mts.map +1 -0
  105. package/esm/http/tracing/tracing-dispatcher.mjs +94 -0
  106. package/esm/http/tracing/tracing-dispatcher.mjs.map +1 -0
  107. package/esm/http/tracing/tracing.type.d.mts +66 -0
  108. package/esm/http/tracing/tracing.type.d.mts.map +1 -0
  109. package/esm/http/types.d.mts +27 -21
  110. package/esm/http/types.d.mts.map +1 -1
  111. package/esm/index.d.mts +9 -4
  112. package/esm/index.mjs +7 -3
  113. package/esm/production/esbuild-preflight.mjs +47 -0
  114. package/esm/production/esbuild-preflight.mjs.map +1 -0
  115. package/esm/production/production-builder.mjs +19 -15
  116. package/esm/production/production-builder.mjs.map +1 -1
  117. package/esm/restful/restful.d.mts +1 -1
  118. package/esm/router/log-request-lifecycle.mjs +12 -2
  119. package/esm/router/log-request-lifecycle.mjs.map +1 -1
  120. package/esm/router/router.d.mts.map +1 -1
  121. package/esm/router/router.mjs +10 -0
  122. package/esm/router/router.mjs.map +1 -1
  123. package/esm/router/types.d.mts +1 -1
  124. package/esm/socket/utils.mjs +1 -2
  125. package/esm/socket/utils.mjs.map +1 -1
  126. package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
  127. package/esm/storage/drivers/cloud-driver.mjs +2 -5
  128. package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
  129. package/esm/warlock-config/types.d.mts +12 -0
  130. package/esm/warlock-config/types.d.mts.map +1 -1
  131. package/llms-full.txt +314 -46
  132. package/llms.txt +1 -0
  133. package/package.json +11 -12
  134. package/skills/add-connector/SKILL.md +1 -1
  135. package/skills/build-restful/SKILL.md +2 -2
  136. package/skills/configure-app/SKILL.md +31 -0
  137. package/skills/create-controller/SKILL.md +4 -4
  138. package/skills/request-tracing/SKILL.md +208 -0
  139. package/skills/send-response/SKILL.md +16 -0
  140. package/skills/store-file/SKILL.md +1 -1
  141. package/skills/upload-file/SKILL.md +2 -2
  142. package/skills/use-app-context/SKILL.md +2 -2
  143. package/skills/use-localization/SKILL.md +24 -21
  144. package/skills/use-middleware/SKILL.md +2 -2
  145. package/skills/use-repository/SKILL.md +1 -1
  146. package/skills/use-request-locals/SKILL.md +1 -1
  147. package/skills/wire-socket/SKILL.md +3 -3
  148. package/skills/write-middleware/SKILL.md +11 -5
  149. package/skills/write-use-case/SKILL.md +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"router.mjs","names":[],"sources":["../../../../../../../core/src/router/router.ts"],"sourcesContent":["import proxy, { type FastifyHttpProxyOptions } from \"@fastify/http-proxy\";\nimport fastifyStatic, { type FastifyStaticOptions } from \"@fastify/static\";\nimport { ltrim, merge, toCamelCase, trim } from \"@mongez/reinforcements\";\nimport { isEmpty } from \"@mongez/supportive-is\";\nimport { log } from \"@warlock.js/logger\";\nimport type { FastifyReply, FastifyRequest } from \"fastify\";\nimport { container } from \"../container\";\nimport { Request } from \"../http/request\";\nimport { Response } from \"../http/response\";\nimport { type FastifyInstance } from \"../http/server\";\nimport { describeRouteForLog } from \"./describe-route-for-log\";\nimport { logRequestLifecycle } from \"./log-request-lifecycle\";\nimport {\n forgetPositionalHandlerSuspects,\n inspectHandlerSignature,\n reportPositionalHandlerSuspects,\n} from \"./positional-handler-diagnostics\";\nimport { normalizeRoutePath } from \"./normalize-route-path\";\nimport { RouteBuilder } from \"./route-builder\";\nimport { RouteRegistry } from \"./route-registry\";\nimport { routeNameMethodSuffix } from \"./route-name-method-suffix\";\nimport type {\n GroupedRoutesOptions,\n HttpContext,\n RequestHandler,\n RequestHandlerType,\n RequestHandlerValidation,\n ResourceMethod,\n Route,\n RouteOptions,\n RouteResource,\n RouterGroupCallback,\n RouterStacks,\n} from \"./types\";\n\n/**\n * A Fastify lifecycle hook as it may be declared in a route's `serverOptions`.\n * Fastify accepts a single hook or an array in either position.\n */\ntype RouteLifecycleHook = (\n request: FastifyRequest,\n reply: FastifyReply,\n) => unknown | Promise<unknown>;\n\n/**\n * The pre-handler phases the dev server forwards from a route's `serverOptions`.\n *\n * These are the admission-control phases and they share the `(request, reply)`\n * signature. `preParsing` is deliberately absent — it must return the payload\n * stream, so forwarding it generically would corrupt the body rather than\n * guard it. `bodyLimit` cannot be forwarded at all; Fastify reads it at\n * registration time and no hook can bound a body already being parsed.\n */\nconst FORWARDED_ROUTE_PHASES = [\"onRequest\", \"preValidation\", \"preHandler\"] as const;\n\ntype ForwardedRoutePhase = (typeof FORWARDED_ROUTE_PHASES)[number];\n\n/**\n * The route the dev dispatcher matched, stashed on the request so the hooks and\n * the wildcard handler all act on one match rather than each matching again.\n */\ntype DevMatchedRoute = { route: Route; params: Record<string, string> };\n\ntype DevDispatchRequest = FastifyRequest & {\n matchedDevRoute?: DevMatchedRoute;\n};\n\nfunction toHookList(\n hook: RouteLifecycleHook | RouteLifecycleHook[] | undefined,\n): RouteLifecycleHook[] {\n if (!hook) {\n return [];\n }\n\n return Array.isArray(hook) ? hook : [hook];\n}\n\n/**\n * Run a matched route's hooks for one phase.\n *\n * Returning the reply is how an async Fastify hook signals that it has answered\n * the request; the chain must stop there, so the value is propagated rather\n * than discarded.\n */\nasync function runRouteHooks(\n route: Route,\n phase: ForwardedRoutePhase,\n request: FastifyRequest,\n reply: FastifyReply,\n) {\n const hooks = toHookList(\n route.serverOptions?.[phase] as RouteLifecycleHook | RouteLifecycleHook[] | undefined,\n );\n\n for (const hook of hooks) {\n const outcome = await hook(request, reply);\n\n if (outcome === reply) {\n return reply;\n }\n }\n\n return undefined;\n}\n\n/**\n * Describe one claimant of a route name, for the duplicate-name error.\n *\n * `warlock dev` registers SSR page routes on the same router as API routes, so\n * the two share a single route-name namespace and collide across kinds. The\n * error is only actionable if it says which claimant is which, so `isPage` —\n * the router's only route-kind discriminator — is what separates them here.\n *\n * `sourceFile` is stamped only on routes registered through `withSourceFile`.\n * Routes registered without that scope have no owner, so say the source is\n * unknown rather than printing an empty string.\n */\nfunction describeRouteClaimant(route: Route) {\n const type = route.isPage ? \"page route\" : \"API route\";\n const source = route.sourceFile ? `declared in ${route.sourceFile}` : \"source file unknown\";\n\n return `${type} ${route.method} ${route.path} (${source})`;\n}\n\nexport class Router {\n /**\n * Routes list\n */\n private routes: Route[] = [];\n\n /**\n * Bumped on every mutation of {@link routes}.\n *\n * The dev dispatcher caches a route registry and needs to know when it is\n * stale. Comparing `routes.length` would miss an HMR reload that replaces a\n * route without changing the count — the registry would then keep matching\n * paths that no longer exist. A version counter cannot miss that.\n */\n private routesVersion = 0;\n\n /**\n * In-progress atomic replacement, when one exists. Reads continue to use\n * {@link routes}; mutations and collision checks use this isolated draft.\n */\n private routeReplacementTransaction?: {\n routes: Route[];\n removedSourceFiles: Set<string>;\n addedRoutes: Route[];\n };\n\n /**\n * Router Instance\n */\n private static instance: Router;\n\n /**\n * Static paths\n */\n protected staticDirectories: FastifyStaticOptions[] = [];\n\n /**\n * Event listeners\n */\n protected eventListeners: Record<string, ((router: Router, server: FastifyInstance) => void)[]> =\n {};\n\n /**\n * Stacks\n * Stacks will be used for grouping routes to add prefix, name or middleware\n */\n protected stacks: RouterStacks = {\n prefix: [],\n name: [],\n middleware: [],\n };\n\n /**\n * Get router instance\n */\n public static getInstance() {\n if (!Router.instance) {\n Router.instance = new Router();\n }\n\n return Router.instance;\n }\n\n private constructor() {\n //\n }\n\n /**\n * Listen to router before scan\n */\n public beforeScanning(callback: (router: Router, server: FastifyInstance) => void) {\n this.eventListeners.beforeScan = [...(this.eventListeners.beforeScan || []), callback];\n\n return this;\n }\n\n /**\n * Listen to router after scanning\n */\n public afterScanning(callback: (router: Router, server: FastifyInstance) => void) {\n this.eventListeners.afterScanning = [...(this.eventListeners.afterScanning || []), callback];\n\n return this;\n }\n\n /**\n * Redirect path to another path\n */\n public redirect(from: string, to: string, redirectMode: \"temporary\" | \"permanent\" = \"temporary\") {\n return this.get(from, ({ response }) => {\n response.redirect(to, redirectMode === \"temporary\" ? 302 : 301);\n });\n }\n\n /**\n * Server static folder\n */\n public directory(options: FastifyStaticOptions) {\n this.staticDirectories.push(options);\n\n return this;\n }\n\n /**\n * Serve file\n */\n public file(path: string, location: string, cacheTime?: number) {\n return this.get(path, ({ response }) => {\n response.sendFile(location, cacheTime);\n });\n }\n\n /**\n * Serve cached file, it will cache the file to 1 year by default\n */\n public cachedFile(path: string, location: string, cacheTime?: number) {\n return this.get(path, ({ response }) => {\n response.sendCachedFile(location, cacheTime);\n });\n }\n\n /**\n * Serve list of files\n */\n public files(files: Record<string, string>, cacheTime?: number) {\n for (const [path, location] of Object.entries(files)) {\n this.get(path, ({ response }) => {\n response.sendFile(location, cacheTime);\n });\n }\n }\n\n /**\n * Serve list of cached files, it will cache the file to 1 year by default\n */\n public cachedFiles(files: Record<string, string>, cacheTime?: number) {\n for (const [path, location] of Object.entries(files)) {\n this.get(path, ({ response }) => {\n response.sendCachedFile(location, cacheTime);\n });\n }\n }\n\n /**\n * Add proxy route\n */\n public proxy(\n path: string,\n baseUrl: string,\n options?: Omit<FastifyHttpProxyOptions, \"prefix\" | \"upstream\">,\n ): this;\n public proxy(options: FastifyHttpProxyOptions): this;\n public proxy(...args: any[]) {\n this.beforeScanning((_router, server) => {\n if (args.length === 1) {\n server.register(proxy, args[0]);\n } else {\n server.register(proxy, {\n prefix: args[0],\n upstream: args[1],\n ...args[2],\n });\n }\n });\n\n return this;\n }\n\n /**\n * Add route to routes list\n */\n public add(\n method: Route[\"method\"],\n path: string | string[],\n handler: RequestHandlerType,\n options: RouteOptions = {},\n ) {\n if (Array.isArray(path)) {\n path.forEach((p) => this.add(method, p, handler, options));\n return this;\n }\n\n const prefix = this.stacks.prefix.reduce((path, prefix) => {\n return normalizeRoutePath(path, prefix);\n }, \"\");\n\n const name = this.stacks.name.reduceRight(\n (name, prefixName) => {\n return trim(prefixName + \".\" + name, \".\");\n },\n options.name || trim(path.replace(/\\//g, \".\"), \".\"),\n );\n\n // The one place a route path becomes canonical. `normalizeRoutePath` is\n // exported so the build-time page-route manifest can produce the very same\n // string without registering anything.\n path = normalizeRoutePath(prefix, path);\n\n const middlewarePrecedence = options.middlewarePrecedence || \"after\";\n\n if (middlewarePrecedence === \"before\") {\n options.middleware = [...(options.middleware || []), ...this.stacks.middleware];\n } else {\n options.middleware = [...this.stacks.middleware, ...(options.middleware || [])];\n }\n\n if (Array.isArray(handler)) {\n const [controller, action] = handler;\n\n if (typeof controller[action] !== \"function\") {\n throw new Error(\n `Invalid controller action \"${action}\" for controller \"${controller.constructor.name}\"`,\n );\n }\n\n handler = controller[action].bind(controller) as RequestHandler;\n\n if (!handler.validation) {\n handler.validation = {};\n if (controller[`${action}ValidationSchema`]) {\n handler.validation.schema = controller[`${action}ValidationSchema`]();\n }\n\n if (controller[`${action}Validate`]) {\n handler.validation.validate = controller[`${action}Validate`];\n }\n }\n }\n\n const routeData: Route = {\n method,\n path,\n handler,\n ...options,\n name,\n rateLimit: options.rateLimit,\n $prefix: prefix || \"/\",\n // it must be a new array to avoid modifying the original array\n $prefixStack: [...this.stacks.prefix],\n // Inject source file from stacks if set\n sourceFile: this.stacks.sourceFile || \"\",\n };\n\n const routes = this.routeReplacementTransaction?.routes ?? this.routes;\n\n if (routeData.name) {\n // check if the name exists\n const route = routes.find((route) => route.name === routeData.name);\n\n if (route) {\n // check again if the route name exists with the same method\n if (route.method === routeData.method) {\n throw new Error(\n `Route name \"${routeData.name}\" is already taken.\\n` +\n ` already registered: ${describeRouteClaimant(route)}\\n` +\n ` now being added: ${describeRouteClaimant(routeData)}\\n` +\n `Pages and API routes share one route-name namespace, so a name can only be ` +\n `claimed once. Rename one of them via the \"name\" route option.`,\n );\n } else {\n routeData.name += routeNameMethodSuffix(routeData.method);\n }\n }\n }\n\n // Registration-time only — nothing here runs per request. Suspects are\n // collected now and reported together at boot (see `scan`), so a route file\n // written against v4 produces one legible list instead of a mystery 500 per\n // request. Recorded after the collision check so a route that never lands\n // is never reported.\n if (this.routeReplacementTransaction) {\n this.routeReplacementTransaction.addedRoutes.push(routeData);\n } else {\n inspectHandlerSignature(routeData.handler, routeData);\n }\n\n routes.push(routeData);\n\n if (!this.routeReplacementTransaction) {\n this.routesVersion++;\n }\n\n return this;\n }\n\n /**\n * Add a request that accepts all methods\n */\n public any(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"all\" as Route[\"method\"], path, handler, options);\n }\n\n /**\n * Add get request method\n */\n public get(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"GET\", path, handler, options);\n }\n\n /**\n * Add post request method\n */\n public post(path: string | string[], handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"POST\", path, handler, options);\n }\n\n /**\n * Add put request method\n */\n public put(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"PUT\", path, handler, options);\n }\n\n /**\n * Add delete request method\n */\n public delete(path: string | string[], handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"DELETE\", path, handler, options);\n }\n\n /**\n * Add patch request method\n */\n public patch(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"PATCH\", path, handler, options);\n }\n\n /**\n * Add head request method\n */\n public head(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"HEAD\", path, handler, options);\n }\n\n /**\n * Add options request method\n */\n public options(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"OPTIONS\", path, handler, options);\n }\n\n /**\n * Get a chainable route builder for the same route path\n */\n public route(path: string, options: RouteOptions = {}) {\n return new RouteBuilder(this, path, options);\n }\n\n /**\n * Add full restful resource routes\n * This method will generate the following routes:\n * 1. GET /path: list all resources\n * 2. GET /path/:id: get a single resource\n * 3. POST /path: create a new resource\n * 4. PUT /path/:id: update a resource\n * 5. DELETE /path/:id: delete a resource\n * 6. PATCH /path/:id: update a resource partially\n */\n public restfulResource(\n path: string,\n resource: RouteResource,\n options: RouteOptions & {\n only?: ResourceMethod[];\n except?: ResourceMethod[];\n replace?: Partial<Record<ResourceMethod, RequestHandler>> & {\n bulkDelete?: RequestHandler;\n };\n } = {},\n ) {\n // Derive the base resource name from the path before the group below\n // clears it for use as the routes' relative path. `options.name`, when\n // given, replaces the path-derived name entirely (see the explicit\n // `name` passed to `group()`) rather than composing with it.\n const baseResourceName = options.name || toCamelCase(ltrim(path, \"/\"));\n\n return this.group({ prefix: path, name: baseResourceName }, () => {\n const path = \"\";\n\n // clone the resource so we don't mess up with it\n const routeResource = resource;\n\n const isAcceptableResource = (type: ResourceMethod) => {\n return Boolean(\n // check if the route is not excluded\n (!options.except || !options.except.includes(type)) &&\n // check if the only option is set and the route is included\n (!options.only || options.only.includes(type)),\n );\n };\n\n if (routeResource.list && isAcceptableResource(\"list\")) {\n this.get(path, options.replace?.list || routeResource.list.bind(routeResource), {\n ...options,\n name: \"list\",\n restful: true,\n });\n }\n\n if (routeResource.get && isAcceptableResource(\"get\")) {\n this.get(path + \"/:id\", options.replace?.get || routeResource.get.bind(routeResource), {\n ...options,\n name: \"single\",\n restful: true,\n });\n }\n\n if (routeResource.create && isAcceptableResource(\"create\")) {\n const handler = options.replace?.create || this.manageValidation(routeResource, \"create\");\n\n this.post(path, handler, {\n ...options,\n name: \"create\",\n restful: true,\n });\n }\n\n if (routeResource.update && isAcceptableResource(\"update\")) {\n const handler = options.replace?.update || this.manageValidation(routeResource, \"update\");\n\n this.put(path + \"/:id\", handler, {\n ...options,\n name: \"update\",\n restful: true,\n });\n }\n\n if (routeResource.patch && isAcceptableResource(\"patch\")) {\n const handler = options.replace?.patch || this.manageValidation(routeResource, \"patch\");\n\n this.patch(path + \"/:id\", handler, {\n ...options,\n name: \"patch\",\n restful: true,\n });\n }\n\n if (routeResource.delete && isAcceptableResource(\"delete\")) {\n this.delete(\n path + \"/:id\",\n options.replace?.delete || routeResource.delete.bind(routeResource),\n {\n ...options,\n name: \"delete\",\n restful: true,\n },\n );\n }\n\n if (routeResource.bulkDelete && isAcceptableResource(\"delete\")) {\n this.delete(\n path,\n options.replace?.bulkDelete || routeResource.bulkDelete.bind(routeResource),\n {\n ...options,\n name: \"bulkDelete\",\n restful: true,\n },\n );\n }\n\n return this;\n });\n }\n\n /**\n * Group routes with options\n */\n public group(options: GroupedRoutesOptions, callback: RouterGroupCallback) {\n const {\n prefix,\n // name must always be defined because\n // if there are multiple groups without name\n // they might generate the same route name\n // thus causing an error\n // in this case we need always to make sure that\n // the name is always defined.\n name = prefix ? trim(prefix.replace(/\\//g, \".\"), \".\") : undefined,\n middleware,\n } = options;\n\n if (prefix) {\n this.stacks.prefix.push(prefix);\n }\n\n if (name) {\n this.stacks.name.push(name);\n }\n\n if (middleware) {\n this.stacks.middleware.push(...middleware);\n }\n\n try {\n callback();\n } finally {\n // Always pop/splice this group's stacks, even if the callback throws,\n // so a throwing group never leaks state onto the process-global singleton.\n if (prefix) {\n this.stacks.prefix.pop();\n }\n\n if (name) {\n this.stacks.name.pop();\n }\n\n if (middleware) {\n this.stacks.middleware.splice(\n this.stacks.middleware.length - middleware.length,\n middleware.length,\n );\n }\n }\n\n return this;\n }\n\n /**\n * Add prefix to all routes in the given callback\n */\n public prefix(prefix: string, callback: () => void) {\n return this.group({ prefix }, callback);\n }\n\n /**\n * Add API version prefix to all routes in the given callback\n * Automatically formats the version as /v{version}\n * @example\n * router.version(\"1\", () => {\n * router.get(\"/users\", getUsersV1); // /v1/users\n * });\n *\n * router.version(\"2\", () => {\n * router.get(\"/users\", getUsersV2); // /v2/users\n * });\n */\n public version(version: string | number, callback: () => void) {\n return this.prefix(`/v${version}`, callback);\n }\n\n /**\n * Wrap route additions with a source file path\n * Used for tracking which routes come from which file (for HMR)\n * @param sourceFile Relative path to the source file (e.g., \"src/app/users/routes.ts\")\n * @param callback Function that adds routes (will have sourceFile injected)\n */\n public async withSourceFile<T = any>(\n sourceFile: string,\n callback: () => T | Promise<T>,\n ): Promise<T | undefined> {\n // Set source file in stacks\n this.stacks.sourceFile = sourceFile;\n\n try {\n // Execute callback (routes added here will have sourceFile injected)\n return await callback();\n } catch (error) {\n // Log loudly, then RETHROW. Swallowing here silently dropped a whole\n // route file (duplicate name, invalid action, import error) so the\n // surface 404'd with no boot error. The caller decides what a throw\n // means: at boot it aborts the boot, during HMR the dev server's\n // batch-reload handler catches it, prints it, and keeps serving.\n log.error({\n module: \"router\",\n action: \"registerRoutes\",\n message: `Failed to register routes from \"${sourceFile}\"`,\n context: { sourceFile, error },\n });\n\n throw error;\n } finally {\n // Always clear source file from stacks so a throwing file never leaks\n // its sourceFile onto the process-global singleton.\n delete this.stacks.sourceFile;\n }\n }\n\n /**\n * Remove all routes that belong to a specific source file\n * Used when reloading routes files via HMR\n * @param sourceFile Relative path to the source file\n */\n public removeRoutesBySourceFile(sourceFile: string): void {\n if (this.routeReplacementTransaction) {\n this.routeReplacementTransaction.routes = this.routeReplacementTransaction.routes.filter(\n (route) => route.sourceFile !== sourceFile,\n );\n this.routeReplacementTransaction.removedSourceFiles.add(sourceFile);\n return;\n }\n\n this.routes = this.routes.filter((route) => route.sourceFile !== sourceFile);\n\n // Keep the signature diagnostics in step, so a reload that fixes a handler\n // stops reporting the version it replaced.\n forgetPositionalHandlerSuspects(sourceFile);\n\n this.routesVersion++;\n }\n\n /**\n * Atomically replace every route owned by the given exact source-file keys.\n *\n * The installer mutates an isolated draft. Public reads keep seeing the old\n * live table across awaits, and a failed installer discards every draft\n * mutation. A successful installer swaps the whole draft in one synchronous\n * commit and invalidates the dev route registry exactly once.\n */\n public async replaceRoutesBySourceFiles<T>(\n sourceFiles: Iterable<string>,\n install: () => T | Promise<T>,\n ): Promise<T> {\n if (this.routeReplacementTransaction) {\n throw new Error(\"Cannot nest route replacement transactions\");\n }\n\n const removedSourceFiles = new Set(sourceFiles);\n\n this.routeReplacementTransaction = {\n routes: this.routes.filter((route) => !removedSourceFiles.has(route.sourceFile)),\n removedSourceFiles,\n addedRoutes: [],\n };\n\n try {\n const result = await install();\n const transaction = this.routeReplacementTransaction!;\n\n this.routes = transaction.routes;\n this.routeReplacementTransaction = undefined;\n\n // Diagnostic state follows the same commit boundary as the route table.\n // Added routes were deliberately not inspected while still speculative.\n for (const sourceFile of transaction.removedSourceFiles) {\n forgetPositionalHandlerSuspects(sourceFile);\n }\n\n const committedRoutes = new Set(transaction.routes);\n\n for (const route of transaction.addedRoutes) {\n if (committedRoutes.has(route)) {\n inspectHandlerSignature(route.handler, route);\n }\n }\n\n this.routesVersion++;\n\n return result;\n } catch (error) {\n this.routeReplacementTransaction = undefined;\n throw error;\n }\n }\n\n /**\n * Manage validation system for the given resource\n */\n private manageValidation(resource: RouteResource, method: \"create\" | \"update\" | \"patch\") {\n const handler = resource[method]?.bind(resource) as RequestHandler;\n\n const methodValidation = resource?.validation?.[method];\n\n if (method === \"patch\") {\n handler.validation = methodValidation;\n\n if (handler.validation?.validate) {\n handler.validation.validate = handler.validation.validate.bind(resource);\n }\n\n if (resource.validation?.patch) {\n handler.validation = merge(resource.validation.patch, handler.validation);\n }\n\n return handler;\n }\n\n if (!resource.validation || (!methodValidation && !resource.validation.all)) return handler;\n\n if (resource.validation.all) {\n const validationMethods = {\n all: resource?.validation?.all?.validate,\n [method]: methodValidation?.validate,\n };\n\n const validation: RequestHandlerValidation = {};\n\n if (resource.validation.all.schema || methodValidation?.schema) {\n if (!methodValidation?.schema && resource.validation.all.schema) {\n // Case 2: Only all.schema exists - clone it for this method\n validation.schema = resource.validation.all.schema;\n } else if (methodValidation?.schema && resource.validation.all.schema) {\n // Case 3: Both exist - merge them (all is base, method overrides)\n validation.schema = resource.validation.all.schema.merge(methodValidation.schema);\n } else if (methodValidation?.schema && !resource.validation.all.schema) {\n // Case 1: Only method schema exists - use it directly\n validation.schema = methodValidation.schema;\n }\n // Case 4: Neither exists - handled by outer if being false\n }\n\n if (validationMethods.all || validationMethods[method]) {\n validation.validate = async ({ request, response }: HttpContext) => {\n if (validationMethods.all) {\n const output = await validationMethods.all.call(resource, { request, response });\n\n if (output) return output;\n }\n\n if (validationMethods[method]) {\n return await validationMethods[method]?.call(resource, { request, response });\n }\n\n return;\n };\n }\n\n if (!isEmpty(validation)) {\n handler.validation = validation;\n }\n } else {\n handler.validation = resource.validation[method];\n\n if (handler.validation?.validate) {\n handler.validation.validate = handler.validation.validate.bind(resource);\n }\n }\n\n return handler;\n }\n\n /**\n * Get all routes list\n */\n public list() {\n return this.routes;\n }\n\n /**\n * Number of routes currently registered on the singleton.\n *\n * Used as a readiness signal: a successful boot that ends up with zero\n * routes almost always means a route module failed to register silently\n * (see `withSourceFile`), so callers can surface that instead of letting\n * the whole surface 404.\n */\n public routeCount(): number {\n return this.routes.length;\n }\n\n /**\n * Register routes to the server\n */\n public scan(server: FastifyInstance) {\n // Every route is registered by the time we scan, so this is the first\n // moment the whole list can be reported at once — and it is still before a\n // single request is served.\n reportPositionalHandlerSuspects();\n\n this.eventListeners.beforeScan?.forEach((callback) => callback(this, server));\n\n this.routes.forEach((route) => {\n const requestMethod = route.method.toLowerCase();\n const requestMethodFunction = server[requestMethod].bind(server);\n\n const options = {\n ...route.serverOptions,\n config: {\n ...route.serverOptions?.config,\n ...(route.rateLimit && { rateLimit: route.rateLimit }),\n },\n };\n\n requestMethodFunction(\n route.path,\n options,\n async (baseRequest: FastifyRequest, reply: FastifyReply) => {\n const { output, response } = await this.handleRoute(route)(baseRequest, reply);\n\n return output || response.baseResponse;\n },\n );\n });\n\n for (const directoryOptions of this.staticDirectories) {\n server.register(fastifyStatic, {\n ...directoryOptions,\n decorateReply: false,\n });\n }\n\n this.eventListeners.afterScanning?.forEach((callback) => callback(this, server));\n }\n\n /**\n * Scan routes for the development server\n * Uses wildcard routing with find-my-way for HMR support\n */\n public scanDevServer(server: FastifyInstance) {\n // Same boot-time report as production `scan`. Routes reloaded later by HMR\n // are still recorded, but only surface on the next scan or via\n // `warlock doctor`.\n reportPositionalHandlerSuspects();\n\n this.eventListeners.beforeScan?.forEach((callback) => callback(this, server));\n\n let routeRegistry: RouteRegistry | undefined;\n let registryVersion = -1;\n\n // Rebuilt when the route table changes rather than per request. Building it\n // inside the handler re-registered every route on every hit — and `all`\n // routes expand into seven registrations each. Keyed on `routesVersion`\n // rather than on the route count, so an HMR reload that swaps a route\n // without changing the count still invalidates.\n const resolveRouteRegistry = () => {\n if (routeRegistry && registryVersion === this.routesVersion) {\n return routeRegistry;\n }\n\n routeRegistry = new RouteRegistry();\n\n routeRegistry.register(this.routes);\n\n registryVersion = this.routesVersion;\n\n return routeRegistry;\n };\n\n // Matching happens here, in Fastify's own onRequest phase, so a route's\n // `serverOptions.onRequest` still runs BEFORE body parsing. Production\n // `scan()` gets that from per-route registration; the wildcard dispatcher\n // has no per-route slot, so without this the hook silently never runs.\n server.addHook(\n \"onRequest\",\n async (fastifyRequest: FastifyRequest, fastifyReply: FastifyReply) => {\n const match = resolveRouteRegistry().find(fastifyRequest.method, fastifyRequest.url);\n\n if (!match) {\n return undefined;\n }\n\n (fastifyRequest as DevDispatchRequest).matchedDevRoute = match;\n\n fastifyRequest.params = match.params;\n\n return runRouteHooks(match.route, \"onRequest\", fastifyRequest, fastifyReply);\n },\n );\n\n for (const phase of FORWARDED_ROUTE_PHASES.filter((name) => name !== \"onRequest\")) {\n server.addHook(phase, async (fastifyRequest: FastifyRequest, fastifyReply: FastifyReply) => {\n const match = (fastifyRequest as DevDispatchRequest).matchedDevRoute;\n\n if (!match) {\n return undefined;\n }\n\n return runRouteHooks(match.route, phase, fastifyRequest, fastifyReply);\n });\n }\n\n // Shared handler for wildcard routing\n const wildcardHandler = async (fastifyRequest: FastifyRequest, fastifyReply: FastifyReply) => {\n const match = (fastifyRequest as DevDispatchRequest).matchedDevRoute;\n\n // No match found - return 404\n if (!match) {\n return fastifyReply.code(404).send({\n error: \"Route not found\",\n path: fastifyRequest.url,\n method: fastifyRequest.method,\n });\n }\n\n try {\n // Call the matched route handler\n const { output, response } = await this.handleRoute(match.route)(\n fastifyRequest,\n fastifyReply,\n );\n\n return output || response.baseResponse;\n } catch (error) {\n log.error(\"router\", \"dev-dispatch\", error as Error, {\n method: fastifyRequest.method,\n url: fastifyRequest.url,\n });\n\n throw error;\n }\n };\n\n // Register wildcard route for all methods EXCEPT OPTIONS (to avoid conflict with CORS)\n // OPTIONS is handled by @fastify/cors plugin for preflight requests\n const methods = [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\"] as const;\n for (const method of methods) {\n server.route({\n method,\n url: \"*\",\n handler: wildcardHandler,\n });\n }\n\n // Register static directories\n for (const directoryOptions of this.staticDirectories) {\n server.register(fastifyStatic, {\n ...directoryOptions,\n decorateReply: false,\n });\n }\n\n this.eventListeners.afterScanning?.forEach((callback) => callback(this, server));\n }\n\n /**\n * Get the route path for the given route name\n */\n public getRoute(name: string, params: any = {}) {\n const route = this.routes.find((route) => route.name === name);\n\n if (!route) {\n throw new Error(`Route name \"${name}\" not found`);\n }\n\n let path = route.path;\n\n if (route.path.includes(\":\")) {\n Object.keys(params).forEach((key) => {\n path = path.replace(\":\" + key, params[key]);\n });\n }\n\n return path;\n }\n\n /**\n * Handle the given route\n */\n private handleRoute(route: Route) {\n return async (fastifyRequest: FastifyRequest, fastifyResponse: FastifyReply) => {\n const request = new Request();\n const response = new Response();\n response.setResponse(fastifyResponse);\n request.response = response;\n\n response.request = request;\n\n request.setRequest(fastifyRequest).setRoute(route);\n\n /*\n Both ends of the entry, not just the start — see\n `log-request-lifecycle.ts`. A start line on its own made a hung request\n and an instant one look identical, and the completion line is what makes\n a MISSING one legible as a hang.\n */\n const result = await logRequestLifecycle(\n {\n info: (entry) => log.info(entry),\n warn: (entry) => log.warn(entry),\n error: (entry) => log.error(entry),\n now: () => performance.now(),\n },\n {\n module: \"route\",\n action: describeRouteForLog(route.method, route.path),\n requestId: request.id,\n context: { request, response },\n // Read after the run: the status is decided during the request.\n statusCode: () => fastifyResponse.statusCode,\n },\n () => request.execute(),\n );\n\n return {\n output: result,\n response,\n request,\n };\n };\n }\n}\n\nexport const router = Router.getInstance();\n\ncontainer.set(\"router\", router);\n"],"mappings":"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"}
1
+ {"version":3,"file":"router.mjs","names":[],"sources":["../../../../../../../core/src/router/router.ts"],"sourcesContent":["import proxy, { type FastifyHttpProxyOptions } from \"@fastify/http-proxy\";\nimport fastifyStatic, { type FastifyStaticOptions } from \"@fastify/static\";\nimport { ltrim, merge, toCamelCase, trim } from \"@mongez/reinforcements\";\nimport { isEmpty } from \"@mongez/supportive-is\";\nimport { log } from \"@warlock.js/logger\";\nimport type { FastifyReply, FastifyRequest } from \"fastify\";\nimport { container } from \"../container\";\nimport { Request } from \"../http/request\";\nimport { Response } from \"../http/response\";\nimport { type FastifyInstance } from \"../http/server\";\nimport { buildTracingContext, dispatchPhase, isTracingEnabled } from \"../http/tracing\";\nimport { describeRouteForLog } from \"./describe-route-for-log\";\nimport { logRequestLifecycle } from \"./log-request-lifecycle\";\nimport {\n forgetPositionalHandlerSuspects,\n inspectHandlerSignature,\n reportPositionalHandlerSuspects,\n} from \"./positional-handler-diagnostics\";\nimport { normalizeRoutePath } from \"./normalize-route-path\";\nimport { RouteBuilder } from \"./route-builder\";\nimport { RouteRegistry } from \"./route-registry\";\nimport { routeNameMethodSuffix } from \"./route-name-method-suffix\";\nimport type {\n GroupedRoutesOptions,\n HttpContext,\n RequestHandler,\n RequestHandlerType,\n RequestHandlerValidation,\n ResourceMethod,\n Route,\n RouteOptions,\n RouteResource,\n RouterGroupCallback,\n RouterStacks,\n} from \"./types\";\n\n/**\n * A Fastify lifecycle hook as it may be declared in a route's `serverOptions`.\n * Fastify accepts a single hook or an array in either position.\n */\ntype RouteLifecycleHook = (\n request: FastifyRequest,\n reply: FastifyReply,\n) => unknown | Promise<unknown>;\n\n/**\n * The pre-handler phases the dev server forwards from a route's `serverOptions`.\n *\n * These are the admission-control phases and they share the `(request, reply)`\n * signature. `preParsing` is deliberately absent — it must return the payload\n * stream, so forwarding it generically would corrupt the body rather than\n * guard it. `bodyLimit` cannot be forwarded at all; Fastify reads it at\n * registration time and no hook can bound a body already being parsed.\n */\nconst FORWARDED_ROUTE_PHASES = [\"onRequest\", \"preValidation\", \"preHandler\"] as const;\n\ntype ForwardedRoutePhase = (typeof FORWARDED_ROUTE_PHASES)[number];\n\n/**\n * The route the dev dispatcher matched, stashed on the request so the hooks and\n * the wildcard handler all act on one match rather than each matching again.\n */\ntype DevMatchedRoute = { route: Route; params: Record<string, string> };\n\ntype DevDispatchRequest = FastifyRequest & {\n matchedDevRoute?: DevMatchedRoute;\n};\n\nfunction toHookList(\n hook: RouteLifecycleHook | RouteLifecycleHook[] | undefined,\n): RouteLifecycleHook[] {\n if (!hook) {\n return [];\n }\n\n return Array.isArray(hook) ? hook : [hook];\n}\n\n/**\n * Run a matched route's hooks for one phase.\n *\n * Returning the reply is how an async Fastify hook signals that it has answered\n * the request; the chain must stop there, so the value is propagated rather\n * than discarded.\n */\nasync function runRouteHooks(\n route: Route,\n phase: ForwardedRoutePhase,\n request: FastifyRequest,\n reply: FastifyReply,\n) {\n const hooks = toHookList(\n route.serverOptions?.[phase] as RouteLifecycleHook | RouteLifecycleHook[] | undefined,\n );\n\n for (const hook of hooks) {\n const outcome = await hook(request, reply);\n\n if (outcome === reply) {\n return reply;\n }\n }\n\n return undefined;\n}\n\n/**\n * Describe one claimant of a route name, for the duplicate-name error.\n *\n * `warlock dev` registers SSR page routes on the same router as API routes, so\n * the two share a single route-name namespace and collide across kinds. The\n * error is only actionable if it says which claimant is which, so `isPage` —\n * the router's only route-kind discriminator — is what separates them here.\n *\n * `sourceFile` is stamped only on routes registered through `withSourceFile`.\n * Routes registered without that scope have no owner, so say the source is\n * unknown rather than printing an empty string.\n */\nfunction describeRouteClaimant(route: Route) {\n const type = route.isPage ? \"page route\" : \"API route\";\n const source = route.sourceFile ? `declared in ${route.sourceFile}` : \"source file unknown\";\n\n return `${type} ${route.method} ${route.path} (${source})`;\n}\n\nexport class Router {\n /**\n * Routes list\n */\n private routes: Route[] = [];\n\n /**\n * Bumped on every mutation of {@link routes}.\n *\n * The dev dispatcher caches a route registry and needs to know when it is\n * stale. Comparing `routes.length` would miss an HMR reload that replaces a\n * route without changing the count — the registry would then keep matching\n * paths that no longer exist. A version counter cannot miss that.\n */\n private routesVersion = 0;\n\n /**\n * In-progress atomic replacement, when one exists. Reads continue to use\n * {@link routes}; mutations and collision checks use this isolated draft.\n */\n private routeReplacementTransaction?: {\n routes: Route[];\n removedSourceFiles: Set<string>;\n addedRoutes: Route[];\n };\n\n /**\n * Router Instance\n */\n private static instance: Router;\n\n /**\n * Static paths\n */\n protected staticDirectories: FastifyStaticOptions[] = [];\n\n /**\n * Event listeners\n */\n protected eventListeners: Record<string, ((router: Router, server: FastifyInstance) => void)[]> =\n {};\n\n /**\n * Stacks\n * Stacks will be used for grouping routes to add prefix, name or middleware\n */\n protected stacks: RouterStacks = {\n prefix: [],\n name: [],\n middleware: [],\n };\n\n /**\n * Get router instance\n */\n public static getInstance() {\n if (!Router.instance) {\n Router.instance = new Router();\n }\n\n return Router.instance;\n }\n\n private constructor() {\n //\n }\n\n /**\n * Listen to router before scan\n */\n public beforeScanning(callback: (router: Router, server: FastifyInstance) => void) {\n this.eventListeners.beforeScan = [...(this.eventListeners.beforeScan || []), callback];\n\n return this;\n }\n\n /**\n * Listen to router after scanning\n */\n public afterScanning(callback: (router: Router, server: FastifyInstance) => void) {\n this.eventListeners.afterScanning = [...(this.eventListeners.afterScanning || []), callback];\n\n return this;\n }\n\n /**\n * Redirect path to another path\n */\n public redirect(from: string, to: string, redirectMode: \"temporary\" | \"permanent\" = \"temporary\") {\n return this.get(from, ({ response }) => {\n response.redirect(to, redirectMode === \"temporary\" ? 302 : 301);\n });\n }\n\n /**\n * Server static folder\n */\n public directory(options: FastifyStaticOptions) {\n this.staticDirectories.push(options);\n\n return this;\n }\n\n /**\n * Serve file\n */\n public file(path: string, location: string, cacheTime?: number) {\n return this.get(path, ({ response }) => {\n response.sendFile(location, cacheTime);\n });\n }\n\n /**\n * Serve cached file, it will cache the file to 1 year by default\n */\n public cachedFile(path: string, location: string, cacheTime?: number) {\n return this.get(path, ({ response }) => {\n response.sendCachedFile(location, cacheTime);\n });\n }\n\n /**\n * Serve list of files\n */\n public files(files: Record<string, string>, cacheTime?: number) {\n for (const [path, location] of Object.entries(files)) {\n this.get(path, ({ response }) => {\n response.sendFile(location, cacheTime);\n });\n }\n }\n\n /**\n * Serve list of cached files, it will cache the file to 1 year by default\n */\n public cachedFiles(files: Record<string, string>, cacheTime?: number) {\n for (const [path, location] of Object.entries(files)) {\n this.get(path, ({ response }) => {\n response.sendCachedFile(location, cacheTime);\n });\n }\n }\n\n /**\n * Add proxy route\n */\n public proxy(\n path: string,\n baseUrl: string,\n options?: Omit<FastifyHttpProxyOptions, \"prefix\" | \"upstream\">,\n ): this;\n public proxy(options: FastifyHttpProxyOptions): this;\n public proxy(...args: any[]) {\n this.beforeScanning((_router, server) => {\n if (args.length === 1) {\n server.register(proxy, args[0]);\n } else {\n server.register(proxy, {\n prefix: args[0],\n upstream: args[1],\n ...args[2],\n });\n }\n });\n\n return this;\n }\n\n /**\n * Add route to routes list\n */\n public add(\n method: Route[\"method\"],\n path: string | string[],\n handler: RequestHandlerType,\n options: RouteOptions = {},\n ) {\n if (Array.isArray(path)) {\n path.forEach((p) => this.add(method, p, handler, options));\n return this;\n }\n\n const prefix = this.stacks.prefix.reduce((path, prefix) => {\n return normalizeRoutePath(path, prefix);\n }, \"\");\n\n const name = this.stacks.name.reduceRight(\n (name, prefixName) => {\n return trim(prefixName + \".\" + name, \".\");\n },\n options.name || trim(path.replace(/\\//g, \".\"), \".\"),\n );\n\n // The one place a route path becomes canonical. `normalizeRoutePath` is\n // exported so the build-time page-route manifest can produce the very same\n // string without registering anything.\n path = normalizeRoutePath(prefix, path);\n\n const middlewarePrecedence = options.middlewarePrecedence || \"after\";\n\n if (middlewarePrecedence === \"before\") {\n options.middleware = [...(options.middleware || []), ...this.stacks.middleware];\n } else {\n options.middleware = [...this.stacks.middleware, ...(options.middleware || [])];\n }\n\n if (Array.isArray(handler)) {\n const [controller, action] = handler;\n\n if (typeof controller[action] !== \"function\") {\n throw new Error(\n `Invalid controller action \"${action}\" for controller \"${controller.constructor.name}\"`,\n );\n }\n\n handler = controller[action].bind(controller) as RequestHandler;\n\n if (!handler.validation) {\n handler.validation = {};\n if (controller[`${action}ValidationSchema`]) {\n handler.validation.schema = controller[`${action}ValidationSchema`]();\n }\n\n if (controller[`${action}Validate`]) {\n handler.validation.validate = controller[`${action}Validate`];\n }\n }\n }\n\n const routeData: Route = {\n method,\n path,\n handler,\n ...options,\n name,\n rateLimit: options.rateLimit,\n $prefix: prefix || \"/\",\n // it must be a new array to avoid modifying the original array\n $prefixStack: [...this.stacks.prefix],\n // Inject source file from stacks if set\n sourceFile: this.stacks.sourceFile || \"\",\n };\n\n const routes = this.routeReplacementTransaction?.routes ?? this.routes;\n\n if (routeData.name) {\n // check if the name exists\n const route = routes.find((route) => route.name === routeData.name);\n\n if (route) {\n // check again if the route name exists with the same method\n if (route.method === routeData.method) {\n throw new Error(\n `Route name \"${routeData.name}\" is already taken.\\n` +\n ` already registered: ${describeRouteClaimant(route)}\\n` +\n ` now being added: ${describeRouteClaimant(routeData)}\\n` +\n `Pages and API routes share one route-name namespace, so a name can only be ` +\n `claimed once. Rename one of them via the \"name\" route option.`,\n );\n } else {\n routeData.name += routeNameMethodSuffix(routeData.method);\n }\n }\n }\n\n // Registration-time only — nothing here runs per request. Suspects are\n // collected now and reported together at boot (see `scan`), so a route file\n // written against v4 produces one legible list instead of a mystery 500 per\n // request. Recorded after the collision check so a route that never lands\n // is never reported.\n if (this.routeReplacementTransaction) {\n this.routeReplacementTransaction.addedRoutes.push(routeData);\n } else {\n inspectHandlerSignature(routeData.handler, routeData);\n }\n\n routes.push(routeData);\n\n if (!this.routeReplacementTransaction) {\n this.routesVersion++;\n }\n\n return this;\n }\n\n /**\n * Add a request that accepts all methods\n */\n public any(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"all\" as Route[\"method\"], path, handler, options);\n }\n\n /**\n * Add get request method\n */\n public get(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"GET\", path, handler, options);\n }\n\n /**\n * Add post request method\n */\n public post(path: string | string[], handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"POST\", path, handler, options);\n }\n\n /**\n * Add put request method\n */\n public put(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"PUT\", path, handler, options);\n }\n\n /**\n * Add delete request method\n */\n public delete(path: string | string[], handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"DELETE\", path, handler, options);\n }\n\n /**\n * Add patch request method\n */\n public patch(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"PATCH\", path, handler, options);\n }\n\n /**\n * Add head request method\n */\n public head(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"HEAD\", path, handler, options);\n }\n\n /**\n * Add options request method\n */\n public options(path: string, handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.add(\"OPTIONS\", path, handler, options);\n }\n\n /**\n * Get a chainable route builder for the same route path\n */\n public route(path: string, options: RouteOptions = {}) {\n return new RouteBuilder(this, path, options);\n }\n\n /**\n * Add full restful resource routes\n * This method will generate the following routes:\n * 1. GET /path: list all resources\n * 2. GET /path/:id: get a single resource\n * 3. POST /path: create a new resource\n * 4. PUT /path/:id: update a resource\n * 5. DELETE /path/:id: delete a resource\n * 6. PATCH /path/:id: update a resource partially\n */\n public restfulResource(\n path: string,\n resource: RouteResource,\n options: RouteOptions & {\n only?: ResourceMethod[];\n except?: ResourceMethod[];\n replace?: Partial<Record<ResourceMethod, RequestHandler>> & {\n bulkDelete?: RequestHandler;\n };\n } = {},\n ) {\n // Derive the base resource name from the path before the group below\n // clears it for use as the routes' relative path. `options.name`, when\n // given, replaces the path-derived name entirely (see the explicit\n // `name` passed to `group()`) rather than composing with it.\n const baseResourceName = options.name || toCamelCase(ltrim(path, \"/\"));\n\n return this.group({ prefix: path, name: baseResourceName }, () => {\n const path = \"\";\n\n // clone the resource so we don't mess up with it\n const routeResource = resource;\n\n const isAcceptableResource = (type: ResourceMethod) => {\n return Boolean(\n // check if the route is not excluded\n (!options.except || !options.except.includes(type)) &&\n // check if the only option is set and the route is included\n (!options.only || options.only.includes(type)),\n );\n };\n\n if (routeResource.list && isAcceptableResource(\"list\")) {\n this.get(path, options.replace?.list || routeResource.list.bind(routeResource), {\n ...options,\n name: \"list\",\n restful: true,\n });\n }\n\n if (routeResource.get && isAcceptableResource(\"get\")) {\n this.get(path + \"/:id\", options.replace?.get || routeResource.get.bind(routeResource), {\n ...options,\n name: \"single\",\n restful: true,\n });\n }\n\n if (routeResource.create && isAcceptableResource(\"create\")) {\n const handler = options.replace?.create || this.manageValidation(routeResource, \"create\");\n\n this.post(path, handler, {\n ...options,\n name: \"create\",\n restful: true,\n });\n }\n\n if (routeResource.update && isAcceptableResource(\"update\")) {\n const handler = options.replace?.update || this.manageValidation(routeResource, \"update\");\n\n this.put(path + \"/:id\", handler, {\n ...options,\n name: \"update\",\n restful: true,\n });\n }\n\n if (routeResource.patch && isAcceptableResource(\"patch\")) {\n const handler = options.replace?.patch || this.manageValidation(routeResource, \"patch\");\n\n this.patch(path + \"/:id\", handler, {\n ...options,\n name: \"patch\",\n restful: true,\n });\n }\n\n if (routeResource.delete && isAcceptableResource(\"delete\")) {\n this.delete(\n path + \"/:id\",\n options.replace?.delete || routeResource.delete.bind(routeResource),\n {\n ...options,\n name: \"delete\",\n restful: true,\n },\n );\n }\n\n if (routeResource.bulkDelete && isAcceptableResource(\"delete\")) {\n this.delete(\n path,\n options.replace?.bulkDelete || routeResource.bulkDelete.bind(routeResource),\n {\n ...options,\n name: \"bulkDelete\",\n restful: true,\n },\n );\n }\n\n return this;\n });\n }\n\n /**\n * Group routes with options\n */\n public group(options: GroupedRoutesOptions, callback: RouterGroupCallback) {\n const {\n prefix,\n // name must always be defined because\n // if there are multiple groups without name\n // they might generate the same route name\n // thus causing an error\n // in this case we need always to make sure that\n // the name is always defined.\n name = prefix ? trim(prefix.replace(/\\//g, \".\"), \".\") : undefined,\n middleware,\n } = options;\n\n if (prefix) {\n this.stacks.prefix.push(prefix);\n }\n\n if (name) {\n this.stacks.name.push(name);\n }\n\n if (middleware) {\n this.stacks.middleware.push(...middleware);\n }\n\n try {\n callback();\n } finally {\n // Always pop/splice this group's stacks, even if the callback throws,\n // so a throwing group never leaks state onto the process-global singleton.\n if (prefix) {\n this.stacks.prefix.pop();\n }\n\n if (name) {\n this.stacks.name.pop();\n }\n\n if (middleware) {\n this.stacks.middleware.splice(\n this.stacks.middleware.length - middleware.length,\n middleware.length,\n );\n }\n }\n\n return this;\n }\n\n /**\n * Add prefix to all routes in the given callback\n */\n public prefix(prefix: string, callback: () => void) {\n return this.group({ prefix }, callback);\n }\n\n /**\n * Add API version prefix to all routes in the given callback\n * Automatically formats the version as /v{version}\n * @example\n * router.version(\"1\", () => {\n * router.get(\"/users\", getUsersV1); // /v1/users\n * });\n *\n * router.version(\"2\", () => {\n * router.get(\"/users\", getUsersV2); // /v2/users\n * });\n */\n public version(version: string | number, callback: () => void) {\n return this.prefix(`/v${version}`, callback);\n }\n\n /**\n * Wrap route additions with a source file path\n * Used for tracking which routes come from which file (for HMR)\n * @param sourceFile Relative path to the source file (e.g., \"src/app/users/routes.ts\")\n * @param callback Function that adds routes (will have sourceFile injected)\n */\n public async withSourceFile<T = any>(\n sourceFile: string,\n callback: () => T | Promise<T>,\n ): Promise<T | undefined> {\n // Set source file in stacks\n this.stacks.sourceFile = sourceFile;\n\n try {\n // Execute callback (routes added here will have sourceFile injected)\n return await callback();\n } catch (error) {\n // Log loudly, then RETHROW. Swallowing here silently dropped a whole\n // route file (duplicate name, invalid action, import error) so the\n // surface 404'd with no boot error. The caller decides what a throw\n // means: at boot it aborts the boot, during HMR the dev server's\n // batch-reload handler catches it, prints it, and keeps serving.\n log.error({\n module: \"router\",\n action: \"registerRoutes\",\n message: `Failed to register routes from \"${sourceFile}\"`,\n context: { sourceFile, error },\n });\n\n throw error;\n } finally {\n // Always clear source file from stacks so a throwing file never leaks\n // its sourceFile onto the process-global singleton.\n delete this.stacks.sourceFile;\n }\n }\n\n /**\n * Remove all routes that belong to a specific source file\n * Used when reloading routes files via HMR\n * @param sourceFile Relative path to the source file\n */\n public removeRoutesBySourceFile(sourceFile: string): void {\n if (this.routeReplacementTransaction) {\n this.routeReplacementTransaction.routes = this.routeReplacementTransaction.routes.filter(\n (route) => route.sourceFile !== sourceFile,\n );\n this.routeReplacementTransaction.removedSourceFiles.add(sourceFile);\n return;\n }\n\n this.routes = this.routes.filter((route) => route.sourceFile !== sourceFile);\n\n // Keep the signature diagnostics in step, so a reload that fixes a handler\n // stops reporting the version it replaced.\n forgetPositionalHandlerSuspects(sourceFile);\n\n this.routesVersion++;\n }\n\n /**\n * Atomically replace every route owned by the given exact source-file keys.\n *\n * The installer mutates an isolated draft. Public reads keep seeing the old\n * live table across awaits, and a failed installer discards every draft\n * mutation. A successful installer swaps the whole draft in one synchronous\n * commit and invalidates the dev route registry exactly once.\n */\n public async replaceRoutesBySourceFiles<T>(\n sourceFiles: Iterable<string>,\n install: () => T | Promise<T>,\n ): Promise<T> {\n if (this.routeReplacementTransaction) {\n throw new Error(\"Cannot nest route replacement transactions\");\n }\n\n const removedSourceFiles = new Set(sourceFiles);\n\n this.routeReplacementTransaction = {\n routes: this.routes.filter((route) => !removedSourceFiles.has(route.sourceFile)),\n removedSourceFiles,\n addedRoutes: [],\n };\n\n try {\n const result = await install();\n const transaction = this.routeReplacementTransaction!;\n\n this.routes = transaction.routes;\n this.routeReplacementTransaction = undefined;\n\n // Diagnostic state follows the same commit boundary as the route table.\n // Added routes were deliberately not inspected while still speculative.\n for (const sourceFile of transaction.removedSourceFiles) {\n forgetPositionalHandlerSuspects(sourceFile);\n }\n\n const committedRoutes = new Set(transaction.routes);\n\n for (const route of transaction.addedRoutes) {\n if (committedRoutes.has(route)) {\n inspectHandlerSignature(route.handler, route);\n }\n }\n\n this.routesVersion++;\n\n return result;\n } catch (error) {\n this.routeReplacementTransaction = undefined;\n throw error;\n }\n }\n\n /**\n * Manage validation system for the given resource\n */\n private manageValidation(resource: RouteResource, method: \"create\" | \"update\" | \"patch\") {\n const handler = resource[method]?.bind(resource) as RequestHandler;\n\n const methodValidation = resource?.validation?.[method];\n\n if (method === \"patch\") {\n handler.validation = methodValidation;\n\n if (handler.validation?.validate) {\n handler.validation.validate = handler.validation.validate.bind(resource);\n }\n\n if (resource.validation?.patch) {\n handler.validation = merge(resource.validation.patch, handler.validation);\n }\n\n return handler;\n }\n\n if (!resource.validation || (!methodValidation && !resource.validation.all)) return handler;\n\n if (resource.validation.all) {\n const validationMethods = {\n all: resource?.validation?.all?.validate,\n [method]: methodValidation?.validate,\n };\n\n const validation: RequestHandlerValidation = {};\n\n if (resource.validation.all.schema || methodValidation?.schema) {\n if (!methodValidation?.schema && resource.validation.all.schema) {\n // Case 2: Only all.schema exists - clone it for this method\n validation.schema = resource.validation.all.schema;\n } else if (methodValidation?.schema && resource.validation.all.schema) {\n // Case 3: Both exist - merge them (all is base, method overrides)\n validation.schema = resource.validation.all.schema.merge(methodValidation.schema);\n } else if (methodValidation?.schema && !resource.validation.all.schema) {\n // Case 1: Only method schema exists - use it directly\n validation.schema = methodValidation.schema;\n }\n // Case 4: Neither exists - handled by outer if being false\n }\n\n if (validationMethods.all || validationMethods[method]) {\n validation.validate = async ({ request, response }: HttpContext) => {\n if (validationMethods.all) {\n const output = await validationMethods.all.call(resource, { request, response });\n\n if (output) return output;\n }\n\n if (validationMethods[method]) {\n return await validationMethods[method]?.call(resource, { request, response });\n }\n\n return;\n };\n }\n\n if (!isEmpty(validation)) {\n handler.validation = validation;\n }\n } else {\n handler.validation = resource.validation[method];\n\n if (handler.validation?.validate) {\n handler.validation.validate = handler.validation.validate.bind(resource);\n }\n }\n\n return handler;\n }\n\n /**\n * Get all routes list\n */\n public list() {\n return this.routes;\n }\n\n /**\n * Number of routes currently registered on the singleton.\n *\n * Used as a readiness signal: a successful boot that ends up with zero\n * routes almost always means a route module failed to register silently\n * (see `withSourceFile`), so callers can surface that instead of letting\n * the whole surface 404.\n */\n public routeCount(): number {\n return this.routes.length;\n }\n\n /**\n * Register routes to the server\n */\n public scan(server: FastifyInstance) {\n // Every route is registered by the time we scan, so this is the first\n // moment the whole list can be reported at once — and it is still before a\n // single request is served.\n reportPositionalHandlerSuspects();\n\n this.eventListeners.beforeScan?.forEach((callback) => callback(this, server));\n\n this.routes.forEach((route) => {\n const requestMethod = route.method.toLowerCase();\n const requestMethodFunction = server[requestMethod].bind(server);\n\n const options = {\n ...route.serverOptions,\n config: {\n ...route.serverOptions?.config,\n ...(route.rateLimit && { rateLimit: route.rateLimit }),\n },\n };\n\n requestMethodFunction(\n route.path,\n options,\n async (baseRequest: FastifyRequest, reply: FastifyReply) => {\n const { output, response } = await this.handleRoute(route)(baseRequest, reply);\n\n return output || response.baseResponse;\n },\n );\n });\n\n for (const directoryOptions of this.staticDirectories) {\n server.register(fastifyStatic, {\n ...directoryOptions,\n decorateReply: false,\n });\n }\n\n this.eventListeners.afterScanning?.forEach((callback) => callback(this, server));\n }\n\n /**\n * Scan routes for the development server\n * Uses wildcard routing with find-my-way for HMR support\n */\n public scanDevServer(server: FastifyInstance) {\n // Same boot-time report as production `scan`. Routes reloaded later by HMR\n // are still recorded, but only surface on the next scan or via\n // `warlock doctor`.\n reportPositionalHandlerSuspects();\n\n this.eventListeners.beforeScan?.forEach((callback) => callback(this, server));\n\n let routeRegistry: RouteRegistry | undefined;\n let registryVersion = -1;\n\n // Rebuilt when the route table changes rather than per request. Building it\n // inside the handler re-registered every route on every hit — and `all`\n // routes expand into seven registrations each. Keyed on `routesVersion`\n // rather than on the route count, so an HMR reload that swaps a route\n // without changing the count still invalidates.\n const resolveRouteRegistry = () => {\n if (routeRegistry && registryVersion === this.routesVersion) {\n return routeRegistry;\n }\n\n routeRegistry = new RouteRegistry();\n\n routeRegistry.register(this.routes);\n\n registryVersion = this.routesVersion;\n\n return routeRegistry;\n };\n\n // Matching happens here, in Fastify's own onRequest phase, so a route's\n // `serverOptions.onRequest` still runs BEFORE body parsing. Production\n // `scan()` gets that from per-route registration; the wildcard dispatcher\n // has no per-route slot, so without this the hook silently never runs.\n server.addHook(\n \"onRequest\",\n async (fastifyRequest: FastifyRequest, fastifyReply: FastifyReply) => {\n const match = resolveRouteRegistry().find(fastifyRequest.method, fastifyRequest.url);\n\n if (!match) {\n return undefined;\n }\n\n (fastifyRequest as DevDispatchRequest).matchedDevRoute = match;\n\n fastifyRequest.params = match.params;\n\n return runRouteHooks(match.route, \"onRequest\", fastifyRequest, fastifyReply);\n },\n );\n\n for (const phase of FORWARDED_ROUTE_PHASES.filter((name) => name !== \"onRequest\")) {\n server.addHook(phase, async (fastifyRequest: FastifyRequest, fastifyReply: FastifyReply) => {\n const match = (fastifyRequest as DevDispatchRequest).matchedDevRoute;\n\n if (!match) {\n return undefined;\n }\n\n return runRouteHooks(match.route, phase, fastifyRequest, fastifyReply);\n });\n }\n\n // Shared handler for wildcard routing\n const wildcardHandler = async (fastifyRequest: FastifyRequest, fastifyReply: FastifyReply) => {\n const match = (fastifyRequest as DevDispatchRequest).matchedDevRoute;\n\n // No match found - return 404\n if (!match) {\n return fastifyReply.code(404).send({\n error: \"Route not found\",\n path: fastifyRequest.url,\n method: fastifyRequest.method,\n });\n }\n\n try {\n // Call the matched route handler\n const { output, response } = await this.handleRoute(match.route)(\n fastifyRequest,\n fastifyReply,\n );\n\n return output || response.baseResponse;\n } catch (error) {\n log.error(\"router\", \"dev-dispatch\", error as Error, {\n method: fastifyRequest.method,\n url: fastifyRequest.url,\n });\n\n throw error;\n }\n };\n\n // Register wildcard route for all methods EXCEPT OPTIONS (to avoid conflict with CORS)\n // OPTIONS is handled by @fastify/cors plugin for preflight requests\n const methods = [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\"] as const;\n for (const method of methods) {\n server.route({\n method,\n url: \"*\",\n handler: wildcardHandler,\n });\n }\n\n // Register static directories\n for (const directoryOptions of this.staticDirectories) {\n server.register(fastifyStatic, {\n ...directoryOptions,\n decorateReply: false,\n });\n }\n\n this.eventListeners.afterScanning?.forEach((callback) => callback(this, server));\n }\n\n /**\n * Get the route path for the given route name\n */\n public getRoute(name: string, params: any = {}) {\n const route = this.routes.find((route) => route.name === name);\n\n if (!route) {\n throw new Error(`Route name \"${name}\" not found`);\n }\n\n let path = route.path;\n\n if (route.path.includes(\":\")) {\n Object.keys(params).forEach((key) => {\n path = path.replace(\":\" + key, params[key]);\n });\n }\n\n return path;\n }\n\n /**\n * Handle the given route\n */\n private handleRoute(route: Route) {\n return async (fastifyRequest: FastifyRequest, fastifyResponse: FastifyReply) => {\n const request = new Request();\n const response = new Response();\n response.setResponse(fastifyResponse);\n request.response = response;\n\n response.request = request;\n\n // \"route.match\" tracing phase: the pattern is\n // already known here (`route.path`), so this brackets the cost of\n // stamping the request/route onto the `Request` instance rather than\n // route-registry matching itself, which happens upstream of this\n // function for both the production `scan()` path and the dev wildcard\n // dispatcher (both funnel into `handleRoute`).\n const tracingEnabled = isTracingEnabled();\n const routeMatchStartedAt = tracingEnabled ? performance.now() : 0;\n\n request.setRequest(fastifyRequest).setRoute(route);\n\n if (tracingEnabled) {\n dispatchPhase(buildTracingContext(request), {\n name: \"route.match\",\n durationMs: performance.now() - routeMatchStartedAt,\n attrs: { route: route.path },\n });\n }\n\n /*\n Both ends of the entry, not just the start — see\n `log-request-lifecycle.ts`. A start line on its own made a hung request\n and an instant one look identical, and the completion line is what makes\n a MISSING one legible as a hang.\n */\n const result = await logRequestLifecycle(\n {\n info: (entry) => log.info(entry),\n warn: (entry) => log.warn(entry),\n error: (entry) => log.error(entry),\n now: () => performance.now(),\n },\n {\n module: \"route\",\n action: describeRouteForLog(route.method, route.path),\n requestId: request.id,\n context: { request, response },\n request,\n // Read after the run: the status is decided during the request.\n statusCode: () => fastifyResponse.statusCode,\n },\n () => request.execute(),\n );\n\n return {\n output: result,\n response,\n request,\n };\n };\n }\n}\n\nexport const router = Router.getInstance();\n\ncontainer.set(\"router\", router);\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { Request } from "../http/request.mjs";
2
1
  import { Response } from "../http/response.mjs";
2
+ import { Request } from "../http/request.mjs";
3
3
  import { ReturnedResponse } from "../http/types.mjs";
4
4
  import { ResponseSchema } from "../resource/types.mjs";
5
5
  import { ObjectValidator } from "@warlock.js/seal";
@@ -5,8 +5,7 @@ import { container } from "../container/index.mjs";
5
5
  * Get socket server instance
6
6
  */
7
7
  function getSocketServer() {
8
- if (container.has("socket")) return container.get("socket");
9
- return null;
8
+ return container.tryGet("socket") ?? null;
10
9
  }
11
10
 
12
11
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","names":[],"sources":["../../../../../../../core/src/socket/utils.ts"],"sourcesContent":["import { type Server } from \"socket.io\";\nimport { container } from \"../container\";\n\n/**\n * Get socket server instance\n */\nexport function getSocketServer(): Server | null {\n if (container.has(\"socket\")) {\n return container.get(\"socket\");\n }\n\n return null;\n}\n"],"mappings":";;;;;;AAMA,SAAgB,kBAAiC;CAC/C,IAAI,UAAU,IAAI,QAAQ,GACxB,OAAO,UAAU,IAAI,QAAQ;CAG/B,OAAO;AACT"}
1
+ {"version":3,"file":"utils.mjs","names":[],"sources":["../../../../../../../core/src/socket/utils.ts"],"sourcesContent":["import { type Server } from \"socket.io\";\nimport { container } from \"../container\";\n\n/**\n * Get socket server instance\n */\nexport function getSocketServer(): Server | null {\n return container.tryGet(\"socket\") ?? null;\n}\n"],"mappings":";;;;;;AAMA,SAAgB,kBAAiC;CAC/C,OAAO,UAAU,OAAO,QAAQ,KAAK;AACvC"}
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-driver.d.mts","names":[],"sources":["../../../../../../../../core/src/storage/drivers/cloud-driver.ts"],"mappings":";;;;;;AA6DA;;;;AAAuC;AAuFvC;;iBAvFsB,MAAA,IAAU,OAAO;;;;;;;;;;;;;;;;;uBAuFjB,WAAA,kBACH,OAAA,CAAQ,yBAAA,IAA6B,yBAAA,aAC3C,0BAAA;EAgBe,OAAA,EAAS,QAAA;EAiPhC;;;EAAA,UA7PO,MAAA,EAAS,YAAA,qCAAiD,QAAA;EAoU1B;;;EAAA,UA/ThC,WAAA;IACR,UAAA;IACA,cAAA;IACA,UAAA;IACA,iBAAA;EAAA;cAGwB,OAAA,EAAS,QAAA;EAghBhC;;;EAAA,kBA/esB,IAAA,EAAM,iBAAA;EAsjBsB;;;;EAAA,SAhjBrC,GAAA,CAAI,QAAA;EAkmBiD;;;;EAAA,UA5lB3D,WAAA;EA4sB4C;;;;;;;;;EA3rB/C,WAAA,CAAY,QAAA;EA9ER;;;;EAAA,UAsGD,aAAA,CAAc,IAAA;EAlG4C;;;;;;;;;;EAAA,UAmHpD,SAAA,IACd,SAAA,QAAiB,OAAA,CAAQ,CAAA,GACzB,aAAA,YACC,OAAA,CAAQ,CAAA;EAnEK;;;;;;;;;;;;;;;;;;EAAA,UAqHN,gBAAA,CAAiB,KAAA;EAoCzB;;;EADW,GAAA,CACX,IAAA,EAAM,MAAA,EACN,QAAA,UACA,OAAA,GAAU,UAAA,GACT,OAAA,CAAQ,oBAAA;EAAR;;;;EA0CU,SAAA,CACX,MAAA,EAAQ,QAAA,EACR,QAAA,UACA,OAAA,GAAU,UAAA,GACT,OAAA,CAAQ,oBAAA;EAFT;;;EAgDW,GAAA,CAAI,QAAA,WAAmB,OAAA,CAAQ,MAAA;EA9CjC;;;EAuEE,SAAA,CAAU,QAAA,WAAmB,OAAA,CAAQ,QAAA;EAzBN;;;EAkD/B,MAAA,CAAO,QAAA,WAAmB,OAAA;EAzBW;;;EA8CrC,UAAA,CAAW,SAAA,aAAsB,OAAA,CAAQ,gBAAA;EAAzC;;;;;;;;;EAqDA,eAAA,CAAgB,aAAA,WAAwB,OAAA;EAqFxC;;;EA3BA,MAAA,CAAO,QAAA,WAAmB,OAAA;EAkC1B;;;EAPA,YAAA,CAAa,QAAA,UAAkB,SAAA,YAAmB,OAAA;EAOa;;;EAA/D,eAAA,CAAgB,QAAA,UAAkB,OAAA,GAAU,gBAAA,GAAmB,OAAA;EAwB1E;;;EAFW,qBAAA,CACX,QAAA,UACA,OAAA,GAAU,sBAAA,GACT,OAAA;EA4BsC;;;EAA5B,QAAA,CAAS,QAAA,WAAmB,OAAA,CAAQ,eAAA;EA+BZ;;;EAAxB,IAAA,CAAK,QAAA,WAAmB,OAAA;EAYQ;;;EAAhC,IAAA,CAAK,IAAA,UAAc,EAAA,WAAa,OAAA,CAAQ,oBAAA;EAyCrB;;;EAAnB,IAAA,CAAK,IAAA,UAAc,EAAA,WAAa,OAAA,CAAQ,oBAAA;EASnC;;;EAAL,IAAA,CAAK,SAAA,UAAmB,OAAA,GAAU,WAAA,GAAc,OAAA,CAAQ,eAAA;EAAA;;;EAwD9D,SAAA;EAcsB;;;EAPtB,SAAA;EA6BoB;;;EAtBd,eAAA,CAAgB,QAAA,UAAkB,YAAA,WAAuB,OAAA;EA0CzD;;;EApBA,aAAA,CAAc,QAAA,UAAkB,UAAA,EAAY,cAAA,GAAiB,OAAA;EAoDhE;;;EAhCG,aAAA,CAAc,QAAA,WAAmB,OAAA,CAAQ,cAAA;EAuC9B;;AAAgB;EAAhB,UAPd,aAAA,CAAc,MAAA,EAAQ,MAAA;;;;YAOtB,aAAA,CAAc,QAAA;AAAA"}
1
+ {"version":3,"file":"cloud-driver.d.mts","names":[],"sources":["../../../../../../../../core/src/storage/drivers/cloud-driver.ts"],"mappings":";;;;;;AA0DA;;;;AAAuC;AAuFvC;;iBAvFsB,MAAA,IAAU,OAAO;;;;;;;;;;;;;;;;;uBAuFjB,WAAA,kBACH,OAAA,CAAQ,yBAAA,IAA6B,yBAAA,aAC3C,0BAAA;EAgBe,OAAA,EAAS,QAAA;EAiPhC;;;EAAA,UA7PO,MAAA,EAAS,YAAA,qCAAiD,QAAA;EAoU1B;;;EAAA,UA/ThC,WAAA;IACR,UAAA;IACA,cAAA;IACA,UAAA;IACA,iBAAA;EAAA;cAGwB,OAAA,EAAS,QAAA;EAghBhC;;;EAAA,kBA/esB,IAAA,EAAM,iBAAA;EAsjBsB;;;;EAAA,SAhjBrC,GAAA,CAAI,QAAA;EAkmBiD;;;;EAAA,UA5lB3D,WAAA;EA4sB4C;;;;;;;;;EA3rB/C,WAAA,CAAY,QAAA;EA9ER;;;;EAAA,UAsGD,aAAA,CAAc,IAAA;EAlG4C;;;;;;;;;;EAAA,UAmHpD,SAAA,IACd,SAAA,QAAiB,OAAA,CAAQ,CAAA,GACzB,aAAA,YACC,OAAA,CAAQ,CAAA;EAnEK;;;;;;;;;;;;;;;;;;EAAA,UAqHN,gBAAA,CAAiB,KAAA;EAoCzB;;;EADW,GAAA,CACX,IAAA,EAAM,MAAA,EACN,QAAA,UACA,OAAA,GAAU,UAAA,GACT,OAAA,CAAQ,oBAAA;EAAR;;;;EA0CU,SAAA,CACX,MAAA,EAAQ,QAAA,EACR,QAAA,UACA,OAAA,GAAU,UAAA,GACT,OAAA,CAAQ,oBAAA;EAFT;;;EAgDW,GAAA,CAAI,QAAA,WAAmB,OAAA,CAAQ,MAAA;EA9CjC;;;EAuEE,SAAA,CAAU,QAAA,WAAmB,OAAA,CAAQ,QAAA;EAzBN;;;EAkD/B,MAAA,CAAO,QAAA,WAAmB,OAAA;EAzBW;;;EA8CrC,UAAA,CAAW,SAAA,aAAsB,OAAA,CAAQ,gBAAA;EAAzC;;;;;;;;;EAqDA,eAAA,CAAgB,aAAA,WAAwB,OAAA;EAqFxC;;;EA3BA,MAAA,CAAO,QAAA,WAAmB,OAAA;EAkC1B;;;EAPA,YAAA,CAAa,QAAA,UAAkB,SAAA,YAAmB,OAAA;EAOa;;;EAA/D,eAAA,CAAgB,QAAA,UAAkB,OAAA,GAAU,gBAAA,GAAmB,OAAA;EAwB1E;;;EAFW,qBAAA,CACX,QAAA,UACA,OAAA,GAAU,sBAAA,GACT,OAAA;EA4BsC;;;EAA5B,QAAA,CAAS,QAAA,WAAmB,OAAA,CAAQ,eAAA;EA+BZ;;;EAAxB,IAAA,CAAK,QAAA,WAAmB,OAAA;EAYQ;;;EAAhC,IAAA,CAAK,IAAA,UAAc,EAAA,WAAa,OAAA,CAAQ,oBAAA;EAyCrB;;;EAAnB,IAAA,CAAK,IAAA,UAAc,EAAA,WAAa,OAAA,CAAQ,oBAAA;EASnC;;;EAAL,IAAA,CAAK,SAAA,UAAmB,OAAA,GAAU,WAAA,GAAc,OAAA,CAAQ,eAAA;EAAA;;;EAwD9D,SAAA;EAcsB;;;EAPtB,SAAA;EA6BoB;;;EAtBd,eAAA,CAAgB,QAAA,UAAkB,YAAA,WAAuB,OAAA;EA0CzD;;;EApBA,aAAA,CAAc,QAAA,UAAkB,UAAA,EAAY,cAAA,GAAiB,OAAA;EAoDhE;;;EAhCG,aAAA,CAAc,QAAA,WAAmB,OAAA,CAAQ,cAAA;EAuC9B;;AAAgB;EAAhB,UAPd,aAAA,CAAc,MAAA,EAAQ,MAAA;;;;YAOtB,aAAA,CAAc,QAAA;AAAA"}
@@ -18,12 +18,9 @@ const S3_INSTALL_INSTRUCTIONS = `
18
18
  Cloud storage requires the AWS S3 SDK packages.
19
19
  Install them with:
20
20
 
21
- npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
21
+ warlock add s3
22
22
 
23
- Or with your preferred package manager:
24
-
25
- pnpm add @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
26
- yarn add @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
23
+ (raw packages: @aws-sdk/client-s3, @aws-sdk/lib-storage, @aws-sdk/s3-request-presigner)
27
24
  `.trim();
28
25
  /**
29
26
  * Atomic initialization promise to handle concurrent calls to loadS3
@@ -1 +1 @@
1
- {"version":3,"file":"cloud-driver.mjs","names":[],"sources":["../../../../../../../../core/src/storage/drivers/cloud-driver.ts"],"sourcesContent":["import crypto from \"crypto\";\nimport { createRequire } from \"module\";\nimport type { Readable } from \"stream\";\nimport { storageDriverContext } from \"../context/storage-driver-context\";\nimport type {\n CloudStorageDriverContract,\n CloudStorageDriverOptions,\n CloudStorageFileData,\n DeleteManyResult,\n FileVisibility,\n ListOptions,\n PresignedOptions,\n PresignedUploadOptions,\n PutOptions,\n StorageDriverType,\n StorageFileInfo,\n} from \"../types\";\nimport { getMimeType } from \"../utils/mime\";\n\n// ============================================================\n// Lazy-loaded S3 SDK Types\n// ============================================================\n\n/**\n * Cached S3 SDK modules (loaded once, reused)\n */\nlet S3Client: typeof import(\"@aws-sdk/client-s3\");\nlet S3Storage: typeof import(\"@aws-sdk/lib-storage\");\nlet S3Presigner: typeof import(\"@aws-sdk/s3-request-presigner\");\n\nlet isModuleExists: boolean | null = null;\n\n/**\n * Installation instructions for S3 SDK packages\n */\nconst S3_INSTALL_INSTRUCTIONS = `\nCloud storage requires the AWS S3 SDK packages.\nInstall them with:\n\n npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner\n\nOr with your preferred package manager:\n\n pnpm add @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner\n yarn add @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner\n`.trim();\n\n/**\n * Atomic initialization promise to handle concurrent calls to loadS3\n */\nlet initializationPromise: Promise<void> | null = null;\n\n/**\n * Load S3 modules lazily\n *\n * @example\n * await loadS3();\n * if (isModuleExists) {\n * // Safe to use S3Client, S3Storage, S3Presigner\n * }\n */\nexport async function loadS3(): Promise<void> {\n // If already successfully loaded, no need to do anything\n if (isModuleExists === true) return;\n\n // If currently loading, return the existing promise so the caller waits\n if (initializationPromise) return initializationPromise;\n\n initializationPromise = (async () => {\n try {\n // Use Promise.all to load modules in parallel for efficiency\n const [client, storage, presigner] = await Promise.all([\n import(\"@aws-sdk/client-s3\"),\n import(\"@aws-sdk/lib-storage\"),\n import(\"@aws-sdk/s3-request-presigner\"),\n ]);\n\n S3Client = client;\n S3Storage = storage;\n S3Presigner = presigner;\n\n isModuleExists = true;\n } catch {\n // Mark as failed if any import fails (usually means packages aren't installed)\n isModuleExists = false;\n }\n })();\n\n return initializationPromise;\n}\n\nloadS3();\n\n/**\n * Synchronous fallback loader.\n *\n * `loadS3()` above is async and fire-and-forget at module load, so on the\n * very first *synchronous* `CloudDriver` construction the dynamic `import()`\n * microtask has not yet resolved — `isModuleExists` is still `null` even when\n * the SDK is genuinely installed. The constructor's install-check would then\n * throw the misleading \"SDK not installed\" error.\n *\n * This probes the modules synchronously via `require` (the AWS SDK ships\n * CJS-compatible builds) so a present SDK resolves the install-check\n * immediately, without waiting on the async path. Returns `true` when all\n * three modules loaded, `false` otherwise. Only invoked while\n * `isModuleExists` is still indeterminate.\n */\nfunction loadS3Sync(): boolean {\n if (isModuleExists !== null) {\n return isModuleExists;\n }\n\n try {\n // `createRequire` gives an ESM-safe synchronous `require` regardless of\n // whether this module runs as CJS or ESM at runtime.\n const req = createRequire(import.meta.url);\n S3Client = req(\"@aws-sdk/client-s3\");\n S3Storage = req(\"@aws-sdk/lib-storage\");\n S3Presigner = req(\"@aws-sdk/s3-request-presigner\");\n isModuleExists = true;\n } catch {\n isModuleExists = false;\n }\n\n return isModuleExists;\n}\n\n// ============================================================\n// CloudDriver Base Class\n// ============================================================\n\n/**\n * Base abstract class for all S3-compatible cloud storage drivers\n *\n * This class contains all shared logic for S3-compatible storage services\n * including AWS S3, Cloudflare R2, DigitalOcean Spaces, and others.\n *\n * **Important:** S3 SDK packages are lazy-loaded on first use.\n * Users must install them separately:\n * ```\n * npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner\n * ```\n *\n * Subclasses must implement:\n * - `name`: Driver identifier (e.g., \"s3\", \"r2\", \"spaces\")\n * - `url()`: Returns the public URL for a file (provider-specific format)\n */\nexport abstract class CloudDriver<\n TOptions extends Partial<CloudStorageDriverOptions> = CloudStorageDriverOptions,\n> implements CloudStorageDriverContract {\n /**\n * S3-compatible client (lazy-loaded)\n */\n protected client!: InstanceType<typeof import(\"@aws-sdk/client-s3\").S3Client>;\n\n /**\n * Retry configuration\n */\n protected retryConfig: {\n maxRetries: number;\n initialDelayMs: number;\n maxDelayMs: number;\n backoffMultiplier: number;\n };\n\n public constructor(public options: TOptions) {\n // The async loadS3() may not have resolved yet on the first synchronous\n // construction. When the flag is still indeterminate, probe synchronously\n // so a genuinely-installed SDK never triggers the misleading throw below.\n if (isModuleExists === null) {\n loadS3Sync();\n }\n\n if (!isModuleExists) {\n throw new Error(S3_INSTALL_INSTRUCTIONS);\n }\n\n this.client = new S3Client.S3Client({\n region: this.options.region!,\n credentials: {\n accessKeyId: this.options.accessKeyId!,\n secretAccessKey: this.options.secretAccessKey!,\n },\n ...(this.getEndpoint() && { endpoint: this.getEndpoint() }),\n });\n\n // Initialize retry configuration\n this.retryConfig = {\n maxRetries: this.options.retry?.maxRetries ?? 3,\n initialDelayMs: this.options.retry?.initialDelayMs ?? 1000,\n maxDelayMs: this.options.retry?.maxDelayMs ?? 10000,\n backoffMultiplier: this.options.retry?.backoffMultiplier ?? 2,\n };\n }\n\n /**\n * Driver name identifier\n */\n public abstract readonly name: StorageDriverType;\n\n /**\n * Get public URL for file\n * Must be implemented by subclasses with provider-specific format\n */\n public abstract url(location: string): string;\n\n /**\n * Get endpoint URL\n * Can be overridden by subclasses for provider-specific endpoints\n */\n protected getEndpoint(): string | undefined {\n return this.options.endpoint;\n }\n\n // ============================================================\n // Helper Methods\n // ============================================================\n\n /**\n * Apply prefix to location path\n *\n * Priority: context prefix > driver options prefix > no prefix\n * This allows multi-tenant scenarios where context overrides driver config.\n *\n * @param location - Original location path\n * @returns Location with prefix applied if one exists\n */\n public applyPrefix(location: string): string {\n // Check context prefix first (highest priority)\n const contextPrefix = storageDriverContext.getPrefix();\n const prefix = contextPrefix || this.options.prefix;\n\n if (!prefix) {\n return location;\n }\n\n const cleanPrefix = prefix.replace(/\\/+$/, \"\");\n const cleanLocation = location.replace(/^\\/+/, \"\");\n\n // Avoid double-prefixing\n if (cleanLocation.startsWith(cleanPrefix + \"/\") || cleanLocation === cleanPrefix) {\n return cleanLocation;\n }\n\n return `${cleanPrefix}/${cleanLocation}`;\n }\n\n /**\n * Normalize storage path (remove double slashes, sanitize)\n * @internal\n */\n protected normalizePath(path: string): string {\n return path\n .replace(/\\/+/g, \"/\") // Remove multiple slashes\n .replace(/^\\//, \"\") // Remove leading slash\n .trim();\n }\n\n /**\n * Execute an operation with retry logic\n *\n * Retries on transient errors with exponential backoff.\n *\n * @param operation - Async operation to execute\n * @param operationName - Name for logging\n * @returns Result of the operation\n * @internal\n */\n protected async withRetry<T>(\n operation: () => Promise<T>,\n operationName: string = \"operation\",\n ): Promise<T> {\n const { maxRetries, initialDelayMs, backoffMultiplier, maxDelayMs } = this.retryConfig;\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await operation();\n } catch (error) {\n lastError = error as Error;\n\n // Don't retry on the last attempt\n if (attempt === maxRetries - 1) {\n break;\n }\n\n // Check if error is retryable\n if (!this.isRetryableError(error)) {\n throw error; // Non-retryable error, fail immediately\n }\n\n // Calculate delay with exponential backoff\n const delayMs = Math.min(initialDelayMs * Math.pow(backoffMultiplier, attempt), maxDelayMs);\n\n // Wait before retrying\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n }\n\n throw new Error(`${operationName} failed after ${maxRetries} attempts: ${lastError?.message}`);\n }\n\n /**\n * Check if an error is retryable\n *\n * Retries on:\n * - Network errors\n * - 5xx server errors\n * - Rate limiting (429)\n * - Timeout errors\n *\n * Does NOT retry on:\n * - 4xx client errors (except 429)\n * - Authentication errors\n * - Not found errors\n *\n * @param error - Error to check\n * @returns true if error is retryable\n * @internal\n */\n protected isRetryableError(error: any): boolean {\n // Network errors\n if (error.code === \"ECONNRESET\" || error.code === \"ETIMEDOUT\" || error.code === \"ENOTFOUND\") {\n return true;\n }\n\n // S3 SDK errors\n if (error.name === \"NetworkingError\" || error.name === \"TimeoutError\") {\n return true;\n }\n\n // HTTP status codes\n const statusCode = error.$metadata?.httpStatusCode || error.statusCode;\n if (statusCode) {\n // 5xx server errors are retryable\n if (statusCode >= 500 && statusCode < 600) {\n return true;\n }\n\n // 429 Too Many Requests is retryable\n if (statusCode === 429) {\n return true;\n }\n }\n\n return false;\n }\n\n // ============================================================\n // Core File Operations\n // ============================================================\n\n /**\n * Put file to cloud storage\n */\n public async put(\n file: Buffer,\n location: string,\n options?: PutOptions,\n ): Promise<CloudStorageFileData> {\n return this.withRetry(async () => {\n const { PutObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const hash = this.calculateHash(file);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n const command = new PutObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n Body: file,\n ContentType: mimeType,\n CacheControl: options?.cacheControl,\n ContentDisposition: options?.contentDisposition,\n Metadata: options?.metadata,\n ACL: options?.visibility === \"public\" ? \"public-read\" : undefined,\n });\n\n const result = await this.client.send(command);\n\n return {\n path: location,\n url: this.url(location),\n size: file.length,\n hash,\n mimeType,\n driver: this.name,\n bucket: this.options.bucket!,\n region: this.options.region!,\n etag: result.ETag,\n versionId: result.VersionId,\n };\n }, \"put\");\n }\n\n /**\n * Put file from a readable stream (for large files)\n * Uses S3 multipart upload for efficient streaming\n */\n public async putStream(\n stream: Readable,\n location: string,\n options?: PutOptions,\n ): Promise<CloudStorageFileData> {\n return this.withRetry(async () => {\n const { Upload } = S3Storage;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n const upload = new Upload({\n client: this.client,\n params: {\n Bucket: this.options.bucket,\n Key: location,\n Body: stream,\n ContentType: mimeType,\n CacheControl: options?.cacheControl,\n ContentDisposition: options?.contentDisposition,\n Metadata: options?.metadata,\n ACL: options?.visibility === \"public\" ? \"public-read\" : undefined,\n },\n });\n\n const result = await upload.done();\n\n // Get file info for size and hash\n const info = await this.metadata(location);\n\n return {\n path: location,\n url: this.url(location),\n size: info.size,\n hash: info.etag?.replace(/\"/g, \"\") || \"\",\n mimeType,\n driver: this.name,\n bucket: this.options.bucket!,\n region: this.options.region!,\n etag: result.ETag,\n versionId: result.VersionId,\n };\n }, \"putStream\");\n }\n\n /**\n * Get file contents as Buffer\n */\n public async get(location: string): Promise<Buffer> {\n return this.withRetry(async () => {\n const { GetObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new GetObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n const result = await this.client.send(command);\n\n if (!result.Body) {\n throw new Error(`File not found: ${location}`);\n }\n\n return Buffer.from(await result.Body.transformToByteArray());\n }, \"get\");\n }\n\n /**\n * Get file as a readable stream (for large files)\n */\n public async getStream(location: string): Promise<Readable> {\n return this.withRetry(async () => {\n const { GetObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new GetObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n const result = await this.client.send(command);\n\n if (!result.Body) {\n throw new Error(`File not found: ${location}`);\n }\n\n return result.Body as Readable;\n }, \"getStream\");\n }\n\n /**\n * Delete a file\n */\n public async delete(location: string): Promise<boolean> {\n return this.withRetry(async () => {\n const { DeleteObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new DeleteObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n await this.client.send(command);\n\n return true;\n }, \"delete\");\n }\n\n /**\n * Delete multiple files at once (uses batch delete for efficiency)\n */\n public async deleteMany(locations: string[]): Promise<DeleteManyResult[]> {\n if (locations.length === 0) {\n return [];\n }\n\n return this.withRetry(async () => {\n const { DeleteObjectsCommand } = S3Client;\n\n // Apply storage prefix to all locations\n const prefixedLocations = locations.map((loc) => this.applyPrefix(loc));\n\n const command = new DeleteObjectsCommand({\n Bucket: this.options.bucket,\n Delete: {\n Objects: prefixedLocations.map((Key) => ({ Key })),\n Quiet: false,\n },\n });\n\n const result = await this.client.send(command);\n const results: DeleteManyResult[] = [];\n\n // Process successful deletes\n for (const deleted of result.Deleted || []) {\n if (deleted.Key) {\n results.push({ location: deleted.Key, deleted: true });\n }\n }\n\n // Process errors\n for (const error of result.Errors || []) {\n if (error.Key) {\n results.push({\n location: error.Key,\n deleted: false,\n error: error.Message || \"Unknown error\",\n });\n }\n }\n\n return results;\n }, \"deleteMany\");\n }\n\n /**\n * Delete directory (recursively deletes all objects with matching prefix)\n *\n * S3/R2 doesn't have true directories - only key prefixes.\n * This method lists all objects with the prefix and deletes them in batches.\n *\n * @param directoryPath - Directory prefix to delete\n * @returns true when all objects are deleted\n */\n public async deleteDirectory(directoryPath: string): Promise<boolean> {\n const { ListObjectsV2Command, DeleteObjectsCommand } = S3Client;\n\n // Apply storage prefix\n directoryPath = this.applyPrefix(directoryPath);\n\n // Ensure directory path ends with / for proper prefix matching\n const prefix = directoryPath.endsWith(\"/\") ? directoryPath : `${directoryPath}/`;\n\n // Drive the loop with the ListObjectsV2 IsTruncated / ContinuationToken\n // contract rather than guessing termination from page size. Each page\n // carries its own ContinuationToken so we never re-list page one.\n let continuationToken: string | undefined;\n\n do {\n const listResult = await this.withRetry(\n () =>\n this.client.send(\n new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: prefix,\n ContinuationToken: continuationToken,\n }),\n ),\n \"deleteDirectory:list\",\n );\n\n const keys = (listResult.Contents || [])\n .map((object) => object.Key)\n .filter((key): key is string => Boolean(key));\n\n if (keys.length > 0) {\n // Delete the raw keys directly — they already carry the full prefix\n // from the listing, so we bypass deleteMany's applyPrefix step.\n await this.withRetry(\n () =>\n this.client.send(\n new DeleteObjectsCommand({\n Bucket: this.options.bucket,\n Delete: {\n Objects: keys.map((Key) => ({ Key })),\n Quiet: true,\n },\n }),\n ),\n \"deleteDirectory:delete\",\n );\n }\n\n continuationToken = listResult.IsTruncated ? listResult.NextContinuationToken : undefined;\n } while (continuationToken);\n\n return true;\n }\n\n /**\n * Check if file exists\n */\n public async exists(location: string): Promise<boolean> {\n // No retry for exists - it should be fast and failure means non-existent\n try {\n const { HeadObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n await this.client.send(command);\n return true;\n } catch {\n return false;\n }\n }\n\n // ============================================================\n // URL Operations\n // ============================================================\n\n /**\n * Get a temporary presigned URL (alias for getPresignedUrl)\n */\n public async temporaryUrl(location: string, expiresIn = 3600): Promise<string> {\n return this.getPresignedUrl(location, { expiresIn });\n }\n\n /**\n * Get presigned URL for downloading\n */\n public async getPresignedUrl(location: string, options?: PresignedOptions): Promise<string> {\n return this.withRetry(async () => {\n const { GetObjectCommand } = S3Client;\n const { getSignedUrl } = S3Presigner;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new GetObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n return getSignedUrl(this.client, command, {\n expiresIn: options?.expiresIn || 3600,\n });\n }, \"getPresignedUrl\");\n }\n\n /**\n * Get presigned URL for uploading\n */\n public async getPresignedUploadUrl(\n location: string,\n options?: PresignedUploadOptions,\n ): Promise<string> {\n return this.withRetry(async () => {\n const { PutObjectCommand } = S3Client;\n const { getSignedUrl } = S3Presigner;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new PutObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n ContentType: options?.contentType,\n Metadata: options?.metadata,\n });\n\n return getSignedUrl(this.client, command, {\n expiresIn: options?.expiresIn || 3600,\n });\n }, \"getPresignedUploadUrl\");\n }\n\n // ============================================================\n // Metadata Operations\n // ============================================================\n\n /**\n * Get file info/metadata without downloading\n */\n public async metadata(location: string): Promise<StorageFileInfo> {\n return this.withRetry(async () => {\n const { HeadObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n const result = await this.client.send(command);\n const name = location.split(\"/\").pop() || \"\";\n\n return {\n path: location,\n name,\n size: result.ContentLength || 0,\n isDirectory: false,\n lastModified: result.LastModified,\n mimeType: result.ContentType || this.guessMimeType(location),\n etag: result.ETag,\n storageClass: result.StorageClass,\n };\n }, \"metadata\");\n }\n\n /**\n * Get file size in bytes (shortcut for metadata().size)\n */\n public async size(location: string): Promise<number> {\n const info = await this.metadata(location);\n return info.size;\n }\n\n // ============================================================\n // File Operations\n // ============================================================\n\n /**\n * Copy file to a new location\n */\n public async copy(from: string, to: string): Promise<CloudStorageFileData> {\n return this.withRetry(async () => {\n const { CopyObjectCommand, HeadObjectCommand } = S3Client;\n\n // Apply storage prefix to both paths\n from = this.applyPrefix(from);\n to = this.applyPrefix(to);\n\n const command = new CopyObjectCommand({\n Bucket: this.options.bucket,\n CopySource: `${this.options.bucket}/${from}`,\n Key: to,\n });\n\n const result = await this.client.send(command);\n\n // Get file metadata\n const headCommand = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: to,\n });\n const headResult = await this.client.send(headCommand);\n\n return {\n path: to,\n url: this.url(to),\n size: headResult.ContentLength || 0,\n hash: headResult.ETag?.replace(/\"/g, \"\") || \"\",\n mimeType: headResult.ContentType || this.guessMimeType(to),\n driver: this.name,\n bucket: this.options.bucket!,\n region: this.options.region!,\n etag: result.CopyObjectResult?.ETag,\n versionId: result.VersionId,\n };\n }, \"copy\");\n }\n\n /**\n * Move file to a new location\n */\n public async move(from: string, to: string): Promise<CloudStorageFileData> {\n const file = await this.copy(from, to);\n await this.delete(from);\n return file;\n }\n\n /**\n * List files in a directory\n */\n public async list(directory: string, options?: ListOptions): Promise<StorageFileInfo[]> {\n return this.withRetry(async () => {\n const { ListObjectsV2Command } = S3Client;\n\n // Apply storage prefix\n directory = this.applyPrefix(directory);\n\n const command = new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: directory,\n MaxKeys: options?.limit,\n ContinuationToken: options?.cursor,\n Delimiter: options?.recursive ? undefined : \"/\",\n });\n\n const result = await this.client.send(command);\n const files: StorageFileInfo[] = [];\n\n // Add files\n for (const object of result.Contents || []) {\n if (!object.Key) continue;\n\n files.push({\n path: object.Key,\n name: object.Key.split(\"/\").pop() || \"\",\n size: object.Size || 0,\n isDirectory: false,\n lastModified: object.LastModified,\n etag: object.ETag,\n storageClass: object.StorageClass,\n });\n }\n\n // Add directories\n for (const prefix of result.CommonPrefixes || []) {\n if (!prefix.Prefix) continue;\n\n files.push({\n path: prefix.Prefix,\n name: prefix.Prefix.split(\"/\").filter(Boolean).pop() || \"\",\n size: 0,\n isDirectory: true,\n });\n }\n\n return files;\n }, \"list\");\n }\n\n // ============================================================\n // Cloud-Specific Operations\n // ============================================================\n\n /**\n * Get bucket name\n */\n public getBucket(): string {\n return this.options.bucket!;\n }\n\n /**\n * Get region\n */\n public getRegion(): string {\n return this.options.region!;\n }\n\n /**\n * Set storage class (e.g., STANDARD, GLACIER, etc.)\n */\n public async setStorageClass(location: string, storageClass: string): Promise<void> {\n return this.withRetry(async () => {\n const { CopyObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new CopyObjectCommand({\n Bucket: this.options.bucket,\n CopySource: `${this.options.bucket}/${location}`,\n Key: location,\n StorageClass: storageClass as any,\n MetadataDirective: \"COPY\",\n });\n\n await this.client.send(command);\n }, \"setStorageClass\");\n }\n\n /**\n * Set file visibility (public or private)\n */\n public async setVisibility(location: string, visibility: FileVisibility): Promise<void> {\n return this.withRetry(async () => {\n const { PutObjectAclCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new PutObjectAclCommand({\n Bucket: this.options.bucket,\n Key: location,\n ACL: visibility === \"public\" ? \"public-read\" : \"private\",\n });\n\n await this.client.send(command);\n }, \"setVisibility\");\n }\n\n /**\n * Get file visibility\n */\n public async getVisibility(location: string): Promise<FileVisibility> {\n return this.withRetry(async () => {\n const { GetObjectAclCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new GetObjectAclCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n const result = await this.client.send(command);\n\n // Check if any grant allows public read\n const hasPublicRead = result.Grants?.some(\n (grant) =>\n grant.Grantee?.URI === \"http://acs.amazonaws.com/groups/global/AllUsers\" &&\n grant.Permission === \"READ\",\n );\n\n return hasPublicRead ? \"public\" : \"private\";\n }, \"getVisibility\");\n }\n\n // ============================================================\n // Utilities\n // ============================================================\n\n /**\n * Calculate SHA-256 hash\n */\n protected calculateHash(buffer: Buffer): string {\n return crypto.createHash(\"sha256\").update(new Uint8Array(buffer)).digest(\"hex\");\n }\n\n /**\n * Guess MIME type from file extension\n */\n protected guessMimeType(location: string): string {\n return getMimeType(location);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cloud-driver.mjs","names":[],"sources":["../../../../../../../../core/src/storage/drivers/cloud-driver.ts"],"sourcesContent":["import crypto from \"crypto\";\nimport { createRequire } from \"module\";\nimport type { Readable } from \"stream\";\nimport { storageDriverContext } from \"../context/storage-driver-context\";\nimport type {\n CloudStorageDriverContract,\n CloudStorageDriverOptions,\n CloudStorageFileData,\n DeleteManyResult,\n FileVisibility,\n ListOptions,\n PresignedOptions,\n PresignedUploadOptions,\n PutOptions,\n StorageDriverType,\n StorageFileInfo,\n} from \"../types\";\nimport { getMimeType } from \"../utils/mime\";\n\n// ============================================================\n// Lazy-loaded S3 SDK Types\n// ============================================================\n\n/**\n * Cached S3 SDK modules (loaded once, reused)\n */\nlet S3Client: typeof import(\"@aws-sdk/client-s3\");\nlet S3Storage: typeof import(\"@aws-sdk/lib-storage\");\nlet S3Presigner: typeof import(\"@aws-sdk/s3-request-presigner\");\n\nlet isModuleExists: boolean | null = null;\n\n/**\n * Installation instructions for S3 SDK packages\n */\nconst S3_INSTALL_INSTRUCTIONS = `\nCloud storage requires the AWS S3 SDK packages.\nInstall them with:\n\n warlock add s3\n\n(raw packages: @aws-sdk/client-s3, @aws-sdk/lib-storage, @aws-sdk/s3-request-presigner)\n`.trim();\n\n/**\n * Atomic initialization promise to handle concurrent calls to loadS3\n */\nlet initializationPromise: Promise<void> | null = null;\n\n/**\n * Load S3 modules lazily\n *\n * @example\n * await loadS3();\n * if (isModuleExists) {\n * // Safe to use S3Client, S3Storage, S3Presigner\n * }\n */\nexport async function loadS3(): Promise<void> {\n // If already successfully loaded, no need to do anything\n if (isModuleExists === true) return;\n\n // If currently loading, return the existing promise so the caller waits\n if (initializationPromise) return initializationPromise;\n\n initializationPromise = (async () => {\n try {\n // Use Promise.all to load modules in parallel for efficiency\n const [client, storage, presigner] = await Promise.all([\n import(\"@aws-sdk/client-s3\"),\n import(\"@aws-sdk/lib-storage\"),\n import(\"@aws-sdk/s3-request-presigner\"),\n ]);\n\n S3Client = client;\n S3Storage = storage;\n S3Presigner = presigner;\n\n isModuleExists = true;\n } catch {\n // Mark as failed if any import fails (usually means packages aren't installed)\n isModuleExists = false;\n }\n })();\n\n return initializationPromise;\n}\n\nloadS3();\n\n/**\n * Synchronous fallback loader.\n *\n * `loadS3()` above is async and fire-and-forget at module load, so on the\n * very first *synchronous* `CloudDriver` construction the dynamic `import()`\n * microtask has not yet resolved — `isModuleExists` is still `null` even when\n * the SDK is genuinely installed. The constructor's install-check would then\n * throw the misleading \"SDK not installed\" error.\n *\n * This probes the modules synchronously via `require` (the AWS SDK ships\n * CJS-compatible builds) so a present SDK resolves the install-check\n * immediately, without waiting on the async path. Returns `true` when all\n * three modules loaded, `false` otherwise. Only invoked while\n * `isModuleExists` is still indeterminate.\n */\nfunction loadS3Sync(): boolean {\n if (isModuleExists !== null) {\n return isModuleExists;\n }\n\n try {\n // `createRequire` gives an ESM-safe synchronous `require` regardless of\n // whether this module runs as CJS or ESM at runtime.\n const req = createRequire(import.meta.url);\n S3Client = req(\"@aws-sdk/client-s3\");\n S3Storage = req(\"@aws-sdk/lib-storage\");\n S3Presigner = req(\"@aws-sdk/s3-request-presigner\");\n isModuleExists = true;\n } catch {\n isModuleExists = false;\n }\n\n return isModuleExists;\n}\n\n// ============================================================\n// CloudDriver Base Class\n// ============================================================\n\n/**\n * Base abstract class for all S3-compatible cloud storage drivers\n *\n * This class contains all shared logic for S3-compatible storage services\n * including AWS S3, Cloudflare R2, DigitalOcean Spaces, and others.\n *\n * **Important:** S3 SDK packages are lazy-loaded on first use.\n * Users must install them separately:\n * ```\n * npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner\n * ```\n *\n * Subclasses must implement:\n * - `name`: Driver identifier (e.g., \"s3\", \"r2\", \"spaces\")\n * - `url()`: Returns the public URL for a file (provider-specific format)\n */\nexport abstract class CloudDriver<\n TOptions extends Partial<CloudStorageDriverOptions> = CloudStorageDriverOptions,\n> implements CloudStorageDriverContract {\n /**\n * S3-compatible client (lazy-loaded)\n */\n protected client!: InstanceType<typeof import(\"@aws-sdk/client-s3\").S3Client>;\n\n /**\n * Retry configuration\n */\n protected retryConfig: {\n maxRetries: number;\n initialDelayMs: number;\n maxDelayMs: number;\n backoffMultiplier: number;\n };\n\n public constructor(public options: TOptions) {\n // The async loadS3() may not have resolved yet on the first synchronous\n // construction. When the flag is still indeterminate, probe synchronously\n // so a genuinely-installed SDK never triggers the misleading throw below.\n if (isModuleExists === null) {\n loadS3Sync();\n }\n\n if (!isModuleExists) {\n throw new Error(S3_INSTALL_INSTRUCTIONS);\n }\n\n this.client = new S3Client.S3Client({\n region: this.options.region!,\n credentials: {\n accessKeyId: this.options.accessKeyId!,\n secretAccessKey: this.options.secretAccessKey!,\n },\n ...(this.getEndpoint() && { endpoint: this.getEndpoint() }),\n });\n\n // Initialize retry configuration\n this.retryConfig = {\n maxRetries: this.options.retry?.maxRetries ?? 3,\n initialDelayMs: this.options.retry?.initialDelayMs ?? 1000,\n maxDelayMs: this.options.retry?.maxDelayMs ?? 10000,\n backoffMultiplier: this.options.retry?.backoffMultiplier ?? 2,\n };\n }\n\n /**\n * Driver name identifier\n */\n public abstract readonly name: StorageDriverType;\n\n /**\n * Get public URL for file\n * Must be implemented by subclasses with provider-specific format\n */\n public abstract url(location: string): string;\n\n /**\n * Get endpoint URL\n * Can be overridden by subclasses for provider-specific endpoints\n */\n protected getEndpoint(): string | undefined {\n return this.options.endpoint;\n }\n\n // ============================================================\n // Helper Methods\n // ============================================================\n\n /**\n * Apply prefix to location path\n *\n * Priority: context prefix > driver options prefix > no prefix\n * This allows multi-tenant scenarios where context overrides driver config.\n *\n * @param location - Original location path\n * @returns Location with prefix applied if one exists\n */\n public applyPrefix(location: string): string {\n // Check context prefix first (highest priority)\n const contextPrefix = storageDriverContext.getPrefix();\n const prefix = contextPrefix || this.options.prefix;\n\n if (!prefix) {\n return location;\n }\n\n const cleanPrefix = prefix.replace(/\\/+$/, \"\");\n const cleanLocation = location.replace(/^\\/+/, \"\");\n\n // Avoid double-prefixing\n if (cleanLocation.startsWith(cleanPrefix + \"/\") || cleanLocation === cleanPrefix) {\n return cleanLocation;\n }\n\n return `${cleanPrefix}/${cleanLocation}`;\n }\n\n /**\n * Normalize storage path (remove double slashes, sanitize)\n * @internal\n */\n protected normalizePath(path: string): string {\n return path\n .replace(/\\/+/g, \"/\") // Remove multiple slashes\n .replace(/^\\//, \"\") // Remove leading slash\n .trim();\n }\n\n /**\n * Execute an operation with retry logic\n *\n * Retries on transient errors with exponential backoff.\n *\n * @param operation - Async operation to execute\n * @param operationName - Name for logging\n * @returns Result of the operation\n * @internal\n */\n protected async withRetry<T>(\n operation: () => Promise<T>,\n operationName: string = \"operation\",\n ): Promise<T> {\n const { maxRetries, initialDelayMs, backoffMultiplier, maxDelayMs } = this.retryConfig;\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await operation();\n } catch (error) {\n lastError = error as Error;\n\n // Don't retry on the last attempt\n if (attempt === maxRetries - 1) {\n break;\n }\n\n // Check if error is retryable\n if (!this.isRetryableError(error)) {\n throw error; // Non-retryable error, fail immediately\n }\n\n // Calculate delay with exponential backoff\n const delayMs = Math.min(initialDelayMs * Math.pow(backoffMultiplier, attempt), maxDelayMs);\n\n // Wait before retrying\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n }\n\n throw new Error(`${operationName} failed after ${maxRetries} attempts: ${lastError?.message}`);\n }\n\n /**\n * Check if an error is retryable\n *\n * Retries on:\n * - Network errors\n * - 5xx server errors\n * - Rate limiting (429)\n * - Timeout errors\n *\n * Does NOT retry on:\n * - 4xx client errors (except 429)\n * - Authentication errors\n * - Not found errors\n *\n * @param error - Error to check\n * @returns true if error is retryable\n * @internal\n */\n protected isRetryableError(error: any): boolean {\n // Network errors\n if (error.code === \"ECONNRESET\" || error.code === \"ETIMEDOUT\" || error.code === \"ENOTFOUND\") {\n return true;\n }\n\n // S3 SDK errors\n if (error.name === \"NetworkingError\" || error.name === \"TimeoutError\") {\n return true;\n }\n\n // HTTP status codes\n const statusCode = error.$metadata?.httpStatusCode || error.statusCode;\n if (statusCode) {\n // 5xx server errors are retryable\n if (statusCode >= 500 && statusCode < 600) {\n return true;\n }\n\n // 429 Too Many Requests is retryable\n if (statusCode === 429) {\n return true;\n }\n }\n\n return false;\n }\n\n // ============================================================\n // Core File Operations\n // ============================================================\n\n /**\n * Put file to cloud storage\n */\n public async put(\n file: Buffer,\n location: string,\n options?: PutOptions,\n ): Promise<CloudStorageFileData> {\n return this.withRetry(async () => {\n const { PutObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const hash = this.calculateHash(file);\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n const command = new PutObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n Body: file,\n ContentType: mimeType,\n CacheControl: options?.cacheControl,\n ContentDisposition: options?.contentDisposition,\n Metadata: options?.metadata,\n ACL: options?.visibility === \"public\" ? \"public-read\" : undefined,\n });\n\n const result = await this.client.send(command);\n\n return {\n path: location,\n url: this.url(location),\n size: file.length,\n hash,\n mimeType,\n driver: this.name,\n bucket: this.options.bucket!,\n region: this.options.region!,\n etag: result.ETag,\n versionId: result.VersionId,\n };\n }, \"put\");\n }\n\n /**\n * Put file from a readable stream (for large files)\n * Uses S3 multipart upload for efficient streaming\n */\n public async putStream(\n stream: Readable,\n location: string,\n options?: PutOptions,\n ): Promise<CloudStorageFileData> {\n return this.withRetry(async () => {\n const { Upload } = S3Storage;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const mimeType = options?.mimeType || this.guessMimeType(location);\n\n const upload = new Upload({\n client: this.client,\n params: {\n Bucket: this.options.bucket,\n Key: location,\n Body: stream,\n ContentType: mimeType,\n CacheControl: options?.cacheControl,\n ContentDisposition: options?.contentDisposition,\n Metadata: options?.metadata,\n ACL: options?.visibility === \"public\" ? \"public-read\" : undefined,\n },\n });\n\n const result = await upload.done();\n\n // Get file info for size and hash\n const info = await this.metadata(location);\n\n return {\n path: location,\n url: this.url(location),\n size: info.size,\n hash: info.etag?.replace(/\"/g, \"\") || \"\",\n mimeType,\n driver: this.name,\n bucket: this.options.bucket!,\n region: this.options.region!,\n etag: result.ETag,\n versionId: result.VersionId,\n };\n }, \"putStream\");\n }\n\n /**\n * Get file contents as Buffer\n */\n public async get(location: string): Promise<Buffer> {\n return this.withRetry(async () => {\n const { GetObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new GetObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n const result = await this.client.send(command);\n\n if (!result.Body) {\n throw new Error(`File not found: ${location}`);\n }\n\n return Buffer.from(await result.Body.transformToByteArray());\n }, \"get\");\n }\n\n /**\n * Get file as a readable stream (for large files)\n */\n public async getStream(location: string): Promise<Readable> {\n return this.withRetry(async () => {\n const { GetObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new GetObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n const result = await this.client.send(command);\n\n if (!result.Body) {\n throw new Error(`File not found: ${location}`);\n }\n\n return result.Body as Readable;\n }, \"getStream\");\n }\n\n /**\n * Delete a file\n */\n public async delete(location: string): Promise<boolean> {\n return this.withRetry(async () => {\n const { DeleteObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new DeleteObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n await this.client.send(command);\n\n return true;\n }, \"delete\");\n }\n\n /**\n * Delete multiple files at once (uses batch delete for efficiency)\n */\n public async deleteMany(locations: string[]): Promise<DeleteManyResult[]> {\n if (locations.length === 0) {\n return [];\n }\n\n return this.withRetry(async () => {\n const { DeleteObjectsCommand } = S3Client;\n\n // Apply storage prefix to all locations\n const prefixedLocations = locations.map((loc) => this.applyPrefix(loc));\n\n const command = new DeleteObjectsCommand({\n Bucket: this.options.bucket,\n Delete: {\n Objects: prefixedLocations.map((Key) => ({ Key })),\n Quiet: false,\n },\n });\n\n const result = await this.client.send(command);\n const results: DeleteManyResult[] = [];\n\n // Process successful deletes\n for (const deleted of result.Deleted || []) {\n if (deleted.Key) {\n results.push({ location: deleted.Key, deleted: true });\n }\n }\n\n // Process errors\n for (const error of result.Errors || []) {\n if (error.Key) {\n results.push({\n location: error.Key,\n deleted: false,\n error: error.Message || \"Unknown error\",\n });\n }\n }\n\n return results;\n }, \"deleteMany\");\n }\n\n /**\n * Delete directory (recursively deletes all objects with matching prefix)\n *\n * S3/R2 doesn't have true directories - only key prefixes.\n * This method lists all objects with the prefix and deletes them in batches.\n *\n * @param directoryPath - Directory prefix to delete\n * @returns true when all objects are deleted\n */\n public async deleteDirectory(directoryPath: string): Promise<boolean> {\n const { ListObjectsV2Command, DeleteObjectsCommand } = S3Client;\n\n // Apply storage prefix\n directoryPath = this.applyPrefix(directoryPath);\n\n // Ensure directory path ends with / for proper prefix matching\n const prefix = directoryPath.endsWith(\"/\") ? directoryPath : `${directoryPath}/`;\n\n // Drive the loop with the ListObjectsV2 IsTruncated / ContinuationToken\n // contract rather than guessing termination from page size. Each page\n // carries its own ContinuationToken so we never re-list page one.\n let continuationToken: string | undefined;\n\n do {\n const listResult = await this.withRetry(\n () =>\n this.client.send(\n new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: prefix,\n ContinuationToken: continuationToken,\n }),\n ),\n \"deleteDirectory:list\",\n );\n\n const keys = (listResult.Contents || [])\n .map((object) => object.Key)\n .filter((key): key is string => Boolean(key));\n\n if (keys.length > 0) {\n // Delete the raw keys directly — they already carry the full prefix\n // from the listing, so we bypass deleteMany's applyPrefix step.\n await this.withRetry(\n () =>\n this.client.send(\n new DeleteObjectsCommand({\n Bucket: this.options.bucket,\n Delete: {\n Objects: keys.map((Key) => ({ Key })),\n Quiet: true,\n },\n }),\n ),\n \"deleteDirectory:delete\",\n );\n }\n\n continuationToken = listResult.IsTruncated ? listResult.NextContinuationToken : undefined;\n } while (continuationToken);\n\n return true;\n }\n\n /**\n * Check if file exists\n */\n public async exists(location: string): Promise<boolean> {\n // No retry for exists - it should be fast and failure means non-existent\n try {\n const { HeadObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n await this.client.send(command);\n return true;\n } catch {\n return false;\n }\n }\n\n // ============================================================\n // URL Operations\n // ============================================================\n\n /**\n * Get a temporary presigned URL (alias for getPresignedUrl)\n */\n public async temporaryUrl(location: string, expiresIn = 3600): Promise<string> {\n return this.getPresignedUrl(location, { expiresIn });\n }\n\n /**\n * Get presigned URL for downloading\n */\n public async getPresignedUrl(location: string, options?: PresignedOptions): Promise<string> {\n return this.withRetry(async () => {\n const { GetObjectCommand } = S3Client;\n const { getSignedUrl } = S3Presigner;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new GetObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n return getSignedUrl(this.client, command, {\n expiresIn: options?.expiresIn || 3600,\n });\n }, \"getPresignedUrl\");\n }\n\n /**\n * Get presigned URL for uploading\n */\n public async getPresignedUploadUrl(\n location: string,\n options?: PresignedUploadOptions,\n ): Promise<string> {\n return this.withRetry(async () => {\n const { PutObjectCommand } = S3Client;\n const { getSignedUrl } = S3Presigner;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new PutObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n ContentType: options?.contentType,\n Metadata: options?.metadata,\n });\n\n return getSignedUrl(this.client, command, {\n expiresIn: options?.expiresIn || 3600,\n });\n }, \"getPresignedUploadUrl\");\n }\n\n // ============================================================\n // Metadata Operations\n // ============================================================\n\n /**\n * Get file info/metadata without downloading\n */\n public async metadata(location: string): Promise<StorageFileInfo> {\n return this.withRetry(async () => {\n const { HeadObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n const result = await this.client.send(command);\n const name = location.split(\"/\").pop() || \"\";\n\n return {\n path: location,\n name,\n size: result.ContentLength || 0,\n isDirectory: false,\n lastModified: result.LastModified,\n mimeType: result.ContentType || this.guessMimeType(location),\n etag: result.ETag,\n storageClass: result.StorageClass,\n };\n }, \"metadata\");\n }\n\n /**\n * Get file size in bytes (shortcut for metadata().size)\n */\n public async size(location: string): Promise<number> {\n const info = await this.metadata(location);\n return info.size;\n }\n\n // ============================================================\n // File Operations\n // ============================================================\n\n /**\n * Copy file to a new location\n */\n public async copy(from: string, to: string): Promise<CloudStorageFileData> {\n return this.withRetry(async () => {\n const { CopyObjectCommand, HeadObjectCommand } = S3Client;\n\n // Apply storage prefix to both paths\n from = this.applyPrefix(from);\n to = this.applyPrefix(to);\n\n const command = new CopyObjectCommand({\n Bucket: this.options.bucket,\n CopySource: `${this.options.bucket}/${from}`,\n Key: to,\n });\n\n const result = await this.client.send(command);\n\n // Get file metadata\n const headCommand = new HeadObjectCommand({\n Bucket: this.options.bucket,\n Key: to,\n });\n const headResult = await this.client.send(headCommand);\n\n return {\n path: to,\n url: this.url(to),\n size: headResult.ContentLength || 0,\n hash: headResult.ETag?.replace(/\"/g, \"\") || \"\",\n mimeType: headResult.ContentType || this.guessMimeType(to),\n driver: this.name,\n bucket: this.options.bucket!,\n region: this.options.region!,\n etag: result.CopyObjectResult?.ETag,\n versionId: result.VersionId,\n };\n }, \"copy\");\n }\n\n /**\n * Move file to a new location\n */\n public async move(from: string, to: string): Promise<CloudStorageFileData> {\n const file = await this.copy(from, to);\n await this.delete(from);\n return file;\n }\n\n /**\n * List files in a directory\n */\n public async list(directory: string, options?: ListOptions): Promise<StorageFileInfo[]> {\n return this.withRetry(async () => {\n const { ListObjectsV2Command } = S3Client;\n\n // Apply storage prefix\n directory = this.applyPrefix(directory);\n\n const command = new ListObjectsV2Command({\n Bucket: this.options.bucket,\n Prefix: directory,\n MaxKeys: options?.limit,\n ContinuationToken: options?.cursor,\n Delimiter: options?.recursive ? undefined : \"/\",\n });\n\n const result = await this.client.send(command);\n const files: StorageFileInfo[] = [];\n\n // Add files\n for (const object of result.Contents || []) {\n if (!object.Key) continue;\n\n files.push({\n path: object.Key,\n name: object.Key.split(\"/\").pop() || \"\",\n size: object.Size || 0,\n isDirectory: false,\n lastModified: object.LastModified,\n etag: object.ETag,\n storageClass: object.StorageClass,\n });\n }\n\n // Add directories\n for (const prefix of result.CommonPrefixes || []) {\n if (!prefix.Prefix) continue;\n\n files.push({\n path: prefix.Prefix,\n name: prefix.Prefix.split(\"/\").filter(Boolean).pop() || \"\",\n size: 0,\n isDirectory: true,\n });\n }\n\n return files;\n }, \"list\");\n }\n\n // ============================================================\n // Cloud-Specific Operations\n // ============================================================\n\n /**\n * Get bucket name\n */\n public getBucket(): string {\n return this.options.bucket!;\n }\n\n /**\n * Get region\n */\n public getRegion(): string {\n return this.options.region!;\n }\n\n /**\n * Set storage class (e.g., STANDARD, GLACIER, etc.)\n */\n public async setStorageClass(location: string, storageClass: string): Promise<void> {\n return this.withRetry(async () => {\n const { CopyObjectCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new CopyObjectCommand({\n Bucket: this.options.bucket,\n CopySource: `${this.options.bucket}/${location}`,\n Key: location,\n StorageClass: storageClass as any,\n MetadataDirective: \"COPY\",\n });\n\n await this.client.send(command);\n }, \"setStorageClass\");\n }\n\n /**\n * Set file visibility (public or private)\n */\n public async setVisibility(location: string, visibility: FileVisibility): Promise<void> {\n return this.withRetry(async () => {\n const { PutObjectAclCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new PutObjectAclCommand({\n Bucket: this.options.bucket,\n Key: location,\n ACL: visibility === \"public\" ? \"public-read\" : \"private\",\n });\n\n await this.client.send(command);\n }, \"setVisibility\");\n }\n\n /**\n * Get file visibility\n */\n public async getVisibility(location: string): Promise<FileVisibility> {\n return this.withRetry(async () => {\n const { GetObjectAclCommand } = S3Client;\n\n // Apply storage prefix\n location = this.applyPrefix(location);\n\n const command = new GetObjectAclCommand({\n Bucket: this.options.bucket,\n Key: location,\n });\n\n const result = await this.client.send(command);\n\n // Check if any grant allows public read\n const hasPublicRead = result.Grants?.some(\n (grant) =>\n grant.Grantee?.URI === \"http://acs.amazonaws.com/groups/global/AllUsers\" &&\n grant.Permission === \"READ\",\n );\n\n return hasPublicRead ? \"public\" : \"private\";\n }, \"getVisibility\");\n }\n\n // ============================================================\n // Utilities\n // ============================================================\n\n /**\n * Calculate SHA-256 hash\n */\n protected calculateHash(buffer: Buffer): string {\n return crypto.createHash(\"sha256\").update(new Uint8Array(buffer)).digest(\"hex\");\n }\n\n /**\n * Guess MIME type from file extension\n */\n protected guessMimeType(location: string): string {\n return getMimeType(location);\n }\n}\n"],"mappings":"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"}
@@ -189,6 +189,18 @@ type WarlockConfig = {
189
189
  * @default false
190
190
  */
191
191
  transpileCacheDebug?: boolean;
192
+ /**
193
+ * Print a one-line, per-phase timing breakdown next to the existing
194
+ * `hmr update` line on every hot reload: watcher settle (chokidar's
195
+ * `awaitWriteFinish`), debounce wait, module-graph invalidation,
196
+ * re-import, and connector restart. Opt-in and off by default — the
197
+ * marks themselves are cheap `performance.now()` calls, but the
198
+ * raw-fs-event bookkeeping behind the watcher-settle phase is skipped
199
+ * entirely unless this is on, so a disabled flag costs nothing beyond
200
+ * one boolean check per reload.
201
+ * @default false
202
+ */
203
+ timings?: boolean;
192
204
  };
193
205
  /**
194
206
  * Database configuration
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/warlock-config/types.ts"],"mappings":";;;;;;;;;AAWA;;;KAAY,aAAA;EAqBF;;;EAjBR,MAAA;IACE,IAAA;IACA,IAAA;IACA,cAAA;EAAA;EAHF;;;;;;;;;;;EAiBA,KAAA,GAAQ,IAAA,CAAK,YAAA;IA8DX;;;;;IAxDA,MAAA;IA+FW;;;;;;;;;;IApFX,YAAA;IAsJF;;;;;IAhJE,OAAA;IAyLA;;AAAO;;;;;;;;;;;;;;IAxKP,YAAA;;;;;;;;;;;;;;;;IAgBA,OAAA;;;;;;IAMA,MAAA;;;;;;IAMA,SAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;EA2BF,UAAA,GAAa,SAAA;;;;EAKb,GAAA;IACE,QAAA,GAAW,UAAA;EAAA;;;;EAMb,SAAA;;;;IAIE,KAAA;;;;;;MAME,OAAA;;;;;;MAMA,OAAA;IAAA;;;;IAKF,cAAA,GAAiB,yBAAA;;;;;IAKjB,eAAA;;;;;;;;;IASA,eAAA;;;;;;;;;IASA,qBAAA;;;;;;;;;;IAUA,mBAAA;EAAA;;;;EAMF,QAAA;;;;;;;;;;;;;;;;;;IAkBE,UAAA,GAAa,KAAA,CAAM,oBAAA;EAAA;;;;;;;EASrB,OAAA;;;;;;;IAOE,OAAA;;;;;;IAOA,OAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/warlock-config/types.ts"],"mappings":";;;;;;;;;AAWA;;;KAAY,aAAA;EAqBF;;;EAjBR,MAAA;IACE,IAAA;IACA,IAAA;IACA,cAAA;EAAA;EAHF;;;;;;;;;;;EAiBA,KAAA,GAAQ,IAAA,CAAK,YAAA;IA8DX;;;;;IAxDA,MAAA;IA+FW;;;;;;;;;;IApFX,YAAA;IA4JA;;;;;IAtJA,OAAA;IA8LA;;;AAOO;;;;;;;;;;;;;IApLP,YAAA;;;;;;;;;;;;;;;;IAgBA,OAAA;;;;;;IAMA,MAAA;;;;;;IAMA,SAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;EA2BF,UAAA,GAAa,SAAA;;;;EAKb,GAAA;IACE,QAAA,GAAW,UAAA;EAAA;;;;EAMb,SAAA;;;;IAIE,KAAA;;;;;;MAME,OAAA;;;;;;MAMA,OAAA;IAAA;;;;IAKF,cAAA,GAAiB,yBAAA;;;;;IAKjB,eAAA;;;;;;;;;IASA,eAAA;;;;;;;;;IASA,qBAAA;;;;;;;;;;IAUA,mBAAA;;;;;;;;;;;;IAYA,OAAA;EAAA;;;;EAMF,QAAA;;;;;;;;;;;;;;;;;;IAkBE,UAAA,GAAa,KAAA,CAAM,oBAAA;EAAA;;;;;;;EASrB,OAAA;;;;;;;IAOE,OAAA;;;;;;IAOA,OAAA;EAAA;AAAA"}