@rebasepro/server 0.16.0 → 0.16.1-canary.g0d7af95

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 (219) hide show
  1. package/dist/{GCSStorageController-BEmDYFKc.js → GCSStorageController-Bl7nmhdv.js} +7 -6
  2. package/dist/{GCSStorageController-BEmDYFKc.js.map → GCSStorageController-Bl7nmhdv.js.map} +1 -1
  3. package/dist/{S3StorageController-B2EDXXMH.js → S3StorageController-CWvvrRpF.js} +7 -6
  4. package/dist/{S3StorageController-B2EDXXMH.js.map → S3StorageController-CWvvrRpF.js.map} +1 -1
  5. package/dist/{admin_block-DLILvzle.js → admin_block-BeypnEfb.js} +6 -6
  6. package/dist/admin_block-BeypnEfb.js.map +1 -0
  7. package/dist/api/ast-schema-editor.d.ts +23 -0
  8. package/dist/api/contract-routes.d.ts +1 -1
  9. package/dist/api/errors.d.ts +1 -1
  10. package/dist/api/index.d.ts +3 -3
  11. package/dist/api/live-schema-routes.d.ts +122 -0
  12. package/dist/api/logs-routes.d.ts +1 -1
  13. package/dist/api/mount.d.ts +41 -0
  14. package/dist/api/rest/api-generator.d.ts +2 -2
  15. package/dist/api/rest/index.d.ts +1 -1
  16. package/dist/api/rest/query-parser.d.ts +1 -1
  17. package/dist/api/schema-editor-routes.d.ts +1 -1
  18. package/dist/api/types.d.ts +12 -4
  19. package/dist/{schema-editor-routes-CV9k0w3G.js → ast-schema-editor-BrarYZCq.js} +37 -45
  20. package/dist/ast-schema-editor-BrarYZCq.js.map +1 -0
  21. package/dist/auth/adapter-middleware.d.ts +2 -2
  22. package/dist/auth/admin-roles-route.d.ts +2 -2
  23. package/dist/auth/admin-user-ops.d.ts +3 -3
  24. package/dist/auth/admin-users-route.d.ts +4 -6
  25. package/dist/auth/api-keys/api-key-middleware.d.ts +2 -2
  26. package/dist/auth/api-keys/api-key-permission-guard.d.ts +1 -1
  27. package/dist/auth/api-keys/api-key-routes.d.ts +2 -2
  28. package/dist/auth/api-keys/api-key-store.d.ts +1 -1
  29. package/dist/auth/api-keys/index.d.ts +9 -9
  30. package/dist/auth/apple-oauth.d.ts +2 -2
  31. package/dist/auth/auth-hooks.d.ts +4 -4
  32. package/dist/auth/bitbucket-oauth.d.ts +2 -2
  33. package/dist/auth/builtin-auth-adapter.d.ts +8 -4
  34. package/dist/auth/capabilities.d.ts +51 -0
  35. package/dist/auth/captcha.d.ts +86 -0
  36. package/dist/auth/cookie-utils.d.ts +2 -2
  37. package/dist/auth/discord-oauth.d.ts +2 -2
  38. package/dist/auth/facebook-oauth.d.ts +2 -2
  39. package/dist/auth/github-oauth.d.ts +2 -2
  40. package/dist/auth/gitlab-oauth.d.ts +2 -2
  41. package/dist/auth/google-oauth.d.ts +1 -1
  42. package/dist/auth/index.d.ts +58 -54
  43. package/dist/auth/jwks-routes.d.ts +1 -1
  44. package/dist/auth/jwt.d.ts +1 -1
  45. package/dist/auth/linkedin-oauth.d.ts +2 -2
  46. package/dist/auth/magic-link-routes.d.ts +10 -3
  47. package/dist/auth/mfa-gate.d.ts +1 -1
  48. package/dist/auth/mfa-routes.d.ts +3 -3
  49. package/dist/auth/microsoft-oauth.d.ts +2 -2
  50. package/dist/auth/middleware.d.ts +6 -7
  51. package/dist/auth/otp-routes.d.ts +89 -0
  52. package/dist/auth/rate-limiter.d.ts +2 -2
  53. package/dist/auth/require-auth.d.ts +1 -1
  54. package/dist/auth/reset-password-admin.d.ts +4 -4
  55. package/dist/auth/routes.d.ts +18 -10
  56. package/dist/auth/session-routes.d.ts +3 -3
  57. package/dist/auth/slack-oauth.d.ts +2 -2
  58. package/dist/auth/spotify-oauth.d.ts +2 -2
  59. package/dist/auth/token-revocation.d.ts +2 -2
  60. package/dist/auth/twitter-oauth.d.ts +2 -2
  61. package/dist/{auth-5Et5mnUA.js → auth-DU-nUjPp.js} +604 -4376
  62. package/dist/auth-DU-nUjPp.js.map +1 -0
  63. package/dist/backup/backup-common.d.ts +1 -1
  64. package/dist/backup/backup-routes.d.ts +3 -3
  65. package/dist/backup/index.d.ts +3 -3
  66. package/dist/{backup-C6ljYVTp.js → backup-CRdZkA6c.js} +7 -6
  67. package/dist/{backup-C6ljYVTp.js.map → backup-CRdZkA6c.js.map} +1 -1
  68. package/dist/boot/boot.d.ts +5 -5
  69. package/dist/boot/bundle.d.ts +10 -1
  70. package/dist/boot/driver.d.ts +1 -1
  71. package/dist/boot/env.d.ts +28 -1
  72. package/dist/boot/fetch-bundle.d.ts +144 -1
  73. package/dist/boot/options.d.ts +30 -6
  74. package/dist/boot/resource-adapters.d.ts +31 -0
  75. package/dist/boot/resource-loading.d.ts +10 -0
  76. package/dist/boot/role.d.ts +3 -2
  77. package/dist/boot/sources.d.ts +8 -2
  78. package/dist/boot/version-skew.d.ts +1 -1
  79. package/dist/collections/BackendCollectionRegistry.d.ts +1 -1
  80. package/dist/collections/loader.d.ts +1 -1
  81. package/dist/collections/validate-config.d.ts +13 -0
  82. package/dist/{contract-routes-DZ-LBpSL.js → contract-routes-D8TvxPLo.js} +8 -7
  83. package/dist/contract-routes-D8TvxPLo.js.map +1 -0
  84. package/dist/cron/cron-routes.d.ts +2 -2
  85. package/dist/cron/cron-scheduler.d.ts +3 -3
  86. package/dist/cron/index.d.ts +8 -8
  87. package/dist/{cron-loader-YhhQeVBM.js → cron-loader-d9WMFENB.js} +8 -7
  88. package/dist/{cron-loader-YhhQeVBM.js.map → cron-loader-d9WMFENB.js.map} +1 -1
  89. package/dist/{cron-routes-maM_RlUu.js → cron-routes-BsukhMGm.js} +9 -8
  90. package/dist/cron-routes-BsukhMGm.js.map +1 -0
  91. package/dist/{cron-scheduler-DIpYBmZP.js → cron-scheduler-BB82dWuU.js} +9 -9
  92. package/dist/cron-scheduler-BB82dWuU.js.map +1 -0
  93. package/dist/{cron-store-DfH_4Cd9.js → cron-store-CbFfQhbg.js} +11 -9
  94. package/dist/{cron-store-DfH_4Cd9.js.map → cron-store-CbFfQhbg.js.map} +1 -1
  95. package/dist/ddl-bootstrap-5YZCZ8qk.js +146 -0
  96. package/dist/ddl-bootstrap-5YZCZ8qk.js.map +1 -0
  97. package/dist/deploy/pod-contract.d.ts +139 -0
  98. package/dist/dev-secrets.d.ts +52 -0
  99. package/dist/{dynamic-import-Dvh-K5fl.js → dynamic-import-X40pTZUQ.js} +5 -4
  100. package/dist/{dynamic-import-Dvh-K5fl.js.map → dynamic-import-X40pTZUQ.js.map} +1 -1
  101. package/dist/email/dev-sink.d.ts +88 -0
  102. package/dist/email/index.d.ts +9 -7
  103. package/dist/email/link-base.d.ts +1 -1
  104. package/dist/email/smtp-email-service.d.ts +2 -2
  105. package/dist/email/templates.d.ts +12 -0
  106. package/dist/email/types.d.ts +18 -6
  107. package/dist/env.d.ts +1 -1
  108. package/dist/{errors-EBYiaJ2E.js → errors-DBwpj9N8.js} +8 -7
  109. package/dist/errors-DBwpj9N8.js.map +1 -0
  110. package/dist/{function-loader-DDS1v7YX.js → function-loader-LLdmBFoL.js} +9 -8
  111. package/dist/{function-loader-DDS1v7YX.js.map → function-loader-LLdmBFoL.js.map} +1 -1
  112. package/dist/{function-routes-Btcez1T-.js → function-routes-ClT6UQpD.js} +8 -7
  113. package/dist/function-routes-ClT6UQpD.js.map +1 -0
  114. package/dist/functions/context.d.ts +141 -0
  115. package/dist/functions/define-function.d.ts +1 -1
  116. package/dist/functions/function-routes.d.ts +9 -3
  117. package/dist/functions/guards.d.ts +76 -0
  118. package/dist/functions/index.d.ts +96 -5
  119. package/dist/functions/index.js +919 -0
  120. package/dist/functions/index.js.map +1 -0
  121. package/dist/functions/internal.d.ts +26 -0
  122. package/dist/functions/proxy.d.ts +1 -1
  123. package/dist/functions/request-timeout.d.ts +9 -1
  124. package/dist/functions/runtime-env.d.ts +92 -0
  125. package/dist/functions/wait-until.d.ts +74 -0
  126. package/dist/history/history-routes.d.ts +2 -2
  127. package/dist/history/index.d.ts +1 -1
  128. package/dist/history-recorder-B67maiTi.js +76 -0
  129. package/dist/history-recorder-B67maiTi.js.map +1 -0
  130. package/dist/history-store-oiqhb3HU.js +210 -0
  131. package/dist/history-store-oiqhb3HU.js.map +1 -0
  132. package/dist/index.d.ts +65 -54
  133. package/dist/index.es.js +3667 -328
  134. package/dist/index.es.js.map +1 -1
  135. package/dist/init/docs.d.ts +1 -1
  136. package/dist/init/middlewares.d.ts +1 -1
  137. package/dist/init/shutdown.d.ts +4 -0
  138. package/dist/init/storage.d.ts +1 -1
  139. package/dist/init/surfaces.d.ts +10 -0
  140. package/dist/init.d.ts +179 -22
  141. package/dist/internal-tables-DpxfPaEB.js +97 -0
  142. package/dist/internal-tables-DpxfPaEB.js.map +1 -0
  143. package/dist/jobs/index.d.ts +5 -5
  144. package/dist/{jobs-CyOKXXlu.js → jobs-B2ELModo.js} +11 -9
  145. package/dist/{jobs-CyOKXXlu.js.map → jobs-B2ELModo.js.map} +1 -1
  146. package/dist/{jwt-DxH9fLPt.js → jwt-DoHkMMWF.js} +9 -8
  147. package/dist/jwt-DoHkMMWF.js.map +1 -0
  148. package/dist/{logger-DfvF_8r-.js → logger-DS03e908.js} +104 -10
  149. package/dist/logger-DS03e908.js.map +1 -0
  150. package/dist/{logs-routes-CWBLQj2l.js → logs-routes-DB72iQSr.js} +36 -9
  151. package/dist/logs-routes-DB72iQSr.js.map +1 -0
  152. package/dist/metrics/history-recorder.d.ts +18 -0
  153. package/dist/metrics/history-store.d.ts +123 -0
  154. package/dist/metrics/index.d.ts +10 -2
  155. package/dist/{openapi-generator-BCKJRUS4.js → openapi-generator-FFGT0H6O.js} +7 -11
  156. package/dist/{openapi-generator-BCKJRUS4.js.map → openapi-generator-FFGT0H6O.js.map} +1 -1
  157. package/dist/{proxy-Bj5DVllb.js → proxy-CMymhnwG.js} +9 -6
  158. package/dist/{proxy-Bj5DVllb.js.map → proxy-CMymhnwG.js.map} +1 -1
  159. package/dist/query-parser--nstjRCh.js +355 -0
  160. package/dist/query-parser--nstjRCh.js.map +1 -0
  161. package/dist/{request-timeout-BuFoEKwT.js → request-timeout-C8gkc-j7.js} +21 -6
  162. package/dist/request-timeout-C8gkc-j7.js.map +1 -0
  163. package/dist/rls-audit/index.d.ts +111 -0
  164. package/dist/{rolldown-runtime-DSJWtz9O.js → rolldown-runtime-dW7B1o5h.js} +4 -3
  165. package/dist/schema-edit/apply-schema-change.d.ts +141 -0
  166. package/dist/schema-edit/github-repository.d.ts +67 -0
  167. package/dist/schema-edit/local-git-repository.d.ts +28 -0
  168. package/dist/schema-edit/project-root.d.ts +43 -0
  169. package/dist/schema-edit/remote-source.d.ts +25 -0
  170. package/dist/schema-edit/schema-edit-permissions.d.ts +129 -0
  171. package/dist/schema-editor-routes-Bf5h5Emf.js +87 -0
  172. package/dist/schema-editor-routes-Bf5h5Emf.js.map +1 -0
  173. package/dist/schemas-DBxgjM9A.js +4153 -0
  174. package/dist/schemas-DBxgjM9A.js.map +1 -0
  175. package/dist/{selection-_z6TM1DB.js → selection-CRpqKUbt.js} +6 -5
  176. package/dist/{selection-_z6TM1DB.js.map → selection-CRpqKUbt.js.map} +1 -1
  177. package/dist/services/webhook-service.d.ts +1 -1
  178. package/dist/singleton.d.ts +7 -0
  179. package/dist/{src-BPfYOeN4.js → src-B-CmIFMr.js} +8 -6
  180. package/dist/src-B-CmIFMr.js.map +1 -0
  181. package/dist/{src-CrCxd8km.js → src-Cdsw7DqV.js} +126 -5
  182. package/dist/src-Cdsw7DqV.js.map +1 -0
  183. package/dist/storage/GCSStorageController.d.ts +1 -1
  184. package/dist/storage/LocalStorageController.d.ts +1 -1
  185. package/dist/storage/S3StorageController.d.ts +1 -1
  186. package/dist/storage/cache-headers.d.ts +87 -0
  187. package/dist/storage/index.d.ts +11 -11
  188. package/dist/storage/path-pattern.d.ts +58 -0
  189. package/dist/storage/policies.d.ts +88 -0
  190. package/dist/storage/range.d.ts +63 -0
  191. package/dist/storage/rendition-cache.d.ts +45 -0
  192. package/dist/storage/routes.d.ts +17 -5
  193. package/dist/storage/storage-registry.d.ts +1 -1
  194. package/dist/storage/triggers.d.ts +66 -0
  195. package/dist/storage/tus-handler.d.ts +34 -3
  196. package/dist/topics/runtime.d.ts +68 -0
  197. package/dist/{types-DSnOC4mF.js → types-BfKcm9do.js} +5 -4
  198. package/dist/{types-DSnOC4mF.js.map → types-BfKcm9do.js.map} +1 -1
  199. package/dist/utils/host.d.ts +58 -0
  200. package/dist/utils/logger.d.ts +0 -15
  201. package/dist/utils/request-id.d.ts +1 -1
  202. package/functions/package.json +24 -0
  203. package/package.json +13 -7
  204. package/dist/admin_block-DLILvzle.js.map +0 -1
  205. package/dist/auth-5Et5mnUA.js.map +0 -1
  206. package/dist/contract-routes-DZ-LBpSL.js.map +0 -1
  207. package/dist/cron-routes-maM_RlUu.js.map +0 -1
  208. package/dist/cron-scheduler-DIpYBmZP.js.map +0 -1
  209. package/dist/ddl-bootstrap-Cywoj8Ta.js +0 -221
  210. package/dist/ddl-bootstrap-Cywoj8Ta.js.map +0 -1
  211. package/dist/errors-EBYiaJ2E.js.map +0 -1
  212. package/dist/function-routes-Btcez1T-.js.map +0 -1
  213. package/dist/jwt-DxH9fLPt.js.map +0 -1
  214. package/dist/logger-DfvF_8r-.js.map +0 -1
  215. package/dist/logs-routes-CWBLQj2l.js.map +0 -1
  216. package/dist/request-timeout-BuFoEKwT.js.map +0 -1
  217. package/dist/schema-editor-routes-CV9k0w3G.js.map +0 -1
  218. package/dist/src-BPfYOeN4.js.map +0 -1
  219. package/dist/src-CrCxd8km.js.map +0 -1
@@ -1,8 +1,9 @@
1
- import { createRequire as __createRequire } from "module";
2
- import "process";
3
- __createRequire(import.meta.url);
4
- import { n as __exportAll } from "./rolldown-runtime-DSJWtz9O.js";
5
- import "./types-DSnOC4mF.js";
1
+ import { createRequire as __rebaseCreateRequire } from "module";
2
+ import __rebaseProcess from "process";
3
+ globalThis.process ??= __rebaseProcess;
4
+ __rebaseCreateRequire(import.meta.url);
5
+ import { n as __exportAll } from "./rolldown-runtime-dW7B1o5h.js";
6
+ import "./types-BfKcm9do.js";
6
7
  //#region src/storage/GCSStorageController.ts
7
8
  /**
8
9
  * Google Cloud Storage controller (works with GCS and Firebase Storage)
@@ -213,4 +214,4 @@ function isNotFoundError(error) {
213
214
  //#endregion
214
215
  export { GCSStorageController_exports as n, GCSStorageController as t };
215
216
 
216
- //# sourceMappingURL=GCSStorageController-BEmDYFKc.js.map
217
+ //# sourceMappingURL=GCSStorageController-Bl7nmhdv.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GCSStorageController-BEmDYFKc.js","names":[],"sources":["../src/storage/GCSStorageController.ts"],"sourcesContent":["/**\n * Google Cloud Storage controller (works with GCS and Firebase Storage)\n *\n * The `@google-cloud/storage` package is loaded lazily so it remains an\n * optional peer dependency — users who don't need GCS never pay for the\n * import cost or need it installed.\n */\n\nimport {\n StorageController,\n GCSStorageConfig,\n DEFAULT_MAX_FILE_SIZE\n} from \"./types\";\nimport {\n UploadFileProps,\n UploadFileResult,\n DownloadConfig,\n DownloadMetadata,\n StorageListResult,\n StorageReference\n} from \"@rebasepro/types\";\n\n// ---------------------------------------------------------------------------\n// Lazy import of @google-cloud/storage\n// ---------------------------------------------------------------------------\n\nlet gcsModule: typeof import(\"@google-cloud/storage\") | null = null;\n\nasync function loadGCS(): Promise<typeof import(\"@google-cloud/storage\")> {\n if (gcsModule) return gcsModule;\n try {\n gcsModule = await import(\"@google-cloud/storage\");\n return gcsModule;\n } catch {\n throw new Error(\n \"Google Cloud Storage is required for the GCS storage controller. \" +\n \"Install it with: pnpm add @google-cloud/storage\"\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// GCSStorageController\n// ---------------------------------------------------------------------------\n\n/**\n * Google Cloud Storage implementation of `StorageController`.\n *\n * Works with standard GCS buckets **and** Firebase Storage buckets\n * (e.g. `\"my-project.appspot.com\"`).\n */\nexport class GCSStorageController implements StorageController {\n private config: GCSStorageConfig;\n private client: InstanceType<typeof import(\"@google-cloud/storage\").Storage> | null = null;\n\n constructor(config: GCSStorageConfig) {\n this.config = config;\n }\n\n // ------------------------------------------------------------------\n // Lazy client initialisation\n // ------------------------------------------------------------------\n\n private async getClient(): Promise<InstanceType<typeof import(\"@google-cloud/storage\").Storage>> {\n if (this.client) return this.client;\n const { Storage } = await loadGCS();\n this.client = new Storage({\n projectId: this.config.projectId,\n keyFilename: this.config.keyFilename,\n credentials: this.config.credentials\n });\n return this.client;\n }\n\n // ------------------------------------------------------------------\n // StorageController interface\n // ------------------------------------------------------------------\n\n getType(): \"gcs\" {\n return \"gcs\";\n }\n\n async putObject({\n file,\n key,\n metadata,\n bucket\n }: UploadFileProps): Promise<UploadFileResult> {\n this.validateFile(file);\n\n const usedBucket = this.getBucket(bucket);\n const client = await this.getClient();\n const gcsFile = client.bucket(usedBucket).file(key);\n\n // Convert File to Buffer\n const arrayBuffer = await file.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n await gcsFile.save(buffer, {\n contentType: file.type,\n metadata: metadata ? { metadata: this.flattenMetadata(metadata) } : undefined\n });\n\n return {\n key,\n bucket: usedBucket,\n storageUrl: `gs://${usedBucket}/${key}`\n };\n }\n\n async getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig> {\n // Handle gs:// and s3:// URLs\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(gs|s3):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n try {\n const client = await this.getClient();\n const gcsFile = client.bucket(resolvedBucket).file(resolvedPath);\n\n // Get metadata first to check existence and populate response\n const [fileMetadata] = await gcsFile.getMetadata();\n\n // Generate a signed URL\n const expiresIn = this.config.signedUrlExpiration ?? 3600;\n const [url] = await gcsFile.getSignedUrl({\n action: \"read\",\n expires: Date.now() + expiresIn * 1000\n });\n\n const metadata: DownloadMetadata = {\n bucket: resolvedBucket,\n fullPath: resolvedPath,\n name: resolvedPath.split(\"/\").pop() || resolvedPath,\n size: Number(fileMetadata.size) || 0,\n contentType: fileMetadata.contentType || \"application/octet-stream\",\n customMetadata: (fileMetadata.metadata as Record<string, string> | undefined) || {}\n };\n\n return {\n url,\n metadata\n };\n } catch (error: unknown) {\n if (isNotFoundError(error)) {\n return {\n url: null,\n fileNotFound: true\n };\n }\n throw error;\n }\n }\n\n async getObject(key: string, bucket?: string): Promise<File | null> {\n // Handle gs:// and s3:// URLs\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(gs|s3):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n try {\n const client = await this.getClient();\n const gcsFile = client.bucket(resolvedBucket).file(resolvedPath);\n\n const [contents] = await gcsFile.download();\n\n // Get content type from metadata\n const [fileMetadata] = await gcsFile.getMetadata();\n const contentType = fileMetadata.contentType || \"application/octet-stream\";\n const fileName = resolvedPath.split(\"/\").pop() || resolvedPath;\n\n const blob = new Blob([new Uint8Array(contents)], { type: contentType });\n return new File([blob], fileName, { type: contentType });\n } catch (error: unknown) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n async deleteObject(key: string, bucket?: string): Promise<void> {\n // Handle gs:// and s3:// URLs\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(gs|s3):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n const client = await this.getClient();\n const gcsFile = client.bucket(resolvedBucket).file(resolvedPath);\n\n await gcsFile.delete({ ignoreNotFound: true });\n }\n\n async listObjects(prefix: string, options?: {\n bucket?: string;\n maxResults?: number;\n pageToken?: string;\n }): Promise<StorageListResult> {\n const resolvedBucket = this.getBucket(options?.bucket);\n const client = await this.getClient();\n const gcsBucket = client.bucket(resolvedBucket);\n\n // Fetch files (objects) matching the prefix\n const [files, , filesApiResponse] = await gcsBucket.getFiles({\n prefix: prefix || undefined,\n delimiter: \"/\",\n maxResults: options?.maxResults ?? 1000,\n pageToken: options?.pageToken,\n autoPaginate: false\n });\n\n const items: StorageReference[] = files.map(file => ({\n bucket: resolvedBucket,\n fullPath: file.name,\n name: file.name.split(\"/\").pop() || \"\",\n parent: null as never,\n root: null as never,\n toString: () => `gs://${resolvedBucket}/${file.name}`\n }));\n\n // Common prefixes (folder-like entries) come from the API response\n const apiPrefixes: string[] =\n (filesApiResponse as Record<string, unknown> | undefined)?.prefixes as string[] ?? [];\n\n const prefixes: StorageReference[] = apiPrefixes.map(p => ({\n bucket: resolvedBucket,\n fullPath: p,\n name: p.replace(/\\/$/, \"\").split(\"/\").pop() || \"\",\n parent: null as never,\n root: null as never,\n toString: () => `gs://${resolvedBucket}/${p}`\n }));\n\n // The next page token is returned on the query object (second element)\n const nextPageToken =\n (filesApiResponse as Record<string, unknown> | undefined)?.nextPageToken as string | undefined;\n\n return {\n items,\n prefixes,\n nextPageToken\n };\n }\n\n // ------------------------------------------------------------------\n // Private helpers\n // ------------------------------------------------------------------\n\n /**\n * Validate file before upload\n */\n private validateFile(file: File): void {\n const maxSize = this.config.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;\n if (file.size > maxSize) {\n throw new Error(`File size ${file.size} exceeds maximum allowed size ${maxSize}`);\n }\n\n if (this.config.allowedMimeTypes && this.config.allowedMimeTypes.length > 0) {\n if (!this.config.allowedMimeTypes.includes(file.type)) {\n throw new Error(`File type ${file.type} is not allowed. Allowed types: ${this.config.allowedMimeTypes.join(\", \")}`);\n }\n }\n }\n\n /**\n * Get the bucket name — either from parameter or config.\n */\n private getBucket(bucket?: string): string {\n // \"default\" is a logical bucket name used by local storage;\n // for GCS it should resolve to the configured bucket.\n if (!bucket || bucket === \"default\") return this.config.bucket;\n return bucket;\n }\n\n /**\n * Flatten nested metadata to string values (GCS custom metadata requirement)\n */\n private flattenMetadata(metadata: Record<string, unknown>): Record<string, string> {\n const flattened: Record<string, string> = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (typeof value === \"string\") {\n flattened[key] = value;\n } else if (value !== undefined && value !== null) {\n flattened[key] = JSON.stringify(value);\n }\n }\n return flattened;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Error helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether an error represents a 404 / Not Found from the GCS client.\n */\nfunction isNotFoundError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null) return false;\n const err = error as Record<string, unknown>;\n if (err[\"code\"] === 404) return true;\n if (typeof err[\"message\"] === \"string\" && /not found/i.test(err[\"message\"])) return true;\n return false;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"GCSStorageController-Bl7nmhdv.js","names":[],"sources":["../src/storage/GCSStorageController.ts"],"sourcesContent":["/**\n * Google Cloud Storage controller (works with GCS and Firebase Storage)\n *\n * The `@google-cloud/storage` package is loaded lazily so it remains an\n * optional peer dependency — users who don't need GCS never pay for the\n * import cost or need it installed.\n */\n\nimport {\n StorageController,\n GCSStorageConfig,\n DEFAULT_MAX_FILE_SIZE\n} from \"./types\";\nimport {\n UploadFileProps,\n UploadFileResult,\n DownloadConfig,\n DownloadMetadata,\n StorageListResult,\n StorageReference\n} from \"@rebasepro/types\";\n\n// ---------------------------------------------------------------------------\n// Lazy import of @google-cloud/storage\n// ---------------------------------------------------------------------------\n\nlet gcsModule: typeof import(\"@google-cloud/storage\") | null = null;\n\nasync function loadGCS(): Promise<typeof import(\"@google-cloud/storage\")> {\n if (gcsModule) return gcsModule;\n try {\n gcsModule = await import(\"@google-cloud/storage\");\n return gcsModule;\n } catch {\n throw new Error(\n \"Google Cloud Storage is required for the GCS storage controller. \" +\n \"Install it with: pnpm add @google-cloud/storage\"\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// GCSStorageController\n// ---------------------------------------------------------------------------\n\n/**\n * Google Cloud Storage implementation of `StorageController`.\n *\n * Works with standard GCS buckets **and** Firebase Storage buckets\n * (e.g. `\"my-project.appspot.com\"`).\n */\nexport class GCSStorageController implements StorageController {\n private config: GCSStorageConfig;\n private client: InstanceType<typeof import(\"@google-cloud/storage\").Storage> | null = null;\n\n constructor(config: GCSStorageConfig) {\n this.config = config;\n }\n\n // ------------------------------------------------------------------\n // Lazy client initialisation\n // ------------------------------------------------------------------\n\n private async getClient(): Promise<InstanceType<typeof import(\"@google-cloud/storage\").Storage>> {\n if (this.client) return this.client;\n const { Storage } = await loadGCS();\n this.client = new Storage({\n projectId: this.config.projectId,\n keyFilename: this.config.keyFilename,\n credentials: this.config.credentials\n });\n return this.client;\n }\n\n // ------------------------------------------------------------------\n // StorageController interface\n // ------------------------------------------------------------------\n\n getType(): \"gcs\" {\n return \"gcs\";\n }\n\n async putObject({\n file,\n key,\n metadata,\n bucket\n }: UploadFileProps): Promise<UploadFileResult> {\n this.validateFile(file);\n\n const usedBucket = this.getBucket(bucket);\n const client = await this.getClient();\n const gcsFile = client.bucket(usedBucket).file(key);\n\n // Convert File to Buffer\n const arrayBuffer = await file.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n await gcsFile.save(buffer, {\n contentType: file.type,\n metadata: metadata ? { metadata: this.flattenMetadata(metadata) } : undefined\n });\n\n return {\n key,\n bucket: usedBucket,\n storageUrl: `gs://${usedBucket}/${key}`\n };\n }\n\n async getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig> {\n // Handle gs:// and s3:// URLs\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(gs|s3):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n try {\n const client = await this.getClient();\n const gcsFile = client.bucket(resolvedBucket).file(resolvedPath);\n\n // Get metadata first to check existence and populate response\n const [fileMetadata] = await gcsFile.getMetadata();\n\n // Generate a signed URL\n const expiresIn = this.config.signedUrlExpiration ?? 3600;\n const [url] = await gcsFile.getSignedUrl({\n action: \"read\",\n expires: Date.now() + expiresIn * 1000\n });\n\n const metadata: DownloadMetadata = {\n bucket: resolvedBucket,\n fullPath: resolvedPath,\n name: resolvedPath.split(\"/\").pop() || resolvedPath,\n size: Number(fileMetadata.size) || 0,\n contentType: fileMetadata.contentType || \"application/octet-stream\",\n customMetadata: (fileMetadata.metadata as Record<string, string> | undefined) || {}\n };\n\n return {\n url,\n metadata\n };\n } catch (error: unknown) {\n if (isNotFoundError(error)) {\n return {\n url: null,\n fileNotFound: true\n };\n }\n throw error;\n }\n }\n\n async getObject(key: string, bucket?: string): Promise<File | null> {\n // Handle gs:// and s3:// URLs\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(gs|s3):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n try {\n const client = await this.getClient();\n const gcsFile = client.bucket(resolvedBucket).file(resolvedPath);\n\n const [contents] = await gcsFile.download();\n\n // Get content type from metadata\n const [fileMetadata] = await gcsFile.getMetadata();\n const contentType = fileMetadata.contentType || \"application/octet-stream\";\n const fileName = resolvedPath.split(\"/\").pop() || resolvedPath;\n\n const blob = new Blob([new Uint8Array(contents)], { type: contentType });\n return new File([blob], fileName, { type: contentType });\n } catch (error: unknown) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n async deleteObject(key: string, bucket?: string): Promise<void> {\n // Handle gs:// and s3:// URLs\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(gs|s3):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n const client = await this.getClient();\n const gcsFile = client.bucket(resolvedBucket).file(resolvedPath);\n\n await gcsFile.delete({ ignoreNotFound: true });\n }\n\n async listObjects(prefix: string, options?: {\n bucket?: string;\n maxResults?: number;\n pageToken?: string;\n }): Promise<StorageListResult> {\n const resolvedBucket = this.getBucket(options?.bucket);\n const client = await this.getClient();\n const gcsBucket = client.bucket(resolvedBucket);\n\n // Fetch files (objects) matching the prefix\n const [files, , filesApiResponse] = await gcsBucket.getFiles({\n prefix: prefix || undefined,\n delimiter: \"/\",\n maxResults: options?.maxResults ?? 1000,\n pageToken: options?.pageToken,\n autoPaginate: false\n });\n\n const items: StorageReference[] = files.map(file => ({\n bucket: resolvedBucket,\n fullPath: file.name,\n name: file.name.split(\"/\").pop() || \"\",\n parent: null as never,\n root: null as never,\n toString: () => `gs://${resolvedBucket}/${file.name}`\n }));\n\n // Common prefixes (folder-like entries) come from the API response\n const apiPrefixes: string[] =\n (filesApiResponse as Record<string, unknown> | undefined)?.prefixes as string[] ?? [];\n\n const prefixes: StorageReference[] = apiPrefixes.map(p => ({\n bucket: resolvedBucket,\n fullPath: p,\n name: p.replace(/\\/$/, \"\").split(\"/\").pop() || \"\",\n parent: null as never,\n root: null as never,\n toString: () => `gs://${resolvedBucket}/${p}`\n }));\n\n // The next page token is returned on the query object (second element)\n const nextPageToken =\n (filesApiResponse as Record<string, unknown> | undefined)?.nextPageToken as string | undefined;\n\n return {\n items,\n prefixes,\n nextPageToken\n };\n }\n\n // ------------------------------------------------------------------\n // Private helpers\n // ------------------------------------------------------------------\n\n /**\n * Validate file before upload\n */\n private validateFile(file: File): void {\n const maxSize = this.config.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;\n if (file.size > maxSize) {\n throw new Error(`File size ${file.size} exceeds maximum allowed size ${maxSize}`);\n }\n\n if (this.config.allowedMimeTypes && this.config.allowedMimeTypes.length > 0) {\n if (!this.config.allowedMimeTypes.includes(file.type)) {\n throw new Error(`File type ${file.type} is not allowed. Allowed types: ${this.config.allowedMimeTypes.join(\", \")}`);\n }\n }\n }\n\n /**\n * Get the bucket name — either from parameter or config.\n */\n private getBucket(bucket?: string): string {\n // \"default\" is a logical bucket name used by local storage;\n // for GCS it should resolve to the configured bucket.\n if (!bucket || bucket === \"default\") return this.config.bucket;\n return bucket;\n }\n\n /**\n * Flatten nested metadata to string values (GCS custom metadata requirement)\n */\n private flattenMetadata(metadata: Record<string, unknown>): Record<string, string> {\n const flattened: Record<string, string> = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (typeof value === \"string\") {\n flattened[key] = value;\n } else if (value !== undefined && value !== null) {\n flattened[key] = JSON.stringify(value);\n }\n }\n return flattened;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Error helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether an error represents a 404 / Not Found from the GCS client.\n */\nfunction isNotFoundError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null) return false;\n const err = error as Record<string, unknown>;\n if (err[\"code\"] === 404) return true;\n if (typeof err[\"message\"] === \"string\" && /not found/i.test(err[\"message\"])) return true;\n return false;\n}\n"],"mappings":"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"}
@@ -1,8 +1,9 @@
1
- import { createRequire as __createRequire } from "module";
2
- import "process";
3
- __createRequire(import.meta.url);
4
- import { n as __exportAll } from "./rolldown-runtime-DSJWtz9O.js";
5
- import "./types-DSnOC4mF.js";
1
+ import { createRequire as __rebaseCreateRequire } from "module";
2
+ import __rebaseProcess from "process";
3
+ globalThis.process ??= __rebaseProcess;
4
+ __rebaseCreateRequire(import.meta.url);
5
+ import { n as __exportAll } from "./rolldown-runtime-dW7B1o5h.js";
6
+ import "./types-BfKcm9do.js";
6
7
  //#region src/storage/S3StorageController.ts
7
8
  var S3StorageController_exports = /* @__PURE__ */ __exportAll({ S3StorageController: () => S3StorageController });
8
9
  var _s3Module;
@@ -246,4 +247,4 @@ var S3StorageController = class {
246
247
  //#endregion
247
248
  export { S3StorageController_exports as n, S3StorageController as t };
248
249
 
249
- //# sourceMappingURL=S3StorageController-B2EDXXMH.js.map
250
+ //# sourceMappingURL=S3StorageController-CWvvrRpF.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"S3StorageController-B2EDXXMH.js","names":[],"sources":["../src/storage/S3StorageController.ts"],"sourcesContent":["/**\n * S3-compatible storage controller (works with AWS S3 and MinIO)\n */\n\nimport type { S3Client as S3ClientType, } from \"@aws-sdk/client-s3\";\nimport { DEFAULT_MAX_FILE_SIZE, S3StorageConfig, StorageController } from \"./types\";\nimport {\n DownloadConfig,\n DownloadMetadata,\n StorageListResult,\n StorageReference,\n UploadFileProps,\n UploadFileResult\n} from \"@rebasepro/types\";\n\nlet _s3Module: typeof import(\"@aws-sdk/client-s3\") | undefined;\nlet _presignerModule: typeof import(\"@aws-sdk/s3-request-presigner\") | undefined;\n\nasync function loadS3() {\n if (!_s3Module) {\n try {\n _s3Module = await import(\"@aws-sdk/client-s3\");\n } catch {\n throw new Error(\n \"@aws-sdk/client-s3 is required for S3 storage. \" +\n \"Install it: pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner\"\n );\n }\n }\n return _s3Module;\n}\n\nasync function loadPresigner() {\n if (!_presignerModule) {\n try {\n _presignerModule = await import(\"@aws-sdk/s3-request-presigner\");\n } catch {\n throw new Error(\n \"@aws-sdk/s3-request-presigner is required for S3 storage. \" +\n \"Install it: pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner\"\n );\n }\n }\n return _presignerModule;\n}\n\n/**\n * S3-compatible storage implementation\n * Works with AWS S3 and MinIO (with forcePathStyle option)\n */\nexport class S3StorageController implements StorageController {\n private config: S3StorageConfig;\n private _client: S3ClientType | undefined;\n\n constructor(config: S3StorageConfig) {\n this.config = config;\n }\n\n /**\n * Lazily create and cache the S3 client on first use\n */\n private async getClient(): Promise<S3ClientType> {\n if (!this._client) {\n const s3 = await loadS3();\n this._client = new s3.S3Client({\n region: this.config.region || \"us-east-1\",\n endpoint: this.config.endpoint,\n forcePathStyle: this.config.forcePathStyle ?? !!this.config.endpoint, // Auto-enable for custom endpoints (MinIO)\n credentials: {\n accessKeyId: this.config.accessKeyId,\n secretAccessKey: this.config.secretAccessKey\n }\n });\n }\n return this._client;\n }\n\n getType(): \"s3\" {\n return \"s3\";\n }\n\n /**\n * Validate file before upload\n */\n private validateFile(file: File): void {\n const maxSize = this.config.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;\n if (file.size > maxSize) {\n throw new Error(`File size ${file.size} exceeds maximum allowed size ${maxSize}`);\n }\n\n if (this.config.allowedMimeTypes && this.config.allowedMimeTypes.length > 0) {\n if (!this.config.allowedMimeTypes.includes(file.type)) {\n throw new Error(`File type ${file.type} is not allowed. Allowed types: ${this.config.allowedMimeTypes.join(\", \")}`);\n }\n }\n }\n\n /**\n * Get the bucket name - either from parameter or config\n */\n private getBucket(bucket?: string): string {\n // \"default\" is a logical bucket name used by local storage;\n // for S3 it should resolve to the configured bucket.\n if (!bucket || bucket === \"default\") return this.config.bucket;\n return bucket;\n }\n\n async putObject({\n file,\n key,\n metadata,\n bucket\n }: UploadFileProps): Promise<UploadFileResult> {\n this.validateFile(file);\n\n const usedBucket = this.getBucket(bucket);\n\n // Convert File to Buffer\n const arrayBuffer = await file.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n const s3 = await loadS3();\n const client = await this.getClient();\n\n const command = new s3.PutObjectCommand({\n Bucket: usedBucket,\n Key: key,\n Body: buffer,\n ContentType: file.type,\n Metadata: metadata ? this.flattenMetadata(metadata) : undefined\n });\n\n await client.send(command);\n\n return {\n key,\n bucket: usedBucket,\n storageUrl: `s3://${usedBucket}/${key}`\n };\n }\n\n /**\n * Flatten nested metadata to string values (S3 requirement)\n */\n private flattenMetadata(metadata: Record<string, unknown>): Record<string, string> {\n const flattened: Record<string, string> = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (typeof value === \"string\") {\n flattened[key] = value;\n } else if (value !== undefined && value !== null) {\n flattened[key] = JSON.stringify(value);\n }\n }\n return flattened;\n }\n\n async getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig> {\n // Handle s3:// and gs:// URLs for backward compatibility\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(s3|gs):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n try {\n const s3 = await loadS3();\n const presigner = await loadPresigner();\n const client = await this.getClient();\n\n // First check if the object exists and get metadata\n const headCommand = new s3.HeadObjectCommand({\n Bucket: resolvedBucket,\n Key: resolvedPath\n });\n\n const headResult = await client.send(headCommand);\n\n // Generate a signed URL\n const getCommand = new s3.GetObjectCommand({\n Bucket: resolvedBucket,\n Key: resolvedPath\n });\n\n const expiresIn = this.config.signedUrlExpiration ?? 3600;\n const url = await presigner.getSignedUrl(client, getCommand, { expiresIn });\n\n const metadata: DownloadMetadata = {\n bucket: resolvedBucket,\n fullPath: resolvedPath,\n name: resolvedPath.split(\"/\").pop() || resolvedPath,\n size: headResult.ContentLength || 0,\n contentType: headResult.ContentType || \"application/octet-stream\",\n customMetadata: headResult.Metadata || {}\n };\n\n return {\n url,\n metadata\n };\n } catch (error: unknown) {\n const s3Error = error as { name?: string; $metadata?: { httpStatusCode?: number } };\n if (s3Error.name === \"NotFound\" || s3Error.$metadata?.httpStatusCode === 404) {\n return {\n url: null,\n fileNotFound: true\n };\n }\n throw error;\n }\n }\n\n async getObject(key: string, bucket?: string): Promise<File | null> {\n // Handle s3:// and gs:// URLs\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(s3|gs):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n try {\n const s3 = await loadS3();\n const client = await this.getClient();\n\n const command = new s3.GetObjectCommand({\n Bucket: resolvedBucket,\n Key: resolvedPath\n });\n\n const response = await client.send(command);\n\n if (!response.Body) {\n return null;\n }\n\n // Convert stream to buffer\n const chunks: Uint8Array[] = [];\n for await (const chunk of response.Body as AsyncIterable<Uint8Array>) {\n chunks.push(chunk);\n }\n const buffer = Buffer.concat(chunks);\n\n const contentType = response.ContentType || \"application/octet-stream\";\n const fileName = resolvedPath.split(\"/\").pop() || resolvedPath;\n\n const blob = new Blob([buffer], { type: contentType });\n return new File([blob], fileName, { type: contentType });\n } catch (error: unknown) {\n const s3Error = error as { name?: string; $metadata?: { httpStatusCode?: number } };\n if (s3Error.name === \"NoSuchKey\" || s3Error.$metadata?.httpStatusCode === 404) {\n return null;\n }\n throw error;\n }\n }\n\n async deleteObject(key: string, bucket?: string): Promise<void> {\n // Handle s3:// and gs:// URLs\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(s3|gs):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n const s3 = await loadS3();\n const client = await this.getClient();\n\n const command = new s3.DeleteObjectCommand({\n Bucket: resolvedBucket,\n Key: resolvedPath\n });\n\n // Deleting something that is already gone is the outcome the caller\n // wanted, so it is not an error. AWS itself answers DeleteObject with\n // 204 for a key that never existed, but S3-compatible backends (MinIO,\n // Ceph, R2 in some configurations) do raise NoSuchKey — and the local\n // controller has always returned quietly in this case. Without this,\n // which of those a deployment happens to run decides whether\n // `DELETE /api/storage/:path` on a stale reference is a 200 or a 500.\n try {\n await client.send(command);\n } catch (error: unknown) {\n const s3Error = error as { name?: string; $metadata?: { httpStatusCode?: number } };\n if (s3Error.name === \"NoSuchKey\" || s3Error.$metadata?.httpStatusCode === 404) {\n return;\n }\n throw error;\n }\n }\n\n async listObjects(prefix: string, options?: {\n bucket?: string;\n maxResults?: number;\n pageToken?: string;\n }): Promise<StorageListResult> {\n const resolvedBucket = this.getBucket(options?.bucket);\n\n const s3 = await loadS3();\n const client = await this.getClient();\n\n const command = new s3.ListObjectsV2Command({\n Bucket: resolvedBucket,\n Prefix: prefix || undefined,\n MaxKeys: options?.maxResults ?? 1000,\n ContinuationToken: options?.pageToken,\n Delimiter: \"/\" // This gives us folder-like behavior\n });\n\n const response = await client.send(command);\n\n const items: StorageReference[] = (response.Contents || []).map(obj => ({\n bucket: resolvedBucket,\n fullPath: obj.Key || \"\",\n name: (obj.Key || \"\").split(\"/\").pop() || \"\",\n parent: null as never,\n root: null as never,\n toString: () => `s3://${resolvedBucket}/${obj.Key}`\n }));\n\n const prefixes: StorageReference[] = (response.CommonPrefixes || []).map(prefix => ({\n bucket: resolvedBucket,\n fullPath: prefix.Prefix || \"\",\n name: (prefix.Prefix || \"\").replace(/\\/$/, \"\").split(\"/\").pop() || \"\",\n parent: null as never,\n root: null as never,\n toString: () => `s3://${resolvedBucket}/${prefix.Prefix}`\n }));\n\n return {\n items,\n prefixes,\n nextPageToken: response.NextContinuationToken\n };\n }\n}\n"],"mappings":";;;;;;;AAeA,IAAI;AACJ,IAAI;AAEJ,eAAe,SAAS;CACpB,IAAI,CAAC,WACD,IAAI;EACA,YAAY,MAAM,OAAO;CAC7B,QAAQ;EACJ,MAAM,IAAI,MACN,sHAEJ;CACJ;CAEJ,OAAO;AACX;AAEA,eAAe,gBAAgB;CAC3B,IAAI,CAAC,kBACD,IAAI;EACA,mBAAmB,MAAM,OAAO;CACpC,QAAQ;EACJ,MAAM,IAAI,MACN,iIAEJ;CACJ;CAEJ,OAAO;AACX;;;;;AAMA,IAAa,sBAAb,MAA8D;CAC1D;CACA;CAEA,YAAY,QAAyB;EACjC,KAAK,SAAS;CAClB;;;;CAKA,MAAc,YAAmC;EAC7C,IAAI,CAAC,KAAK,SAAS;GACf,MAAM,KAAK,MAAM,OAAO;GACxB,KAAK,UAAU,IAAI,GAAG,SAAS;IAC3B,QAAQ,KAAK,OAAO,UAAU;IAC9B,UAAU,KAAK,OAAO;IACtB,gBAAgB,KAAK,OAAO,kBAAkB,CAAC,CAAC,KAAK,OAAO;IAC5D,aAAa;KACT,aAAa,KAAK,OAAO;KACzB,iBAAiB,KAAK,OAAO;IACjC;GACJ,CAAC;EACL;EACA,OAAO,KAAK;CAChB;CAEA,UAAgB;EACZ,OAAO;CACX;;;;CAKA,aAAqB,MAAkB;EACnC,MAAM,UAAU,KAAK,OAAO,eAAA;EAC5B,IAAI,KAAK,OAAO,SACZ,MAAM,IAAI,MAAM,aAAa,KAAK,KAAK,gCAAgC,SAAS;EAGpF,IAAI,KAAK,OAAO,oBAAoB,KAAK,OAAO,iBAAiB,SAAS;OAClE,CAAC,KAAK,OAAO,iBAAiB,SAAS,KAAK,IAAI,GAChD,MAAM,IAAI,MAAM,aAAa,KAAK,KAAK,kCAAkC,KAAK,OAAO,iBAAiB,KAAK,IAAI,GAAG;EAAA;CAG9H;;;;CAKA,UAAkB,QAAyB;EAGvC,IAAI,CAAC,UAAU,WAAW,WAAW,OAAO,KAAK,OAAO;EACxD,OAAO;CACX;CAEA,MAAM,UAAU,EACZ,MACA,KACA,UACA,UAC2C;EAC3C,KAAK,aAAa,IAAI;EAEtB,MAAM,aAAa,KAAK,UAAU,MAAM;EAGxC,MAAM,cAAc,MAAM,KAAK,YAAY;EAC3C,MAAM,SAAS,OAAO,KAAK,WAAW;EAEtC,MAAM,KAAK,MAAM,OAAO;EACxB,MAAM,SAAS,MAAM,KAAK,UAAU;EAEpC,MAAM,UAAU,IAAI,GAAG,iBAAiB;GACpC,QAAQ;GACR,KAAK;GACL,MAAM;GACN,aAAa,KAAK;GAClB,UAAU,WAAW,KAAK,gBAAgB,QAAQ,IAAI,KAAA;EAC1D,CAAC;EAED,MAAM,OAAO,KAAK,OAAO;EAEzB,OAAO;GACH;GACA,QAAQ;GACR,YAAY,QAAQ,WAAW,GAAG;EACtC;CACJ;;;;CAKA,gBAAwB,UAA2D;EAC/E,MAAM,YAAoC,CAAC;EAC3C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,GAC9C,IAAI,OAAO,UAAU,UACjB,UAAU,OAAO;OACd,IAAI,UAAU,KAAA,KAAa,UAAU,MACxC,UAAU,OAAO,KAAK,UAAU,KAAK;EAG7C,OAAO;CACX;CAEA,MAAM,aAAa,KAAa,QAA0C;EAEtE,IAAI,eAAe;EACnB,IAAI,iBAAiB,KAAK,UAAU,MAAM;EAE1C,MAAM,QAAQ,IAAI,MAAM,eAAe;EACvC,IAAI,OAAO;GACP,MAAM,iBAAiB,MAAM,EAAE,CAAC;GAChC,MAAM,kBAAkB,IAAI,UAAU,cAAc;GACpD,MAAM,aAAa,gBAAgB,QAAQ,GAAG;GAC9C,IAAI,aAAa,GAAG;IAChB,iBAAiB,gBAAgB,UAAU,GAAG,UAAU;IACxD,eAAe,gBAAgB,UAAU,aAAa,CAAC;GAC3D;EACJ;EAEA,IAAI;GACA,MAAM,KAAK,MAAM,OAAO;GACxB,MAAM,YAAY,MAAM,cAAc;GACtC,MAAM,SAAS,MAAM,KAAK,UAAU;GAGpC,MAAM,cAAc,IAAI,GAAG,kBAAkB;IACzC,QAAQ;IACR,KAAK;GACT,CAAC;GAED,MAAM,aAAa,MAAM,OAAO,KAAK,WAAW;GAGhD,MAAM,aAAa,IAAI,GAAG,iBAAiB;IACvC,QAAQ;IACR,KAAK;GACT,CAAC;GAED,MAAM,YAAY,KAAK,OAAO,uBAAuB;GAYrD,OAAO;IACH,KAAA,MAZc,UAAU,aAAa,QAAQ,YAAY,EAAE,UAAU,CAAC;IAatE,UAAA;KAVA,QAAQ;KACR,UAAU;KACV,MAAM,aAAa,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;KACvC,MAAM,WAAW,iBAAiB;KAClC,aAAa,WAAW,eAAe;KACvC,gBAAgB,WAAW,YAAY,CAAC;IAKxC;GACJ;EACJ,SAAS,OAAgB;GACrB,MAAM,UAAU;GAChB,IAAI,QAAQ,SAAS,cAAc,QAAQ,WAAW,mBAAmB,KACrE,OAAO;IACH,KAAK;IACL,cAAc;GAClB;GAEJ,MAAM;EACV;CACJ;CAEA,MAAM,UAAU,KAAa,QAAuC;EAEhE,IAAI,eAAe;EACnB,IAAI,iBAAiB,KAAK,UAAU,MAAM;EAE1C,MAAM,QAAQ,IAAI,MAAM,eAAe;EACvC,IAAI,OAAO;GACP,MAAM,iBAAiB,MAAM,EAAE,CAAC;GAChC,MAAM,kBAAkB,IAAI,UAAU,cAAc;GACpD,MAAM,aAAa,gBAAgB,QAAQ,GAAG;GAC9C,IAAI,aAAa,GAAG;IAChB,iBAAiB,gBAAgB,UAAU,GAAG,UAAU;IACxD,eAAe,gBAAgB,UAAU,aAAa,CAAC;GAC3D;EACJ;EAEA,IAAI;GACA,MAAM,KAAK,MAAM,OAAO;GACxB,MAAM,SAAS,MAAM,KAAK,UAAU;GAEpC,MAAM,UAAU,IAAI,GAAG,iBAAiB;IACpC,QAAQ;IACR,KAAK;GACT,CAAC;GAED,MAAM,WAAW,MAAM,OAAO,KAAK,OAAO;GAE1C,IAAI,CAAC,SAAS,MACV,OAAO;GAIX,MAAM,SAAuB,CAAC;GAC9B,WAAW,MAAM,SAAS,SAAS,MAC/B,OAAO,KAAK,KAAK;GAErB,MAAM,SAAS,OAAO,OAAO,MAAM;GAEnC,MAAM,cAAc,SAAS,eAAe;GAC5C,MAAM,WAAW,aAAa,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;GAElD,MAAM,OAAO,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE,MAAM,YAAY,CAAC;GACrD,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,UAAU,EAAE,MAAM,YAAY,CAAC;EAC3D,SAAS,OAAgB;GACrB,MAAM,UAAU;GAChB,IAAI,QAAQ,SAAS,eAAe,QAAQ,WAAW,mBAAmB,KACtE,OAAO;GAEX,MAAM;EACV;CACJ;CAEA,MAAM,aAAa,KAAa,QAAgC;EAE5D,IAAI,eAAe;EACnB,IAAI,iBAAiB,KAAK,UAAU,MAAM;EAE1C,MAAM,QAAQ,IAAI,MAAM,eAAe;EACvC,IAAI,OAAO;GACP,MAAM,iBAAiB,MAAM,EAAE,CAAC;GAChC,MAAM,kBAAkB,IAAI,UAAU,cAAc;GACpD,MAAM,aAAa,gBAAgB,QAAQ,GAAG;GAC9C,IAAI,aAAa,GAAG;IAChB,iBAAiB,gBAAgB,UAAU,GAAG,UAAU;IACxD,eAAe,gBAAgB,UAAU,aAAa,CAAC;GAC3D;EACJ;EAEA,MAAM,KAAK,MAAM,OAAO;EACxB,MAAM,SAAS,MAAM,KAAK,UAAU;EAEpC,MAAM,UAAU,IAAI,GAAG,oBAAoB;GACvC,QAAQ;GACR,KAAK;EACT,CAAC;EASD,IAAI;GACA,MAAM,OAAO,KAAK,OAAO;EAC7B,SAAS,OAAgB;GACrB,MAAM,UAAU;GAChB,IAAI,QAAQ,SAAS,eAAe,QAAQ,WAAW,mBAAmB,KACtE;GAEJ,MAAM;EACV;CACJ;CAEA,MAAM,YAAY,QAAgB,SAIH;EAC3B,MAAM,iBAAiB,KAAK,UAAU,SAAS,MAAM;EAErD,MAAM,KAAK,MAAM,OAAO;EACxB,MAAM,SAAS,MAAM,KAAK,UAAU;EAEpC,MAAM,UAAU,IAAI,GAAG,qBAAqB;GACxC,QAAQ;GACR,QAAQ,UAAU,KAAA;GAClB,SAAS,SAAS,cAAc;GAChC,mBAAmB,SAAS;GAC5B,WAAW;EACf,CAAC;EAED,MAAM,WAAW,MAAM,OAAO,KAAK,OAAO;EAoB1C,OAAO;GACH,QAnB+B,SAAS,YAAY,CAAC,EAAA,CAAG,KAAI,SAAQ;IACpE,QAAQ;IACR,UAAU,IAAI,OAAO;IACrB,OAAO,IAAI,OAAO,GAAA,CAAI,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;IAC1C,QAAQ;IACR,MAAM;IACN,gBAAgB,QAAQ,eAAe,GAAG,IAAI;GAClD,EAYI;GACA,WAXkC,SAAS,kBAAkB,CAAC,EAAA,CAAG,KAAI,YAAW;IAChF,QAAQ;IACR,UAAU,OAAO,UAAU;IAC3B,OAAO,OAAO,UAAU,GAAA,CAAI,QAAQ,OAAO,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;IACnE,QAAQ;IACR,MAAM;IACN,gBAAgB,QAAQ,eAAe,GAAG,OAAO;GACrD,EAII;GACA,eAAe,SAAS;EAC5B;CACJ;AACJ"}
1
+ {"version":3,"file":"S3StorageController-CWvvrRpF.js","names":[],"sources":["../src/storage/S3StorageController.ts"],"sourcesContent":["/**\n * S3-compatible storage controller (works with AWS S3 and MinIO)\n */\n\nimport type { S3Client as S3ClientType, } from \"@aws-sdk/client-s3\";\nimport { DEFAULT_MAX_FILE_SIZE, S3StorageConfig, StorageController } from \"./types\";\nimport {\n DownloadConfig,\n DownloadMetadata,\n StorageListResult,\n StorageReference,\n UploadFileProps,\n UploadFileResult\n} from \"@rebasepro/types\";\n\nlet _s3Module: typeof import(\"@aws-sdk/client-s3\") | undefined;\nlet _presignerModule: typeof import(\"@aws-sdk/s3-request-presigner\") | undefined;\n\nasync function loadS3() {\n if (!_s3Module) {\n try {\n _s3Module = await import(\"@aws-sdk/client-s3\");\n } catch {\n throw new Error(\n \"@aws-sdk/client-s3 is required for S3 storage. \" +\n \"Install it: pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner\"\n );\n }\n }\n return _s3Module;\n}\n\nasync function loadPresigner() {\n if (!_presignerModule) {\n try {\n _presignerModule = await import(\"@aws-sdk/s3-request-presigner\");\n } catch {\n throw new Error(\n \"@aws-sdk/s3-request-presigner is required for S3 storage. \" +\n \"Install it: pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner\"\n );\n }\n }\n return _presignerModule;\n}\n\n/**\n * S3-compatible storage implementation\n * Works with AWS S3 and MinIO (with forcePathStyle option)\n */\nexport class S3StorageController implements StorageController {\n private config: S3StorageConfig;\n private _client: S3ClientType | undefined;\n\n constructor(config: S3StorageConfig) {\n this.config = config;\n }\n\n /**\n * Lazily create and cache the S3 client on first use\n */\n private async getClient(): Promise<S3ClientType> {\n if (!this._client) {\n const s3 = await loadS3();\n this._client = new s3.S3Client({\n region: this.config.region || \"us-east-1\",\n endpoint: this.config.endpoint,\n forcePathStyle: this.config.forcePathStyle ?? !!this.config.endpoint, // Auto-enable for custom endpoints (MinIO)\n credentials: {\n accessKeyId: this.config.accessKeyId,\n secretAccessKey: this.config.secretAccessKey\n }\n });\n }\n return this._client;\n }\n\n getType(): \"s3\" {\n return \"s3\";\n }\n\n /**\n * Validate file before upload\n */\n private validateFile(file: File): void {\n const maxSize = this.config.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;\n if (file.size > maxSize) {\n throw new Error(`File size ${file.size} exceeds maximum allowed size ${maxSize}`);\n }\n\n if (this.config.allowedMimeTypes && this.config.allowedMimeTypes.length > 0) {\n if (!this.config.allowedMimeTypes.includes(file.type)) {\n throw new Error(`File type ${file.type} is not allowed. Allowed types: ${this.config.allowedMimeTypes.join(\", \")}`);\n }\n }\n }\n\n /**\n * Get the bucket name - either from parameter or config\n */\n private getBucket(bucket?: string): string {\n // \"default\" is a logical bucket name used by local storage;\n // for S3 it should resolve to the configured bucket.\n if (!bucket || bucket === \"default\") return this.config.bucket;\n return bucket;\n }\n\n async putObject({\n file,\n key,\n metadata,\n bucket\n }: UploadFileProps): Promise<UploadFileResult> {\n this.validateFile(file);\n\n const usedBucket = this.getBucket(bucket);\n\n // Convert File to Buffer\n const arrayBuffer = await file.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n const s3 = await loadS3();\n const client = await this.getClient();\n\n const command = new s3.PutObjectCommand({\n Bucket: usedBucket,\n Key: key,\n Body: buffer,\n ContentType: file.type,\n Metadata: metadata ? this.flattenMetadata(metadata) : undefined\n });\n\n await client.send(command);\n\n return {\n key,\n bucket: usedBucket,\n storageUrl: `s3://${usedBucket}/${key}`\n };\n }\n\n /**\n * Flatten nested metadata to string values (S3 requirement)\n */\n private flattenMetadata(metadata: Record<string, unknown>): Record<string, string> {\n const flattened: Record<string, string> = {};\n for (const [key, value] of Object.entries(metadata)) {\n if (typeof value === \"string\") {\n flattened[key] = value;\n } else if (value !== undefined && value !== null) {\n flattened[key] = JSON.stringify(value);\n }\n }\n return flattened;\n }\n\n async getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig> {\n // Handle s3:// and gs:// URLs for backward compatibility\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(s3|gs):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n try {\n const s3 = await loadS3();\n const presigner = await loadPresigner();\n const client = await this.getClient();\n\n // First check if the object exists and get metadata\n const headCommand = new s3.HeadObjectCommand({\n Bucket: resolvedBucket,\n Key: resolvedPath\n });\n\n const headResult = await client.send(headCommand);\n\n // Generate a signed URL\n const getCommand = new s3.GetObjectCommand({\n Bucket: resolvedBucket,\n Key: resolvedPath\n });\n\n const expiresIn = this.config.signedUrlExpiration ?? 3600;\n const url = await presigner.getSignedUrl(client, getCommand, { expiresIn });\n\n const metadata: DownloadMetadata = {\n bucket: resolvedBucket,\n fullPath: resolvedPath,\n name: resolvedPath.split(\"/\").pop() || resolvedPath,\n size: headResult.ContentLength || 0,\n contentType: headResult.ContentType || \"application/octet-stream\",\n customMetadata: headResult.Metadata || {}\n };\n\n return {\n url,\n metadata\n };\n } catch (error: unknown) {\n const s3Error = error as { name?: string; $metadata?: { httpStatusCode?: number } };\n if (s3Error.name === \"NotFound\" || s3Error.$metadata?.httpStatusCode === 404) {\n return {\n url: null,\n fileNotFound: true\n };\n }\n throw error;\n }\n }\n\n async getObject(key: string, bucket?: string): Promise<File | null> {\n // Handle s3:// and gs:// URLs\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(s3|gs):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n try {\n const s3 = await loadS3();\n const client = await this.getClient();\n\n const command = new s3.GetObjectCommand({\n Bucket: resolvedBucket,\n Key: resolvedPath\n });\n\n const response = await client.send(command);\n\n if (!response.Body) {\n return null;\n }\n\n // Convert stream to buffer\n const chunks: Uint8Array[] = [];\n for await (const chunk of response.Body as AsyncIterable<Uint8Array>) {\n chunks.push(chunk);\n }\n const buffer = Buffer.concat(chunks);\n\n const contentType = response.ContentType || \"application/octet-stream\";\n const fileName = resolvedPath.split(\"/\").pop() || resolvedPath;\n\n const blob = new Blob([buffer], { type: contentType });\n return new File([blob], fileName, { type: contentType });\n } catch (error: unknown) {\n const s3Error = error as { name?: string; $metadata?: { httpStatusCode?: number } };\n if (s3Error.name === \"NoSuchKey\" || s3Error.$metadata?.httpStatusCode === 404) {\n return null;\n }\n throw error;\n }\n }\n\n async deleteObject(key: string, bucket?: string): Promise<void> {\n // Handle s3:// and gs:// URLs\n let resolvedPath = key;\n let resolvedBucket = this.getBucket(bucket);\n\n const match = key.match(/^(s3|gs):\\/\\//);\n if (match) {\n const protocolLength = match[0].length;\n const withoutProtocol = key.substring(protocolLength);\n const firstSlash = withoutProtocol.indexOf(\"/\");\n if (firstSlash > 0) {\n resolvedBucket = withoutProtocol.substring(0, firstSlash);\n resolvedPath = withoutProtocol.substring(firstSlash + 1);\n }\n }\n\n const s3 = await loadS3();\n const client = await this.getClient();\n\n const command = new s3.DeleteObjectCommand({\n Bucket: resolvedBucket,\n Key: resolvedPath\n });\n\n // Deleting something that is already gone is the outcome the caller\n // wanted, so it is not an error. AWS itself answers DeleteObject with\n // 204 for a key that never existed, but S3-compatible backends (MinIO,\n // Ceph, R2 in some configurations) do raise NoSuchKey — and the local\n // controller has always returned quietly in this case. Without this,\n // which of those a deployment happens to run decides whether\n // `DELETE /api/storage/:path` on a stale reference is a 200 or a 500.\n try {\n await client.send(command);\n } catch (error: unknown) {\n const s3Error = error as { name?: string; $metadata?: { httpStatusCode?: number } };\n if (s3Error.name === \"NoSuchKey\" || s3Error.$metadata?.httpStatusCode === 404) {\n return;\n }\n throw error;\n }\n }\n\n async listObjects(prefix: string, options?: {\n bucket?: string;\n maxResults?: number;\n pageToken?: string;\n }): Promise<StorageListResult> {\n const resolvedBucket = this.getBucket(options?.bucket);\n\n const s3 = await loadS3();\n const client = await this.getClient();\n\n const command = new s3.ListObjectsV2Command({\n Bucket: resolvedBucket,\n Prefix: prefix || undefined,\n MaxKeys: options?.maxResults ?? 1000,\n ContinuationToken: options?.pageToken,\n Delimiter: \"/\" // This gives us folder-like behavior\n });\n\n const response = await client.send(command);\n\n const items: StorageReference[] = (response.Contents || []).map(obj => ({\n bucket: resolvedBucket,\n fullPath: obj.Key || \"\",\n name: (obj.Key || \"\").split(\"/\").pop() || \"\",\n parent: null as never,\n root: null as never,\n toString: () => `s3://${resolvedBucket}/${obj.Key}`\n }));\n\n const prefixes: StorageReference[] = (response.CommonPrefixes || []).map(prefix => ({\n bucket: resolvedBucket,\n fullPath: prefix.Prefix || \"\",\n name: (prefix.Prefix || \"\").replace(/\\/$/, \"\").split(\"/\").pop() || \"\",\n parent: null as never,\n root: null as never,\n toString: () => `s3://${resolvedBucket}/${prefix.Prefix}`\n }));\n\n return {\n items,\n prefixes,\n nextPageToken: response.NextContinuationToken\n };\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
- import { createRequire as __createRequire } from "module";
2
- import "process";
3
- __createRequire(import.meta.url);
1
+ import { createRequire as __rebaseCreateRequire } from "module";
2
+ import __rebaseProcess from "process";
3
+ globalThis.process ??= __rebaseProcess;
4
+ __rebaseCreateRequire(import.meta.url);
4
5
  //#region ../types/src/types/admin_block.ts
5
6
  /**
6
7
  * The keys of a collection's admin block, as data.
@@ -74,8 +75,7 @@ var ADMIN_COLLECTION_KEYS = [
74
75
  "selectionController",
75
76
  "selectionEnabled",
76
77
  "sideDialogWidth",
77
- "sort",
78
- "titleProperty"
78
+ "sort"
79
79
  ];
80
80
  /**
81
81
  * Every key that belongs inside a *property's* `admin` block, as data.
@@ -182,4 +182,4 @@ function nestAdminPropertyKeys(property) {
182
182
  //#endregion
183
183
  export { nestAdminPropertyKeys as i, ADMIN_PROPERTY_KEYS as n, nestAdminCollectionKeys as r, ADMIN_COLLECTION_KEYS as t };
184
184
 
185
- //# sourceMappingURL=admin_block-DLILvzle.js.map
185
+ //# sourceMappingURL=admin_block-BeypnEfb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin_block-BeypnEfb.js","names":[],"sources":["../../types/src/types/admin_block.ts"],"sourcesContent":["/**\n * The keys of a collection's admin block, as data.\n *\n * There is no *type* for the block in this package any more, and that is the point:\n * `admin` is not declared on `BaseCollectionConfig` or on any property here, so a\n * BaaS install cannot even write one. `@rebasepro/admin-types` adds the field back by\n * declaration merging, which is why installing it is what makes the admin surface\n * appear.\n *\n * The *list* still has to live here, because three runtime consumers need it and two\n * of them are core — see below.\n */\n\n/**\n * Every key that belongs inside a collection's `admin` block, as data.\n *\n * The type that describes these fields is `AdminCollectionOptions` in\n * `@rebasepro/admin-types`, and it is erased at build time — but three runtime\n * consumers need the list, and two of them are core:\n *\n * - `serializeCollections`, to drop the block from the contract\n * - the ts-morph schema editor in `@rebasepro/server`, which rewrites collection\n * files on disk from the admin panel and has to know where each key goes. A key\n * missing from this list gets written to the *top level* of the file, where the\n * backend ignores it and the panel never finds it again.\n * - the `collections-admin-block` codemod\n *\n * `@rebasepro/admin-types` re-exports this and asserts it names only real option\n * keys; the count is pinned by a test there.\n *\n * @group Models\n */\nexport const ADMIN_COLLECTION_KEYS = [\n \"Actions\",\n \"additionalFields\",\n \"alwaysApplyDefaultValues\",\n \"components\",\n \"customViews\",\n \"defaultEntityAction\",\n \"defaultFilter\",\n \"defaultSelectedView\",\n \"defaultSize\",\n \"defaultViewMode\",\n \"disableDefaultActions\",\n \"display\",\n \"enabledViews\",\n \"entityActions\",\n \"entityViews\",\n \"exportable\",\n \"filterPresets\",\n \"fixedFilter\",\n \"form\",\n \"formAutoSave\",\n \"formView\",\n \"group\",\n \"hideFromEntityViews\",\n \"hideFromNavigation\",\n \"hideIdFromCollection\",\n \"hideIdFromForm\",\n \"icon\",\n \"includeJsonView\",\n \"inlineEditing\",\n \"kanban\",\n \"listProperties\",\n \"localChangesBackup\",\n \"openEntityMode\",\n \"orderProperty\",\n \"pagination\",\n \"previewProperties\",\n \"propertiesOrder\",\n \"selectionController\",\n \"selectionEnabled\",\n \"sideDialogWidth\",\n \"sort\"\n] as const;\n\n/** A key of a collection's `admin` block. @group Models */\nexport type AdminCollectionKey = typeof ADMIN_COLLECTION_KEYS[number];\n\n/**\n * Every key that belongs inside a *property's* `admin` block, as data.\n *\n * The union of `AdminPropertyOptions` and its per-type extensions\n * (`AdminStringOptions`, `AdminArrayOptions`, …) in `@rebasepro/admin-types`.\n * It lives here for the same reason {@link ADMIN_COLLECTION_KEYS} does: the\n * runtime consumers are core packages that the BaaS guard forbids from\n * importing `@rebasepro/admin-types`. Here it is the boot-time collection\n * validator in `@rebasepro/server`, which has to tell \"you left `readOnly` at\n * the top of the property, where nothing reads it\" apart from \"you invented a\n * key we have never heard of\".\n *\n * `@rebasepro/admin-types` re-exports this and asserts it names only real\n * option keys.\n *\n * @group Models\n */\nexport const ADMIN_PROPERTY_KEYS = [\n \"canAddElements\",\n \"clearable\",\n \"columnWidth\",\n \"customProps\",\n \"disabled\",\n \"expanded\",\n \"Field\",\n \"Filter\",\n \"filterOperators\",\n \"fixedFilter\",\n \"hideFromCollection\",\n \"includeEntityLink\",\n \"includeId\",\n \"markdown\",\n \"minimalistView\",\n \"multiline\",\n \"Preview\",\n \"previewAsTag\",\n \"previewProperties\",\n \"readOnly\",\n \"sortable\",\n \"span\",\n \"spreadChildren\",\n \"urlPreview\",\n \"widget\",\n] as const;\n\n/** A key of a property's `admin` block. @group Models */\nexport type AdminPropertyKey = typeof ADMIN_PROPERTY_KEYS[number];\n\n/**\n * Move flattened admin keys back down into the `admin` block.\n *\n * The admin panel works with a *flat* view model — the block merged onto the\n * collection — so what comes back from a form has `icon` and `defaultViewMode`\n * at the top level while `admin` still holds whatever the file was loaded with.\n * This is the way back.\n *\n * **The top-level value wins.** It is the one the form just wrote; the block is\n * the copy the collection was loaded with, and preferring it resolves every edit\n * in favour of the value the user changed away from.\n *\n * This lives here, next to the key lists, because it had two implementations —\n * `toAdminCollectionConfig` in `@rebasepro/admin-types` and `nestAdminKeys` in\n * `@rebasepro/server`'s schema editor — that agreed on everything except that\n * precedence, which is the only part that decides whether a save is visible.\n *\n * @group Models\n */\nexport function nestAdminKeysOf(\n source: Record<string, unknown>,\n adminKeys: readonly string[]\n): Record<string, unknown> {\n const keys = new Set<string>(adminKeys);\n const top: Record<string, unknown> = {};\n const block: Record<string, unknown> = { ...((source.admin as Record<string, unknown> | undefined) ?? {}) };\n\n for (const [key, value] of Object.entries(source)) {\n if (key === \"admin\") continue;\n if (keys.has(key)) block[key] = value;\n else top[key] = value;\n }\n\n if (Object.keys(block).length > 0) top.admin = block;\n return top;\n}\n\n/**\n * {@link nestAdminKeysOf} for a collection.\n *\n * @group Models\n */\nexport function nestAdminCollectionKeys(collection: Record<string, unknown>): Record<string, unknown> {\n return nestAdminKeysOf(collection, ADMIN_COLLECTION_KEYS);\n}\n\n/**\n * {@link nestAdminKeysOf} for a property, applied to its children too.\n *\n * A map property carries `properties`, an array property carries `of`, and both\n * hold properties with `admin` blocks of their own. A flat `readOnly` left on a\n * child is as dead — and as fatal at the next boot — as one left on the parent,\n * so the walk goes all the way down.\n *\n * @group Models\n */\nexport function nestAdminPropertyKeys(property: Record<string, unknown>): Record<string, unknown> {\n const nested = nestAdminKeysOf(property, ADMIN_PROPERTY_KEYS);\n\n const children = nested.properties;\n if (children && typeof children === \"object\" && !Array.isArray(children)) {\n nested.properties = Object.fromEntries(\n Object.entries(children as Record<string, unknown>).map(([key, child]) => [\n key,\n child && typeof child === \"object\" && !Array.isArray(child)\n ? nestAdminPropertyKeys(child as Record<string, unknown>)\n : child\n ])\n );\n }\n\n const of = nested.of;\n if (Array.isArray(of)) {\n nested.of = of.map(entry => entry && typeof entry === \"object\" && !Array.isArray(entry)\n ? nestAdminPropertyKeys(entry as Record<string, unknown>)\n : entry);\n } else if (of && typeof of === \"object\") {\n nested.of = nestAdminPropertyKeys(of as Record<string, unknown>);\n }\n\n return nested;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,wBAAwB;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;;;;;;;;;;;;;;;;;AAsBA,IAAa,sBAAsB;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,gBACZ,QACA,WACuB;CACvB,MAAM,OAAO,IAAI,IAAY,SAAS;CACtC,MAAM,MAA+B,CAAC;CACtC,MAAM,QAAiC,EAAE,GAAK,OAAO,SAAiD,CAAC,EAAG;CAE1G,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;EAC/C,IAAI,QAAQ,SAAS;EACrB,IAAI,KAAK,IAAI,GAAG,GAAG,MAAM,OAAO;OAC3B,IAAI,OAAO;CACpB;CAEA,IAAI,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,GAAG,IAAI,QAAQ;CAC/C,OAAO;AACX;;;;;;AAOA,SAAgB,wBAAwB,YAA8D;CAClG,OAAO,gBAAgB,YAAY,qBAAqB;AAC5D;;;;;;;;;;;AAYA,SAAgB,sBAAsB,UAA4D;CAC9F,MAAM,SAAS,gBAAgB,UAAU,mBAAmB;CAE5D,MAAM,WAAW,OAAO;CACxB,IAAI,YAAY,OAAO,aAAa,YAAY,CAAC,MAAM,QAAQ,QAAQ,GACnE,OAAO,aAAa,OAAO,YACvB,OAAO,QAAQ,QAAmC,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,CACtE,KACA,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IACpD,sBAAsB,KAAgC,IACtD,KACV,CAAC,CACL;CAGJ,MAAM,KAAK,OAAO;CAClB,IAAI,MAAM,QAAQ,EAAE,GAChB,OAAO,KAAK,GAAG,KAAI,UAAS,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAChF,sBAAsB,KAAgC,IACtD,KAAK;MACR,IAAI,MAAM,OAAO,OAAO,UAC3B,OAAO,KAAK,sBAAsB,EAA6B;CAGnE,OAAO;AACX"}
@@ -16,6 +16,29 @@ export declare class AstSchemaEditor {
16
16
  * Sanitize collectionId to prevent path traversal attacks.
17
17
  * Only allows alphanumeric characters, underscores, and hyphens.
18
18
  */
19
+ /**
20
+ * The variable name the generated file binds the collection to.
21
+ *
22
+ * `sanitizeCollectionId` guards the FILENAME, and permits hyphens and a
23
+ * leading digit — both legal in a filename and neither legal in a
24
+ * JavaScript identifier. So a collection created from the admin panel as
25
+ * `my-notes` (the documented slug shape) or `2024 Archive` (auto-slugged to
26
+ * `2024_archive`) wrote:
27
+ *
28
+ * const my-notesCollection: CollectionConfig = … "',' expected"
29
+ * const 2024_archiveCollection: CollectionConfig = … "Numeric separators
30
+ * are not allowed here"
31
+ *
32
+ * The panel reported success, and the next boot failed for EVERY collection
33
+ * in the directory — the loader imports all of them — while the editor could
34
+ * no longer parse the file it had just written, so it could not fix itself.
35
+ *
36
+ * Separators camel-case rather than vanish, so `my-notes` and `my_notes`
37
+ * stay distinct; a leading digit is prefixed rather than stripped, so
38
+ * `2024_archive` stays distinct from `archive`. Byte-identical for every
39
+ * slug that already produced a valid identifier.
40
+ */
41
+ private static collectionVarName;
19
42
  private sanitizeCollectionId;
20
43
  /**
21
44
  * Resolve a file path and ensure it falls within the collectionsDir.
@@ -1,6 +1,6 @@
1
1
  import { Hono } from "hono";
2
2
  import { type CollectionConfig } from "@rebasepro/types";
3
- import type { HonoEnv } from "./types";
3
+ import type { HonoEnv } from "./types.js";
4
4
  /**
5
5
  * The project contract endpoint.
6
6
  *
@@ -1,5 +1,5 @@
1
1
  import type { ErrorHandler } from "hono";
2
- import type { HonoEnv } from "./types";
2
+ import type { HonoEnv } from "./types.js";
3
3
  /**
4
4
  * Standardized API error class.
5
5
  * Throw this from any route handler — the errorHandler middleware
@@ -2,6 +2,6 @@
2
2
  * API generation infrastructure for Rebase
3
3
  * Automatically generates REST APIs from CollectionConfig definitions
4
4
  */
5
- export * from "./rest";
6
- export * from "./types";
7
- export * from "./errors";
5
+ export * from "./rest/index.js";
6
+ export * from "./types.js";
7
+ export * from "./errors.js";
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Creating and altering collections against a running backend.
3
+ *
4
+ * The existing schema editor rewrites collection source and is off in
5
+ * production, for an honest reason: a deployed server's files are rebuilt from
6
+ * the repository on every deploy, so an edit there would be discarded. These
7
+ * routes are the answer to that — they commit the change to the repository the
8
+ * server is running in, and *then* apply the DDL, so the edit survives the next
9
+ * deploy because the deploy is built from it.
10
+ *
11
+ * Two routes, and the first one is the important one.
12
+ *
13
+ * ## Why `plan` exists separately from `apply`
14
+ *
15
+ * `POST /plan` has no side effects and answers what would happen. That is not a
16
+ * convenience: a schema change has three possible verdicts and two of them are
17
+ * refusals, so a UI that could only try-and-see would be asking people to
18
+ * discover `needs-migration` by pressing a button on a live database.
19
+ *
20
+ * It also surfaces the `diverges` cases, which are the ones nothing else in the
21
+ * system reports — a required property that will arrive nullable, an enum value
22
+ * that will never land. Those apply "successfully" today and leave a database
23
+ * that does not match its configuration.
24
+ *
25
+ * ## What these routes will not do
26
+ *
27
+ * Anything the boot-time ensure path cannot express. No drops, no type changes,
28
+ * no primary keys. The planner refuses them and this refuses to route around it
29
+ * — `applySchemaChange` re-checks rather than trusting the plan it is handed.
30
+ */
31
+ import { Hono } from "hono";
32
+ import { type CollectionConfig, type DatabaseAdmin, type SchemaCommitPaths } from "@rebasepro/types";
33
+ import { HonoEnv } from "./types.js";
34
+ import { type SchemaEditRepository } from "../schema-edit/apply-schema-change.js";
35
+ import { type SchemaEditPolicy } from "../schema-edit/schema-edit-permissions.js";
36
+ export interface LiveSchemaRoutesConfig {
37
+ /**
38
+ * The collections the running server was built from — the `before` side.
39
+ * Read at request time, because the registry is assembled after the routes
40
+ * are mounted.
41
+ */
42
+ getCollections: () => CollectionConfig[];
43
+ /** The driver's admin, for planning and for running the DDL. */
44
+ getAdmin: () => DatabaseAdmin | undefined;
45
+ /**
46
+ * The working tree to commit into, or undefined when there is none — a
47
+ * bundle deployment has compiled output and no source, and this stays off
48
+ * there rather than inventing somewhere to write.
49
+ */
50
+ getRepository: (author?: {
51
+ name: string;
52
+ email: string;
53
+ }) => SchemaEditRepository | undefined;
54
+ /**
55
+ * Rewrite the collection source for a proposed change, returning the files
56
+ * that changed. Supplied by the caller because it is the AST editor's job,
57
+ * and that needs `ts-morph`, which is an optional dependency.
58
+ */
59
+ writeSource?: (change: ProposedChange) => Promise<{
60
+ path: string;
61
+ contents: string;
62
+ }[]>;
63
+ /**
64
+ * Which paths {@link writeSource} will touch, without touching them.
65
+ *
66
+ * The dirty-tree check needs them *before* the write, or it reads the
67
+ * change's own edit as somebody else's work in progress and refuses.
68
+ */
69
+ sourcePathsFor?: (change: ProposedChange) => string[];
70
+ /**
71
+ * Where this project's generated artifacts belong, relative to the
72
+ * repository.
73
+ *
74
+ * Absent for a project that *is* the repository, which is what the defaults
75
+ * describe. Present for one in a subdirectory, where the defaults would
76
+ * write `backend/` and `drizzle/` beside `.git` and leave the project's real
77
+ * generated files untouched — committing a source change alongside a stale
78
+ * schema, which is the failure committing the whole change exists to avoid.
79
+ */
80
+ commitPaths?: Partial<SchemaCommitPaths>;
81
+ /**
82
+ * Whether this project replays versioned migrations to build an
83
+ * environment.
84
+ *
85
+ * If it does, applying here is not the whole job: the DDL runs against
86
+ * *this* database and `drizzle/schema.sql` is committed, but no migration
87
+ * is written — a server cannot mint one, since that needs Atlas and a
88
+ * throwaway database. The next environment built by replaying migrations
89
+ * would not have this change, and nothing would have said so.
90
+ */
91
+ usesVersionedMigrations?: () => boolean;
92
+ /**
93
+ * Who may apply a change, as opposed to preview one.
94
+ *
95
+ * See `schema-edit-permissions.ts`. The short version: applying writes a
96
+ * commit, a commit needs an author, and a credential is not an author.
97
+ */
98
+ policy?: SchemaEditPolicy;
99
+ }
100
+ /** What the panel posts: one collection, in the shape it should end up. */
101
+ export interface ProposedChange {
102
+ collectionId: string;
103
+ /** The whole collection as it should be after the edit. */
104
+ collection: Record<string, unknown>;
105
+ }
106
+ /**
107
+ * The git identity for the caller.
108
+ *
109
+ * `undefined` when the request carries no user — a service key, say — which
110
+ * lets the repository fall back to its own git config rather than inventing a
111
+ * name for somebody.
112
+ */
113
+ export declare function commitAuthor(user: unknown): {
114
+ name: string;
115
+ email: string;
116
+ } | undefined;
117
+ /**
118
+ * The proposed collection set: everything as it is, with one collection
119
+ * replaced or added.
120
+ */
121
+ export declare function proposedCollections(current: CollectionConfig[], change: ProposedChange): CollectionConfig[];
122
+ export declare function createLiveSchemaRoutes(config: LiveSchemaRoutesConfig): Hono<HonoEnv>;
@@ -1,6 +1,6 @@
1
1
  import { Hono } from "hono";
2
2
  import type { MiddlewareHandler } from "hono";
3
- import type { HonoEnv } from "./types";
3
+ import type { HonoEnv } from "./types.js";
4
4
  export interface LogEntry {
5
5
  id: string;
6
6
  timestamp: string;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Mounting a surface at its canonical path, and at the path it used to have.
3
+ *
4
+ * Several admin-only surfaces grew up at the top level — `/api/cron`,
5
+ * `/api/logs`, `/api/schema-editor` — while the ones added later went under
6
+ * `/api/admin`. Nothing was wrong with either choice on its own; what was wrong
7
+ * was having both, because the shape of the path stopped predicting whether a
8
+ * caller needed to be an admin. `docs/api-conventions.md` states the rule the
9
+ * repo now follows and this module is how existing callers survive it.
10
+ *
11
+ * ## Why an alias rather than a redirect
12
+ *
13
+ * A 308 would be correct and would break things anyway: `fetch` follows
14
+ * redirects but drops an `Authorization` header on a cross-origin hop, and a
15
+ * good deal of client code posts to these paths with one attached. An alias
16
+ * serves the same handler at both paths, so nothing observable changes for a
17
+ * caller that has not moved yet.
18
+ *
19
+ * The alias is not silent. Every response through it carries `Deprecation` and
20
+ * a `Link` naming the successor, which is what makes the old path removable
21
+ * later: an operator can find the callers in their own logs rather than
22
+ * discovering them when the path is gone.
23
+ */
24
+ import { Hono } from "hono";
25
+ import type { HonoEnv } from "./types.js";
26
+ export interface MountOptions {
27
+ /** Where the surface lives now. */
28
+ canonical: string;
29
+ /** Where it used to live, when it has moved. */
30
+ legacy?: string;
31
+ /** For the boot log — "Cron", "Logs". */
32
+ surface: string;
33
+ }
34
+ /**
35
+ * Mount `router` at its canonical path, and at its legacy path when it has one.
36
+ *
37
+ * The same router instance serves both, so the two paths cannot drift: there is
38
+ * one set of handlers and one gate, and an alias that forgot to apply the admin
39
+ * gate is not a state this can reach.
40
+ */
41
+ export declare function mountWithLegacyAlias(app: Hono<HonoEnv>, router: Hono<HonoEnv>, options: MountOptions): void;
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
2
  import { AuthAdapter, DataDriver, CollectionConfig } from "@rebasepro/types";
3
- import { HonoEnv } from "../types";
4
- import { type ListLimitOptions } from "./query-parser";
3
+ import { HonoEnv } from "../types.js";
4
+ import { type ListLimitOptions } from "./query-parser.js";
5
5
  /**
6
6
  * Lightweight REST API generator that leverages existing Rebase DataDriver.
7
7
  * Supports `include` query parameter for eager-loading relations via Drizzle.
@@ -1 +1 @@
1
- export * from "./api-generator";
1
+ export * from "./api-generator.js";
@@ -1,5 +1,5 @@
1
1
  import type { ListLimitBounds, OrderByTuple } from "@rebasepro/types";
2
- import { QueryOptions } from "../types";
2
+ import { QueryOptions } from "../types.js";
3
3
  export declare const mapOperator: (op: string) => import("@rebasepro/types").WhereFilterOp | null;
4
4
  type OrderByEntry = {
5
5
  field: string;
@@ -1,3 +1,3 @@
1
1
  import { Hono } from "hono";
2
- import { HonoEnv } from "./types";
2
+ import { HonoEnv } from "./types.js";
3
3
  export declare function createSchemaEditorRoutes(collectionsDir: string): Hono<HonoEnv>;
@@ -1,7 +1,6 @@
1
- import { VectorSearchParams, LogicalCondition, FilterValues } from "@rebasepro/types";
2
- import type { AuthResult } from "../auth/middleware";
3
- import { DataDriver } from "@rebasepro/types";
4
- import type { ApiKeyMasked } from "../auth/api-keys/api-key-types";
1
+ import type { VectorSearchParams, LogicalCondition, FilterValues, DataDriver } from "@rebasepro/types";
2
+ import type { AuthResult } from "../auth/middleware.js";
3
+ import type { ApiKeyMasked } from "../auth/api-keys/api-key-types.js";
5
4
  /**
6
5
  * Hono Environment Variables
7
6
  * Passed to generic Hono<HonoEnv> to type `c.get()`
@@ -12,6 +11,15 @@ export type HonoEnv = {
12
11
  uid?: string;
13
12
  roles?: string[];
14
13
  };
14
+ /**
15
+ * The address a one-time-code request named, lower-cased.
16
+ *
17
+ * Stashed by a middleware so the per-address verification limiter has
18
+ * something to key on: the limiter runs before the handler that parses
19
+ * the body, and without this it would key every attempt into one global
20
+ * bucket — a limiter in name only. See `auth/otp-routes.ts`.
21
+ */
22
+ otpEmail?: string;
15
23
  driver?: DataDriver;
16
24
  /** Set when the request is authenticated via a Service API Key. */
17
25
  apiKey?: ApiKeyMasked;