@streetjs/storage 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +719 -0
- package/dist/access.d.ts +95 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +114 -0
- package/dist/access.js.map +1 -0
- package/dist/cli/commands.d.ts +88 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +369 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/generators.d.ts +41 -0
- package/dist/cli/generators.d.ts.map +1 -0
- package/dist/cli/generators.js +119 -0
- package/dist/cli/generators.js.map +1 -0
- package/dist/directory.d.ts +88 -0
- package/dist/directory.d.ts.map +1 -0
- package/dist/directory.js +148 -0
- package/dist/directory.js.map +1 -0
- package/dist/driver.d.ts +83 -0
- package/dist/driver.d.ts.map +1 -0
- package/dist/driver.js +16 -0
- package/dist/driver.js.map +1 -0
- package/dist/drivers/azure.d.ts +239 -0
- package/dist/drivers/azure.d.ts.map +1 -0
- package/dist/drivers/azure.js +476 -0
- package/dist/drivers/azure.js.map +1 -0
- package/dist/drivers/backblaze.d.ts +105 -0
- package/dist/drivers/backblaze.d.ts.map +1 -0
- package/dist/drivers/backblaze.js +279 -0
- package/dist/drivers/backblaze.js.map +1 -0
- package/dist/drivers/gcs.d.ts +239 -0
- package/dist/drivers/gcs.d.ts.map +1 -0
- package/dist/drivers/gcs.js +454 -0
- package/dist/drivers/gcs.js.map +1 -0
- package/dist/drivers/local.d.ts +126 -0
- package/dist/drivers/local.d.ts.map +1 -0
- package/dist/drivers/local.js +321 -0
- package/dist/drivers/local.js.map +1 -0
- package/dist/drivers/memory.d.ts +104 -0
- package/dist/drivers/memory.d.ts.map +1 -0
- package/dist/drivers/memory.js +197 -0
- package/dist/drivers/memory.js.map +1 -0
- package/dist/drivers/minio.d.ts +90 -0
- package/dist/drivers/minio.d.ts.map +1 -0
- package/dist/drivers/minio.js +276 -0
- package/dist/drivers/minio.js.map +1 -0
- package/dist/drivers/r2.d.ts +100 -0
- package/dist/drivers/r2.d.ts.map +1 -0
- package/dist/drivers/r2.js +240 -0
- package/dist/drivers/r2.js.map +1 -0
- package/dist/drivers/s3-base.d.ts +270 -0
- package/dist/drivers/s3-base.d.ts.map +1 -0
- package/dist/drivers/s3-base.js +436 -0
- package/dist/drivers/s3-base.js.map +1 -0
- package/dist/drivers/s3.d.ts +111 -0
- package/dist/drivers/s3.d.ts.map +1 -0
- package/dist/drivers/s3.js +311 -0
- package/dist/drivers/s3.js.map +1 -0
- package/dist/drivers/supabase.d.ts +246 -0
- package/dist/drivers/supabase.d.ts.map +1 -0
- package/dist/drivers/supabase.js +502 -0
- package/dist/drivers/supabase.js.map +1 -0
- package/dist/errors.d.ts +115 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +131 -0
- package/dist/errors.js.map +1 -0
- package/dist/examples/storage-demo.d.ts +53 -0
- package/dist/examples/storage-demo.d.ts.map +1 -0
- package/dist/examples/storage-demo.js +242 -0
- package/dist/examples/storage-demo.js.map +1 -0
- package/dist/facade.d.ts +192 -0
- package/dist/facade.d.ts.map +1 -0
- package/dist/facade.js +898 -0
- package/dist/facade.js.map +1 -0
- package/dist/image.d.ts +94 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +224 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/events.d.ts +108 -0
- package/dist/integrations/events.d.ts.map +1 -0
- package/dist/integrations/events.js +111 -0
- package/dist/integrations/events.js.map +1 -0
- package/dist/integrations/queue.d.ts +89 -0
- package/dist/integrations/queue.d.ts.map +1 -0
- package/dist/integrations/queue.js +91 -0
- package/dist/integrations/queue.js.map +1 -0
- package/dist/integrations/realtime.d.ts +95 -0
- package/dist/integrations/realtime.d.ts.map +1 -0
- package/dist/integrations/realtime.js +90 -0
- package/dist/integrations/realtime.js.map +1 -0
- package/dist/lifecycle.d.ts +137 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +240 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/metadata.d.ts +109 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +139 -0
- package/dist/metadata.js.map +1 -0
- package/dist/multipart.d.ts +108 -0
- package/dist/multipart.d.ts.map +1 -0
- package/dist/multipart.js +200 -0
- package/dist/multipart.js.map +1 -0
- package/dist/observability.d.ts +82 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +175 -0
- package/dist/observability.js.map +1 -0
- package/dist/plugin.d.ts +82 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +100 -0
- package/dist/plugin.js.map +1 -0
- package/dist/resumable.d.ts +118 -0
- package/dist/resumable.d.ts.map +1 -0
- package/dist/resumable.js +223 -0
- package/dist/resumable.js.map +1 -0
- package/dist/search.d.ts +57 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +172 -0
- package/dist/search.js.map +1 -0
- package/dist/signed-url.d.ts +110 -0
- package/dist/signed-url.d.ts.map +1 -0
- package/dist/signed-url.js +237 -0
- package/dist/signed-url.js.map +1 -0
- package/dist/testing/index.d.ts +219 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +393 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tests/access.test.d.ts +2 -0
- package/dist/tests/access.test.d.ts.map +1 -0
- package/dist/tests/access.test.js +270 -0
- package/dist/tests/access.test.js.map +1 -0
- package/dist/tests/azure-driver.test.d.ts +2 -0
- package/dist/tests/azure-driver.test.d.ts.map +1 -0
- package/dist/tests/azure-driver.test.js +183 -0
- package/dist/tests/azure-driver.test.js.map +1 -0
- package/dist/tests/backblaze-driver.test.d.ts +2 -0
- package/dist/tests/backblaze-driver.test.d.ts.map +1 -0
- package/dist/tests/backblaze-driver.test.js +185 -0
- package/dist/tests/backblaze-driver.test.js.map +1 -0
- package/dist/tests/bridge-degradation.test.d.ts +2 -0
- package/dist/tests/bridge-degradation.test.d.ts.map +1 -0
- package/dist/tests/bridge-degradation.test.js +138 -0
- package/dist/tests/bridge-degradation.test.js.map +1 -0
- package/dist/tests/cli.test.d.ts +2 -0
- package/dist/tests/cli.test.d.ts.map +1 -0
- package/dist/tests/cli.test.js +127 -0
- package/dist/tests/cli.test.js.map +1 -0
- package/dist/tests/contract.d.ts +131 -0
- package/dist/tests/contract.d.ts.map +1 -0
- package/dist/tests/contract.js +222 -0
- package/dist/tests/contract.js.map +1 -0
- package/dist/tests/contract.test.d.ts +2 -0
- package/dist/tests/contract.test.d.ts.map +1 -0
- package/dist/tests/contract.test.js +110 -0
- package/dist/tests/contract.test.js.map +1 -0
- package/dist/tests/directory.test.d.ts +2 -0
- package/dist/tests/directory.test.d.ts.map +1 -0
- package/dist/tests/directory.test.js +125 -0
- package/dist/tests/directory.test.js.map +1 -0
- package/dist/tests/errors.test.d.ts +2 -0
- package/dist/tests/errors.test.d.ts.map +1 -0
- package/dist/tests/errors.test.js +93 -0
- package/dist/tests/errors.test.js.map +1 -0
- package/dist/tests/events-bridge.test.d.ts +2 -0
- package/dist/tests/events-bridge.test.d.ts.map +1 -0
- package/dist/tests/events-bridge.test.js +228 -0
- package/dist/tests/events-bridge.test.js.map +1 -0
- package/dist/tests/example-smoke.test.d.ts +2 -0
- package/dist/tests/example-smoke.test.d.ts.map +1 -0
- package/dist/tests/example-smoke.test.js +34 -0
- package/dist/tests/example-smoke.test.js.map +1 -0
- package/dist/tests/gcs-driver.test.d.ts +2 -0
- package/dist/tests/gcs-driver.test.d.ts.map +1 -0
- package/dist/tests/gcs-driver.test.js +197 -0
- package/dist/tests/gcs-driver.test.js.map +1 -0
- package/dist/tests/image.test.d.ts +2 -0
- package/dist/tests/image.test.d.ts.map +1 -0
- package/dist/tests/image.test.js +102 -0
- package/dist/tests/image.test.js.map +1 -0
- package/dist/tests/lifecycle.test.d.ts +2 -0
- package/dist/tests/lifecycle.test.d.ts.map +1 -0
- package/dist/tests/lifecycle.test.js +171 -0
- package/dist/tests/lifecycle.test.js.map +1 -0
- package/dist/tests/local-driver.test.d.ts +2 -0
- package/dist/tests/local-driver.test.d.ts.map +1 -0
- package/dist/tests/local-driver.test.js +153 -0
- package/dist/tests/local-driver.test.js.map +1 -0
- package/dist/tests/local-streaming.test.d.ts +2 -0
- package/dist/tests/local-streaming.test.d.ts.map +1 -0
- package/dist/tests/local-streaming.test.js +121 -0
- package/dist/tests/local-streaming.test.js.map +1 -0
- package/dist/tests/memory-driver.test.d.ts +2 -0
- package/dist/tests/memory-driver.test.d.ts.map +1 -0
- package/dist/tests/memory-driver.test.js +180 -0
- package/dist/tests/memory-driver.test.js.map +1 -0
- package/dist/tests/memory-streaming.test.d.ts +2 -0
- package/dist/tests/memory-streaming.test.d.ts.map +1 -0
- package/dist/tests/memory-streaming.test.js +92 -0
- package/dist/tests/memory-streaming.test.js.map +1 -0
- package/dist/tests/minio-driver.test.d.ts +2 -0
- package/dist/tests/minio-driver.test.d.ts.map +1 -0
- package/dist/tests/minio-driver.test.js +146 -0
- package/dist/tests/minio-driver.test.js.map +1 -0
- package/dist/tests/non-s3-integration.test.d.ts +2 -0
- package/dist/tests/non-s3-integration.test.d.ts.map +1 -0
- package/dist/tests/non-s3-integration.test.js +201 -0
- package/dist/tests/non-s3-integration.test.js.map +1 -0
- package/dist/tests/observability.registration.test.d.ts +2 -0
- package/dist/tests/observability.registration.test.d.ts.map +1 -0
- package/dist/tests/observability.registration.test.js +103 -0
- package/dist/tests/observability.registration.test.js.map +1 -0
- package/dist/tests/observability.test.d.ts +2 -0
- package/dist/tests/observability.test.d.ts.map +1 -0
- package/dist/tests/observability.test.js +240 -0
- package/dist/tests/observability.test.js.map +1 -0
- package/dist/tests/plugin.test.d.ts +2 -0
- package/dist/tests/plugin.test.d.ts.map +1 -0
- package/dist/tests/plugin.test.js +112 -0
- package/dist/tests/plugin.test.js.map +1 -0
- package/dist/tests/prop-concurrent.test.d.ts +2 -0
- package/dist/tests/prop-concurrent.test.d.ts.map +1 -0
- package/dist/tests/prop-concurrent.test.js +59 -0
- package/dist/tests/prop-concurrent.test.js.map +1 -0
- package/dist/tests/prop-copy.test.d.ts +2 -0
- package/dist/tests/prop-copy.test.d.ts.map +1 -0
- package/dist/tests/prop-copy.test.js +63 -0
- package/dist/tests/prop-copy.test.js.map +1 -0
- package/dist/tests/prop-delete.test.d.ts +2 -0
- package/dist/tests/prop-delete.test.d.ts.map +1 -0
- package/dist/tests/prop-delete.test.js +35 -0
- package/dist/tests/prop-delete.test.js.map +1 -0
- package/dist/tests/prop-directory.test.d.ts +2 -0
- package/dist/tests/prop-directory.test.d.ts.map +1 -0
- package/dist/tests/prop-directory.test.js +118 -0
- package/dist/tests/prop-directory.test.js.map +1 -0
- package/dist/tests/prop-driver-conformance.test.d.ts +2 -0
- package/dist/tests/prop-driver-conformance.test.d.ts.map +1 -0
- package/dist/tests/prop-driver-conformance.test.js +110 -0
- package/dist/tests/prop-driver-conformance.test.js.map +1 -0
- package/dist/tests/prop-driver-equivalence.test.d.ts +2 -0
- package/dist/tests/prop-driver-equivalence.test.d.ts.map +1 -0
- package/dist/tests/prop-driver-equivalence.test.js +163 -0
- package/dist/tests/prop-driver-equivalence.test.js.map +1 -0
- package/dist/tests/prop-lifecycle.test.d.ts +2 -0
- package/dist/tests/prop-lifecycle.test.d.ts.map +1 -0
- package/dist/tests/prop-lifecycle.test.js +83 -0
- package/dist/tests/prop-lifecycle.test.js.map +1 -0
- package/dist/tests/prop-list.test.d.ts +2 -0
- package/dist/tests/prop-list.test.d.ts.map +1 -0
- package/dist/tests/prop-list.test.js +53 -0
- package/dist/tests/prop-list.test.js.map +1 -0
- package/dist/tests/prop-metadata.test.d.ts +2 -0
- package/dist/tests/prop-metadata.test.d.ts.map +1 -0
- package/dist/tests/prop-metadata.test.js +87 -0
- package/dist/tests/prop-metadata.test.js.map +1 -0
- package/dist/tests/prop-move.test.d.ts +2 -0
- package/dist/tests/prop-move.test.d.ts.map +1 -0
- package/dist/tests/prop-move.test.js +67 -0
- package/dist/tests/prop-move.test.js.map +1 -0
- package/dist/tests/prop-multipart-abort.test.d.ts +2 -0
- package/dist/tests/prop-multipart-abort.test.d.ts.map +1 -0
- package/dist/tests/prop-multipart-abort.test.js +48 -0
- package/dist/tests/prop-multipart-abort.test.js.map +1 -0
- package/dist/tests/prop-multipart.test.d.ts +2 -0
- package/dist/tests/prop-multipart.test.d.ts.map +1 -0
- package/dist/tests/prop-multipart.test.js +65 -0
- package/dist/tests/prop-multipart.test.js.map +1 -0
- package/dist/tests/prop-resumable.test.d.ts +2 -0
- package/dist/tests/prop-resumable.test.d.ts.map +1 -0
- package/dist/tests/prop-resumable.test.js +78 -0
- package/dist/tests/prop-resumable.test.js.map +1 -0
- package/dist/tests/prop-search.test.d.ts +2 -0
- package/dist/tests/prop-search.test.d.ts.map +1 -0
- package/dist/tests/prop-search.test.js +120 -0
- package/dist/tests/prop-search.test.js.map +1 -0
- package/dist/tests/prop-signed-url.test.d.ts +2 -0
- package/dist/tests/prop-signed-url.test.d.ts.map +1 -0
- package/dist/tests/prop-signed-url.test.js +74 -0
- package/dist/tests/prop-signed-url.test.js.map +1 -0
- package/dist/tests/prop-streaming.test.d.ts +2 -0
- package/dist/tests/prop-streaming.test.d.ts.map +1 -0
- package/dist/tests/prop-streaming.test.js +34 -0
- package/dist/tests/prop-streaming.test.js.map +1 -0
- package/dist/tests/prop-upload-download.test.d.ts +2 -0
- package/dist/tests/prop-upload-download.test.d.ts.map +1 -0
- package/dist/tests/prop-upload-download.test.js +30 -0
- package/dist/tests/prop-upload-download.test.js.map +1 -0
- package/dist/tests/prop-validation.test.d.ts +2 -0
- package/dist/tests/prop-validation.test.d.ts.map +1 -0
- package/dist/tests/prop-validation.test.js +50 -0
- package/dist/tests/prop-validation.test.js.map +1 -0
- package/dist/tests/prop-versioning.test.d.ts +2 -0
- package/dist/tests/prop-versioning.test.d.ts.map +1 -0
- package/dist/tests/prop-versioning.test.js +74 -0
- package/dist/tests/prop-versioning.test.js.map +1 -0
- package/dist/tests/queue-bridge.test.d.ts +2 -0
- package/dist/tests/queue-bridge.test.d.ts.map +1 -0
- package/dist/tests/queue-bridge.test.js +111 -0
- package/dist/tests/queue-bridge.test.js.map +1 -0
- package/dist/tests/r2-driver.test.d.ts +2 -0
- package/dist/tests/r2-driver.test.d.ts.map +1 -0
- package/dist/tests/r2-driver.test.js +143 -0
- package/dist/tests/r2-driver.test.js.map +1 -0
- package/dist/tests/realtime-bridge.test.d.ts +2 -0
- package/dist/tests/realtime-bridge.test.d.ts.map +1 -0
- package/dist/tests/realtime-bridge.test.js +106 -0
- package/dist/tests/realtime-bridge.test.js.map +1 -0
- package/dist/tests/resumable.test.d.ts +2 -0
- package/dist/tests/resumable.test.d.ts.map +1 -0
- package/dist/tests/resumable.test.js +104 -0
- package/dist/tests/resumable.test.js.map +1 -0
- package/dist/tests/s3-base.test.d.ts +2 -0
- package/dist/tests/s3-base.test.d.ts.map +1 -0
- package/dist/tests/s3-base.test.js +289 -0
- package/dist/tests/s3-base.test.js.map +1 -0
- package/dist/tests/s3-integration.test.d.ts +2 -0
- package/dist/tests/s3-integration.test.d.ts.map +1 -0
- package/dist/tests/s3-integration.test.js +270 -0
- package/dist/tests/s3-integration.test.js.map +1 -0
- package/dist/tests/s3.test.d.ts +2 -0
- package/dist/tests/s3.test.d.ts.map +1 -0
- package/dist/tests/s3.test.js +203 -0
- package/dist/tests/s3.test.js.map +1 -0
- package/dist/tests/scaffold.test.d.ts +2 -0
- package/dist/tests/scaffold.test.d.ts.map +1 -0
- package/dist/tests/scaffold.test.js +13 -0
- package/dist/tests/scaffold.test.js.map +1 -0
- package/dist/tests/search.test.d.ts +2 -0
- package/dist/tests/search.test.d.ts.map +1 -0
- package/dist/tests/search.test.js +106 -0
- package/dist/tests/search.test.js.map +1 -0
- package/dist/tests/supabase-driver.test.d.ts +2 -0
- package/dist/tests/supabase-driver.test.d.ts.map +1 -0
- package/dist/tests/supabase-driver.test.js +196 -0
- package/dist/tests/supabase-driver.test.js.map +1 -0
- package/dist/tests/testing-utilities.test.d.ts +2 -0
- package/dist/tests/testing-utilities.test.d.ts.map +1 -0
- package/dist/tests/testing-utilities.test.js +138 -0
- package/dist/tests/testing-utilities.test.js.map +1 -0
- package/dist/tests/validation.test.d.ts +2 -0
- package/dist/tests/validation.test.d.ts.map +1 -0
- package/dist/tests/validation.test.js +135 -0
- package/dist/tests/validation.test.js.map +1 -0
- package/dist/tests/versioning.test.d.ts +2 -0
- package/dist/tests/versioning.test.d.ts.map +1 -0
- package/dist/tests/versioning.test.js +128 -0
- package/dist/tests/versioning.test.js.map +1 -0
- package/dist/types.d.ts +232 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +105 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +225 -0
- package/dist/validation.js.map +1 -0
- package/dist/versioning.d.ts +100 -0
- package/dist/versioning.d.ts.map +1 -0
- package/dist/versioning.js +179 -0
- package/dist/versioning.js.map +1 -0
- package/package.json +137 -0
package/README.md
ADDED
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/hassanmubiru/StreetJS/main/docs/assets/images/logo-512.png" alt="StreetJS logo" width="100" height="100">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# @streetjs/storage
|
|
6
|
+
|
|
7
|
+
StreetJS Core v2 **Pillar 4**: a production-grade, strongly typed, cloud-agnostic
|
|
8
|
+
object storage framework. One consistent API surface across every supported
|
|
9
|
+
provider — switching between Memory, Local, S3, Cloudflare R2, Supabase, Google
|
|
10
|
+
Cloud Storage, Azure Blob, MinIO, and Backblaze B2 is a **configuration change,
|
|
11
|
+
never an application rewrite**.
|
|
12
|
+
|
|
13
|
+
- **Unified facade** — `createStorage(config)` returns a `Storage` whose method
|
|
14
|
+
signatures are identical no matter which driver backs it.
|
|
15
|
+
- **Zero-dependency core** — `streetjs` is the only runtime dependency. The
|
|
16
|
+
`memory` and `local` drivers need nothing else.
|
|
17
|
+
- **Optional provider SDKs** — every cloud provider lives behind its own
|
|
18
|
+
submodule (`@streetjs/storage/s3`, `/r2`, `/supabase`, `/gcs`, `/azure`,
|
|
19
|
+
`/minio`, `/backblaze`) and its SDK is an **optional peer dependency**, pulled
|
|
20
|
+
in only when you actually use that provider.
|
|
21
|
+
- **Full feature set** — object operations, streaming, multipart, resumable
|
|
22
|
+
uploads, signed URLs, validation, typed metadata, versioning, lifecycle rules,
|
|
23
|
+
image processing, a directory API, and search.
|
|
24
|
+
- **Ecosystem bridges** — optional, no-hard-dependency integration with
|
|
25
|
+
`@streetjs/events`, `@streetjs/queue`, and `@streetjs/realtime`, plus reuse of
|
|
26
|
+
the core `MetricsRegistry` / `HealthCheckRegistry`.
|
|
27
|
+
- **In-process test doubles** — `@streetjs/storage/testing`.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @streetjs/storage
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
That is all you need for the `memory` and `local` drivers. Cloud providers each
|
|
36
|
+
require their own optional peer SDK — see the per-provider guides below.
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { createStorage } from "@streetjs/storage";
|
|
42
|
+
|
|
43
|
+
// A zero-dependency, in-memory store — great for tests and getting started.
|
|
44
|
+
const storage = createStorage({ provider: "memory" });
|
|
45
|
+
|
|
46
|
+
// Put returns the typed object metadata.
|
|
47
|
+
const meta = await storage.put("avatars/u1.png", pngBytes, {
|
|
48
|
+
contentType: "image/png",
|
|
49
|
+
owner: "user-1",
|
|
50
|
+
});
|
|
51
|
+
console.log(meta.size, meta.etag, meta.checksum, meta.createdAt);
|
|
52
|
+
|
|
53
|
+
// Get returns a discriminated result — no throw on a missing key.
|
|
54
|
+
const result = await storage.get("avatars/u1.png");
|
|
55
|
+
if (result.found) {
|
|
56
|
+
console.log(result.bytes, result.metadata);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The rest of the object surface.
|
|
60
|
+
await storage.exists("avatars/u1.png"); // boolean
|
|
61
|
+
await storage.copy("avatars/u1.png", "avatars/u1-copy.png");
|
|
62
|
+
await storage.move("avatars/u1-copy.png", "trash/u1.png");
|
|
63
|
+
await storage.rename("trash/u1.png", "trash/old.png");
|
|
64
|
+
const items = await storage.list("avatars/"); // [{ key, size, updatedAt }]
|
|
65
|
+
const stat = await storage.stat("avatars/u1.png"); // metadata | null
|
|
66
|
+
await storage.delete("avatars/u1.png");
|
|
67
|
+
|
|
68
|
+
await storage.close();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Use the `local` driver to persist to the filesystem. It requires a `root`:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { createStorage } from "@streetjs/storage";
|
|
75
|
+
|
|
76
|
+
const storage = createStorage({ provider: "local", root: "/var/data/uploads" });
|
|
77
|
+
await storage.put("reports/2024.csv", csvBytes, { contentType: "text/csv" });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Configuration
|
|
81
|
+
|
|
82
|
+
`createStorage(config)` accepts:
|
|
83
|
+
|
|
84
|
+
| Field | Type | Purpose |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `provider` | `"memory" \| "local" \| string` | Selects a built-in driver, or names a cloud provider when you also pass `driver`. |
|
|
87
|
+
| `driver` | `StorageDriver` | A pre-constructed cloud driver (from a submodule). Takes precedence over `provider`. |
|
|
88
|
+
| `root` | `string` | Filesystem root for the `local` driver. |
|
|
89
|
+
| `clock` | `Clock` | Inject deterministic time (timestamps, signed-URL expiry, lifecycle age). |
|
|
90
|
+
| `validation` | `ValidationConfig` | Upload validation pipeline (see Validation). |
|
|
91
|
+
| `versioning` | `boolean` | Snapshot prior content on overwrite. |
|
|
92
|
+
| `signingSecret` | `string` | HMAC key for simulated signed URLs. |
|
|
93
|
+
| `metrics` | `MetricsRegistry` | Register storage metrics with the core registry. |
|
|
94
|
+
| `health` | `HealthCheckRegistry` | Register storage health checks with the core registry. |
|
|
95
|
+
| `auth` | `AuthLike` | Structural auth bridge for access-control decisions. |
|
|
96
|
+
| `imageCodec` | `ImageCodec` | Structural codec that performs image transforms. |
|
|
97
|
+
| `bridges` | `{ events?, queue?, realtime? }` | Structural ecosystem bridges. |
|
|
98
|
+
|
|
99
|
+
If `provider` is an unknown name and no `driver` is supplied, `createStorage`
|
|
100
|
+
throws a descriptive `StorageConfigError` and returns no instance.
|
|
101
|
+
|
|
102
|
+
## Providers
|
|
103
|
+
|
|
104
|
+
Every cloud driver exposes **two construction styles**:
|
|
105
|
+
|
|
106
|
+
- A synchronous `create…Driver(client, options?)` that wraps an **injected**
|
|
107
|
+
structural client. No SDK is loaded — ideal for tests and for supplying your
|
|
108
|
+
own client.
|
|
109
|
+
- An asynchronous `connect…Driver(config)` / `create…DriverFromConfig(config)`
|
|
110
|
+
that **builds its own client** by lazily `import()`-ing the provider SDK. If
|
|
111
|
+
the optional peer SDK is not installed, it throws a `StorageConfigError`.
|
|
112
|
+
|
|
113
|
+
For cloud providers you construct the driver, then pass it to `createStorage`
|
|
114
|
+
as a pre-constructed `driver`:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
const driver = await connectSomeDriver({ /* ... */ });
|
|
118
|
+
const storage = createStorage({ provider: "s3", driver });
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Memory (`provider: "memory"`)
|
|
122
|
+
|
|
123
|
+
Zero external dependencies, fully in-process. Every feature is simulated over
|
|
124
|
+
in-memory primitives. Best for tests, examples, and ephemeral workloads.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { createStorage } from "@streetjs/storage";
|
|
128
|
+
|
|
129
|
+
const storage = createStorage({ provider: "memory" });
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Local filesystem (`provider: "local"`)
|
|
133
|
+
|
|
134
|
+
Zero external dependencies. Persists objects under a `root` directory.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { createStorage } from "@streetjs/storage";
|
|
138
|
+
|
|
139
|
+
const storage = createStorage({ provider: "local", root: "/var/data/uploads" });
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Amazon S3 — `@streetjs/storage/s3`
|
|
143
|
+
|
|
144
|
+
Optional peer dependencies: `@aws-sdk/client-s3` and (for signed URLs)
|
|
145
|
+
`@aws-sdk/s3-request-presigner`.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { createStorage } from "@streetjs/storage";
|
|
149
|
+
import {
|
|
150
|
+
createS3StorageDriver,
|
|
151
|
+
createS3StorageDriverFromConfig,
|
|
152
|
+
} from "@streetjs/storage/s3";
|
|
153
|
+
|
|
154
|
+
// Build a client from config (lazily imports the AWS SDK):
|
|
155
|
+
const driver = await createS3StorageDriverFromConfig({
|
|
156
|
+
bucket: "my-bucket",
|
|
157
|
+
region: "us-east-1",
|
|
158
|
+
credentials: {
|
|
159
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
160
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Or wrap an injected, SDK-free structural client (no AWS SDK needed):
|
|
165
|
+
// const driver = createS3StorageDriver(myS3ClientLike);
|
|
166
|
+
|
|
167
|
+
const storage = createStorage({ provider: "s3", driver });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Cloudflare R2 — `@streetjs/storage/r2`
|
|
171
|
+
|
|
172
|
+
R2 speaks the S3 API. Optional peer dependency: `@aws-sdk/client-s3`.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { createStorage } from "@streetjs/storage";
|
|
176
|
+
import {
|
|
177
|
+
connectCloudflareR2Driver,
|
|
178
|
+
createCloudflareR2Driver,
|
|
179
|
+
} from "@streetjs/storage/r2";
|
|
180
|
+
|
|
181
|
+
const driver = await connectCloudflareR2Driver({
|
|
182
|
+
accountId: process.env.R2_ACCOUNT_ID!,
|
|
183
|
+
bucket: "my-bucket",
|
|
184
|
+
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
|
185
|
+
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
|
186
|
+
// endpoint defaults to https://<accountId>.r2.cloudflarestorage.com
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// SDK-free alternative with an injected client:
|
|
190
|
+
// const driver = createCloudflareR2Driver(myS3ClientLike);
|
|
191
|
+
|
|
192
|
+
const storage = createStorage({ provider: "r2", driver });
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Supabase Storage — `@streetjs/storage/supabase`
|
|
196
|
+
|
|
197
|
+
Optional peer dependency: `@supabase/supabase-js`.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { createStorage } from "@streetjs/storage";
|
|
201
|
+
import {
|
|
202
|
+
connectSupabaseStorageDriver,
|
|
203
|
+
createSupabaseStorageDriver,
|
|
204
|
+
} from "@streetjs/storage/supabase";
|
|
205
|
+
|
|
206
|
+
const driver = await connectSupabaseStorageDriver({
|
|
207
|
+
url: process.env.SUPABASE_URL!,
|
|
208
|
+
key: process.env.SUPABASE_SERVICE_ROLE_KEY!,
|
|
209
|
+
bucket: "uploads",
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// SDK-free alternative with an injected structural client:
|
|
213
|
+
// const driver = createSupabaseStorageDriver(mySupabaseClientLike);
|
|
214
|
+
|
|
215
|
+
const storage = createStorage({ provider: "supabase", driver });
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Google Cloud Storage — `@streetjs/storage/gcs`
|
|
219
|
+
|
|
220
|
+
Optional peer dependency: `@google-cloud/storage`.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { createStorage } from "@streetjs/storage";
|
|
224
|
+
import {
|
|
225
|
+
connectGoogleCloudStorageDriver,
|
|
226
|
+
createGoogleCloudStorageDriver,
|
|
227
|
+
} from "@streetjs/storage/gcs";
|
|
228
|
+
|
|
229
|
+
const driver = await connectGoogleCloudStorageDriver({
|
|
230
|
+
bucket: "my-bucket",
|
|
231
|
+
projectId: process.env.GCP_PROJECT_ID,
|
|
232
|
+
keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// SDK-free alternative with an injected structural client:
|
|
236
|
+
// const driver = createGoogleCloudStorageDriver(myGcsClientLike);
|
|
237
|
+
|
|
238
|
+
const storage = createStorage({ provider: "gcs", driver });
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Azure Blob Storage — `@streetjs/storage/azure`
|
|
242
|
+
|
|
243
|
+
Optional peer dependency: `@azure/storage-blob`.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { createStorage } from "@streetjs/storage";
|
|
247
|
+
import {
|
|
248
|
+
connectAzureBlobDriver,
|
|
249
|
+
createAzureBlobDriver,
|
|
250
|
+
} from "@streetjs/storage/azure";
|
|
251
|
+
|
|
252
|
+
const driver = await connectAzureBlobDriver({
|
|
253
|
+
container: "uploads",
|
|
254
|
+
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING!,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// SDK-free alternative with an injected structural client:
|
|
258
|
+
// const driver = createAzureBlobDriver(myAzureBlobClientLike);
|
|
259
|
+
|
|
260
|
+
const storage = createStorage({ provider: "azure", driver });
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### MinIO — `@streetjs/storage/minio`
|
|
264
|
+
|
|
265
|
+
Self-hosted, S3-compatible. Optional peer dependency: `minio`.
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
import { createStorage } from "@streetjs/storage";
|
|
269
|
+
import { connectMinIODriver, createMinIODriver } from "@streetjs/storage/minio";
|
|
270
|
+
|
|
271
|
+
const driver = await connectMinIODriver({
|
|
272
|
+
bucket: "uploads",
|
|
273
|
+
endPoint: "127.0.0.1",
|
|
274
|
+
port: 9000,
|
|
275
|
+
useSSL: false,
|
|
276
|
+
accessKey: process.env.MINIO_ACCESS_KEY!,
|
|
277
|
+
secretKey: process.env.MINIO_SECRET_KEY!,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// SDK-free alternative with an injected S3-compatible client:
|
|
281
|
+
// const driver = createMinIODriver(myS3ClientLike);
|
|
282
|
+
|
|
283
|
+
const storage = createStorage({ provider: "minio", driver });
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Backblaze B2 — `@streetjs/storage/backblaze`
|
|
287
|
+
|
|
288
|
+
B2 exposes a fully S3-compatible API. Optional peer dependency:
|
|
289
|
+
`@aws-sdk/client-s3`.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import { createStorage } from "@streetjs/storage";
|
|
293
|
+
import { createBackblazeB2Driver } from "@streetjs/storage/backblaze";
|
|
294
|
+
|
|
295
|
+
// From connection config (async — lazily imports the S3 SDK):
|
|
296
|
+
const driver = await createBackblazeB2Driver({
|
|
297
|
+
bucket: "my-bucket",
|
|
298
|
+
endpoint: "https://s3.us-west-002.backblazeb2.com",
|
|
299
|
+
region: "us-west-002",
|
|
300
|
+
credentials: {
|
|
301
|
+
accessKeyId: process.env.B2_KEY_ID!, // B2 keyId
|
|
302
|
+
secretAccessKey: process.env.B2_APP_KEY!, // B2 applicationKey
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// SDK-free alternative with an injected client (sync):
|
|
307
|
+
// const driver = createBackblazeB2Driver(myS3ClientLike);
|
|
308
|
+
|
|
309
|
+
const storage = createStorage({ provider: "backblaze", driver });
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Streaming
|
|
313
|
+
|
|
314
|
+
Transfer large files without buffering the whole object in memory.
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import { createReadStream, createWriteStream } from "node:fs";
|
|
318
|
+
import { pipeline } from "node:stream/promises";
|
|
319
|
+
|
|
320
|
+
// Write from a Node Readable.
|
|
321
|
+
await storage.putStream(
|
|
322
|
+
"videos/clip.mp4",
|
|
323
|
+
createReadStream("./clip.mp4"),
|
|
324
|
+
{ contentType: "video/mp4" },
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Read into a Node Writable.
|
|
328
|
+
const download = await storage.getStream("videos/clip.mp4");
|
|
329
|
+
await pipeline(download, createWriteStream("./out.mp4"));
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`getStream` throws when the key does not exist.
|
|
333
|
+
|
|
334
|
+
## Multipart Uploads
|
|
335
|
+
|
|
336
|
+
Upload very large objects as independent parts, then assemble them.
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
const uploadId = await storage.createMultipartUpload("archives/big.tar", {
|
|
340
|
+
contentType: "application/x-tar",
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const part1 = await storage.uploadPart(uploadId, 1, chunk1);
|
|
344
|
+
const part2 = await storage.uploadPart(uploadId, 2, chunk2);
|
|
345
|
+
|
|
346
|
+
// Complete assembles the parts in order into a single object.
|
|
347
|
+
const meta = await storage.completeMultipartUpload(uploadId, [part1, part2]);
|
|
348
|
+
|
|
349
|
+
// Or discard everything:
|
|
350
|
+
// await storage.abortMultipartUpload(uploadId);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
The concatenation of the ordered parts equals the final object content.
|
|
354
|
+
|
|
355
|
+
## Resumable Uploads
|
|
356
|
+
|
|
357
|
+
Continue an interrupted upload from the last persisted offset instead of
|
|
358
|
+
restarting.
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
const sessionId = await storage.startUpload("backups/db.sql", {
|
|
362
|
+
contentType: "application/sql",
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// If a transfer is interrupted, resume with the remaining stream.
|
|
366
|
+
const meta = await storage.resumeUpload(sessionId, remainingStream);
|
|
367
|
+
|
|
368
|
+
// Cancel a session that is not already completing:
|
|
369
|
+
// await storage.cancelUpload(sessionId);
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
A completed resumable upload yields the same content as an equivalent
|
|
373
|
+
uninterrupted upload.
|
|
374
|
+
|
|
375
|
+
## Signed URLs
|
|
376
|
+
|
|
377
|
+
Time-limited URLs that authorize exactly one operation (`GET`, `PUT`, or
|
|
378
|
+
`DELETE`) on a key. Configure a `signingSecret` for the simulated signer used by
|
|
379
|
+
the memory/local drivers.
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
const storage = createStorage({ provider: "memory", signingSecret: "s3cr3t" });
|
|
383
|
+
|
|
384
|
+
const getUrl = await storage.signedUrl("reports/2024.pdf", "GET", {
|
|
385
|
+
expiresInMs: 10 * 60 * 1000, // 10 minutes
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const putUrl = await storage.signedUrl("uploads/incoming.bin", "PUT", {
|
|
389
|
+
expiresInMs: 60_000,
|
|
390
|
+
contentType: "application/octet-stream",
|
|
391
|
+
maxSize: 5 * 1024 * 1024,
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Signed-URL options: `expiresInMs`, `headers`, `contentType`, `maxSize`,
|
|
396
|
+
`metadata`. A URL used after its expiration is rejected; used before expiration
|
|
397
|
+
for the authorized operation, it is permitted.
|
|
398
|
+
|
|
399
|
+
## Validation
|
|
400
|
+
|
|
401
|
+
Run a configurable pipeline before any bytes are persisted. A rejected upload
|
|
402
|
+
aborts with a descriptive `ValidationError` and leaves no partial object.
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
const storage = createStorage({
|
|
406
|
+
provider: "memory",
|
|
407
|
+
validation: {
|
|
408
|
+
allowedMimeTypes: ["image/png", "image/jpeg"],
|
|
409
|
+
allowedExtensions: [".png", ".jpg", ".jpeg"],
|
|
410
|
+
maxSize: 2 * 1024 * 1024,
|
|
411
|
+
filenamePattern: /^[\w./-]+$/,
|
|
412
|
+
requireChecksum: false,
|
|
413
|
+
custom: (input) =>
|
|
414
|
+
input.key.startsWith("public/")
|
|
415
|
+
? { ok: false, error: "public/ is reserved" }
|
|
416
|
+
: { ok: true },
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## Typed Metadata
|
|
422
|
+
|
|
423
|
+
Every object carries a typed `StorageObjectMetadata`: `key`, `size`,
|
|
424
|
+
`contentType`, `etag`, `checksum`, `owner`, `tenant`, `accessLevel`,
|
|
425
|
+
`createdAt`, `updatedAt`, `custom`, and optional `versionId`. Written metadata
|
|
426
|
+
round-trips through read, and the field set is preserved across providers.
|
|
427
|
+
|
|
428
|
+
```ts
|
|
429
|
+
await storage.put("docs/spec.md", bytes, {
|
|
430
|
+
contentType: "text/markdown",
|
|
431
|
+
owner: "user-1",
|
|
432
|
+
tenant: "acme",
|
|
433
|
+
accessLevel: "private",
|
|
434
|
+
custom: { project: "atlas" },
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const meta = await storage.stat("docs/spec.md");
|
|
438
|
+
// meta?.custom.project === "atlas"
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## Access Control
|
|
442
|
+
|
|
443
|
+
Set a per-object `accessLevel` and supply a structural `auth` bridge to make
|
|
444
|
+
authenticated, role-based, and tenant-aware decisions. Access levels: `public`,
|
|
445
|
+
`private`, `signed`, `authenticated`, `role-based`, `tenant-aware`. A denied
|
|
446
|
+
operation throws an `AuthorizationError` before any read or write occurs;
|
|
447
|
+
`public` objects are readable without authentication.
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
const storage = createStorage({
|
|
451
|
+
provider: "memory",
|
|
452
|
+
auth: {
|
|
453
|
+
can: ({ operation, accessLevel, owner }) => {
|
|
454
|
+
if (accessLevel === "public" && operation === "read") return true;
|
|
455
|
+
return owner === currentUserId; // your own logic
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Lifecycle Rules
|
|
462
|
+
|
|
463
|
+
Apply age/state-based policies. When a rule fires, the events bridge (if wired)
|
|
464
|
+
publishes the corresponding lifecycle event.
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
// Delete objects under "temp/" older than 7 days.
|
|
468
|
+
await storage.applyLifecycle({ type: "delete-after-days", days: 7, prefix: "temp/" });
|
|
469
|
+
|
|
470
|
+
// Archive after N months.
|
|
471
|
+
await storage.applyLifecycle({ type: "archive-after-months", months: 6, prefix: "logs/" });
|
|
472
|
+
|
|
473
|
+
// Expire temporary uploads after a duration.
|
|
474
|
+
await storage.applyLifecycle({ type: "expire-temp-uploads", afterMs: 3_600_000 });
|
|
475
|
+
|
|
476
|
+
// Move to cold storage after N days.
|
|
477
|
+
await storage.applyLifecycle({
|
|
478
|
+
type: "move-to-cold",
|
|
479
|
+
afterDays: 30,
|
|
480
|
+
coldPrefix: "cold/",
|
|
481
|
+
prefix: "media/",
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Each returns the `LifecycleOutcome[]` describing which keys were acted on. A
|
|
486
|
+
qualifying object is acted on exactly once.
|
|
487
|
+
|
|
488
|
+
## Image Processing
|
|
489
|
+
|
|
490
|
+
`storage.images.transform(key, operations)` produces a transformed variant and
|
|
491
|
+
returns its metadata. Supported operations: `resize`, `crop`, `rotate`, `fit`,
|
|
492
|
+
`thumbnail`, `compress`; output formats: `webp`, `avif`, `png`, `jpeg`. Supply a
|
|
493
|
+
structural `imageCodec` in config to perform the pixel work. A non-image source
|
|
494
|
+
yields a descriptive error and never mutates the source.
|
|
495
|
+
|
|
496
|
+
```ts
|
|
497
|
+
const storage = createStorage({ provider: "memory", imageCodec: myCodec });
|
|
498
|
+
|
|
499
|
+
await storage.put("avatars/u1.png", pngBytes, { contentType: "image/png" });
|
|
500
|
+
|
|
501
|
+
const thumb = await storage.images.transform("avatars/u1.png", {
|
|
502
|
+
resize: { width: 128, height: 128 },
|
|
503
|
+
format: "webp",
|
|
504
|
+
quality: 80,
|
|
505
|
+
});
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
## Directory API
|
|
509
|
+
|
|
510
|
+
Prefix-based hierarchy that works even on prefix-only providers.
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
await storage.directory.mkdir("projects/atlas/");
|
|
514
|
+
const children = await storage.directory.listDirectory("projects/"); // immediate children
|
|
515
|
+
const allKeys = await storage.directory.walk("projects/"); // every key beneath
|
|
516
|
+
const { removed } = await storage.directory.removeDirectory("projects/atlas/");
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## Search
|
|
520
|
+
|
|
521
|
+
Filter stored objects by attributes. Only objects matching **every** supplied
|
|
522
|
+
filter are returned; an empty match set returns `[]`.
|
|
523
|
+
|
|
524
|
+
```ts
|
|
525
|
+
const results = await storage.search({
|
|
526
|
+
prefix: "media/",
|
|
527
|
+
contentType: "image/png",
|
|
528
|
+
owner: "user-1",
|
|
529
|
+
tenant: "acme",
|
|
530
|
+
minSize: 1_024,
|
|
531
|
+
maxSize: 5_000_000,
|
|
532
|
+
updatedAfter: Date.now() - 86_400_000,
|
|
533
|
+
metadata: { project: "atlas" },
|
|
534
|
+
});
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## Queue Integration
|
|
538
|
+
|
|
539
|
+
Hand heavy work to `@streetjs/queue` without a hard dependency. Provide a
|
|
540
|
+
structural `bridges.queue` (`{ dispatch(job, payload) }`). Dispatch is
|
|
541
|
+
fire-and-forget and never breaks a storage operation, so storage keeps working
|
|
542
|
+
whether or not the queue is present.
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
import { bridgeStorageQueue } from "@streetjs/storage";
|
|
546
|
+
|
|
547
|
+
const storage = createStorage({
|
|
548
|
+
provider: "memory",
|
|
549
|
+
bridges: { queue: myQueueLike }, // { dispatch(job, payload) }
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// Or use the bridge directly to dispatch typed jobs:
|
|
553
|
+
const publisher = bridgeStorageQueue(myQueueLike);
|
|
554
|
+
publisher.thumbnail("avatars/u1.png");
|
|
555
|
+
publisher.virusScan("uploads/incoming.bin");
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Job names: `storage.thumbnail`, `storage.virus-scan`, `storage.ocr`,
|
|
559
|
+
`storage.pdf-process`, `storage.transcode`, `storage.image-optimize`,
|
|
560
|
+
`storage.archive`.
|
|
561
|
+
|
|
562
|
+
## Events Integration
|
|
563
|
+
|
|
564
|
+
Publish typed storage events through `@streetjs/events` (no hard/circular
|
|
565
|
+
dependency). Provide a structural `bridges.events` (`{ publish(event, payload) }`).
|
|
566
|
+
|
|
567
|
+
```ts
|
|
568
|
+
import { bridgeStorageEvents } from "@streetjs/storage";
|
|
569
|
+
|
|
570
|
+
const storage = createStorage({
|
|
571
|
+
provider: "memory",
|
|
572
|
+
bridges: { events: myEventsLike }, // { publish(event, payload) }
|
|
573
|
+
});
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Object mutations publish `storage.uploaded`, `storage.updated`,
|
|
577
|
+
`storage.deleted`, `storage.moved`, `storage.restored`, and `storage.expired`;
|
|
578
|
+
each payload carries the affected `key` and, when available, its metadata.
|
|
579
|
+
|
|
580
|
+
## Realtime Integration
|
|
581
|
+
|
|
582
|
+
Broadcast upload progress through `@streetjs/realtime` without a hard
|
|
583
|
+
dependency. Provide a structural `bridges.realtime`
|
|
584
|
+
(`{ broadcast(channel, event, payload) }`).
|
|
585
|
+
|
|
586
|
+
```ts
|
|
587
|
+
import { bridgeStorageRealtime, STORAGE_UPLOAD_CHANNEL } from "@streetjs/storage";
|
|
588
|
+
|
|
589
|
+
const storage = createStorage({
|
|
590
|
+
provider: "memory",
|
|
591
|
+
bridges: { realtime: myRealtimeLike },
|
|
592
|
+
});
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Events are broadcast on the `storage.uploads` channel: `upload.started`,
|
|
596
|
+
`upload.progress`, `upload.completed`, and `upload.failed`. A missing realtime
|
|
597
|
+
bridge simply means no broadcasts — uploads proceed unaffected.
|
|
598
|
+
|
|
599
|
+
## Plugin
|
|
600
|
+
|
|
601
|
+
Register storage in the StreetJS plugin system with `StoragePlugin`. Its options
|
|
602
|
+
extend `StorageConfig`, so provider, bridges, and the `metrics` / `health`
|
|
603
|
+
registries are all supplied through the plugin. The live facade is available via
|
|
604
|
+
`plugin.storage` after load.
|
|
605
|
+
|
|
606
|
+
```ts
|
|
607
|
+
import { StoragePlugin } from "@streetjs/storage";
|
|
608
|
+
|
|
609
|
+
const plugin = new StoragePlugin({
|
|
610
|
+
provider: "local",
|
|
611
|
+
root: "/var/data/uploads",
|
|
612
|
+
metrics,
|
|
613
|
+
health,
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
await plugin.onLoad(app);
|
|
617
|
+
const storage = plugin.storage!; // the live Storage facade
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
## Observability
|
|
621
|
+
|
|
622
|
+
Pass the core `MetricsRegistry` and/or `HealthCheckRegistry` and storage records
|
|
623
|
+
metrics (uploads, downloads, bytes, active/failed uploads, usage, latency,
|
|
624
|
+
multipart, resumable) and registers health checks (connectivity, writability,
|
|
625
|
+
readability, quota). `storage.stats()` returns a live snapshot; `storage.probe()`
|
|
626
|
+
returns a connectivity/quota probe.
|
|
627
|
+
|
|
628
|
+
```ts
|
|
629
|
+
const storage = createStorage({ provider: "memory", metrics, health });
|
|
630
|
+
const snapshot = storage.stats();
|
|
631
|
+
const probe = await storage.probe();
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
## CLI
|
|
635
|
+
|
|
636
|
+
`StorageCommands` provides `make:storage`, `storage:list`, `storage:sync`,
|
|
637
|
+
`storage:clean`, `storage:migrate`, and `storage:verify`. These are **registered
|
|
638
|
+
by your application through the core `CliKernel`** (construct `StorageCommands`
|
|
639
|
+
and register it) — they are not part of the standalone `@streetjs/cli` (`street`)
|
|
640
|
+
built-in command set. Once registered they are invoked as shown below:
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
street make:storage Avatars --dir src/storage --provider local
|
|
644
|
+
street storage:list uploads/ --provider local --root /var/data/uploads
|
|
645
|
+
street storage:clean temp/ --provider local --root /var/data/uploads
|
|
646
|
+
street storage:sync media/ --from-provider local --from-root ./a --to-provider memory
|
|
647
|
+
street storage:migrate media/ --from-provider local --from-root ./a --to-provider local --to-root ./b
|
|
648
|
+
street storage:verify --provider local --root /var/data/uploads
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
## Testing
|
|
652
|
+
|
|
653
|
+
`@streetjs/storage/testing` provides in-process, zero-network doubles that
|
|
654
|
+
implement the same contract as production drivers.
|
|
655
|
+
|
|
656
|
+
```ts
|
|
657
|
+
import {
|
|
658
|
+
MemoryStorage,
|
|
659
|
+
FakeStorage,
|
|
660
|
+
StorageHarness,
|
|
661
|
+
FakeUpload,
|
|
662
|
+
FakeDownload,
|
|
663
|
+
} from "@streetjs/storage/testing";
|
|
664
|
+
|
|
665
|
+
// A real facade over the in-memory driver.
|
|
666
|
+
const storage = MemoryStorage();
|
|
667
|
+
|
|
668
|
+
// A double with an advanceable clock for time-sensitive behavior.
|
|
669
|
+
const fake = new FakeStorage();
|
|
670
|
+
await fake.put("k", "hello");
|
|
671
|
+
fake.advanceTime(60_000); // move time forward (signed-URL expiry, lifecycle age)
|
|
672
|
+
|
|
673
|
+
// A harness bundling a clock, storage, and assertion helpers.
|
|
674
|
+
const harness = new StorageHarness();
|
|
675
|
+
await harness.storage.put("a.txt", "hi");
|
|
676
|
+
await harness.assertExists("a.txt");
|
|
677
|
+
await harness.assertContent("a.txt", "hi");
|
|
678
|
+
await harness.assertKeys("", ["a.txt"]);
|
|
679
|
+
|
|
680
|
+
// Chunked upload / download doubles.
|
|
681
|
+
const upload = new FakeUpload(storage, "big.bin");
|
|
682
|
+
upload.write(chunk1);
|
|
683
|
+
upload.write(chunk2);
|
|
684
|
+
await upload.complete();
|
|
685
|
+
|
|
686
|
+
const download = new FakeDownload(storage, "big.bin");
|
|
687
|
+
const text = await download.text();
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
`MemoryStorageDriver` is also re-exported for substitution at the driver level.
|
|
691
|
+
|
|
692
|
+
## Migration
|
|
693
|
+
|
|
694
|
+
Coming from an older, service/provider-based storage layer? The main changes:
|
|
695
|
+
|
|
696
|
+
- Construct with the `createStorage(config)` factory instead of
|
|
697
|
+
instantiating a service class. Pick a driver with `provider` (built-in) or a
|
|
698
|
+
pre-constructed `driver` (cloud).
|
|
699
|
+
- Use the object-operation names `put` / `get` / `delete` (rather than
|
|
700
|
+
`upload` / `download` / `remove`). `get` returns a `{ found, bytes, metadata }`
|
|
701
|
+
result instead of throwing on a missing key.
|
|
702
|
+
- Signed URLs come from `storage.signedUrl(key, op, options)` with
|
|
703
|
+
`op` being `"GET" | "PUT" | "DELETE"` and durations expressed in **milliseconds**
|
|
704
|
+
via `expiresInMs`.
|
|
705
|
+
- Validation, versioning, lifecycle, images, directory, and search are
|
|
706
|
+
first-class methods on the facade rather than separate hooks.
|
|
707
|
+
|
|
708
|
+
To move existing data **between stores**, use the CLI:
|
|
709
|
+
|
|
710
|
+
```bash
|
|
711
|
+
# Copy (source untouched):
|
|
712
|
+
street storage:sync <prefix> --from-provider <p> --to-provider <p>
|
|
713
|
+
# Move (copy, then remove from source):
|
|
714
|
+
street storage:migrate <prefix> --from-provider <p> --to-provider <p>
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
## License
|
|
718
|
+
|
|
719
|
+
MIT
|