@rebasepro/server 0.23.0 → 0.24.1-canary.g19596ef
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -3
- package/bin/rebase-server.js +4 -2
- package/dist/GCSStorageController-DEga1ULx.js +402 -0
- package/dist/GCSStorageController-DEga1ULx.js.map +1 -0
- package/dist/S3StorageController-Btx-6bJ5.js +480 -0
- package/dist/S3StorageController-Btx-6bJ5.js.map +1 -0
- package/dist/api/ast-schema-editor.d.ts +92 -1
- package/dist/api/errors.d.ts +41 -0
- package/dist/api/live-schema-routes.d.ts +64 -10
- package/dist/api/logs-routes.d.ts +39 -1
- package/dist/api/mount-path.d.ts +30 -0
- package/dist/api/mount.d.ts +6 -13
- package/dist/api/openapi-generator.d.ts +17 -0
- package/dist/api/rest/api-generator.d.ts +97 -11
- package/dist/api/rest/auth-collection-writes.d.ts +28 -21
- package/dist/api/rest/field-access-query.d.ts +28 -8
- package/dist/api/rest/field-ops.d.ts +19 -7
- package/dist/api/rest/query-body.d.ts +10 -0
- package/dist/api/rest/write-validation.d.ts +9 -7
- package/dist/api/schema-editor-routes.d.ts +10 -1
- package/dist/api/sql-export-routes.d.ts +8 -0
- package/dist/api/types.d.ts +52 -1
- package/dist/{ast-schema-editor-Mvr50v_S.js → ast-schema-editor-mCeSC03x.js} +310 -12
- package/dist/ast-schema-editor-mCeSC03x.js.map +1 -0
- package/dist/auth/access.d.ts +156 -0
- package/dist/auth/adapter-middleware.d.ts +2 -1
- package/dist/auth/address-ownership.d.ts +16 -1
- package/dist/auth/admin-roles-route.d.ts +4 -2
- package/dist/auth/admin-roles.d.ts +17 -20
- package/dist/auth/admin-user-ops.d.ts +24 -2
- package/dist/auth/admin-users-route.d.ts +2 -0
- package/dist/auth/api-keys/api-key-middleware.d.ts +64 -56
- package/dist/auth/api-keys/api-key-routes.d.ts +44 -11
- package/dist/auth/api-keys/api-key-store.d.ts +31 -8
- package/dist/auth/api-keys/api-key-types.d.ts +14 -16
- package/dist/auth/api-keys/http-operation.d.ts +19 -0
- package/dist/auth/api-keys/index.d.ts +11 -11
- package/dist/auth/api-keys/key-grant.d.ts +41 -0
- package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
- package/dist/auth/auth-hooks.d.ts +46 -7
- package/dist/auth/builtin-auth-adapter.d.ts +21 -0
- package/dist/auth/cookie-utils.d.ts +7 -0
- package/dist/auth/deliverable-address.d.ts +6 -0
- package/dist/auth/email-change-routes.d.ts +44 -0
- package/dist/auth/expired-token-sweep.d.ts +71 -0
- package/dist/auth/impersonation.d.ts +110 -0
- package/dist/auth/index.d.ts +4 -2
- package/dist/auth/interfaces.d.ts +120 -59
- package/dist/auth/jwks-routes.d.ts +5 -1
- package/dist/auth/jwt-crypto.d.ts +7 -0
- package/dist/auth/jwt-keys.d.ts +4 -0
- package/dist/auth/jwt.d.ts +319 -157
- package/dist/auth/magic-link-routes.d.ts +5 -6
- package/dist/auth/mfa-crypto.d.ts +9 -3
- package/dist/auth/mfa-gate.d.ts +4 -2
- package/dist/auth/mfa-routes.d.ts +8 -9
- package/dist/auth/middleware.d.ts +26 -7
- package/dist/auth/otp-routes.d.ts +5 -6
- package/dist/auth/passwordless-signup.d.ts +27 -0
- package/dist/auth/platform-token.d.ts +122 -0
- package/dist/auth/rate-limiter.d.ts +99 -31
- package/dist/auth/registration-policy.d.ts +17 -0
- package/dist/auth/require-auth.d.ts +1 -1
- package/dist/auth/routes.d.ts +61 -0
- package/dist/auth/scope-routes.d.ts +22 -0
- package/dist/auth/session-routes.d.ts +17 -6
- package/dist/auth/token-revocation.d.ts +56 -1
- package/dist/auth/user-picking.d.ts +37 -0
- package/dist/auth/verify-credential.d.ts +49 -0
- package/dist/{auth-B-GIMpDG.js → auth-CSBky1mt.js} +4564 -1929
- package/dist/auth-CSBky1mt.js.map +1 -0
- package/dist/auth_adapter-CIItCTc8.js +21 -0
- package/dist/auth_adapter-CIItCTc8.js.map +1 -0
- package/dist/backend/create-backend.d.ts +281 -0
- package/dist/backend/default-backend.d.ts +35 -0
- package/dist/backend/no-collections.d.ts +19 -0
- package/dist/backend/options.d.ts +912 -0
- package/dist/backend/scope.d.ts +23 -0
- package/dist/backend/services.d.ts +54 -0
- package/dist/backend-DTAOsLQc.js.map +1 -1
- package/dist/backup/backup-common.d.ts +24 -3
- package/dist/backup/backup-routes.d.ts +46 -5
- package/dist/backup/backup-schedule.d.ts +33 -0
- package/dist/backup/backup-storage.d.ts +16 -0
- package/dist/backup/index.d.ts +5 -1
- package/dist/backup-DBGRUVTS.js +585 -0
- package/dist/backup-DBGRUVTS.js.map +1 -0
- package/dist/boot/boot.d.ts +2 -2
- package/dist/boot/bundle.d.ts +19 -0
- package/dist/boot/driver.d.ts +3 -0
- package/dist/boot/env.d.ts +58 -6
- package/dist/boot/http-server.d.ts +22 -0
- package/dist/boot/options.d.ts +16 -1
- package/dist/boot/provision.d.ts +4 -2
- package/dist/boot/resource-adapters.d.ts +11 -14
- package/dist/boot/runtime-cors.d.ts +17 -0
- package/dist/boot/security-headers.d.ts +26 -0
- package/dist/boot/sources.d.ts +9 -0
- package/dist/boot/static-routing.d.ts +65 -0
- package/dist/collection_patch-BRu-BvDv.js +472 -0
- package/dist/collection_patch-BRu-BvDv.js.map +1 -0
- package/dist/{contract-routes-CbFjuBwa.js → contract-routes-DiFwiLg2.js} +5 -8
- package/dist/contract-routes-DiFwiLg2.js.map +1 -0
- package/dist/cron/cron-loader.d.ts +77 -18
- package/dist/cron/cron-routes.d.ts +1 -1
- package/dist/cron/cron-scheduler.d.ts +94 -23
- package/dist/cron/cron-store.d.ts +37 -5
- package/dist/cron/define-cron.d.ts +6 -2
- package/dist/cron/index.d.ts +3 -3
- package/dist/cron-loader-NHqRfKF0.js +202 -0
- package/dist/cron-loader-NHqRfKF0.js.map +1 -0
- package/dist/{cron-routes-eE8nif_b.js → cron-routes-DsH8Jwn5.js} +34 -8
- package/dist/cron-routes-DsH8Jwn5.js.map +1 -0
- package/dist/{cron-scheduler-B0pLfAix.js → cron-scheduler-D1DT0nw5.js} +349 -65
- package/dist/cron-scheduler-D1DT0nw5.js.map +1 -0
- package/dist/cron-store-DEpVstUU.js +577 -0
- package/dist/cron-store-DEpVstUU.js.map +1 -0
- package/dist/{ddl-bootstrap-C6mo0Kmz.js → ddl-bootstrap-BNqi2NZe.js} +2 -2
- package/dist/{ddl-bootstrap-C6mo0Kmz.js.map → ddl-bootstrap-BNqi2NZe.js.map} +1 -1
- package/dist/email/index.d.ts +2 -2
- package/dist/email/smtp-email-service.d.ts +23 -0
- package/dist/email/templates.d.ts +22 -0
- package/dist/email/types.d.ts +44 -0
- package/dist/env.d.ts +1 -39
- package/dist/{errors-DWsX4yTd.js → errors-Cqp86KnA.js} +263 -10
- package/dist/errors-Cqp86KnA.js.map +1 -0
- package/dist/filter-dialect-O1HK6Nvr.js +1628 -0
- package/dist/filter-dialect-O1HK6Nvr.js.map +1 -0
- package/dist/{function-loader-xnbDAPfa.js → function-loader-DbCSoNJj.js} +69 -39
- package/dist/function-loader-DbCSoNJj.js.map +1 -0
- package/dist/{function-routes-Chet4-lB.js → function-routes-BSTClQNa.js} +28 -15
- package/dist/function-routes-BSTClQNa.js.map +1 -0
- package/dist/functions/context.d.ts +26 -6
- package/dist/functions/define-function.d.ts +10 -6
- package/dist/functions/function-loader.d.ts +75 -18
- package/dist/functions/function-routes.d.ts +2 -1
- package/dist/functions/guards.d.ts +22 -5
- package/dist/functions/index.d.ts +6 -5
- package/dist/functions/index.js +148 -50
- package/dist/functions/index.js.map +1 -1
- package/dist/functions/internal.d.ts +2 -2
- package/dist/functions/proxy.d.ts +6 -2
- package/dist/functions/request-timeout.d.ts +7 -5
- package/dist/history/history-routes.d.ts +9 -0
- package/dist/history/retention-sweep.d.ts +81 -0
- package/dist/{history-recorder-B4MpJfJK.js → history-recorder-RZ1Ow4YY.js} +4 -4
- package/dist/{history-recorder-B4MpJfJK.js.map → history-recorder-RZ1Ow4YY.js.map} +1 -1
- package/dist/{history-store-BhxWOuz9.js → history-store-DPofGVDp.js} +2 -2
- package/dist/{history-store-BhxWOuz9.js.map → history-store-DPofGVDp.js.map} +1 -1
- package/dist/index.d.ts +32 -13
- package/dist/index.es.js +25329 -18393
- package/dist/index.es.js.map +1 -1
- package/dist/init/docs.d.ts +35 -10
- package/dist/init/health.d.ts +17 -2
- package/dist/init/process-safety.d.ts +7 -2
- package/dist/init/shutdown.d.ts +26 -1
- package/dist/init.d.ts +49 -811
- package/dist/internal-tables-L2t39RdT.js +136 -0
- package/dist/internal-tables-L2t39RdT.js.map +1 -0
- package/dist/jobs/job-queue.d.ts +8 -0
- package/dist/jobs/job-store.d.ts +24 -0
- package/dist/jobs/types.d.ts +25 -4
- package/dist/{jobs-CazMYhyy.js → jobs-8G-_agld.js} +235 -25
- package/dist/jobs-8G-_agld.js.map +1 -0
- package/dist/{jwt-DnQHNFCl.js → jwt-CBaFc34Y.js} +546 -447
- package/dist/jwt-CBaFc34Y.js.map +1 -0
- package/dist/{keys-CogCQpxG.js → keys-COzQ2aE8.js} +3 -17
- package/dist/{keys-CogCQpxG.js.map → keys-COzQ2aE8.js.map} +1 -1
- package/dist/load-failure-L5hvfDoG.js +80 -0
- package/dist/load-failure-L5hvfDoG.js.map +1 -0
- package/dist/{logger-DO2PZc4i.js → logger-Bt32sHFw.js} +41 -5
- package/dist/logger-Bt32sHFw.js.map +1 -0
- package/dist/{logs-routes-Bj4TYYUl.js → logs-routes-DQJhrf-b.js} +48 -8
- package/dist/logs-routes-DQJhrf-b.js.map +1 -0
- package/dist/mcp/consent-page.d.ts +1 -1
- package/dist/mcp/mcp-routes.d.ts +13 -0
- package/dist/mcp/mcp-tools.d.ts +23 -9
- package/dist/mcp/oauth-metadata.d.ts +21 -16
- package/dist/mcp/oauth-routes.d.ts +14 -1
- package/dist/metrics/index.d.ts +18 -0
- package/dist/metrics/pool-wait.d.ts +50 -0
- package/dist/offline-QNQgsPOq-B2yGeXkn.js +3305 -0
- package/dist/offline-QNQgsPOq-B2yGeXkn.js.map +1 -0
- package/dist/offline-store-Cm5sAX81-CALiO4Gn.js +1915 -0
- package/dist/offline-store-Cm5sAX81-CALiO4Gn.js.map +1 -0
- package/dist/{openapi-generator-O_O24MAT.js → openapi-generator-CeGW2dLC.js} +455 -80
- package/dist/openapi-generator-CeGW2dLC.js.map +1 -0
- package/dist/policy-PdzX-Opd.js +121 -0
- package/dist/policy-PdzX-Opd.js.map +1 -0
- package/dist/project_manifest-R6y_KHms.js +59 -0
- package/dist/project_manifest-R6y_KHms.js.map +1 -0
- package/dist/{proxy-Czngl3p9.js → proxy-BEKbsil6.js} +5 -4
- package/dist/proxy-BEKbsil6.js.map +1 -0
- package/dist/{query-parser-DGRVFNM3.js → query-parser-ECxP0nDw.js} +497 -52
- package/dist/query-parser-ECxP0nDw.js.map +1 -0
- package/dist/{request-timeout-C_4C2BeR.js → request-timeout-BiRelY-U.js} +17 -10
- package/dist/request-timeout-BiRelY-U.js.map +1 -0
- package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
- package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
- package/dist/schema-edit/apply-schema-change.d.ts +73 -3
- package/dist/schema-edit/local-source.d.ts +29 -0
- package/dist/schema-edit/project-root.d.ts +3 -2
- package/dist/schema-edit/remote-source.d.ts +20 -5
- package/dist/schema-edit/source-editor.d.ts +28 -0
- package/dist/schema-editor-routes-DRXBAbZy.js +136 -0
- package/dist/schema-editor-routes-DRXBAbZy.js.map +1 -0
- package/dist/serve-spa.d.ts +58 -0
- package/dist/services/outbound-post.d.ts +50 -0
- package/dist/services/outbound-url-guard.d.ts +34 -5
- package/dist/services/routed-realtime-service.d.ts +16 -0
- package/dist/services/webhook-service.d.ts +66 -14
- package/dist/singleton.d.ts +9 -5
- package/dist/soft-delete-params-CBryZXMq.js +668 -0
- package/dist/soft-delete-params-CBryZXMq.js.map +1 -0
- package/dist/sql-export-routes-D8ZaRvoR.js +240 -0
- package/dist/sql-export-routes-D8ZaRvoR.js.map +1 -0
- package/dist/{src-vkcwKXbT.js → src-D5Y3N-S4.js} +440 -20
- package/dist/src-D5Y3N-S4.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +48 -2
- package/dist/storage/LocalStorageController.d.ts +69 -2
- package/dist/storage/S3StorageController.d.ts +39 -3
- package/dist/storage/image-transform.d.ts +15 -0
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/maintenance-routes.d.ts +23 -0
- package/dist/storage/maintenance.d.ts +126 -0
- package/dist/storage/property-limits.d.ts +41 -6
- package/dist/storage/rendition-cache.d.ts +9 -11
- package/dist/storage/request-keys.d.ts +15 -0
- package/dist/storage/request-principals.d.ts +30 -0
- package/dist/storage/requested-object.d.ts +74 -0
- package/dist/storage/routes.d.ts +52 -14
- package/dist/storage/source-variables.d.ts +10 -0
- package/dist/storage/streams.d.ts +42 -0
- package/dist/storage/tus-handler.d.ts +76 -7
- package/dist/storage/types.d.ts +101 -7
- package/dist/storage/upload-validation.d.ts +13 -0
- package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
- package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
- package/dist/strings.es-BeZWAodn.js +256 -0
- package/dist/strings.es-BeZWAodn.js.map +1 -0
- package/dist/upload-validation-CVobI5xO.js +172 -0
- package/dist/upload-validation-CVobI5xO.js.map +1 -0
- package/dist/utils/byte-size.d.ts +10 -0
- package/dist/utils/compression.d.ts +6 -0
- package/dist/utils/load-failure.d.ts +14 -0
- package/dist/utils/logger.d.ts +21 -0
- package/package.json +5 -5
- package/dist/GCSStorageController-CjrA4PMo.js +0 -242
- package/dist/GCSStorageController-CjrA4PMo.js.map +0 -1
- package/dist/S3StorageController-B6pKDNVj.js +0 -264
- package/dist/S3StorageController-B6pKDNVj.js.map +0 -1
- package/dist/admin-roles-vYdp_Pil.js +0 -36
- package/dist/admin-roles-vYdp_Pil.js.map +0 -1
- package/dist/admin_block-DxKLmdiv.js +0 -206
- package/dist/admin_block-DxKLmdiv.js.map +0 -1
- package/dist/ast-schema-editor-Mvr50v_S.js.map +0 -1
- package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
- package/dist/auth-B-GIMpDG.js.map +0 -1
- package/dist/backup-D7YR94N3.js +0 -253
- package/dist/backup-D7YR94N3.js.map +0 -1
- package/dist/contract-routes-CbFjuBwa.js.map +0 -1
- package/dist/cron-loader-DfTj2Hbi.js +0 -118
- package/dist/cron-loader-DfTj2Hbi.js.map +0 -1
- package/dist/cron-routes-eE8nif_b.js.map +0 -1
- package/dist/cron-scheduler-B0pLfAix.js.map +0 -1
- package/dist/cron-store-TcoGz-xS.js +0 -305
- package/dist/cron-store-TcoGz-xS.js.map +0 -1
- package/dist/dynamic-import-X40pTZUQ.js +0 -23
- package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
- package/dist/errors-DWsX4yTd.js.map +0 -1
- package/dist/function-loader-xnbDAPfa.js.map +0 -1
- package/dist/function-routes-Chet4-lB.js.map +0 -1
- package/dist/jobs-CazMYhyy.js.map +0 -1
- package/dist/jwt-DnQHNFCl.js.map +0 -1
- package/dist/logger-DO2PZc4i.js.map +0 -1
- package/dist/logs-routes-Bj4TYYUl.js.map +0 -1
- package/dist/openapi-generator-O_O24MAT.js.map +0 -1
- package/dist/proxy-Czngl3p9.js.map +0 -1
- package/dist/query-parser-DGRVFNM3.js.map +0 -1
- package/dist/request-timeout-C_4C2BeR.js.map +0 -1
- package/dist/schema-editor-routes-C5-lh_jO.js +0 -87
- package/dist/schema-editor-routes-C5-lh_jO.js.map +0 -1
- package/dist/src-pmvW7BFx.js +0 -6688
- package/dist/src-pmvW7BFx.js.map +0 -1
- package/dist/src-vkcwKXbT.js.map +0 -1
- package/dist/types-BfKcm9do.js +0 -39
- package/dist/types-BfKcm9do.js.map +0 -1
|
@@ -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
|
*
|
|
@@ -63,19 +97,52 @@ export declare class LocalStorageController implements StorageController {
|
|
|
63
97
|
* depend on it having run.
|
|
64
98
|
*/
|
|
65
99
|
private getFullPath;
|
|
100
|
+
/** See {@link StorageController.maxFileSize}. */
|
|
101
|
+
maxFileSize(): number;
|
|
66
102
|
/**
|
|
67
103
|
* Validate file before upload
|
|
68
104
|
*/
|
|
69
105
|
private validateFile;
|
|
70
|
-
putObject({ file, key, metadata, bucket }: UploadFileProps): Promise<UploadFileResult>;
|
|
106
|
+
putObject({ file, key, metadata, bucket }: UploadFileProps, options?: PutObjectOptions): Promise<UploadFileResult>;
|
|
71
107
|
getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig>;
|
|
72
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>;
|
|
73
122
|
deleteObject(key: string, bucket?: string): Promise<void>;
|
|
74
123
|
listObjects(prefix: string, options?: {
|
|
75
124
|
bucket?: string;
|
|
76
125
|
maxResults?: number;
|
|
77
126
|
pageToken?: string;
|
|
78
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;
|
|
79
146
|
/**
|
|
80
147
|
* Get the absolute filesystem path for serving files
|
|
81
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)
|
|
@@ -24,6 +24,8 @@ export declare class S3StorageController implements StorageController {
|
|
|
24
24
|
* can reach — and a mistyped one came back as "file not found".
|
|
25
25
|
*/
|
|
26
26
|
knownBuckets(): string[];
|
|
27
|
+
/** See {@link StorageController.maxFileSize}. */
|
|
28
|
+
maxFileSize(): number;
|
|
27
29
|
/**
|
|
28
30
|
* Validate file before upload
|
|
29
31
|
*/
|
|
@@ -32,13 +34,47 @@ export declare class S3StorageController implements StorageController {
|
|
|
32
34
|
* Get the bucket name - either from parameter or config
|
|
33
35
|
*/
|
|
34
36
|
private getBucket;
|
|
35
|
-
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;
|
|
36
50
|
/**
|
|
37
51
|
* Flatten nested metadata to string values (S3 requirement)
|
|
38
52
|
*/
|
|
39
53
|
private flattenMetadata;
|
|
40
54
|
getSignedUrl(key: string, bucket?: string): Promise<DownloadConfig>;
|
|
41
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>;
|
|
42
78
|
deleteObject(key: string, bucket?: string): Promise<void>;
|
|
43
79
|
listObjects(prefix: string, options?: {
|
|
44
80
|
bucket?: string;
|
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
* adjustment using the `sharp` library. Results are cached in an LRU
|
|
6
6
|
* in-memory cache to avoid redundant processing.
|
|
7
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Raised by {@link transformImage} when `sharp` is not installed. The storage
|
|
10
|
+
* routes never reach it: they ask {@link imageTransformsAvailable} first and
|
|
11
|
+
* serve the original.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ImageTransformUnavailableError extends Error {
|
|
14
|
+
constructor();
|
|
15
|
+
}
|
|
16
|
+
/** Whether this process can transform images — whether `sharp` loads. */
|
|
17
|
+
export declare function imageTransformsAvailable(): Promise<boolean>;
|
|
8
18
|
/** Options that can be specified via query parameters. */
|
|
9
19
|
export interface ImageTransformOptions {
|
|
10
20
|
width?: number;
|
|
@@ -70,6 +80,11 @@ export declare const DECODABLE_FORMATS: Set<string>;
|
|
|
70
80
|
export declare class UntransformableImageError extends Error {
|
|
71
81
|
constructor(message: string);
|
|
72
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* The content type a transform with these options produces: its `format`, or
|
|
85
|
+
* WebP when it names none, as {@link transformImage} encodes it.
|
|
86
|
+
*/
|
|
87
|
+
export declare function transformContentType(options: ImageTransformOptions): string;
|
|
73
88
|
/** Check whether a content type is a transformable image. */
|
|
74
89
|
export declare function isTransformableImage(contentType: string): boolean;
|
|
75
90
|
/**
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -21,8 +21,8 @@ export * from "./storage-registry.js";
|
|
|
21
21
|
export { parseTransformOptions, transformImage, isTransformableImage, TransformCache } from "./image-transform.js";
|
|
22
22
|
export type { ImageTransformOptions } from "./image-transform.js";
|
|
23
23
|
export { TusHandler } from "./tus-handler.js";
|
|
24
|
-
export { createUploadConstraintResolver, assertUploadWithinPropertyLimits, isAcceptedFile, readUploadPropertyContext, UPLOAD_COLLECTION_FIELD, UPLOAD_PROPERTY_FIELD } from "./property-limits.js";
|
|
25
|
-
export type { UploadConstraints, ResolveUploadConstraints } from "./property-limits.js";
|
|
24
|
+
export { createUploadConstraintResolver, createUploadPathResolver, assertUploadWithinPropertyLimits, assertUploadWithinPathLimits, isAcceptedFile, readUploadPropertyContext, UPLOAD_COLLECTION_FIELD, UPLOAD_PROPERTY_FIELD } from "./property-limits.js";
|
|
25
|
+
export type { UploadConstraints, ResolveUploadConstraints, ResolveUploadPathConstraints } from "./property-limits.js";
|
|
26
26
|
import { BackendStorageConfig, StorageController } from "./types.js";
|
|
27
27
|
/**
|
|
28
28
|
* Create a storage controller from a config object.
|
|
@@ -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
|
+
}>;
|
|
@@ -12,12 +12,14 @@ import type { CollectionConfig } from "@rebasepro/types";
|
|
|
12
12
|
* executable in the avatar bucket, and the config that said otherwise was never
|
|
13
13
|
* consulted.
|
|
14
14
|
*
|
|
15
|
-
* The limits are per *property*,
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
15
|
+
* The limits are per *property*, and the server finds the property two ways.
|
|
16
|
+
* By where the file lands: a property's `storagePath` is where its files go, so
|
|
17
|
+
* every upload into it meets the property's limits (`createUploadPathResolver`)
|
|
18
|
+
* — the panel never named its property, and a direct caller need not. And by
|
|
19
|
+
* name, when the upload carries context (a form field on `POST /upload`,
|
|
20
|
+
* `Upload-Metadata` on the resumable path), which covers a property whose
|
|
21
|
+
* `storagePath` is a function. A key no property claims, uploaded with no
|
|
22
|
+
* context, meets the source's own limit alone.
|
|
21
23
|
*/
|
|
22
24
|
/** What a property says a file may be. */
|
|
23
25
|
export interface UploadConstraints {
|
|
@@ -39,6 +41,39 @@ export type ResolveUploadConstraints = (collectionSlug: string, propertyPath: st
|
|
|
39
41
|
* to choose.
|
|
40
42
|
*/
|
|
41
43
|
export declare function createUploadConstraintResolver(collections: readonly CollectionConfig[]): ResolveUploadConstraints;
|
|
44
|
+
/**
|
|
45
|
+
* The limits of every property whose storage path a key falls in.
|
|
46
|
+
*
|
|
47
|
+
* @param storageId the source the upload is written to, canonical.
|
|
48
|
+
* @param key the canonical key the upload is written to.
|
|
49
|
+
*/
|
|
50
|
+
export type ResolveUploadPathConstraints = (storageId: string, key: string) => UploadConstraints[];
|
|
51
|
+
/**
|
|
52
|
+
* The storage-path rules of the collections this backend serves.
|
|
53
|
+
*
|
|
54
|
+
* A property's `maxSize` and `acceptedFiles` used to be checked only when an
|
|
55
|
+
* upload named the property — which the panel never did, and a direct caller
|
|
56
|
+
* simply does not — so the limits the docs called "enforced by the server"
|
|
57
|
+
* held for nobody. A property's `storagePath` is where its files go, so it is
|
|
58
|
+
* also where its limits hold: every upload into that path, through any door,
|
|
59
|
+
* meets them.
|
|
60
|
+
*
|
|
61
|
+
* A path computed by a function cannot be read without running it, and has no
|
|
62
|
+
* rule; neither has a property that declares no limits.
|
|
63
|
+
*/
|
|
64
|
+
export declare function createUploadPathResolver(collections: readonly CollectionConfig[]): ResolveUploadPathConstraints;
|
|
65
|
+
/**
|
|
66
|
+
* Refuse an upload that no property claiming its path would accept.
|
|
67
|
+
*
|
|
68
|
+
* Several properties may share a path; the file is for one of them, so it is
|
|
69
|
+
* enough that one accepts it. When none does, the first one's refusal is the
|
|
70
|
+
* answer, naming its property.
|
|
71
|
+
*/
|
|
72
|
+
export declare function assertUploadWithinPathLimits(constraints: readonly UploadConstraints[], file: {
|
|
73
|
+
size?: number;
|
|
74
|
+
type?: string;
|
|
75
|
+
name?: string;
|
|
76
|
+
}): void;
|
|
42
77
|
/** Is this file one the property accepts? */
|
|
43
78
|
export declare function isAcceptedFile(accepted: readonly string[] | undefined, contentType: string | undefined, fileName: string | undefined): boolean;
|
|
44
79
|
/**
|
|
@@ -28,8 +28,15 @@ export interface DurableRendition {
|
|
|
28
28
|
contentType: string;
|
|
29
29
|
}
|
|
30
30
|
export interface DurableRenditionCache {
|
|
31
|
-
/**
|
|
32
|
-
|
|
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;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* write path's idea of an acceptable key and bucket the same.
|
|
11
11
|
*/
|
|
12
12
|
import { ApiError } from "../api/errors.js";
|
|
13
|
+
import { type RequestedStorageObject } from "./requested-object.js";
|
|
13
14
|
import type { StorageController } from "./types.js";
|
|
14
15
|
/**
|
|
15
16
|
* Canonicalize a caller-supplied storage key, answering 400 when it names
|
|
@@ -22,6 +23,20 @@ import type { StorageController } from "./types.js";
|
|
|
22
23
|
* See `keys.ts` for why an unacceptable key is refused rather than repaired.
|
|
23
24
|
*/
|
|
24
25
|
export declare function canonicalKeyOrBadRequest(key: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* The object a `/file/*`, `/metadata/*` or `DELETE /file/*` request's wildcard
|
|
28
|
+
* path names, answering 400 when it names none.
|
|
29
|
+
*
|
|
30
|
+
* Derived by {@link requestedStorageObject}, the function `publicObjectAuth`
|
|
31
|
+
* and `fileTokenAuth` derive it with, so the key the route acts on is the key
|
|
32
|
+
* they decided about.
|
|
33
|
+
*/
|
|
34
|
+
export declare function requestedObjectOrBadRequest(wildcard: string): RequestedStorageObject;
|
|
35
|
+
/**
|
|
36
|
+
* {@link requestedObjectOrBadRequest} for a path that is already text rather
|
|
37
|
+
* than a URL path — the folder route's JSON body.
|
|
38
|
+
*/
|
|
39
|
+
export declare function objectOfPathOrBadRequest(path: string): RequestedStorageObject;
|
|
25
40
|
/**
|
|
26
41
|
* Canonicalize a caller-supplied bucket name, answering 400 when it is not one.
|
|
27
42
|
*
|
|
@@ -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;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which object a storage request's URL path names.
|
|
3
|
+
*
|
|
4
|
+
* Three parties read the path of a `GET /file/*`, `GET /metadata/*` or
|
|
5
|
+
* `DELETE /file/*`: the route, which serves or deletes an object;
|
|
6
|
+
* `publicObjectAuth`, which lets an anonymous caller through when that object
|
|
7
|
+
* is public; and `fileTokenAuth`, which compares it with the path a download
|
|
8
|
+
* token grants. A decision is only about the object acted on if it is made on
|
|
9
|
+
* the same key, so all three derive it here — the wildcard, the decoding, the
|
|
10
|
+
* bucket segment and the canonical key — and none of them spells a step of its
|
|
11
|
+
* own.
|
|
12
|
+
*
|
|
13
|
+
* They used to. `publicObjectAuth` judged the raw path with a check that strips
|
|
14
|
+
* everything up to a `://`, while the route canonicalized it, folding `//` to
|
|
15
|
+
* `/`: `GET /file/notes://public/secret.txt` was public to the one and served
|
|
16
|
+
* the private key `notes:/public/secret.txt` from the other, with the authorize
|
|
17
|
+
* hook never asked.
|
|
18
|
+
*
|
|
19
|
+
* Nothing here parses a scheme. A request path is a key, optionally behind the
|
|
20
|
+
* one bucket segment the routes recognise; a `://` in it is a `:` followed by
|
|
21
|
+
* an empty segment, and canonicalization folds the empty segment away.
|
|
22
|
+
*/
|
|
23
|
+
/** The object a request path names. */
|
|
24
|
+
export interface RequestedStorageObject {
|
|
25
|
+
/** The bucket. Always `"default"`: the only bucket a request path can name. */
|
|
26
|
+
bucket: string;
|
|
27
|
+
/** The canonical key — the one the hook is asked about and the controller acts on. */
|
|
28
|
+
key: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The wildcard part of a storage route's path, still percent-encoded.
|
|
32
|
+
*
|
|
33
|
+
* Hono's `c.req.param('*')` does not work reliably in sub-routers mounted
|
|
34
|
+
* via `app.route(prefix, subRouter)`. Instead it is derived from the
|
|
35
|
+
* fully-resolved `c.req.path` and `c.req.routePath`.
|
|
36
|
+
*
|
|
37
|
+
* For a route `/metadata/*` mounted at `/api/storage`, a request to
|
|
38
|
+
* `/api/storage/metadata/default/file.jpg` yields routePath
|
|
39
|
+
* `/api/storage/metadata/*`. The prefix (everything before `/*`) is stripped,
|
|
40
|
+
* plus one character for the trailing `/`, to obtain `default/file.jpg`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function storageRequestWildcard(c: {
|
|
43
|
+
req: {
|
|
44
|
+
path: string;
|
|
45
|
+
routePath: string;
|
|
46
|
+
};
|
|
47
|
+
}): string;
|
|
48
|
+
/**
|
|
49
|
+
* The object a decoded path names: a key, behind an optional `default/` bucket
|
|
50
|
+
* segment (matched case-insensitively). Throws {@link InvalidStorageKeyError}
|
|
51
|
+
* when the key cannot be canonicalized.
|
|
52
|
+
*
|
|
53
|
+
* For a path that is already text — the folder route's JSON body. A URL path
|
|
54
|
+
* goes through {@link requestedStorageObject}, which decodes it first.
|
|
55
|
+
*/
|
|
56
|
+
export declare function storageObjectOfPath(decodedPath: string): RequestedStorageObject;
|
|
57
|
+
/**
|
|
58
|
+
* The object a request's wildcard path names, or {@link InvalidStorageKeyError}.
|
|
59
|
+
*
|
|
60
|
+
* The wildcard arrives as Hono leaves it — `decodeURI`d, with the reserved
|
|
61
|
+
* characters (`/`, `:`, `%25` among them) still escaped — so one
|
|
62
|
+
* `decodeURIComponent` yields the path the client encoded. A `%` that does not
|
|
63
|
+
* begin an escape names no key, and is refused like any other key that cannot be
|
|
64
|
+
* canonicalized rather than thrown as a `URIError`.
|
|
65
|
+
*/
|
|
66
|
+
export declare function requestedStorageObject(wildcard: string): RequestedStorageObject;
|
|
67
|
+
/**
|
|
68
|
+
* {@link requestedStorageObject}, or `null` when the path names no object.
|
|
69
|
+
*
|
|
70
|
+
* For the middleware, which must fail closed without an exception: a path that
|
|
71
|
+
* names no object is neither public nor covered by any download token, and the
|
|
72
|
+
* route answers it with a 400 of its own.
|
|
73
|
+
*/
|
|
74
|
+
export declare function tryRequestedStorageObject(wildcard: string): RequestedStorageObject | null;
|