@rebasepro/server 0.23.0 → 0.24.1-canary.g56af4a4

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 (261) hide show
  1. package/README.md +1 -1
  2. package/bin/rebase-server.js +4 -2
  3. package/dist/GCSStorageController-DivE9bAx.js +402 -0
  4. package/dist/GCSStorageController-DivE9bAx.js.map +1 -0
  5. package/dist/S3StorageController-D_vKs4kP.js +480 -0
  6. package/dist/S3StorageController-D_vKs4kP.js.map +1 -0
  7. package/dist/api/ast-schema-editor.d.ts +92 -1
  8. package/dist/api/errors.d.ts +9 -0
  9. package/dist/api/live-schema-routes.d.ts +38 -8
  10. package/dist/api/logs-routes.d.ts +39 -1
  11. package/dist/api/openapi-generator.d.ts +17 -0
  12. package/dist/api/rest/api-generator.d.ts +97 -11
  13. package/dist/api/rest/auth-collection-writes.d.ts +28 -21
  14. package/dist/api/rest/field-access-query.d.ts +28 -8
  15. package/dist/api/rest/field-ops.d.ts +19 -7
  16. package/dist/api/rest/query-body.d.ts +10 -0
  17. package/dist/api/rest/write-validation.d.ts +9 -7
  18. package/dist/api/schema-editor-routes.d.ts +10 -1
  19. package/dist/api/sql-export-routes.d.ts +8 -0
  20. package/dist/api/types.d.ts +28 -1
  21. package/dist/{ast-schema-editor-Mvr50v_S.js → ast-schema-editor-CSdL0iCk.js} +310 -12
  22. package/dist/ast-schema-editor-CSdL0iCk.js.map +1 -0
  23. package/dist/auth/access.d.ts +121 -0
  24. package/dist/auth/adapter-middleware.d.ts +2 -1
  25. package/dist/auth/address-ownership.d.ts +16 -1
  26. package/dist/auth/admin-roles-route.d.ts +4 -2
  27. package/dist/auth/admin-roles.d.ts +17 -20
  28. package/dist/auth/admin-user-ops.d.ts +24 -2
  29. package/dist/auth/admin-users-route.d.ts +2 -0
  30. package/dist/auth/api-keys/api-key-middleware.d.ts +56 -55
  31. package/dist/auth/api-keys/api-key-routes.d.ts +41 -11
  32. package/dist/auth/api-keys/api-key-store.d.ts +31 -8
  33. package/dist/auth/api-keys/api-key-types.d.ts +14 -16
  34. package/dist/auth/api-keys/http-operation.d.ts +19 -0
  35. package/dist/auth/api-keys/index.d.ts +11 -11
  36. package/dist/auth/api-keys/key-grant.d.ts +41 -0
  37. package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
  38. package/dist/auth/auth-hooks.d.ts +46 -7
  39. package/dist/auth/builtin-auth-adapter.d.ts +8 -0
  40. package/dist/auth/cookie-utils.d.ts +7 -0
  41. package/dist/auth/deliverable-address.d.ts +6 -0
  42. package/dist/auth/email-change-routes.d.ts +41 -0
  43. package/dist/auth/expired-token-sweep.d.ts +71 -0
  44. package/dist/auth/impersonation.d.ts +110 -0
  45. package/dist/auth/index.d.ts +4 -2
  46. package/dist/auth/interfaces.d.ts +120 -59
  47. package/dist/auth/jwt-crypto.d.ts +7 -0
  48. package/dist/auth/jwt-keys.d.ts +4 -0
  49. package/dist/auth/jwt.d.ts +54 -3
  50. package/dist/auth/magic-link-routes.d.ts +2 -6
  51. package/dist/auth/mfa-routes.d.ts +2 -9
  52. package/dist/auth/middleware.d.ts +17 -5
  53. package/dist/auth/otp-routes.d.ts +2 -6
  54. package/dist/auth/passwordless-signup.d.ts +27 -0
  55. package/dist/auth/platform-token.d.ts +122 -0
  56. package/dist/auth/rate-limiter.d.ts +54 -1
  57. package/dist/auth/registration-policy.d.ts +17 -0
  58. package/dist/auth/routes.d.ts +45 -0
  59. package/dist/auth/scope-routes.d.ts +22 -0
  60. package/dist/auth/session-routes.d.ts +11 -6
  61. package/dist/auth/token-revocation.d.ts +50 -1
  62. package/dist/auth/user-picking.d.ts +35 -0
  63. package/dist/auth/verify-credential.d.ts +28 -0
  64. package/dist/{auth-B-GIMpDG.js → auth-OPq_FeMs.js} +3115 -638
  65. package/dist/auth-OPq_FeMs.js.map +1 -0
  66. package/dist/auth_adapter-CIItCTc8.js +21 -0
  67. package/dist/auth_adapter-CIItCTc8.js.map +1 -0
  68. package/dist/backend-DTAOsLQc.js.map +1 -1
  69. package/dist/backup/backup-common.d.ts +24 -3
  70. package/dist/backup/backup-routes.d.ts +46 -5
  71. package/dist/backup/backup-schedule.d.ts +33 -0
  72. package/dist/backup/backup-storage.d.ts +16 -0
  73. package/dist/backup/index.d.ts +5 -1
  74. package/dist/backup-5d7XhLX3.js +581 -0
  75. package/dist/backup-5d7XhLX3.js.map +1 -0
  76. package/dist/boot/bundle.d.ts +19 -0
  77. package/dist/boot/driver.d.ts +3 -0
  78. package/dist/boot/env.d.ts +56 -4
  79. package/dist/boot/http-server.d.ts +22 -0
  80. package/dist/boot/options.d.ts +16 -1
  81. package/dist/boot/runtime-cors.d.ts +17 -0
  82. package/dist/boot/security-headers.d.ts +26 -0
  83. package/dist/boot/static-routing.d.ts +65 -0
  84. package/dist/collection_patch-BRu-BvDv.js +472 -0
  85. package/dist/collection_patch-BRu-BvDv.js.map +1 -0
  86. package/dist/{contract-routes-CbFjuBwa.js → contract-routes-02lASyJI.js} +5 -8
  87. package/dist/contract-routes-02lASyJI.js.map +1 -0
  88. package/dist/cron/cron-routes.d.ts +1 -1
  89. package/dist/cron/cron-scheduler.d.ts +94 -23
  90. package/dist/cron/cron-store.d.ts +31 -4
  91. package/dist/cron/define-cron.d.ts +6 -2
  92. package/dist/cron/index.d.ts +1 -1
  93. package/dist/{cron-loader-DfTj2Hbi.js → cron-loader-D7OmleAb.js} +11 -7
  94. package/dist/cron-loader-D7OmleAb.js.map +1 -0
  95. package/dist/{cron-routes-eE8nif_b.js → cron-routes-CBfiaqEL.js} +34 -8
  96. package/dist/cron-routes-CBfiaqEL.js.map +1 -0
  97. package/dist/{cron-scheduler-B0pLfAix.js → cron-scheduler-C2vbETMo.js} +349 -65
  98. package/dist/cron-scheduler-C2vbETMo.js.map +1 -0
  99. package/dist/cron-store-B2Ih0gCN.js +540 -0
  100. package/dist/cron-store-B2Ih0gCN.js.map +1 -0
  101. package/dist/{ddl-bootstrap-C6mo0Kmz.js → ddl-bootstrap-BaqMSa4Y.js} +2 -2
  102. package/dist/{ddl-bootstrap-C6mo0Kmz.js.map → ddl-bootstrap-BaqMSa4Y.js.map} +1 -1
  103. package/dist/email/index.d.ts +2 -2
  104. package/dist/email/smtp-email-service.d.ts +23 -0
  105. package/dist/email/templates.d.ts +22 -0
  106. package/dist/email/types.d.ts +44 -0
  107. package/dist/env.d.ts +1 -39
  108. package/dist/{errors-DWsX4yTd.js → errors-D6_y86c5.js} +102 -8
  109. package/dist/errors-D6_y86c5.js.map +1 -0
  110. package/dist/filter-dialect-REl-TjZr.js +1628 -0
  111. package/dist/filter-dialect-REl-TjZr.js.map +1 -0
  112. package/dist/{function-loader-xnbDAPfa.js → function-loader-BDKKiBmy.js} +5 -4
  113. package/dist/function-loader-BDKKiBmy.js.map +1 -0
  114. package/dist/{function-routes-Chet4-lB.js → function-routes-DWB7BBsi.js} +25 -13
  115. package/dist/function-routes-DWB7BBsi.js.map +1 -0
  116. package/dist/functions/context.d.ts +26 -6
  117. package/dist/functions/define-function.d.ts +7 -4
  118. package/dist/functions/guards.d.ts +22 -5
  119. package/dist/functions/index.d.ts +2 -2
  120. package/dist/functions/index.js +114 -37
  121. package/dist/functions/index.js.map +1 -1
  122. package/dist/functions/request-timeout.d.ts +7 -5
  123. package/dist/history/history-routes.d.ts +9 -0
  124. package/dist/history/retention-sweep.d.ts +81 -0
  125. package/dist/{history-recorder-B4MpJfJK.js → history-recorder-DHenr3lm.js} +4 -4
  126. package/dist/{history-recorder-B4MpJfJK.js.map → history-recorder-DHenr3lm.js.map} +1 -1
  127. package/dist/{history-store-BhxWOuz9.js → history-store-DPofGVDp.js} +2 -2
  128. package/dist/{history-store-BhxWOuz9.js.map → history-store-DPofGVDp.js.map} +1 -1
  129. package/dist/index.d.ts +14 -7
  130. package/dist/index.es.js +23363 -17552
  131. package/dist/index.es.js.map +1 -1
  132. package/dist/init/health.d.ts +17 -2
  133. package/dist/init/shutdown.d.ts +20 -0
  134. package/dist/init.d.ts +70 -6
  135. package/dist/internal-tables-L2t39RdT.js +136 -0
  136. package/dist/internal-tables-L2t39RdT.js.map +1 -0
  137. package/dist/jobs/job-queue.d.ts +8 -0
  138. package/dist/jobs/job-store.d.ts +24 -0
  139. package/dist/jobs/types.d.ts +25 -4
  140. package/dist/{jobs-CazMYhyy.js → jobs-BpD4Q0qR.js} +235 -25
  141. package/dist/jobs-BpD4Q0qR.js.map +1 -0
  142. package/dist/{jwt-DnQHNFCl.js → jwt-Bqcu7cGt.js} +207 -77
  143. package/dist/jwt-Bqcu7cGt.js.map +1 -0
  144. package/dist/{keys-CogCQpxG.js → keys-GAVZqbqx.js} +3 -17
  145. package/dist/{keys-CogCQpxG.js.map → keys-GAVZqbqx.js.map} +1 -1
  146. package/dist/load-failure-UvzZgGXT.js +48 -0
  147. package/dist/load-failure-UvzZgGXT.js.map +1 -0
  148. package/dist/{logger-DO2PZc4i.js → logger-D-S-hO5e.js} +26 -3
  149. package/dist/logger-D-S-hO5e.js.map +1 -0
  150. package/dist/{logs-routes-Bj4TYYUl.js → logs-routes-DAdv37GI.js} +48 -8
  151. package/dist/logs-routes-DAdv37GI.js.map +1 -0
  152. package/dist/mcp/consent-page.d.ts +1 -1
  153. package/dist/mcp/mcp-routes.d.ts +7 -0
  154. package/dist/mcp/mcp-tools.d.ts +23 -9
  155. package/dist/mcp/oauth-metadata.d.ts +21 -16
  156. package/dist/mcp/oauth-routes.d.ts +7 -1
  157. package/dist/metrics/index.d.ts +18 -0
  158. package/dist/metrics/pool-wait.d.ts +50 -0
  159. package/dist/offline-8Ks3yp9n-CadBKm2D.js +3305 -0
  160. package/dist/offline-8Ks3yp9n-CadBKm2D.js.map +1 -0
  161. package/dist/offline-store-D9ytNNaa-BCr2vsFB.js +1903 -0
  162. package/dist/offline-store-D9ytNNaa-BCr2vsFB.js.map +1 -0
  163. package/dist/{openapi-generator-O_O24MAT.js → openapi-generator-BqoUh4Ij.js} +455 -80
  164. package/dist/openapi-generator-BqoUh4Ij.js.map +1 -0
  165. package/dist/policy-PdzX-Opd.js +121 -0
  166. package/dist/policy-PdzX-Opd.js.map +1 -0
  167. package/dist/project_manifest-R6y_KHms.js +59 -0
  168. package/dist/project_manifest-R6y_KHms.js.map +1 -0
  169. package/dist/{proxy-Czngl3p9.js → proxy-qRlqeUmO.js} +2 -2
  170. package/dist/{proxy-Czngl3p9.js.map → proxy-qRlqeUmO.js.map} +1 -1
  171. package/dist/{query-parser-DGRVFNM3.js → query-parser-DK9eMkb9.js} +497 -52
  172. package/dist/query-parser-DK9eMkb9.js.map +1 -0
  173. package/dist/{request-timeout-C_4C2BeR.js → request-timeout-Dnl2_OBc.js} +17 -10
  174. package/dist/request-timeout-Dnl2_OBc.js.map +1 -0
  175. package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
  176. package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
  177. package/dist/schema-edit/apply-schema-change.d.ts +73 -3
  178. package/dist/schema-edit/local-source.d.ts +29 -0
  179. package/dist/schema-edit/project-root.d.ts +3 -2
  180. package/dist/schema-edit/remote-source.d.ts +20 -5
  181. package/dist/schema-edit/source-editor.d.ts +28 -0
  182. package/dist/schema-editor-routes-BFf3i8jm.js +136 -0
  183. package/dist/schema-editor-routes-BFf3i8jm.js.map +1 -0
  184. package/dist/serve-spa.d.ts +58 -0
  185. package/dist/services/outbound-post.d.ts +50 -0
  186. package/dist/services/outbound-url-guard.d.ts +34 -5
  187. package/dist/services/routed-realtime-service.d.ts +16 -0
  188. package/dist/services/webhook-service.d.ts +66 -14
  189. package/dist/soft-delete-params-47yBGs3_.js +668 -0
  190. package/dist/soft-delete-params-47yBGs3_.js.map +1 -0
  191. package/dist/sql-export-routes-BNNuGGLP.js +240 -0
  192. package/dist/sql-export-routes-BNNuGGLP.js.map +1 -0
  193. package/dist/{src-vkcwKXbT.js → src-CatHFUym.js} +439 -20
  194. package/dist/src-CatHFUym.js.map +1 -0
  195. package/dist/storage/GCSStorageController.d.ts +48 -2
  196. package/dist/storage/LocalStorageController.d.ts +69 -2
  197. package/dist/storage/S3StorageController.d.ts +39 -3
  198. package/dist/storage/image-transform.d.ts +5 -0
  199. package/dist/storage/index.d.ts +2 -2
  200. package/dist/storage/maintenance-routes.d.ts +23 -0
  201. package/dist/storage/maintenance.d.ts +126 -0
  202. package/dist/storage/property-limits.d.ts +41 -6
  203. package/dist/storage/rendition-cache.d.ts +9 -11
  204. package/dist/storage/request-keys.d.ts +15 -0
  205. package/dist/storage/request-principals.d.ts +30 -0
  206. package/dist/storage/requested-object.d.ts +74 -0
  207. package/dist/storage/routes.d.ts +52 -14
  208. package/dist/storage/source-variables.d.ts +10 -0
  209. package/dist/storage/streams.d.ts +42 -0
  210. package/dist/storage/tus-handler.d.ts +76 -7
  211. package/dist/storage/types.d.ts +101 -7
  212. package/dist/storage/upload-validation.d.ts +13 -0
  213. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
  214. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
  215. package/dist/strings.es-BeZWAodn.js +256 -0
  216. package/dist/strings.es-BeZWAodn.js.map +1 -0
  217. package/dist/upload-validation-Dd-fnsbA.js +172 -0
  218. package/dist/upload-validation-Dd-fnsbA.js.map +1 -0
  219. package/dist/utils/byte-size.d.ts +10 -0
  220. package/dist/utils/compression.d.ts +6 -0
  221. package/dist/utils/load-failure.d.ts +14 -0
  222. package/dist/utils/logger.d.ts +12 -0
  223. package/package.json +5 -5
  224. package/dist/GCSStorageController-CjrA4PMo.js +0 -242
  225. package/dist/GCSStorageController-CjrA4PMo.js.map +0 -1
  226. package/dist/S3StorageController-B6pKDNVj.js +0 -264
  227. package/dist/S3StorageController-B6pKDNVj.js.map +0 -1
  228. package/dist/admin-roles-vYdp_Pil.js +0 -36
  229. package/dist/admin-roles-vYdp_Pil.js.map +0 -1
  230. package/dist/admin_block-DxKLmdiv.js +0 -206
  231. package/dist/admin_block-DxKLmdiv.js.map +0 -1
  232. package/dist/ast-schema-editor-Mvr50v_S.js.map +0 -1
  233. package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
  234. package/dist/auth-B-GIMpDG.js.map +0 -1
  235. package/dist/backup-D7YR94N3.js +0 -253
  236. package/dist/backup-D7YR94N3.js.map +0 -1
  237. package/dist/contract-routes-CbFjuBwa.js.map +0 -1
  238. package/dist/cron-loader-DfTj2Hbi.js.map +0 -1
  239. package/dist/cron-routes-eE8nif_b.js.map +0 -1
  240. package/dist/cron-scheduler-B0pLfAix.js.map +0 -1
  241. package/dist/cron-store-TcoGz-xS.js +0 -305
  242. package/dist/cron-store-TcoGz-xS.js.map +0 -1
  243. package/dist/dynamic-import-X40pTZUQ.js +0 -23
  244. package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
  245. package/dist/errors-DWsX4yTd.js.map +0 -1
  246. package/dist/function-loader-xnbDAPfa.js.map +0 -1
  247. package/dist/function-routes-Chet4-lB.js.map +0 -1
  248. package/dist/jobs-CazMYhyy.js.map +0 -1
  249. package/dist/jwt-DnQHNFCl.js.map +0 -1
  250. package/dist/logger-DO2PZc4i.js.map +0 -1
  251. package/dist/logs-routes-Bj4TYYUl.js.map +0 -1
  252. package/dist/openapi-generator-O_O24MAT.js.map +0 -1
  253. package/dist/query-parser-DGRVFNM3.js.map +0 -1
  254. package/dist/request-timeout-C_4C2BeR.js.map +0 -1
  255. package/dist/schema-editor-routes-C5-lh_jO.js +0 -87
  256. package/dist/schema-editor-routes-C5-lh_jO.js.map +0 -1
  257. package/dist/src-pmvW7BFx.js +0 -6688
  258. package/dist/src-pmvW7BFx.js.map +0 -1
  259. package/dist/src-vkcwKXbT.js.map +0 -1
  260. package/dist/types-BfKcm9do.js +0 -39
  261. package/dist/types-BfKcm9do.js.map +0 -1
@@ -10,20 +10,35 @@ import { Hono } from "hono";
10
10
  import { StorageController, type StorageAuthorize, type StorageAuthorizeData } from "./types.js";
11
11
  import { type StorageRegistry } from "./storage-registry.js";
12
12
  import { type StorageSourceDefinition, type AuthAdapter } from "@rebasepro/types";
13
- import { type ResolveUploadConstraints } from "./property-limits.js";
13
+ import { type ResolveUploadConstraints, type ResolveUploadPathConstraints } from "./property-limits.js";
14
14
  import { HonoEnv } from "../api/types.js";
15
15
  import { type StorageTrigger } from "./triggers.js";
16
16
  import { type RenditionCacheConfig } from "./rendition-cache.js";
17
+ /**
18
+ * The policy every SVG response carries.
19
+ *
20
+ * An SVG embedded with `<img>` never runs script. Opened directly — a link, a
21
+ * new tab, an `<iframe>` — it is a document on the API origin, and its
22
+ * `<script>` would run there, next to the refresh cookie. `sandbox` gives that
23
+ * document an opaque origin with scripts disabled, and `default-src 'none'`
24
+ * stops it loading anything; `style-src 'unsafe-inline'` keeps the inline
25
+ * styles most SVGs are drawn with. It is what GitHub serves user content
26
+ * under. Serving SVG as a download instead made every SVG logo a broken
27
+ * thumbnail in the panel.
28
+ */
29
+ export declare const SVG_CONTENT_SECURITY_POLICY = "default-src 'none'; style-src 'unsafe-inline'; sandbox";
17
30
  /**
18
31
  * Decide what to actually serve for a stored content type.
19
32
  *
20
- * Returns the type to send and whether to force a download. The check is an
33
+ * Returns the type to send, whether to force a download, and the
34
+ * `Content-Security-Policy` the response must carry, if any. The check is an
21
35
  * allowlist rather than a blocklist of dangerous types: the set of types a
22
36
  * browser will execute grows, and the set we want to render inline does not.
23
37
  */
24
38
  export declare function resolveServedContentType(storedContentType: string): {
25
39
  contentType: string;
26
40
  attachment: boolean;
41
+ contentSecurityPolicy?: string;
27
42
  };
28
43
  export interface StorageRoutesConfig {
29
44
  /**
@@ -73,6 +88,13 @@ export interface StorageRoutesConfig {
73
88
  * to the global cap exactly as before.
74
89
  */
75
90
  uploadConstraints?: ResolveUploadConstraints;
91
+ /**
92
+ * The limits of the properties whose `storagePath` an upload's key falls
93
+ * in, by source and key — see `createUploadPathResolver`. Applied to every
94
+ * upload, whether or not it names a property, so a property's limits hold
95
+ * for every file written where its files go.
96
+ */
97
+ uploadPathConstraints?: ResolveUploadPathConstraints;
76
98
  /**
77
99
  * When provided, storage routes delegate auth to this adapter instead
78
100
  * of the built-in JWT module. This mirrors how data routes use
@@ -105,25 +127,41 @@ export interface StorageRoutesConfig {
105
127
  * Run something when an object lands, or when one goes. See `triggers.ts`.
106
128
  */
107
129
  triggers?: StorageTrigger[];
130
+ /**
131
+ * How long the download token `/metadata` mints for a private object stays
132
+ * valid, in seconds. {@link DEFAULT_DOWNLOAD_TOKEN_TTL_SECONDS} unless set;
133
+ * at most {@link MAX_DOWNLOAD_TOKEN_TTL_SECONDS}.
134
+ */
135
+ downloadTokenTtlSeconds?: number;
108
136
  }
137
+ /** Five minutes: long enough to render a page of thumbnails, short enough to be worthless in a log. */
138
+ export declare const DEFAULT_DOWNLOAD_TOKEN_TTL_SECONDS = 300;
109
139
  /**
110
- * Extract the wildcard portion of a route path from the full request path.
111
- *
112
- * Hono's `c.req.param('*')` does not work reliably in sub-routers mounted
113
- * via `app.route(prefix, subRouter)`. Instead we derive the wildcard value
114
- * from the fully-resolved `c.req.path` and `c.req.routePath`.
140
+ * A week. The token travels in a URL — into access logs, Referer headers and
141
+ * browser history — so a lifetime past this is a permanent link to a private
142
+ * file. A file that must be linked forever belongs under the public prefix.
143
+ */
144
+ export declare const MAX_DOWNLOAD_TOKEN_TTL_SECONDS: number;
145
+ /** Most keys `POST /metadata` takes in one request. */
146
+ export declare const DOWNLOAD_METADATA_BATCH_MAX = 100;
147
+ /**
148
+ * What a storage request costs against the data rate limit: one, except a
149
+ * `POST /metadata` of N keys, which costs ⌈N/10⌉.
115
150
  *
116
- * For a route `/metadata/*` mounted at `/api/storage`, a request to
117
- * `/api/storage/metadata/default/file.jpg` yields routePath
118
- * `/api/storage/metadata/*`. We strip the prefix (everything before `/*`)
119
- * plus one character for the trailing `/` to obtain `default/file.jpg`.
151
+ * One request per key let a page of thumbnails spend a hundred requests of a
152
+ * caller's allowance; one per batch would make a batch of a hundred keys a
153
+ * hundred times cheaper to scrape with than the route it replaces. A tenth
154
+ * keeps a budget against enumeration while a page of thumbnails costs ten.
155
+ * Read before the route runs (the limiter is ahead of it); the body is parsed
156
+ * once, and the route reads Hono's cached copy.
120
157
  */
121
- export declare function extractWildcardPath(c: {
158
+ export declare function storageRateLimitCost(c: {
122
159
  req: {
160
+ method: string;
123
161
  path: string;
124
- routePath: string;
162
+ json(): Promise<unknown>;
125
163
  };
126
- }): string;
164
+ }): Promise<number>;
127
165
  /**
128
166
  * Create storage REST API routes
129
167
  */
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The variable that binds a declared object-store source's bucket on a
3
+ * deployment — `S3_BUCKET__MEDIA`, `GCS_BUCKET__ARCHIVE`, `S3_BUCKET` for the
4
+ * default source.
5
+ *
6
+ * One spelling for the three places that tell an operator to set it: the boot
7
+ * warning, the 501 a request to the unbound source answers, and `rebase
8
+ * status`. The 501 used to say "the media suffixed environment variables".
9
+ */
10
+ export declare function storageBucketVariable(key: string, engine: string | undefined): string;
@@ -0,0 +1,42 @@
1
+ /** Anything that hands over bytes a chunk at a time: a Node readable, an SDK body. */
2
+ type ByteSource = AsyncIterable<Uint8Array>;
3
+ /**
4
+ * A byte source as a web stream that reads a chunk only when the consumer asks
5
+ * for one, and releases the source when the consumer stops early.
6
+ *
7
+ * Pull-based with a zero high-water mark, so nothing is read ahead of the
8
+ * reader: a slow client holds one chunk, not a queue of them. A source given
9
+ * as a function is opened on the first read, which keeps a stream nobody reads
10
+ * — a `HEAD`, a 304 decided late — from holding a file descriptor or a
11
+ * connection open. Cancelling (a client that disconnects mid-download)
12
+ * returns the iterator, which destroys a Node stream and so closes its file or
13
+ * socket.
14
+ */
15
+ export declare function webStreamFrom(source: ByteSource | (() => ByteSource)): ReadableStream<Uint8Array>;
16
+ /**
17
+ * A file on disk, or an inclusive byte range of it, as a web stream. The file
18
+ * is opened on the first read; see {@link webStreamFrom}.
19
+ */
20
+ export declare function fileWebStream(filePath: string, range?: {
21
+ start: number;
22
+ end: number;
23
+ }): ReadableStream<Uint8Array>;
24
+ /** A buffer already in memory as a web stream of views onto it, never copies. */
25
+ export declare function bufferWebStream(bytes: Uint8Array, chunkSize?: number): ReadableStream<Uint8Array>;
26
+ /**
27
+ * Read a stream into one buffer.
28
+ *
29
+ * For the one consumer that needs the whole object at once — an image decode.
30
+ * With the size known the bytes are copied once, into a buffer of that size,
31
+ * rather than collected and then concatenated (which holds them twice); a
32
+ * stream that turns out longer or shorter than announced still comes back
33
+ * whole and exact.
34
+ */
35
+ export declare function readStreamFully(stream: ReadableStream<Uint8Array>, expectedSize?: number): Promise<Buffer>;
36
+ /**
37
+ * The bytes of a buffer as a `Uint8Array` the response APIs accept, without
38
+ * copying them. `new Uint8Array(buffer)` copies; a view does not. Only a buffer
39
+ * over shared memory — which nothing here produces — is copied.
40
+ */
41
+ export declare function bytesView(bytes: Uint8Array): Uint8Array<ArrayBuffer>;
42
+ export {};
@@ -10,13 +10,29 @@
10
10
  import type { Context } from "hono";
11
11
  import type { StorageController } from "./types.js";
12
12
  import { type StorageRegistry } from "./storage-registry.js";
13
- import { type ResolveUploadConstraints } from "./property-limits.js";
13
+ import { type ResolveUploadConstraints, type ResolveUploadPathConstraints } from "./property-limits.js";
14
+ /**
15
+ * The header a resumable upload's last `PATCH` answers with the stored object
16
+ * in — `{ key, bucket, storageUrl }`, the `data` of `POST /upload`, as
17
+ * URI-encoded JSON — so a client gets from the resumable path what it gets
18
+ * from the multipart one. `create` answers it with `on-complete`, which tells
19
+ * a client this server will, before it sends a byte.
20
+ */
21
+ export declare const UPLOAD_RESULT_HEADER = "Rebase-Upload-Result";
14
22
  /**
15
23
  * TUS resumable upload handler.
16
24
  *
17
25
  * Each instance manages uploads for a single storage root. The
18
26
  * `storageController` is used to finalize completed uploads by
19
27
  * calling `putObject`.
28
+ *
29
+ * An upload lives in the upload directory, not in this process: its bytes in
30
+ * `<id>`, and what `create` decided about it in `<id>.json` beside them. Its
31
+ * offset is the length of the bytes file. So a restart — a rolling deploy —
32
+ * resumes an upload where it stopped instead of sending the client back to
33
+ * byte 0, and every process that shares the directory (one volume at
34
+ * `STORAGE_PATH`) serves every upload. The map below only remembers records
35
+ * already read.
20
36
  */
21
37
  export declare class TusHandler {
22
38
  private storageController?;
@@ -65,6 +81,18 @@ export declare class TusHandler {
65
81
  * accept is refused before the first chunk rather than after the last.
66
82
  */
67
83
  private uploadConstraints?;
84
+ /**
85
+ * The limits of the properties whose storage path the upload's key
86
+ * falls in — checked whether or not the upload names a property, as
87
+ * `POST /upload` checks them.
88
+ */
89
+ private uploadPathConstraints?;
90
+ /**
91
+ * Upload records this process has read or written, by id. A cache of the
92
+ * `<id>.json` files: it never holds the offset (the bytes file's length
93
+ * does), and an upload another process finished or cancelled is noticed
94
+ * when its bytes file is gone.
95
+ */
68
96
  private uploads;
69
97
  private tusDir;
70
98
  private cleanupTimer?;
@@ -123,21 +151,50 @@ export declare class TusHandler {
123
151
  * and the metadata's content type, so a file the property will not
124
152
  * accept is refused before the first chunk rather than after the last.
125
153
  */
126
- uploadConstraints?: ResolveUploadConstraints | undefined);
154
+ uploadConstraints?: ResolveUploadConstraints | undefined,
155
+ /**
156
+ * The limits of the properties whose storage path the upload's key
157
+ * falls in — checked whether or not the upload names a property, as
158
+ * `POST /upload` checks them.
159
+ */
160
+ uploadPathConstraints?: ResolveUploadPathConstraints | undefined);
127
161
  /** Ensure the temp directory exists. */
128
162
  private ensureDir;
129
163
  /** Start periodic cleanup of stale uploads. */
130
164
  startCleanup(): void;
131
- /** Remove uploads that have been idle for longer than UPLOAD_EXPIRY_MS. */
165
+ /**
166
+ * Remove uploads created more than {@link UPLOAD_EXPIRY_MS} ago.
167
+ *
168
+ * From the directory rather than from this process's memory, so an upload
169
+ * a crashed or replaced process abandoned is collected too. A record is
170
+ * written once, at creation, so its file's mtime is when the upload began;
171
+ * a bytes file with no record (left by an earlier version) is judged by
172
+ * its own.
173
+ */
132
174
  private cleanupStale;
175
+ /** The upload's bytes and record, gone; from every process, since they are files. */
176
+ private forget;
133
177
  /**
134
178
  * Parse the `Upload-Metadata` header.
135
179
  *
136
180
  * Format: `key base64value,key2 base64value2`
137
181
  */
138
182
  private parseMetadata;
139
- /** `OPTIONS /tus` — TUS capability discovery. */
140
- options(): Response;
183
+ /** The controller an upload to this storage source is written to. */
184
+ private targetFor;
185
+ /**
186
+ * The largest upload this source takes: the source's own limit, under the
187
+ * deployment's and the protocol ceiling. One function for what `OPTIONS`
188
+ * advertises and what `create` enforces, which used to be 5 GB and 50 MB.
189
+ */
190
+ private limitFor;
191
+ /**
192
+ * `OPTIONS /tus` — TUS capability discovery.
193
+ *
194
+ * `Tus-Max-Size` is the limit `create` holds this source to, for the source
195
+ * named by `?storageId` (the default one otherwise).
196
+ */
197
+ options(c?: Context): Response;
141
198
  /** `POST /tus` — Create a new upload. */
142
199
  create(c: Context): Promise<Response>;
143
200
  /**
@@ -160,9 +217,21 @@ export declare class TusHandler {
160
217
  * the rest.
161
218
  */
162
219
  private ownedUpload;
220
+ /**
221
+ * The upload with this id: from this process's memory, or from its record
222
+ * in the upload directory — written by another process sharing it, or by
223
+ * this one before a restart. `null` when there is no such upload.
224
+ */
225
+ private lookup;
226
+ /**
227
+ * Bytes received so far: the length of the bytes file. `null` when it is
228
+ * gone — finished or cancelled by another process — and then this one
229
+ * forgets the upload too.
230
+ */
231
+ private offsetOf;
163
232
  /** `HEAD /tus/:id` — Query upload progress. */
164
- head(c: Context, id: string): Response;
165
- /** `PATCH /tus/:id` — Append data to an upload. */
233
+ head(c: Context, id: string): Promise<Response>;
234
+ /** `PATCH /tus/:id` — Write one chunk of an upload, at its offset. */
166
235
  patch(c: Context, id: string): Promise<Response>;
167
236
  /** `DELETE /tus/:id` — Cancel and remove an upload. */
168
237
  delete(c: Context, id: string): Promise<Response>;
@@ -23,6 +23,14 @@ export interface LocalStorageConfig {
23
23
  * production, where an unbound object store stays unbound.
24
24
  */
25
25
  standsInFor?: string;
26
+ /**
27
+ * Set when nothing chose this backend: no `STORAGE_TYPE` and no declared
28
+ * engine, so local disk is only the default a source falls back to.
29
+ * Production drops a local backend either way. This only decides how that
30
+ * is reported: an implicit default being dropped means the project uses no
31
+ * storage, which is not a misconfiguration.
32
+ */
33
+ implicit?: boolean;
26
34
  }
27
35
  /**
28
36
  * S3-compatible storage configuration (works with AWS S3 and MinIO)
@@ -45,8 +53,6 @@ export interface S3StorageConfig {
45
53
  maxFileSize?: number;
46
54
  /** Allowed MIME types (if not set, all types allowed) */
47
55
  allowedMimeTypes?: string[];
48
- /** URL expiration time in seconds for signed URLs (default: 3600) */
49
- signedUrlExpiration?: number;
50
56
  }
51
57
  /**
52
58
  * Google Cloud Storage configuration (works with GCS and Firebase Storage)
@@ -69,8 +75,6 @@ export interface GCSStorageConfig {
69
75
  maxFileSize?: number;
70
76
  /** Allowed MIME types */
71
77
  allowedMimeTypes?: string[];
72
- /** Signed URL expiration in seconds (default: 3600) */
73
- signedUrlExpiration?: number;
74
78
  }
75
79
  /**
76
80
  * Storage configuration — local filesystem, S3-compatible, or Google Cloud Storage.
@@ -88,16 +92,40 @@ export interface GCSStorageConfig {
88
92
  export type BackendStorageConfig = LocalStorageConfig | S3StorageConfig | GCSStorageConfig;
89
93
  /** What a storage request is trying to do to an object. */
90
94
  export type { StorageOperation, StorageAuthorizeUser, StorageAuthorizeContext, StorageAuthorizeData, StorageAuthorize } from "@rebasepro/types";
95
+ /**
96
+ * How the server writes an object. See {@link StorageController.putObject}.
97
+ */
98
+ export interface PutObjectOptions {
99
+ /**
100
+ * Hold this write to the controller's upload limits: `maxFileSize` and
101
+ * `allowedMimeTypes`. Defaults to `true`.
102
+ *
103
+ * Those limits say what a user may upload. A write the server makes for
104
+ * itself, such as a database backup, passes `false`. A dump is neither of
105
+ * an allowed type nor, past a small database, under 50 MB. The upload
106
+ * routes never pass it.
107
+ */
108
+ uploadLimits?: boolean;
109
+ }
91
110
  /**
92
111
  * Storage controller interface for backend implementations
93
112
  */
94
113
  export interface StorageController {
95
114
  /**
96
- * Upload an object
115
+ * Upload an object.
116
+ *
117
+ * A write is held to the controller's upload limits — `maxFileSize` and
118
+ * `allowedMimeTypes` — unless `options.uploadLimits` is `false`. Only the
119
+ * server's own writes pass that; nothing a request sends reaches it.
97
120
  */
98
- putObject(props: UploadFileProps): Promise<UploadFileResult>;
121
+ putObject(props: UploadFileProps, options?: PutObjectOptions): Promise<UploadFileResult>;
99
122
  /**
100
- * Get a download URL (signed URL equivalent) for an object
123
+ * Get a download URL (signed URL equivalent) for an object.
124
+ *
125
+ * For server-side code only: no route hands this URL to a client. A
126
+ * client reads a private object through `/file/*` with the download
127
+ * token `GET /metadata/*` mints. The built-in S3 and GCS controllers sign
128
+ * it for an hour.
101
129
  */
102
130
  getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig>;
103
131
  /**
@@ -120,6 +148,29 @@ export interface StorageController {
120
148
  * Get object as a File
121
149
  */
122
150
  getObject(key: string, bucket?: string): Promise<File | null>;
151
+ /**
152
+ * Describe an object for serving it — its size, stored type and version —
153
+ * without reading its bytes, or `null` when there is no such object.
154
+ *
155
+ * Optional, together with {@link getObjectStream}. A controller that has
156
+ * both is served streamed: `GET /file/*` answers a `304`, a `416`, a `HEAD`
157
+ * and a cached image rendition from this alone, and otherwise reads only
158
+ * the bytes it sends — the requested range, a chunk at a time. One without
159
+ * them is served through {@link getObject}, which holds the whole object in
160
+ * memory for every request, a `Range` request for one megabyte included.
161
+ */
162
+ statObject?(key: string, bucket?: string): Promise<StoredObjectInfo | null>;
163
+ /**
164
+ * Read an object, or one byte range of it, as a stream, or `null` when
165
+ * there is no such object. See {@link statObject}.
166
+ *
167
+ * The stream holds a file or a connection until it is read to the end or
168
+ * cancelled, so a caller that decides not to read it must cancel it.
169
+ */
170
+ getObjectStream?(key: string, options?: {
171
+ bucket?: string;
172
+ range?: StoredObjectRange;
173
+ }): Promise<StoredObjectStream | null>;
123
174
  /**
124
175
  * Delete an object
125
176
  */
@@ -159,6 +210,49 @@ export interface StorageController {
159
210
  * when the caller names none, and each provider maps it to its own.
160
211
  */
161
212
  knownBuckets?(): string[];
213
+ /**
214
+ * The largest file this controller accepts, in bytes.
215
+ *
216
+ * Every door that takes a file reads it, so the number a client is told is
217
+ * the number it is held to: the upload route checks it before anything is
218
+ * stored, the resumable route checks it at creation — before the first
219
+ * chunk rather than after the last — and advertises it as `Tus-Max-Size`,
220
+ * and the server's upload body limit is the largest of them. A controller
221
+ * that does not implement this is held to the deployment-wide limit alone.
222
+ */
223
+ maxFileSize?(): number;
224
+ }
225
+ /**
226
+ * What serving an object needs to know before deciding whether to read it.
227
+ * See {@link StorageController.statObject}.
228
+ */
229
+ export interface StoredObjectInfo {
230
+ /** The whole object's size in bytes. */
231
+ size: number;
232
+ /** The stored content type — the uploader's claim, not a sniffed one. */
233
+ contentType: string;
234
+ /**
235
+ * The object's own modification time, in milliseconds since the epoch.
236
+ * The routes build `ETag`, `Last-Modified` and the transform cache key from
237
+ * it, so it must be the object's version, not the time of the read.
238
+ */
239
+ lastModified: number;
240
+ }
241
+ /** An inclusive byte range, as `Range: bytes=start-end` names one. */
242
+ export interface StoredObjectRange {
243
+ start: number;
244
+ end: number;
245
+ }
246
+ /** An object's bytes as a stream. See {@link StorageController.getObjectStream}. */
247
+ export interface StoredObjectStream extends StoredObjectInfo {
248
+ /** The bytes: the range when {@link range} is set, the whole object otherwise. */
249
+ stream: ReadableStream<Uint8Array>;
250
+ /**
251
+ * The range {@link stream} holds — the one asked for — or absent when it
252
+ * holds the whole object. A controller may answer a range with the whole
253
+ * object, as HTTP lets a server do; the caller then serves it whole.
254
+ */
255
+ range?: StoredObjectRange;
162
256
  }
163
257
  /**
164
258
  * Default maximum file size (50MB)
@@ -0,0 +1,13 @@
1
+ /**
2
+ * What every storage controller checks about a file before it stores it: the
3
+ * source's own size cap and, when configured, its allowed content types.
4
+ *
5
+ * One function for the three controllers, which carried a copy each. Both
6
+ * refusals are `ApiError`s, so they reach the caller as what they are. A plain
7
+ * `Error` was a 500 "Internal Server Error" on `/upload` and a 502 on TUS —
8
+ * after the client had sent every byte.
9
+ */
10
+ export declare function validateUploadedFile(file: {
11
+ size: number;
12
+ type: string;
13
+ }, maxSize: number, allowedMimeTypes: string[] | undefined): void;
@@ -0,0 +1,125 @@
1
+ import { createRequire as __rebaseCreateRequire } from "module";
2
+ import __rebaseProcess from "process";
3
+ globalThis.process ??= __rebaseProcess;
4
+ __rebaseCreateRequire(import.meta.url);
5
+ import "./resumable-upload-threshold-CKv-Ry0l.js";
6
+ //#region ../common/src/util/resumable-upload.ts
7
+ /** What `create` answers in `Rebase-Upload-Result` when the last `PATCH` will carry the stored object there. */
8
+ var RESULT_ON_COMPLETE = "on-complete";
9
+ var DEFAULT_RETRY_DELAYS_MS = [
10
+ 250,
11
+ 1e3,
12
+ 3e3
13
+ ];
14
+ /** `value` as UTF-8, base64-encoded — what `Upload-Metadata` carries. */
15
+ function base64Utf8(value) {
16
+ const bytes = new TextEncoder().encode(value);
17
+ let binary = "";
18
+ for (const byte of bytes) binary += String.fromCharCode(byte);
19
+ return btoa(binary);
20
+ }
21
+ /** Worth asking for the offset and sending again: an offset conflict, a lock, a rate limit, a server fault. */
22
+ var isRetryable = (status) => status === 409 || status === 423 || status === 429 || status >= 500;
23
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
24
+ /** The stored object a finished upload reports, or `null` when the header says nothing readable. */
25
+ function storedObjectOf(header) {
26
+ if (!header) return null;
27
+ let value;
28
+ try {
29
+ value = JSON.parse(decodeURIComponent(header));
30
+ } catch {
31
+ return null;
32
+ }
33
+ if (typeof value !== "object" || value === null) return null;
34
+ const key = "key" in value ? value.key : void 0;
35
+ const bucket = "bucket" in value ? value.bucket : void 0;
36
+ const storageUrl = "storageUrl" in value ? value.storageUrl : void 0;
37
+ if (typeof key !== "string" || typeof bucket !== "string" || typeof storageUrl !== "string") return null;
38
+ return {
39
+ key,
40
+ bucket,
41
+ storageUrl
42
+ };
43
+ }
44
+ /**
45
+ * Upload `file` through a Rebase server's TUS endpoint, one chunk at a time.
46
+ *
47
+ * The server holds every upload to what `POST /storage/upload` holds a file
48
+ * to — the source's and the property's limits, the authorize hook, the bucket
49
+ * — and stores it with the same key, type and custom metadata, and fires the
50
+ * same triggers. So a refusal is thrown as the multipart route would have
51
+ * thrown it. A chunk that fails on the way — a dropped connection, a 5xx, a
52
+ * conflict over the offset — is retried from the offset the server reports.
53
+ *
54
+ * Answers `unavailable` when the resumable path cannot carry this file and
55
+ * the multipart one can: a server without the endpoint (or one too old to
56
+ * report the stored object), an upload another instance cannot see (several
57
+ * instances, no shared volume, no sticky sessions), or a chunk over the
58
+ * server's body limit.
59
+ */
60
+ async function uploadResumable(options) {
61
+ const { file, send } = options;
62
+ const chunkSize = Math.max(1, options.chunkSize ?? 8388608);
63
+ const retryDelays = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
64
+ const tus = { "Tus-Resumable": "1.0.0" };
65
+ const created = await send("POST", "", {
66
+ ...tus,
67
+ "Upload-Length": String(file.size),
68
+ "Upload-Metadata": Object.entries(options.metadata).map(([name, value]) => `${name} ${base64Utf8(value)}`).join(",")
69
+ });
70
+ if (created.status === 404 || created.status === 405 || created.status === 501) return { unavailable: `the server has no resumable upload endpoint (${created.status})` };
71
+ if (created.status < 200 || created.status >= 300) throw created.error ?? /* @__PURE__ */ new Error(`The upload was refused (${created.status}).`);
72
+ const id = created.header("Location")?.split("?")[0].split("/").filter(Boolean).pop();
73
+ if (!id) return { unavailable: "the server did not say where the upload lives" };
74
+ const path = `/${encodeURIComponent(id)}`;
75
+ if (created.header("Rebase-Upload-Result") !== RESULT_ON_COMPLETE) {
76
+ await send("DELETE", path, tus).catch(() => void 0);
77
+ return { unavailable: "the server does not report the object a resumable upload stores" };
78
+ }
79
+ let offset = 0;
80
+ let lastFailure;
81
+ for (let attempt = 0;;) {
82
+ const end = Math.min(file.size, offset + chunkSize);
83
+ let response;
84
+ try {
85
+ response = await send("PATCH", path, {
86
+ ...tus,
87
+ "Upload-Offset": String(offset),
88
+ "Content-Type": "application/offset+octet-stream"
89
+ }, file.slice(offset, end));
90
+ } catch (error) {
91
+ lastFailure = error instanceof Error ? error : new Error(String(error));
92
+ }
93
+ if (response && response.status >= 200 && response.status < 300) {
94
+ attempt = 0;
95
+ const reported = Number(response.header("Upload-Offset"));
96
+ offset = Number.isFinite(reported) && reported > offset ? reported : end;
97
+ if (offset < file.size) continue;
98
+ const stored = storedObjectOf(response.header("Rebase-Upload-Result"));
99
+ if (!stored) throw new Error("The upload finished, but the server did not say where it stored it.");
100
+ return { stored };
101
+ }
102
+ if (response?.status === 404) return { unavailable: "the server no longer knows this upload — another instance may have answered" };
103
+ if (response?.status === 413) {
104
+ await send("DELETE", path, tus).catch(() => void 0);
105
+ return { unavailable: "a chunk is over the server's request body limit" };
106
+ }
107
+ if (response && !isRetryable(response.status)) {
108
+ await send("DELETE", path, tus).catch(() => void 0);
109
+ throw response.error ?? /* @__PURE__ */ new Error(`The upload was refused (${response.status}).`);
110
+ }
111
+ if (response) lastFailure = response.error ?? /* @__PURE__ */ new Error(`The upload failed (${response.status}).`);
112
+ if (attempt >= retryDelays.length) throw lastFailure ?? /* @__PURE__ */ new Error("The upload failed.");
113
+ await sleep(retryDelays[attempt++]);
114
+ try {
115
+ const progress = await send("HEAD", path, tus);
116
+ if (progress.status === 404) return { unavailable: "the server no longer knows this upload — another instance may have answered" };
117
+ const held = Number(progress.header("Upload-Offset"));
118
+ if (progress.status >= 200 && progress.status < 300 && Number.isFinite(held)) offset = held;
119
+ } catch {}
120
+ }
121
+ }
122
+ //#endregion
123
+ export { uploadResumable };
124
+
125
+ //# sourceMappingURL=storage-resumable-VO-cMpse-C_YX71pO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-resumable-VO-cMpse-C_YX71pO.js","names":[],"sources":["../../common/src/util/resumable-upload.ts"],"sourcesContent":["import type { UploadFileResult } from \"@rebasepro/types\";\nimport { RESUMABLE_UPLOAD_THRESHOLD } from \"./resumable-upload-threshold\";\n\n/** One response, as the resumable upload reads it. */\nexport interface ResumableUploadResponse {\n status: number;\n header(name: string): string | null;\n /** The error a non-2xx response stands for, as the transport reports it. */\n error?: Error;\n}\n\n/**\n * Send one TUS request. `path` is `\"\"` for the creation request and\n * `/<upload id>` after it, relative to the source's `…/storage/tus` endpoint.\n * Answers every HTTP status; throws only when no response arrived.\n */\nexport type ResumableUploadSend = (\n method: \"POST\" | \"PATCH\" | \"HEAD\" | \"DELETE\",\n path: string,\n headers: Record<string, string>,\n body?: Blob\n) => Promise<ResumableUploadResponse>;\n\nexport interface ResumableUploadOptions {\n file: Blob;\n /** `Upload-Metadata` entries: `key`, `filename`, `filetype`, `bucket`, `storageId`, `collection`, `property`, `metadata_<name>`. */\n metadata: Record<string, string>;\n send: ResumableUploadSend;\n /** Bytes per `PATCH`. Default {@link RESUMABLE_UPLOAD_THRESHOLD}. */\n chunkSize?: number;\n /** How long to wait before each retry of a chunk; its length is the number of retries. */\n retryDelaysMs?: readonly number[];\n}\n\n/**\n * What a resumable upload came to: the stored object, or — for a reason the\n * multipart route does not share — nothing, and the caller sends the file\n * through `POST /storage/upload` instead.\n */\nexport type ResumableUploadOutcome =\n | { stored: UploadFileResult }\n | { unavailable: string };\n\n/** What `create` answers in `Rebase-Upload-Result` when the last `PATCH` will carry the stored object there. */\nconst RESULT_ON_COMPLETE = \"on-complete\";\nconst DEFAULT_RETRY_DELAYS_MS = [250, 1_000, 3_000];\n\n/** `value` as UTF-8, base64-encoded — what `Upload-Metadata` carries. */\nfunction base64Utf8(value: string): string {\n const bytes = new TextEncoder().encode(value);\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n\n/** Worth asking for the offset and sending again: an offset conflict, a lock, a rate limit, a server fault. */\nconst isRetryable = (status: number) => status === 409 || status === 423 || status === 429 || status >= 500;\n\nconst sleep = (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms));\n\n/** The stored object a finished upload reports, or `null` when the header says nothing readable. */\nfunction storedObjectOf(header: string | null): UploadFileResult | null {\n if (!header) return null;\n let value: unknown;\n try {\n value = JSON.parse(decodeURIComponent(header));\n } catch {\n return null;\n }\n if (typeof value !== \"object\" || value === null) return null;\n const key = \"key\" in value ? value.key : undefined;\n const bucket = \"bucket\" in value ? value.bucket : undefined;\n const storageUrl = \"storageUrl\" in value ? value.storageUrl : undefined;\n if (typeof key !== \"string\" || typeof bucket !== \"string\" || typeof storageUrl !== \"string\") return null;\n return { key, bucket, storageUrl };\n}\n\n/**\n * Upload `file` through a Rebase server's TUS endpoint, one chunk at a time.\n *\n * The server holds every upload to what `POST /storage/upload` holds a file\n * to — the source's and the property's limits, the authorize hook, the bucket\n * — and stores it with the same key, type and custom metadata, and fires the\n * same triggers. So a refusal is thrown as the multipart route would have\n * thrown it. A chunk that fails on the way — a dropped connection, a 5xx, a\n * conflict over the offset — is retried from the offset the server reports.\n *\n * Answers `unavailable` when the resumable path cannot carry this file and\n * the multipart one can: a server without the endpoint (or one too old to\n * report the stored object), an upload another instance cannot see (several\n * instances, no shared volume, no sticky sessions), or a chunk over the\n * server's body limit.\n */\nexport async function uploadResumable(options: ResumableUploadOptions): Promise<ResumableUploadOutcome> {\n const { file, send } = options;\n const chunkSize = Math.max(1, options.chunkSize ?? RESUMABLE_UPLOAD_THRESHOLD);\n const retryDelays = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;\n const tus = { \"Tus-Resumable\": \"1.0.0\" };\n\n const created = await send(\"POST\", \"\", {\n ...tus,\n \"Upload-Length\": String(file.size),\n \"Upload-Metadata\": Object.entries(options.metadata)\n .map(([name, value]) => `${name} ${base64Utf8(value)}`)\n .join(\",\")\n });\n if (created.status === 404 || created.status === 405 || created.status === 501) {\n return { unavailable: `the server has no resumable upload endpoint (${created.status})` };\n }\n if (created.status < 200 || created.status >= 300) {\n throw created.error ?? new Error(`The upload was refused (${created.status}).`);\n }\n const id = created.header(\"Location\")?.split(\"?\")[0].split(\"/\").filter(Boolean).pop();\n if (!id) return { unavailable: \"the server did not say where the upload lives\" };\n const path = `/${encodeURIComponent(id)}`;\n if (created.header(\"Rebase-Upload-Result\") !== RESULT_ON_COMPLETE) {\n await send(\"DELETE\", path, tus).catch(() => undefined);\n return { unavailable: \"the server does not report the object a resumable upload stores\" };\n }\n\n let offset = 0;\n let lastFailure: Error | undefined;\n for (let attempt = 0; ;) {\n const end = Math.min(file.size, offset + chunkSize);\n let response: ResumableUploadResponse | undefined;\n try {\n response = await send(\"PATCH\", path, {\n ...tus,\n \"Upload-Offset\": String(offset),\n \"Content-Type\": \"application/offset+octet-stream\"\n }, file.slice(offset, end));\n } catch (error: unknown) {\n // No response: the connection dropped. Ask where the upload got to.\n lastFailure = error instanceof Error ? error : new Error(String(error));\n }\n\n if (response && response.status >= 200 && response.status < 300) {\n attempt = 0;\n const reported = Number(response.header(\"Upload-Offset\"));\n offset = Number.isFinite(reported) && reported > offset ? reported : end;\n if (offset < file.size) continue;\n const stored = storedObjectOf(response.header(\"Rebase-Upload-Result\"));\n if (!stored) throw new Error(\"The upload finished, but the server did not say where it stored it.\");\n return { stored };\n }\n if (response?.status === 404) {\n return { unavailable: \"the server no longer knows this upload — another instance may have answered\" };\n }\n if (response?.status === 413) {\n await send(\"DELETE\", path, tus).catch(() => undefined);\n return { unavailable: \"a chunk is over the server's request body limit\" };\n }\n if (response && !isRetryable(response.status)) {\n await send(\"DELETE\", path, tus).catch(() => undefined);\n throw response.error ?? new Error(`The upload was refused (${response.status}).`);\n }\n if (response) lastFailure = response.error ?? new Error(`The upload failed (${response.status}).`);\n\n if (attempt >= retryDelays.length) throw lastFailure ?? new Error(\"The upload failed.\");\n await sleep(retryDelays[attempt++]);\n // Resume from what the server holds, which may be more than was\n // acknowledged (the chunk arrived, its answer did not) or less.\n try {\n const progress = await send(\"HEAD\", path, tus);\n if (progress.status === 404) {\n return { unavailable: \"the server no longer knows this upload — another instance may have answered\" };\n }\n const held = Number(progress.header(\"Upload-Offset\"));\n if (progress.status >= 200 && progress.status < 300 && Number.isFinite(held)) offset = held;\n } catch {\n // Still no connection: the next attempt asks again.\n }\n }\n}\n"],"mappings":"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"}