@rebasepro/server 0.24.0 → 0.24.1-canary.gcb918fc

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 (224) hide show
  1. package/README.md +27 -2
  2. package/dist/GCSStorageController-fv6JAZ87.js +402 -0
  3. package/dist/GCSStorageController-fv6JAZ87.js.map +1 -0
  4. package/dist/S3StorageController-BNg-pXiY.js +480 -0
  5. package/dist/S3StorageController-BNg-pXiY.js.map +1 -0
  6. package/dist/api/mount-path.d.ts +30 -0
  7. package/dist/api/mount.d.ts +6 -13
  8. package/dist/api/rest/api-generator.d.ts +53 -1
  9. package/dist/api/rest/auth-collection-writes.d.ts +28 -21
  10. package/dist/api/rest/field-access-query.d.ts +28 -8
  11. package/dist/api/rest/field-ops.d.ts +19 -7
  12. package/dist/api/rest/query-body.d.ts +10 -0
  13. package/dist/api/rest/write-validation.d.ts +7 -5
  14. package/dist/api/schema-editor-routes.d.ts +10 -1
  15. package/dist/api/sql-export-routes.d.ts +8 -0
  16. package/dist/api/types.d.ts +35 -0
  17. package/dist/{ast-schema-editor-CWqS_sLJ.js → ast-schema-editor-mCeSC03x.js} +3 -3
  18. package/dist/{ast-schema-editor-CWqS_sLJ.js.map → ast-schema-editor-mCeSC03x.js.map} +1 -1
  19. package/dist/auth/access.d.ts +57 -8
  20. package/dist/auth/admin-user-ops.d.ts +24 -2
  21. package/dist/auth/admin-users-route.d.ts +1 -0
  22. package/dist/auth/api-keys/api-key-middleware.d.ts +10 -3
  23. package/dist/auth/api-keys/api-key-routes.d.ts +4 -1
  24. package/dist/auth/builtin-auth-adapter.d.ts +13 -0
  25. package/dist/auth/email-change-routes.d.ts +3 -0
  26. package/dist/auth/expired-token-sweep.d.ts +5 -1
  27. package/dist/auth/interfaces.d.ts +10 -0
  28. package/dist/auth/jwks-routes.d.ts +5 -1
  29. package/dist/auth/jwt-crypto.d.ts +7 -0
  30. package/dist/auth/jwt-keys.d.ts +4 -0
  31. package/dist/auth/jwt.d.ts +282 -166
  32. package/dist/auth/magic-link-routes.d.ts +3 -0
  33. package/dist/auth/mfa-crypto.d.ts +9 -3
  34. package/dist/auth/mfa-gate.d.ts +4 -2
  35. package/dist/auth/mfa-routes.d.ts +6 -0
  36. package/dist/auth/middleware.d.ts +9 -2
  37. package/dist/auth/otp-routes.d.ts +3 -0
  38. package/dist/auth/rate-limiter.d.ts +61 -34
  39. package/dist/auth/registration-policy.d.ts +17 -0
  40. package/dist/auth/require-auth.d.ts +1 -1
  41. package/dist/auth/routes.d.ts +16 -0
  42. package/dist/auth/session-routes.d.ts +6 -0
  43. package/dist/auth/user-picking.d.ts +37 -0
  44. package/dist/auth/verify-credential.d.ts +25 -4
  45. package/dist/{auth-DMLngxn_.js → auth-GnONzteu.js} +2869 -1871
  46. package/dist/auth-GnONzteu.js.map +1 -0
  47. package/dist/auth_adapter-CIItCTc8.js +21 -0
  48. package/dist/auth_adapter-CIItCTc8.js.map +1 -0
  49. package/dist/backend/create-backend.d.ts +281 -0
  50. package/dist/backend/default-backend.d.ts +35 -0
  51. package/dist/backend/options.d.ts +912 -0
  52. package/dist/backend/scope.d.ts +23 -0
  53. package/dist/backend/services.d.ts +54 -0
  54. package/dist/backend-DTAOsLQc.js.map +1 -1
  55. package/dist/backup/backup-common.d.ts +14 -3
  56. package/dist/backup/backup-routes.d.ts +23 -2
  57. package/dist/backup/backup-storage.d.ts +3 -1
  58. package/dist/backup/index.d.ts +3 -1
  59. package/dist/{backup-CN0s50D2.js → backup-BnvkSLHi.js} +176 -35
  60. package/dist/backup-BnvkSLHi.js.map +1 -0
  61. package/dist/boot/boot.d.ts +2 -2
  62. package/dist/boot/driver.d.ts +3 -0
  63. package/dist/boot/env.d.ts +9 -2
  64. package/dist/boot/http-server.d.ts +22 -0
  65. package/dist/boot/options.d.ts +16 -1
  66. package/dist/boot/provision.d.ts +4 -2
  67. package/dist/boot/runtime-cors.d.ts +17 -0
  68. package/dist/boot/sources.d.ts +2 -0
  69. package/dist/boot/static-routing.d.ts +9 -0
  70. package/dist/{contract-routes-fz8i4pxs.js → contract-routes-Ey6ftnKH.js} +4 -20
  71. package/dist/contract-routes-Ey6ftnKH.js.map +1 -0
  72. package/dist/cron/cron-loader.d.ts +77 -18
  73. package/dist/cron/cron-routes.d.ts +1 -1
  74. package/dist/cron/cron-scheduler.d.ts +69 -3
  75. package/dist/cron/cron-store.d.ts +33 -5
  76. package/dist/cron/define-cron.d.ts +6 -2
  77. package/dist/cron/index.d.ts +3 -3
  78. package/dist/cron-loader-B-u0snqq.js +202 -0
  79. package/dist/cron-loader-B-u0snqq.js.map +1 -0
  80. package/dist/{cron-routes-Bc-SB0Se.js → cron-routes-OD6BstUj.js} +27 -4
  81. package/dist/cron-routes-OD6BstUj.js.map +1 -0
  82. package/dist/{cron-scheduler-CYQgco86.js → cron-scheduler-idtLPoN-.js} +299 -33
  83. package/dist/cron-scheduler-idtLPoN-.js.map +1 -0
  84. package/dist/cron-store-CZ30ZBi-.js +577 -0
  85. package/dist/cron-store-CZ30ZBi-.js.map +1 -0
  86. package/dist/email/smtp-email-service.d.ts +23 -0
  87. package/dist/email/types.d.ts +18 -0
  88. package/dist/env.d.ts +0 -37
  89. package/dist/filter-dialect-O1HK6Nvr.js +1628 -0
  90. package/dist/filter-dialect-O1HK6Nvr.js.map +1 -0
  91. package/dist/{function-loader-D7o5Epjj.js → function-loader-iKhOZatS.js} +68 -38
  92. package/dist/function-loader-iKhOZatS.js.map +1 -0
  93. package/dist/{function-routes-CaNG4waN.js → function-routes-j9TwpvQe.js} +5 -4
  94. package/dist/function-routes-j9TwpvQe.js.map +1 -0
  95. package/dist/functions/context.d.ts +9 -0
  96. package/dist/functions/define-function.d.ts +10 -6
  97. package/dist/functions/function-loader.d.ts +75 -18
  98. package/dist/functions/function-routes.d.ts +2 -1
  99. package/dist/functions/index.d.ts +5 -4
  100. package/dist/functions/index.js +42 -13
  101. package/dist/functions/index.js.map +1 -1
  102. package/dist/functions/internal.d.ts +2 -2
  103. package/dist/functions/proxy.d.ts +6 -2
  104. package/dist/functions/request-timeout.d.ts +7 -5
  105. package/dist/history/history-routes.d.ts +9 -0
  106. package/dist/history/retention-sweep.d.ts +81 -0
  107. package/dist/{history-recorder-Nr8zLvoU.js → history-recorder-BfWRwmPy.js} +3 -3
  108. package/dist/{history-recorder-Nr8zLvoU.js.map → history-recorder-BfWRwmPy.js.map} +1 -1
  109. package/dist/{history-store-rcAm_xFR.js → history-store-DPofGVDp.js} +2 -2
  110. package/dist/{history-store-rcAm_xFR.js.map → history-store-DPofGVDp.js.map} +1 -1
  111. package/dist/index.d.ts +23 -10
  112. package/dist/index.es.js +24423 -20038
  113. package/dist/index.es.js.map +1 -1
  114. package/dist/init/docs.d.ts +27 -10
  115. package/dist/init/process-safety.d.ts +7 -2
  116. package/dist/init/shutdown.d.ts +18 -3
  117. package/dist/init.d.ts +49 -865
  118. package/dist/internal-tables-L2t39RdT.js +136 -0
  119. package/dist/internal-tables-L2t39RdT.js.map +1 -0
  120. package/dist/jobs/job-queue.d.ts +8 -0
  121. package/dist/jobs/job-store.d.ts +24 -0
  122. package/dist/jobs/types.d.ts +25 -4
  123. package/dist/{jobs-DqYNfquG.js → jobs-BCbflZj2.js} +234 -24
  124. package/dist/jobs-BCbflZj2.js.map +1 -0
  125. package/dist/{jwt-R6bSPMjk.js → jwt-BlvuKoR6.js} +529 -454
  126. package/dist/jwt-BlvuKoR6.js.map +1 -0
  127. package/dist/{keys-GAVZqbqx.js → keys-COzQ2aE8.js} +2 -2
  128. package/dist/{keys-GAVZqbqx.js.map → keys-COzQ2aE8.js.map} +1 -1
  129. package/dist/load-failure-L5hvfDoG.js +80 -0
  130. package/dist/load-failure-L5hvfDoG.js.map +1 -0
  131. package/dist/mcp/mcp-routes.d.ts +6 -0
  132. package/dist/mcp/mcp-tools.d.ts +8 -0
  133. package/dist/mcp/oauth-routes.d.ts +7 -0
  134. package/dist/metrics/index.d.ts +18 -0
  135. package/dist/metrics/pool-wait.d.ts +50 -0
  136. package/dist/offline-QNQgsPOq-B2yGeXkn.js +3305 -0
  137. package/dist/offline-QNQgsPOq-B2yGeXkn.js.map +1 -0
  138. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js +1915 -0
  139. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js.map +1 -0
  140. package/dist/{openapi-generator-DAq_XVDu.js → openapi-generator-D9zUQmEp.js} +366 -82
  141. package/dist/openapi-generator-D9zUQmEp.js.map +1 -0
  142. package/dist/policy-PdzX-Opd.js +121 -0
  143. package/dist/policy-PdzX-Opd.js.map +1 -0
  144. package/dist/project_manifest-R6y_KHms.js +59 -0
  145. package/dist/project_manifest-R6y_KHms.js.map +1 -0
  146. package/dist/{proxy-qRlqeUmO.js → proxy-CkTlMGmh.js} +4 -3
  147. package/dist/proxy-CkTlMGmh.js.map +1 -0
  148. package/dist/{query-parser-BgiKJKvc.js → query-parser-4WkMoA_K.js} +510 -15
  149. package/dist/query-parser-4WkMoA_K.js.map +1 -0
  150. package/dist/{request-timeout-DgH7j8qO.js → request-timeout-Dnl2_OBc.js} +15 -8
  151. package/dist/request-timeout-Dnl2_OBc.js.map +1 -0
  152. package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
  153. package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
  154. package/dist/schema-edit/apply-schema-change.d.ts +10 -0
  155. package/dist/schema-edit/local-source.d.ts +29 -0
  156. package/dist/schema-edit/remote-source.d.ts +13 -3
  157. package/dist/schema-edit/source-editor.d.ts +28 -0
  158. package/dist/{schema-editor-routes-oIyuWl3L.js → schema-editor-routes-C11Tumyd.js} +54 -10
  159. package/dist/schema-editor-routes-C11Tumyd.js.map +1 -0
  160. package/dist/services/outbound-post.d.ts +50 -0
  161. package/dist/services/outbound-url-guard.d.ts +34 -5
  162. package/dist/services/routed-realtime-service.d.ts +5 -0
  163. package/dist/services/webhook-service.d.ts +66 -14
  164. package/dist/singleton.d.ts +9 -5
  165. package/dist/soft-delete-params-DfV3vVN5.js +668 -0
  166. package/dist/soft-delete-params-DfV3vVN5.js.map +1 -0
  167. package/dist/sql-export-routes-ClRwzIsC.js +240 -0
  168. package/dist/sql-export-routes-ClRwzIsC.js.map +1 -0
  169. package/dist/{src-CatHFUym.js → src-D5Y3N-S4.js} +2 -1
  170. package/dist/{src-CatHFUym.js.map → src-D5Y3N-S4.js.map} +1 -1
  171. package/dist/storage/GCSStorageController.d.ts +46 -2
  172. package/dist/storage/LocalStorageController.d.ts +67 -2
  173. package/dist/storage/S3StorageController.d.ts +37 -3
  174. package/dist/storage/image-transform.d.ts +5 -0
  175. package/dist/storage/maintenance-routes.d.ts +23 -0
  176. package/dist/storage/maintenance.d.ts +126 -0
  177. package/dist/storage/rendition-cache.d.ts +9 -11
  178. package/dist/storage/request-principals.d.ts +30 -0
  179. package/dist/storage/routes.d.ts +20 -0
  180. package/dist/storage/source-variables.d.ts +10 -0
  181. package/dist/storage/streams.d.ts +42 -0
  182. package/dist/storage/tus-handler.d.ts +46 -2
  183. package/dist/storage/types.d.ts +82 -7
  184. package/dist/storage/upload-validation.d.ts +13 -0
  185. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
  186. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
  187. package/dist/strings.es-BeZWAodn.js +256 -0
  188. package/dist/strings.es-BeZWAodn.js.map +1 -0
  189. package/dist/upload-validation-Dd-fnsbA.js +172 -0
  190. package/dist/upload-validation-Dd-fnsbA.js.map +1 -0
  191. package/dist/utils/byte-size.d.ts +10 -0
  192. package/dist/utils/compression.d.ts +6 -0
  193. package/dist/utils/load-failure.d.ts +14 -0
  194. package/package.json +5 -5
  195. package/dist/GCSStorageController-BSiP1c-f.js +0 -256
  196. package/dist/GCSStorageController-BSiP1c-f.js.map +0 -1
  197. package/dist/S3StorageController-CAwFRgjV.js +0 -276
  198. package/dist/S3StorageController-CAwFRgjV.js.map +0 -1
  199. package/dist/auth-DMLngxn_.js.map +0 -1
  200. package/dist/backup-CN0s50D2.js.map +0 -1
  201. package/dist/contract-routes-fz8i4pxs.js.map +0 -1
  202. package/dist/cron-loader-CwaANlOG.js +0 -118
  203. package/dist/cron-loader-CwaANlOG.js.map +0 -1
  204. package/dist/cron-routes-Bc-SB0Se.js.map +0 -1
  205. package/dist/cron-scheduler-CYQgco86.js.map +0 -1
  206. package/dist/cron-store-D2Q9-Aco.js +0 -300
  207. package/dist/cron-store-D2Q9-Aco.js.map +0 -1
  208. package/dist/dynamic-import-X40pTZUQ.js +0 -23
  209. package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
  210. package/dist/function-loader-D7o5Epjj.js.map +0 -1
  211. package/dist/function-routes-CaNG4waN.js.map +0 -1
  212. package/dist/jobs-DqYNfquG.js.map +0 -1
  213. package/dist/jwt-R6bSPMjk.js.map +0 -1
  214. package/dist/openapi-generator-DAq_XVDu.js.map +0 -1
  215. package/dist/proxy-qRlqeUmO.js.map +0 -1
  216. package/dist/query-parser-BgiKJKvc.js.map +0 -1
  217. package/dist/request-timeout-DgH7j8qO.js.map +0 -1
  218. package/dist/schema-editor-routes-oIyuWl3L.js.map +0 -1
  219. package/dist/soft-delete-params-BWPilMPF.js +0 -59
  220. package/dist/soft-delete-params-BWPilMPF.js.map +0 -1
  221. package/dist/src-I3aG1PcY.js +0 -6870
  222. package/dist/src-I3aG1PcY.js.map +0 -1
  223. package/dist/types-BfKcm9do.js +0 -39
  224. package/dist/types-BfKcm9do.js.map +0 -1
@@ -5,7 +5,7 @@
5
5
  * optional peer dependency — users who don't need GCS never pay for the
6
6
  * import cost or need it installed.
7
7
  */
8
- import { StorageController, GCSStorageConfig } from "./types.js";
8
+ import { StorageController, GCSStorageConfig, type StoredObjectInfo, type StoredObjectRange, type PutObjectOptions, type StoredObjectStream } from "./types.js";
9
9
  import { UploadFileProps, UploadFileResult, DownloadConfig, DownloadMetadata, StorageListResult } from "@rebasepro/types";
10
10
  /**
11
11
  * Google Cloud Storage implementation of `StorageController`.
@@ -24,7 +24,7 @@ export declare class GCSStorageController implements StorageController {
24
24
  * @see S3StorageController.knownBuckets
25
25
  */
26
26
  knownBuckets(): string[];
27
- putObject({ file, key, metadata, bucket }: UploadFileProps): Promise<UploadFileResult>;
27
+ putObject({ file, key, metadata, bucket }: UploadFileProps, options?: PutObjectOptions): Promise<UploadFileResult>;
28
28
  /**
29
29
  * Read the object's metadata and nothing else — no signing.
30
30
  *
@@ -36,7 +36,51 @@ export declare class GCSStorageController implements StorageController {
36
36
  */
37
37
  getMetadata(key: string, bucket?: string): Promise<DownloadMetadata | null>;
38
38
  getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig>;
39
+ /**
40
+ * The object's bytes, type and version — all three of ONE generation.
41
+ *
42
+ * Described first, then read pinned to the generation described, as
43
+ * {@link getObjectStream} reads. It used to download and then ask for the
44
+ * metadata: an overwrite between the two calls answered the old bytes
45
+ * under the new generation's `updated`, and that `lastModified` keys the
46
+ * routes' `ETag` and transform cache, so the stale bytes — or a rendition
47
+ * of them — were served as current. A pinned read of a generation replaced
48
+ * since it was described fails, and the object is described again.
49
+ */
39
50
  getObject(key: string, bucket?: string): Promise<File | null>;
51
+ /**
52
+ * The bucket and key a call addresses: a `gs://` or `s3://` URL names its
53
+ * own bucket, anything else is a key in the given (or configured) bucket.
54
+ */
55
+ private resolveLocation;
56
+ /** The object's metadata and where it lives, or `null` when there is no such object. */
57
+ private locate;
58
+ /**
59
+ * The generation just described, so an overwrite between describing an
60
+ * object and reading it fails the read instead of sending the new bytes
61
+ * under the old size and version.
62
+ */
63
+ private pinnedFile;
64
+ /**
65
+ * An object stored gzip-encoded (`gsutil cp -z`) is decompressed on the way
66
+ * out, so its stored size is not the size served and a range of the one is
67
+ * not a range of the other. Such an object is read whole, as every object
68
+ * used to be; nothing this server writes is stored that way.
69
+ */
70
+ private readDecompressed;
71
+ /** See {@link StorageController.statObject}. One metadata request; no body is read. */
72
+ statObject(key: string, bucket?: string): Promise<StoredObjectInfo | null>;
73
+ /**
74
+ * See {@link StorageController.getObjectStream}. One metadata request, then
75
+ * a read of exactly the requested bytes, handed on as they arrive.
76
+ *
77
+ * The metadata comes first, not after the download as `getObject` has it:
78
+ * it is what pins the read to one generation.
79
+ */
80
+ getObjectStream(key: string, options?: {
81
+ bucket?: string;
82
+ range?: StoredObjectRange;
83
+ }): Promise<StoredObjectStream | null>;
40
84
  deleteObject(key: string, bucket?: string): Promise<void>;
41
85
  listObjects(prefix: string, options?: {
42
86
  bucket?: string;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Local filesystem storage controller
3
3
  */
4
- import { StorageController, LocalStorageConfig } from "./types.js";
4
+ import { StorageController, LocalStorageConfig, type StoredObjectInfo, type StoredObjectRange, type PutObjectOptions, type StoredObjectStream } from "./types.js";
5
5
  import { UploadFileProps, UploadFileResult, DownloadConfig, StorageListResult } from "@rebasepro/types";
6
6
  /**
7
7
  * Bucket used when a call names none.
@@ -10,6 +10,22 @@ import { UploadFileProps, UploadFileResult, DownloadConfig, StorageListResult }
10
10
  * bare key lives.
11
11
  */
12
12
  export declare const DEFAULT_BUCKET = "default";
13
+ /**
14
+ * Refuse a key that names one of local storage's own files rather than an
15
+ * object: the metadata beside an object, or a write in progress.
16
+ *
17
+ * Both live in the object namespace, beside the objects they serve. A key that
18
+ * names one used to be stored like any other, and so overwrote another
19
+ * object's metadata: uploading `a.png.metadata.json` changed the content type
20
+ * `a.png` is served with — the authorize hook asked about the one key while
21
+ * the other changed — and the uploader's own file vanished from listings and
22
+ * went with `a.png` when that was deleted. Read, it served the metadata.
23
+ *
24
+ * Local only: S3 and GCS keep metadata on the object, so every key there is
25
+ * an object. A 400, at every operation, because every one resolves its path
26
+ * through `getFullPath`.
27
+ */
28
+ export declare function refuseLocalReservedKey(key: string): void;
13
29
  /**
14
30
  * Local filesystem storage implementation
15
31
  * Stores files in a directory structure: {basePath}/{bucket}/{path}
@@ -17,8 +33,26 @@ export declare const DEFAULT_BUCKET = "default";
17
33
  export declare class LocalStorageController implements StorageController {
18
34
  private config;
19
35
  private basePath;
36
+ /**
37
+ * Folder entries read by a listing's first page, for its continuation
38
+ * pages. Keyed by the folder's absolute path (bucket included). Not
39
+ * caller-scoped, because the entries are not: every caller of this method
40
+ * gets the same names, and the routes authorize the prefix before calling
41
+ * it. Insertion-ordered, so the first key is the oldest.
42
+ */
43
+ private readonly listingSnapshots;
20
44
  constructor(config: LocalStorageConfig);
21
45
  getType(): "local";
46
+ /**
47
+ * Whether the file system under the storage root folds case — macOS and
48
+ * Windows by default — so `Avatar.png` and `avatar.png` are one file here
49
+ * and two on S3, GCS or a Linux disk.
50
+ *
51
+ * Read by writing a probe file and asking for it in the other case. The
52
+ * root is created if it is not there yet; the probe is removed. `false`
53
+ * when the root cannot be written: nothing is known.
54
+ */
55
+ isCaseInsensitive(): Promise<boolean>;
22
56
  /**
23
57
  * The bucket directories that exist under the storage root, plus `default`.
24
58
  *
@@ -69,15 +103,46 @@ export declare class LocalStorageController implements StorageController {
69
103
  * Validate file before upload
70
104
  */
71
105
  private validateFile;
72
- putObject({ file, key, metadata, bucket }: UploadFileProps): Promise<UploadFileResult>;
106
+ putObject({ file, key, metadata, bucket }: UploadFileProps, options?: PutObjectOptions): Promise<UploadFileResult>;
73
107
  getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig>;
74
108
  getObject(key: string, bucket?: string): Promise<File | null>;
109
+ /** The file a read of `key` addresses — `getObject`'s resolution, `local://` URLs included. */
110
+ private readPath;
111
+ /** See {@link StorageController.statObject}. A `stat` and the metadata sidecar; the file is not read. */
112
+ statObject(key: string, bucket?: string): Promise<StoredObjectInfo | null>;
113
+ /**
114
+ * See {@link StorageController.getObjectStream}. The file — or only the
115
+ * requested range of it — read from disk a chunk at a time, opened on the
116
+ * first read.
117
+ */
118
+ getObjectStream(key: string, options?: {
119
+ bucket?: string;
120
+ range?: StoredObjectRange;
121
+ }): Promise<StoredObjectStream | null>;
75
122
  deleteObject(key: string, bucket?: string): Promise<void>;
76
123
  listObjects(prefix: string, options?: {
77
124
  bucket?: string;
78
125
  maxResults?: number;
79
126
  pageToken?: string;
80
127
  }): Promise<StorageListResult>;
128
+ /**
129
+ * A folder's entries for a listing page.
130
+ *
131
+ * A page token is an index into these entries, and every continuation
132
+ * page used to read the whole folder again to find its place: a folder of
133
+ * n entries listed in pages of p cost n/p full reads. A first page reads
134
+ * the folder and, when there is a next page, keeps the read; a
135
+ * continuation page indexes into it while all of these hold:
136
+ *
137
+ * - the folder's mtime is the one read with it. Adding, removing or
138
+ * renaming an entry changes it, which is the invalidation;
139
+ * - it is under {@link LISTING_SNAPSHOT_TTL_MS} old;
140
+ * - the folder had not changed in the {@link LISTING_SNAPSHOT_RACY_MS}
141
+ * before the read. Otherwise it is not kept at all.
142
+ *
143
+ * A first page always reads the folder as it is.
144
+ */
145
+ private listingEntries;
81
146
  /**
82
147
  * Get the absolute filesystem path for serving files
83
148
  * Used by the storage routes to serve files directly
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * S3-compatible storage controller (works with AWS S3 and MinIO)
3
3
  */
4
- import { S3StorageConfig, StorageController } from "./types.js";
5
- import { DownloadConfig, StorageListResult, UploadFileProps, UploadFileResult } from "@rebasepro/types";
4
+ import { S3StorageConfig, StorageController, type StoredObjectInfo, type StoredObjectRange, type PutObjectOptions, type StoredObjectStream } from "./types.js";
5
+ import { DownloadConfig, DownloadMetadata, StorageListResult, UploadFileProps, UploadFileResult } from "@rebasepro/types";
6
6
  /**
7
7
  * S3-compatible storage implementation
8
8
  * Works with AWS S3 and MinIO (with forcePathStyle option)
@@ -34,13 +34,47 @@ export declare class S3StorageController implements StorageController {
34
34
  * Get the bucket name - either from parameter or config
35
35
  */
36
36
  private getBucket;
37
- putObject({ file, key, metadata, bucket }: UploadFileProps): Promise<UploadFileResult>;
37
+ putObject({ file, key, metadata, bucket }: UploadFileProps, options?: PutObjectOptions): Promise<UploadFileResult>;
38
+ /**
39
+ * Upload a large file in parts, one part in memory at a time.
40
+ *
41
+ * Reading the whole file into one buffer held it twice over (the
42
+ * `arrayBuffer()`, then the SDK's view and the multipart parser's copy
43
+ * before it), and `PutObject` stops at 5 GB regardless. A slice of a
44
+ * `File` reads only that range — from disk, for the disk-backed files TUS
45
+ * and backups hand over — so memory is one part whatever the file's size.
46
+ * A failure aborts the upload: no partial object appears at the key, and
47
+ * no orphaned parts are kept (and billed).
48
+ */
49
+ private putMultipart;
38
50
  /**
39
51
  * Flatten nested metadata to string values (S3 requirement)
40
52
  */
41
53
  private flattenMetadata;
42
54
  getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig>;
43
55
  getObject(key: string, bucket?: string): Promise<File | null>;
56
+ /**
57
+ * The bucket and key a call addresses: an `s3://` or `gs://` URL names its
58
+ * own bucket, anything else is a key in the given (or configured) bucket.
59
+ */
60
+ private resolveLocation;
61
+ /**
62
+ * See {@link StorageController.getMetadata}. One `HeadObject`. Without it,
63
+ * `GET /metadata/*` fell back to `getSignedUrl`, which signed a URL per
64
+ * call — ~134 µs of CPU — only for the route to throw it away.
65
+ */
66
+ getMetadata(key: string, bucket?: string): Promise<DownloadMetadata | null>;
67
+ /** See {@link StorageController.statObject}. One `HeadObject`; no body is read. */
68
+ statObject(key: string, bucket?: string): Promise<StoredObjectInfo | null>;
69
+ /**
70
+ * See {@link StorageController.getObjectStream}. One `GetObject`, with a
71
+ * `Range` when one is asked for, so only those bytes leave the bucket; the
72
+ * body is handed on as it arrives rather than collected first.
73
+ */
74
+ getObjectStream(key: string, options?: {
75
+ bucket?: string;
76
+ range?: StoredObjectRange;
77
+ }): Promise<StoredObjectStream | null>;
44
78
  deleteObject(key: string, bucket?: string): Promise<void>;
45
79
  listObjects(prefix: string, options?: {
46
80
  bucket?: string;
@@ -70,6 +70,11 @@ export declare const DECODABLE_FORMATS: Set<string>;
70
70
  export declare class UntransformableImageError extends Error {
71
71
  constructor(message: string);
72
72
  }
73
+ /**
74
+ * The content type a transform with these options produces: its `format`, or
75
+ * WebP when it names none, as {@link transformImage} encodes it.
76
+ */
77
+ export declare function transformContentType(options: ImageTransformOptions): string;
73
78
  /** Check whether a content type is a transformable image. */
74
79
  export declare function isTransformableImage(contentType: string): boolean;
75
80
  /**
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `/admin/storage` — the routes behind `rebase storage orphans` and
3
+ * `rebase storage move`. Admin-only; the caller mounts them behind the admin
4
+ * gate (`database:read` to read, `database:write` to delete or move).
5
+ *
6
+ * GET /orphans?storageId=&olderThanHours= objects no row names
7
+ * DELETE /orphans?storageId=&olderThanHours= delete them, found again first
8
+ * POST /move { from, to, prefix?, cursor?, limit?, deleteSource? }
9
+ */
10
+ import { Hono } from "hono";
11
+ import type { CollectionConfig, DataDriver } from "@rebasepro/types";
12
+ import type { HonoEnv } from "../api/types.js";
13
+ import type { StorageController } from "./types.js";
14
+ import type { StorageRegistry } from "./storage-registry.js";
15
+ export interface StorageMaintenanceRoutesConfig {
16
+ registry?: StorageRegistry;
17
+ controller?: StorageController;
18
+ /** The collections whose rows are read for references, read per request. */
19
+ collections: () => readonly CollectionConfig[];
20
+ /** The unscoped driver serving a collection: every row, whoever wrote it. */
21
+ driverFor: (collection: CollectionConfig) => DataDriver | undefined;
22
+ }
23
+ export declare function createStorageMaintenanceRoutes(config: StorageMaintenanceRoutesConfig): Hono<HonoEnv>;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Storage maintenance: the objects a source holds that no row names
3
+ * (`rebase storage orphans`), and copying a folder of objects from one source
4
+ * to another (`rebase storage move`).
5
+ *
6
+ * Nothing deletes a stored file when its field is cleared, its file replaced or
7
+ * its row deleted — two rows may hold one key, so the panel cannot know a file
8
+ * is unused. What can know is a scan of every row against every object, which
9
+ * is what this is. It errs one way only: a string anywhere in any row that
10
+ * could name an object keeps that object, whichever source it is in and
11
+ * whichever property holds it. A file is reported only when nothing points at
12
+ * it at all.
13
+ */
14
+ import type { CollectionConfig, DataDriver } from "@rebasepro/types";
15
+ import type { StorageController } from "./types.js";
16
+ /** One stored object, as a listing describes it. */
17
+ export interface StoredObjectEntry {
18
+ key: string;
19
+ size?: number;
20
+ /** ISO 8601, when the source's listing says. */
21
+ updated?: string;
22
+ }
23
+ /** A request this module cannot act on: a bad cursor, a source that is not served. */
24
+ export declare class InvalidMaintenanceRequestError extends Error {
25
+ }
26
+ /**
27
+ * The objects under a folder of a source, folder by folder, at most `limit` at
28
+ * a time. Hand the returned `cursor` back for the next ones; none means the
29
+ * walk is done. The internal `_rebase/` folder is not walked.
30
+ */
31
+ export declare function walkObjects(controller: StorageController, options?: {
32
+ prefix?: string;
33
+ cursor?: string;
34
+ limit?: number;
35
+ }): Promise<{
36
+ objects: StoredObjectEntry[];
37
+ cursor?: string;
38
+ }>;
39
+ /**
40
+ * The keys one stored string could name an object by.
41
+ *
42
+ * A file field stores a bare key, a `local://`, `s3://` or `gs://` URL with the
43
+ * bucket, or — with `storeUrl` — a download URL; text stores
44
+ * `rebase-storage:` references, and from before those, download URLs. Each
45
+ * spelling yields the key it names, and a key under a leading `default/` is
46
+ * also read without it, as the routes read it.
47
+ */
48
+ export declare function keysNamedBy(value: string): string[];
49
+ /** A collection whose rows could not be read, and why. */
50
+ export interface UnscannedCollection {
51
+ collection: string;
52
+ reason: string;
53
+ }
54
+ /**
55
+ * Remove from `keys` every key some row names.
56
+ *
57
+ * Every row of every collection is read — soft-deleted rows too, since a
58
+ * restore brings their files back — a page at a time, through the driver that
59
+ * serves the collection. What is left in `keys` afterwards is named by no row.
60
+ * A collection that could not be read is returned, and its rows count for
61
+ * nothing: a caller must not treat what is left as unused when any is.
62
+ */
63
+ export declare function removeReferencedKeys(keys: Set<string>, collections: readonly CollectionConfig[], driverFor: (collection: CollectionConfig) => DataDriver | undefined, options?: {
64
+ pageSize?: number;
65
+ }): Promise<{
66
+ scanned: string[];
67
+ unscanned: UnscannedCollection[];
68
+ }>;
69
+ /** What `findOrphans` found. */
70
+ export interface OrphanReport {
71
+ /** Objects in the source, internal ones aside. */
72
+ objects: number;
73
+ /** Objects no row names and older than the grace period. */
74
+ orphans: StoredObjectEntry[];
75
+ /**
76
+ * Objects no row names but too new to call unused — an upload whose row
77
+ * has not been saved yet looks exactly like this — or of unknown age.
78
+ */
79
+ recent: number;
80
+ scanned: string[];
81
+ unscanned: UnscannedCollection[];
82
+ }
83
+ /**
84
+ * The objects of one source that no row names.
85
+ *
86
+ * @param olderThanMs an object written less long ago than this is not called
87
+ * an orphan: the admin panel uploads a file before the row that names it
88
+ * is saved. One whose age the listing does not say is not either.
89
+ */
90
+ export declare function findOrphans(options: {
91
+ controller: StorageController;
92
+ collections: readonly CollectionConfig[];
93
+ driverFor: (collection: CollectionConfig) => DataDriver | undefined;
94
+ olderThanMs: number;
95
+ now?: number;
96
+ }): Promise<OrphanReport>;
97
+ /** What happened to one object in a move. */
98
+ export interface MovedObject {
99
+ key: string;
100
+ outcome: "copied" | "exists" | "failed";
101
+ /** Whether the source's copy was deleted afterwards. */
102
+ deleted?: boolean;
103
+ error?: string;
104
+ }
105
+ /**
106
+ * Copy the objects under a folder of one source into another, under the same
107
+ * keys, `limit` objects per call. Hand the returned `cursor` back for the next
108
+ * ones; none means the folder is done.
109
+ *
110
+ * A key the target already holds at the same size is left alone, so a move
111
+ * that stopped half-way is resumed by running it again. With `deleteSource`,
112
+ * the source's copy is deleted once the target holds one of the same size.
113
+ * The copy is the server's own write: the target's upload limits, which say
114
+ * what a user may upload, do not apply to a file already accepted once.
115
+ */
116
+ export declare function moveObjects(options: {
117
+ from: StorageController;
118
+ to: StorageController;
119
+ prefix?: string;
120
+ cursor?: string;
121
+ limit?: number;
122
+ deleteSource?: boolean;
123
+ }): Promise<{
124
+ moved: MovedObject[];
125
+ cursor?: string;
126
+ }>;
@@ -28,8 +28,15 @@ export interface DurableRendition {
28
28
  contentType: string;
29
29
  }
30
30
  export interface DurableRenditionCache {
31
- /** The stored rendition for this cache key, or null. Never throws. */
32
- get(controller: StorageController, cacheKey: string, bucket: string | undefined): Promise<DurableRendition | null>;
31
+ /**
32
+ * The stored rendition for this cache key, or null. Never throws.
33
+ *
34
+ * `contentType` is the type the transform produces
35
+ * (`transformContentType(options)`). The cache key carries the options,
36
+ * format included, so a key only ever holds a rendition of that type, and
37
+ * one read of that one object key answers the question.
38
+ */
39
+ get(controller: StorageController, cacheKey: string, bucket: string | undefined, contentType: string): Promise<DurableRendition | null>;
33
40
  /** Store a rendition. Never throws: a failed write is a slower cache. */
34
41
  put(controller: StorageController, cacheKey: string, bucket: string | undefined, rendition: DurableRendition): Promise<void>;
35
42
  }
@@ -43,13 +50,4 @@ export interface DurableRenditionCache {
43
50
  * is available to anyone auditing the prefix and to nothing on the hot path.
44
51
  */
45
52
  export declare function renditionKey(cacheKey: string, contentType: string): string;
46
- /**
47
- * A rendition's key when reading, where the content type is not yet known.
48
- *
49
- * Reads try the known extensions rather than storing an index: four `getObject`
50
- * calls at worst is still one round trip in the common case — the first
51
- * candidate is the format the request asked for — and an index is a second
52
- * thing to keep consistent with the objects it describes.
53
- */
54
- export declare function renditionKeyCandidates(cacheKey: string, preferred?: string): string[];
55
53
  export declare function createDurableRenditionCache(): DurableRenditionCache;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The two principals a storage read can be granted without a user: the reader
3
+ * of a declared-public object, and the bearer of a download token.
4
+ *
5
+ * `checkAuthorized` does not ask the `storageAuthorize` hook about either — the
6
+ * public prefix and the token are the authorization. That skip used to be
7
+ * decided on the principal's `uid` string (`"public"`, `"download-token"`), so
8
+ * any identity carrying that uid skipped the hook: a custom `AuthAdapter` whose
9
+ * `verifyRequest` returns `{ uid: "public" }` for a guest could then upload,
10
+ * delete and list anywhere, with the hook never asked.
11
+ *
12
+ * Each principal is now one object, set only by the middleware that granted
13
+ * it (`publicObjectAuth`, `fileTokenAuth`) and recognised by identity. An
14
+ * adapter builds a new object per request, so it can name the uid but never be
15
+ * the grant.
16
+ */
17
+ /** The principal `publicObjectAuth` grants an anonymous read of a public object. */
18
+ export declare const PUBLIC_OBJECT_PRINCIPAL: {
19
+ readonly uid: string;
20
+ readonly roles: string[];
21
+ };
22
+ /** The principal `fileTokenAuth` grants a read covered by a valid download token. */
23
+ export declare const DOWNLOAD_TOKEN_PRINCIPAL: {
24
+ readonly uid: string;
25
+ readonly roles: string[];
26
+ };
27
+ /** Whether `user` is the grant `publicObjectAuth` made — the object, not the uid. */
28
+ export declare function isPublicObjectGrant(user: unknown): boolean;
29
+ /** Whether `user` is the grant `fileTokenAuth` made — the object, not the uid. */
30
+ export declare function isDownloadTokenGrant(user: unknown): boolean;
@@ -142,6 +142,26 @@ export declare const DEFAULT_DOWNLOAD_TOKEN_TTL_SECONDS = 300;
142
142
  * file. A file that must be linked forever belongs under the public prefix.
143
143
  */
144
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⌉.
150
+ *
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.
157
+ */
158
+ export declare function storageRateLimitCost(c: {
159
+ req: {
160
+ method: string;
161
+ path: string;
162
+ json(): Promise<unknown>;
163
+ };
164
+ }): Promise<number>;
145
165
  /**
146
166
  * Create storage REST API routes
147
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 {};
@@ -11,12 +11,28 @@ import type { Context } from "hono";
11
11
  import type { StorageController } from "./types.js";
12
12
  import { type StorageRegistry } from "./storage-registry.js";
13
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?;
@@ -71,6 +87,12 @@ export declare class TusHandler {
71
87
  * `POST /upload` checks them.
72
88
  */
73
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
+ */
74
96
  private uploads;
75
97
  private tusDir;
76
98
  private cleanupTimer?;
@@ -140,8 +162,18 @@ export declare class TusHandler {
140
162
  private ensureDir;
141
163
  /** Start periodic cleanup of stale uploads. */
142
164
  startCleanup(): void;
143
- /** 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
+ */
144
174
  private cleanupStale;
175
+ /** The upload's bytes and record, gone; from every process, since they are files. */
176
+ private forget;
145
177
  /**
146
178
  * Parse the `Upload-Metadata` header.
147
179
  *
@@ -185,8 +217,20 @@ export declare class TusHandler {
185
217
  * the rest.
186
218
  */
187
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;
188
232
  /** `HEAD /tus/:id` — Query upload progress. */
189
- head(c: Context, id: string): Response;
233
+ head(c: Context, id: string): Promise<Response>;
190
234
  /** `PATCH /tus/:id` — Write one chunk of an upload, at its offset. */
191
235
  patch(c: Context, id: string): Promise<Response>;
192
236
  /** `DELETE /tus/:id` — Cancel and remove an upload. */