@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
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @streetjs/storage — LocalStorageDriver (zero-dependency).
|
|
3
|
+
*
|
|
4
|
+
* A filesystem-backed implementation of the {@link StorageDriver} contract that
|
|
5
|
+
* uses only Node's built-in `fs` module (part of the runtime, not an external
|
|
6
|
+
* dependency — Requirement 3.2). Object bytes are written to `root/<key>` and
|
|
7
|
+
* the computed {@link StorageObjectMetadata} is persisted alongside them in a
|
|
8
|
+
* sidecar file at `root/<key>.meta.json`. Parent directories are created on
|
|
9
|
+
* demand so nested key paths (e.g. `a/b/c.txt`) work transparently.
|
|
10
|
+
*
|
|
11
|
+
* The Local driver is observationally equivalent to
|
|
12
|
+
* {@link MemoryStorageDriver} (Requirement 2.2): identical `etag`/`checksum`
|
|
13
|
+
* (sha-256 hex of the stored bytes via `node:crypto`), `size` (byte length),
|
|
14
|
+
* timestamps sourced from an injected {@link Clock} (default `systemClock` from
|
|
15
|
+
* `streetjs`), and defaults (`contentType` `application/octet-stream`,
|
|
16
|
+
* `accessLevel` `private`, `custom` `{}`). Overwriting a key preserves the
|
|
17
|
+
* original `createdAt` and only advances `updatedAt` (Requirements 4.1, 10.1).
|
|
18
|
+
*
|
|
19
|
+
* This module (task 4.1) implements the mandatory primitive object operations:
|
|
20
|
+
* `put` / `get` / `exists` / `delete` / `stat` / `list`. `get` returns a
|
|
21
|
+
* discriminated {@link MaybeObject} so absence is reported consistently rather
|
|
22
|
+
* than thrown (Requirements 4.2, 2.4). `list` returns {@link StorageListItem}s
|
|
23
|
+
* sorted by key and never surfaces the `.meta.json` sidecar files as objects
|
|
24
|
+
* (Requirement 4.9).
|
|
25
|
+
*
|
|
26
|
+
* Streaming (task 4.2) uses `fs.createReadStream` / `fs.createWriteStream` so
|
|
27
|
+
* large files are never fully buffered in memory (Requirement 5.3). `putStream`
|
|
28
|
+
* pipes the incoming stream through a sha-256 hash on the way to disk, tallying
|
|
29
|
+
* the byte length as it goes, then writes the sidecar with the same metadata
|
|
30
|
+
* semantics as `put` (preserving the original `createdAt` on overwrite).
|
|
31
|
+
* `getStream` returns a `fs.createReadStream` of the object and throws
|
|
32
|
+
* {@link NotFoundError} for a missing key (Requirement 5.5).
|
|
33
|
+
*
|
|
34
|
+
* _Requirements: 2.1, 2.2, 3.2, 4.1, 4.2, 4.3, 4.4, 4.9, 4.10, 5.1, 5.2, 5.3, 5.5, 10.1_
|
|
35
|
+
*/
|
|
36
|
+
import { createHash } from "node:crypto";
|
|
37
|
+
import { createReadStream, createWriteStream, existsSync } from "node:fs";
|
|
38
|
+
import * as fs from "node:fs/promises";
|
|
39
|
+
import * as path from "node:path";
|
|
40
|
+
import { pipeline } from "node:stream/promises";
|
|
41
|
+
import { systemClock } from "streetjs";
|
|
42
|
+
import { NotFoundError } from "../errors.js";
|
|
43
|
+
import { buildObjectMetadata } from "../metadata.js";
|
|
44
|
+
/** Suffix identifying the metadata sidecar file for an object. */
|
|
45
|
+
const META_SUFFIX = ".meta.json";
|
|
46
|
+
/**
|
|
47
|
+
* Zero-dependency filesystem {@link StorageDriver}.
|
|
48
|
+
*
|
|
49
|
+
* Object bytes live at `root/<key>`; their metadata is persisted in a sidecar
|
|
50
|
+
* `root/<key>.meta.json`. Observable behavior matches
|
|
51
|
+
* {@link MemoryStorageDriver} so the two drivers are interchangeable.
|
|
52
|
+
*/
|
|
53
|
+
export class LocalStorageDriver {
|
|
54
|
+
/** Stable driver name. */
|
|
55
|
+
name = "local";
|
|
56
|
+
/** Filesystem root under which objects are stored. */
|
|
57
|
+
root;
|
|
58
|
+
/** Injected clock used for `createdAt` / `updatedAt` timestamps. */
|
|
59
|
+
clock;
|
|
60
|
+
constructor(options) {
|
|
61
|
+
this.root = path.resolve(options.root);
|
|
62
|
+
this.clock = options.clock ?? systemClock;
|
|
63
|
+
}
|
|
64
|
+
// ── Mandatory primitives ──────────────────────────────────────────────────
|
|
65
|
+
/**
|
|
66
|
+
* Persist `bytes` under `key`, computing size, sha-256 checksum/etag, and
|
|
67
|
+
* timestamps. Parent directories are created as needed for nested keys. When
|
|
68
|
+
* overwriting an existing key, the original `createdAt` is preserved and only
|
|
69
|
+
* `updatedAt` advances (Requirements 4.1, 10.1).
|
|
70
|
+
*/
|
|
71
|
+
async put(key, bytes, metadata) {
|
|
72
|
+
// Copy defensively so later mutation of the caller's buffer cannot alter
|
|
73
|
+
// what we stored.
|
|
74
|
+
const stored = bytes.slice();
|
|
75
|
+
const checksum = sha256Hex(stored);
|
|
76
|
+
const now = this.clock();
|
|
77
|
+
const existing = await this.readMeta(key);
|
|
78
|
+
// Assemble the typed field set through the single source of truth so the
|
|
79
|
+
// shape and defaults stay identical across every driver (Requirement 10.1).
|
|
80
|
+
const objectMetadata = buildObjectMetadata({
|
|
81
|
+
key,
|
|
82
|
+
size: stored.byteLength,
|
|
83
|
+
checksum,
|
|
84
|
+
createdAt: existing?.createdAt ?? now,
|
|
85
|
+
updatedAt: now,
|
|
86
|
+
write: metadata,
|
|
87
|
+
});
|
|
88
|
+
const objectPath = this.objectPath(key);
|
|
89
|
+
await fs.mkdir(path.dirname(objectPath), { recursive: true });
|
|
90
|
+
await fs.writeFile(objectPath, stored);
|
|
91
|
+
await fs.writeFile(this.metaPath(key), JSON.stringify(objectMetadata), "utf8");
|
|
92
|
+
return objectMetadata;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Read the object at `key`. Returns a discriminated {@link MaybeObject} so
|
|
96
|
+
* absence is reported consistently rather than thrown (Requirements 4.2, 2.4).
|
|
97
|
+
*/
|
|
98
|
+
async get(key) {
|
|
99
|
+
const metadata = await this.readMeta(key);
|
|
100
|
+
if (metadata === null) {
|
|
101
|
+
return { found: false };
|
|
102
|
+
}
|
|
103
|
+
let bytes;
|
|
104
|
+
try {
|
|
105
|
+
bytes = await fs.readFile(this.objectPath(key));
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (isNotFound(error)) {
|
|
109
|
+
return { found: false };
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
return { found: true, bytes: new Uint8Array(bytes), metadata };
|
|
114
|
+
}
|
|
115
|
+
/** Report whether an object is stored under `key` (Requirement 4.3). */
|
|
116
|
+
async exists(key) {
|
|
117
|
+
try {
|
|
118
|
+
await fs.access(this.objectPath(key));
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Remove the object at `key` and its metadata sidecar. Deleting a missing key
|
|
127
|
+
* is a no-op so that a subsequent `exists` returns false either way
|
|
128
|
+
* (Requirement 4.4).
|
|
129
|
+
*/
|
|
130
|
+
async delete(key) {
|
|
131
|
+
await this.unlinkIfExists(this.objectPath(key));
|
|
132
|
+
await this.unlinkIfExists(this.metaPath(key));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Return the metadata for `key` without its content, or `null` if absent
|
|
136
|
+
* (Requirement 4.10).
|
|
137
|
+
*/
|
|
138
|
+
async stat(key) {
|
|
139
|
+
return this.readMeta(key);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Return list items for every key beginning with `prefix`, sorted by key for
|
|
143
|
+
* deterministic ordering (Requirement 4.9). Metadata sidecar files
|
|
144
|
+
* (`.meta.json`) are never surfaced as objects. Honors optional `cursor`
|
|
145
|
+
* (exclusive resume point), `limit`, and `delimiter` (collapse to immediate
|
|
146
|
+
* children on `/`) when supplied, matching {@link MemoryStorageDriver}.
|
|
147
|
+
*/
|
|
148
|
+
async list(prefix, options) {
|
|
149
|
+
const cursor = options?.cursor;
|
|
150
|
+
const useDelimiter = options?.delimiter === true;
|
|
151
|
+
const allKeys = await this.collectKeys();
|
|
152
|
+
let matchedKeys = allKeys.filter((key) => key.startsWith(prefix)).sort();
|
|
153
|
+
if (cursor !== undefined) {
|
|
154
|
+
matchedKeys = matchedKeys.filter((key) => key > cursor);
|
|
155
|
+
}
|
|
156
|
+
const items = [];
|
|
157
|
+
const seenChildren = new Set();
|
|
158
|
+
for (const key of matchedKeys) {
|
|
159
|
+
if (useDelimiter) {
|
|
160
|
+
// Collapse to the immediate child segment under the prefix.
|
|
161
|
+
const rest = key.slice(prefix.length);
|
|
162
|
+
const slashIndex = rest.indexOf("/");
|
|
163
|
+
if (slashIndex !== -1) {
|
|
164
|
+
const child = prefix + rest.slice(0, slashIndex + 1);
|
|
165
|
+
if (seenChildren.has(child)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
seenChildren.add(child);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const metadata = await this.readMeta(key);
|
|
172
|
+
if (metadata === null) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
items.push({
|
|
176
|
+
key,
|
|
177
|
+
size: metadata.size,
|
|
178
|
+
updatedAt: metadata.updatedAt,
|
|
179
|
+
});
|
|
180
|
+
if (options?.limit !== undefined && items.length >= options.limit) {
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return items;
|
|
185
|
+
}
|
|
186
|
+
// ── Streaming ───────────────────────────────────────────────────────────────
|
|
187
|
+
/**
|
|
188
|
+
* Persist a streamed upload to `root/<key>` using `fs.createWriteStream` so
|
|
189
|
+
* the object is never fully buffered in memory (Requirements 5.1, 5.3).
|
|
190
|
+
* Parent directories are created on demand. As bytes flow to disk they are
|
|
191
|
+
* piped through a sha-256 hash and the running byte count is tallied, giving
|
|
192
|
+
* the `checksum`/`etag`/`size` without a second pass. Once the stream is
|
|
193
|
+
* drained the metadata sidecar is written with the same semantics as
|
|
194
|
+
* {@link put}: an existing key's original `createdAt` is preserved and only
|
|
195
|
+
* `updatedAt` advances (Requirements 4.1, 10.1).
|
|
196
|
+
*/
|
|
197
|
+
async putStream(key, stream, metadata) {
|
|
198
|
+
// Read any existing metadata up front so an overwrite preserves createdAt.
|
|
199
|
+
const existing = await this.readMeta(key);
|
|
200
|
+
const objectPath = this.objectPath(key);
|
|
201
|
+
await fs.mkdir(path.dirname(objectPath), { recursive: true });
|
|
202
|
+
const hash = createHash("sha256");
|
|
203
|
+
let size = 0;
|
|
204
|
+
// Pipe the source through a transform that hashes and counts each chunk on
|
|
205
|
+
// its way to the write stream. Nothing is retained in memory beyond the
|
|
206
|
+
// current chunk, so arbitrarily large uploads stream straight to disk.
|
|
207
|
+
await pipeline(stream, async function* hashAndCount(source) {
|
|
208
|
+
for await (const chunk of source) {
|
|
209
|
+
const buf = Buffer.isBuffer(chunk)
|
|
210
|
+
? chunk
|
|
211
|
+
: Buffer.from(chunk);
|
|
212
|
+
hash.update(buf);
|
|
213
|
+
size += buf.byteLength;
|
|
214
|
+
yield buf;
|
|
215
|
+
}
|
|
216
|
+
}, createWriteStream(objectPath));
|
|
217
|
+
const checksum = hash.digest("hex");
|
|
218
|
+
const now = this.clock();
|
|
219
|
+
// Assemble the typed field set through the single source of truth so the
|
|
220
|
+
// shape and defaults stay identical across every driver (Requirement 10.1).
|
|
221
|
+
const objectMetadata = buildObjectMetadata({
|
|
222
|
+
key,
|
|
223
|
+
size,
|
|
224
|
+
checksum,
|
|
225
|
+
createdAt: existing?.createdAt ?? now,
|
|
226
|
+
updatedAt: now,
|
|
227
|
+
write: metadata,
|
|
228
|
+
});
|
|
229
|
+
await fs.writeFile(this.metaPath(key), JSON.stringify(objectMetadata), "utf8");
|
|
230
|
+
return objectMetadata;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Return a `fs.createReadStream` of the object at `key` so readers pull bytes
|
|
234
|
+
* incrementally without buffering the whole object (Requirements 5.2, 5.3).
|
|
235
|
+
* Throws {@link NotFoundError} when the key is missing (Requirement 5.5).
|
|
236
|
+
*/
|
|
237
|
+
async getStream(key) {
|
|
238
|
+
const objectPath = this.objectPath(key);
|
|
239
|
+
try {
|
|
240
|
+
await fs.access(objectPath);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
if (isNotFound(error)) {
|
|
244
|
+
throw new NotFoundError(key);
|
|
245
|
+
}
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
return createReadStream(objectPath);
|
|
249
|
+
}
|
|
250
|
+
// ── Internal helpers ────────────────────────────────────────────────────────
|
|
251
|
+
/** Absolute filesystem path to the object bytes for `key`. */
|
|
252
|
+
objectPath(key) {
|
|
253
|
+
return path.join(this.root, key);
|
|
254
|
+
}
|
|
255
|
+
/** Absolute filesystem path to the metadata sidecar for `key`. */
|
|
256
|
+
metaPath(key) {
|
|
257
|
+
return path.join(this.root, key + META_SUFFIX);
|
|
258
|
+
}
|
|
259
|
+
/** Read and parse the metadata sidecar for `key`, or `null` when absent. */
|
|
260
|
+
async readMeta(key) {
|
|
261
|
+
let raw;
|
|
262
|
+
try {
|
|
263
|
+
raw = await fs.readFile(this.metaPath(key), "utf8");
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
if (isNotFound(error)) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
return JSON.parse(raw);
|
|
272
|
+
}
|
|
273
|
+
/** Unlink a path, ignoring the case where it does not exist. */
|
|
274
|
+
async unlinkIfExists(target) {
|
|
275
|
+
try {
|
|
276
|
+
await fs.unlink(target);
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
if (!isNotFound(error)) {
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Walk the storage root and return every object key (the relative path from
|
|
286
|
+
* `root`, using `/` separators). Metadata sidecar files are excluded so they
|
|
287
|
+
* are never surfaced as objects.
|
|
288
|
+
*/
|
|
289
|
+
async collectKeys() {
|
|
290
|
+
if (!existsSync(this.root)) {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
const keys = [];
|
|
294
|
+
const walk = async (dir) => {
|
|
295
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
296
|
+
for (const entry of entries) {
|
|
297
|
+
const full = path.join(dir, entry.name);
|
|
298
|
+
if (entry.isDirectory()) {
|
|
299
|
+
await walk(full);
|
|
300
|
+
}
|
|
301
|
+
else if (entry.isFile() && !entry.name.endsWith(META_SUFFIX)) {
|
|
302
|
+
const relative = path.relative(this.root, full).split(path.sep).join("/");
|
|
303
|
+
keys.push(relative);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
await walk(this.root);
|
|
308
|
+
return keys;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/** Compute the lowercase sha-256 hex digest of `bytes`. */
|
|
312
|
+
function sha256Hex(bytes) {
|
|
313
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
314
|
+
}
|
|
315
|
+
/** True when `error` is a Node "file not found" (ENOENT) error. */
|
|
316
|
+
function isNotFound(error) {
|
|
317
|
+
return (typeof error === "object" &&
|
|
318
|
+
error !== null &&
|
|
319
|
+
error.code === "ENOENT");
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=local.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/drivers/local.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @streetjs/storage — MemoryStorageDriver (zero-dependency).
|
|
3
|
+
*
|
|
4
|
+
* An in-memory implementation of the {@link StorageDriver} contract backed by a
|
|
5
|
+
* `Map<string, StoredEntry>` where every entry holds the object's `Uint8Array`
|
|
6
|
+
* bytes plus its computed {@link StorageObjectMetadata}. It requires no external
|
|
7
|
+
* runtime dependency (Requirement 3.2), making it ideal for tests and the
|
|
8
|
+
* example app while exercising the exact same facade code paths the cloud
|
|
9
|
+
* drivers use.
|
|
10
|
+
*
|
|
11
|
+
* This module (task 3.1) implements the mandatory primitive object operations:
|
|
12
|
+
* `put` / `get` / `exists` / `delete` / `stat` / `list`. Object identity fields
|
|
13
|
+
* (`etag`, `checksum`, `size`) and timestamps (`createdAt` / `updatedAt`) are
|
|
14
|
+
* computed at write time — the checksum/etag are the sha-256 hex digest of the
|
|
15
|
+
* stored bytes (via `node:crypto`), the size is the byte length, and timestamps
|
|
16
|
+
* come from an injected {@link Clock} (default `systemClock` from `streetjs`) so
|
|
17
|
+
* time is deterministic in tests.
|
|
18
|
+
*
|
|
19
|
+
* Streaming (`putStream` / `getStream`, task 3.2) is implemented over the
|
|
20
|
+
* in-memory buffer using Node stream primitives: `putStream` consumes the
|
|
21
|
+
* supplied `Readable` through a `pipeline` into a collecting `Writable`,
|
|
22
|
+
* assembling the bytes and persisting them with the same computed metadata as
|
|
23
|
+
* `put`; `getStream` returns a `Readable` that emits the stored bytes as a
|
|
24
|
+
* single chunk and throws {@link NotFoundError} for a missing key.
|
|
25
|
+
*
|
|
26
|
+
* _Requirements: 2.1, 3.2, 4.1, 4.2, 4.3, 4.4, 4.9, 4.10, 5.1, 5.2, 5.5, 10.1_
|
|
27
|
+
*/
|
|
28
|
+
import { type Clock } from "streetjs";
|
|
29
|
+
import type { MaybeObject, NodeReadable, StorageDriver } from "../driver.js";
|
|
30
|
+
import type { ListOptions, StorageListItem, StorageObjectMetadata, WriteMetadata } from "../types.js";
|
|
31
|
+
/** Options for constructing a {@link MemoryStorageDriver}. */
|
|
32
|
+
export interface MemoryStorageDriverOptions {
|
|
33
|
+
/** Injected clock for deterministic timestamps in tests. Default `systemClock`. */
|
|
34
|
+
readonly clock?: Clock;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Zero-dependency in-memory {@link StorageDriver}.
|
|
38
|
+
*
|
|
39
|
+
* Backed by a `Map<string, StoredEntry>`. All bytes are copied on the way in and
|
|
40
|
+
* on the way out so external mutation of a caller's buffer never corrupts stored
|
|
41
|
+
* content and vice versa.
|
|
42
|
+
*/
|
|
43
|
+
export declare class MemoryStorageDriver implements StorageDriver {
|
|
44
|
+
/** Stable driver name. */
|
|
45
|
+
readonly name = "memory";
|
|
46
|
+
/** Backing store: key → stored bytes + metadata. */
|
|
47
|
+
private readonly store;
|
|
48
|
+
/** Injected clock used for `createdAt` / `updatedAt` timestamps. */
|
|
49
|
+
private readonly clock;
|
|
50
|
+
constructor(options?: MemoryStorageDriverOptions);
|
|
51
|
+
/**
|
|
52
|
+
* Persist `bytes` under `key`, computing size, sha-256 checksum/etag, and
|
|
53
|
+
* timestamps. When overwriting an existing key, the original `createdAt` is
|
|
54
|
+
* preserved and only `updatedAt` advances (Requirements 4.1, 10.1).
|
|
55
|
+
*/
|
|
56
|
+
put(key: string, bytes: Uint8Array, metadata: WriteMetadata): Promise<StorageObjectMetadata>;
|
|
57
|
+
/**
|
|
58
|
+
* Read the object at `key`. Returns a discriminated {@link MaybeObject} so
|
|
59
|
+
* absence is reported consistently rather than thrown (Requirements 4.2, 2.4).
|
|
60
|
+
* The returned bytes are a copy of the stored content.
|
|
61
|
+
*/
|
|
62
|
+
get(key: string): Promise<MaybeObject>;
|
|
63
|
+
/** Report whether an object is stored under `key` (Requirement 4.3). */
|
|
64
|
+
exists(key: string): Promise<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* Remove the object at `key`. Deleting a missing key is a no-op so that a
|
|
67
|
+
* subsequent `exists` returns false either way (Requirement 4.4).
|
|
68
|
+
*/
|
|
69
|
+
delete(key: string): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Return the metadata for `key` without its content, or `null` if absent
|
|
72
|
+
* (Requirement 4.10).
|
|
73
|
+
*/
|
|
74
|
+
stat(key: string): Promise<StorageObjectMetadata | null>;
|
|
75
|
+
/**
|
|
76
|
+
* Return list items for every key beginning with `prefix`, sorted by key for
|
|
77
|
+
* deterministic ordering (Requirement 4.9). Honors optional `cursor`
|
|
78
|
+
* (exclusive resume point), `limit`, and `delimiter` (collapse to immediate
|
|
79
|
+
* children on `/`) when supplied.
|
|
80
|
+
*/
|
|
81
|
+
list(prefix: string, options?: ListOptions): Promise<StorageListItem[]>;
|
|
82
|
+
/**
|
|
83
|
+
* Consume a Node {@link Readable} and persist the assembled bytes under `key`
|
|
84
|
+
* with the same computed metadata as {@link put} (Requirement 5.1).
|
|
85
|
+
*
|
|
86
|
+
* The stream is drained through a `pipeline` into a collecting `Writable`,
|
|
87
|
+
* which gives correct backpressure and propagates read/abort errors (a failed
|
|
88
|
+
* source stream rejects the returned promise and stores nothing). Each chunk
|
|
89
|
+
* is normalized to a `Buffer`; the concatenation is handed to {@link put} so
|
|
90
|
+
* checksum/etag/size/timestamps are computed identically to a buffered write.
|
|
91
|
+
*/
|
|
92
|
+
putStream(key: string, stream: NodeReadable, metadata: WriteMetadata): Promise<StorageObjectMetadata>;
|
|
93
|
+
/**
|
|
94
|
+
* Return a Node {@link Readable} of the stored bytes at `key` (Requirement
|
|
95
|
+
* 5.2). Throws {@link NotFoundError} for a missing key (Requirement 5.5).
|
|
96
|
+
*
|
|
97
|
+
* The stored bytes are copied and wrapped in a single-element array so
|
|
98
|
+
* `Readable.from` emits them as one intact chunk (passing a `Buffer` directly
|
|
99
|
+
* would iterate it byte-by-byte). The copy ensures a consumer draining the
|
|
100
|
+
* stream can never observe or mutate the backing store.
|
|
101
|
+
*/
|
|
102
|
+
getStream(key: string): Promise<NodeReadable>;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/drivers/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAMH,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAEnD,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,aAAa,EACd,MAAM,cAAc,CAAC;AAGtB,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,qBAAqB,EACrB,aAAa,EACd,MAAM,aAAa,CAAC;AAQrB,8DAA8D;AAC9D,MAAM,WAAW,0BAA0B;IACzC,mFAAmF;IACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACxB;AAED;;;;;;GAMG;AACH,qBAAa,mBAAoB,YAAW,aAAa;IACvD,0BAA0B;IAC1B,QAAQ,CAAC,IAAI,YAAY;IAEzB,oDAAoD;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IAExD,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;gBAElB,OAAO,GAAE,0BAA+B;IAMpD;;;;OAIG;IACG,GAAG,CACP,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,qBAAqB,CAAC;IAuBjC;;;;OAIG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAQ5C,wEAAwE;IAClE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI3C;;;OAGG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC;;;OAGG;IACG,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;IAI9D;;;;;OAKG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAiD7E;;;;;;;;;OASG;IACG,SAAS,CACb,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,qBAAqB,CAAC;IAkBjC;;;;;;;;OAQG;IACG,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;CAOpD"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @streetjs/storage — MemoryStorageDriver (zero-dependency).
|
|
3
|
+
*
|
|
4
|
+
* An in-memory implementation of the {@link StorageDriver} contract backed by a
|
|
5
|
+
* `Map<string, StoredEntry>` where every entry holds the object's `Uint8Array`
|
|
6
|
+
* bytes plus its computed {@link StorageObjectMetadata}. It requires no external
|
|
7
|
+
* runtime dependency (Requirement 3.2), making it ideal for tests and the
|
|
8
|
+
* example app while exercising the exact same facade code paths the cloud
|
|
9
|
+
* drivers use.
|
|
10
|
+
*
|
|
11
|
+
* This module (task 3.1) implements the mandatory primitive object operations:
|
|
12
|
+
* `put` / `get` / `exists` / `delete` / `stat` / `list`. Object identity fields
|
|
13
|
+
* (`etag`, `checksum`, `size`) and timestamps (`createdAt` / `updatedAt`) are
|
|
14
|
+
* computed at write time — the checksum/etag are the sha-256 hex digest of the
|
|
15
|
+
* stored bytes (via `node:crypto`), the size is the byte length, and timestamps
|
|
16
|
+
* come from an injected {@link Clock} (default `systemClock` from `streetjs`) so
|
|
17
|
+
* time is deterministic in tests.
|
|
18
|
+
*
|
|
19
|
+
* Streaming (`putStream` / `getStream`, task 3.2) is implemented over the
|
|
20
|
+
* in-memory buffer using Node stream primitives: `putStream` consumes the
|
|
21
|
+
* supplied `Readable` through a `pipeline` into a collecting `Writable`,
|
|
22
|
+
* assembling the bytes and persisting them with the same computed metadata as
|
|
23
|
+
* `put`; `getStream` returns a `Readable` that emits the stored bytes as a
|
|
24
|
+
* single chunk and throws {@link NotFoundError} for a missing key.
|
|
25
|
+
*
|
|
26
|
+
* _Requirements: 2.1, 3.2, 4.1, 4.2, 4.3, 4.4, 4.9, 4.10, 5.1, 5.2, 5.5, 10.1_
|
|
27
|
+
*/
|
|
28
|
+
import { createHash } from "node:crypto";
|
|
29
|
+
import { Readable, Writable } from "node:stream";
|
|
30
|
+
import { pipeline } from "node:stream/promises";
|
|
31
|
+
import { systemClock } from "streetjs";
|
|
32
|
+
import { NotFoundError } from "../errors.js";
|
|
33
|
+
import { buildObjectMetadata } from "../metadata.js";
|
|
34
|
+
/**
|
|
35
|
+
* Zero-dependency in-memory {@link StorageDriver}.
|
|
36
|
+
*
|
|
37
|
+
* Backed by a `Map<string, StoredEntry>`. All bytes are copied on the way in and
|
|
38
|
+
* on the way out so external mutation of a caller's buffer never corrupts stored
|
|
39
|
+
* content and vice versa.
|
|
40
|
+
*/
|
|
41
|
+
export class MemoryStorageDriver {
|
|
42
|
+
/** Stable driver name. */
|
|
43
|
+
name = "memory";
|
|
44
|
+
/** Backing store: key → stored bytes + metadata. */
|
|
45
|
+
store = new Map();
|
|
46
|
+
/** Injected clock used for `createdAt` / `updatedAt` timestamps. */
|
|
47
|
+
clock;
|
|
48
|
+
constructor(options = {}) {
|
|
49
|
+
this.clock = options.clock ?? systemClock;
|
|
50
|
+
}
|
|
51
|
+
// ── Mandatory primitives ──────────────────────────────────────────────────
|
|
52
|
+
/**
|
|
53
|
+
* Persist `bytes` under `key`, computing size, sha-256 checksum/etag, and
|
|
54
|
+
* timestamps. When overwriting an existing key, the original `createdAt` is
|
|
55
|
+
* preserved and only `updatedAt` advances (Requirements 4.1, 10.1).
|
|
56
|
+
*/
|
|
57
|
+
async put(key, bytes, metadata) {
|
|
58
|
+
// Copy defensively so later mutation of the caller's buffer cannot alter
|
|
59
|
+
// what we stored.
|
|
60
|
+
const stored = bytes.slice();
|
|
61
|
+
const checksum = sha256Hex(stored);
|
|
62
|
+
const now = this.clock();
|
|
63
|
+
const existing = this.store.get(key);
|
|
64
|
+
// Assemble the typed field set through the single source of truth so the
|
|
65
|
+
// shape and defaults stay identical across every driver (Requirement 10.1).
|
|
66
|
+
const objectMetadata = buildObjectMetadata({
|
|
67
|
+
key,
|
|
68
|
+
size: stored.byteLength,
|
|
69
|
+
checksum,
|
|
70
|
+
createdAt: existing?.metadata.createdAt ?? now,
|
|
71
|
+
updatedAt: now,
|
|
72
|
+
write: metadata,
|
|
73
|
+
});
|
|
74
|
+
this.store.set(key, { bytes: stored, metadata: objectMetadata });
|
|
75
|
+
return objectMetadata;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Read the object at `key`. Returns a discriminated {@link MaybeObject} so
|
|
79
|
+
* absence is reported consistently rather than thrown (Requirements 4.2, 2.4).
|
|
80
|
+
* The returned bytes are a copy of the stored content.
|
|
81
|
+
*/
|
|
82
|
+
async get(key) {
|
|
83
|
+
const entry = this.store.get(key);
|
|
84
|
+
if (entry === undefined) {
|
|
85
|
+
return { found: false };
|
|
86
|
+
}
|
|
87
|
+
return { found: true, bytes: entry.bytes.slice(), metadata: entry.metadata };
|
|
88
|
+
}
|
|
89
|
+
/** Report whether an object is stored under `key` (Requirement 4.3). */
|
|
90
|
+
async exists(key) {
|
|
91
|
+
return this.store.has(key);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Remove the object at `key`. Deleting a missing key is a no-op so that a
|
|
95
|
+
* subsequent `exists` returns false either way (Requirement 4.4).
|
|
96
|
+
*/
|
|
97
|
+
async delete(key) {
|
|
98
|
+
this.store.delete(key);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Return the metadata for `key` without its content, or `null` if absent
|
|
102
|
+
* (Requirement 4.10).
|
|
103
|
+
*/
|
|
104
|
+
async stat(key) {
|
|
105
|
+
return this.store.get(key)?.metadata ?? null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Return list items for every key beginning with `prefix`, sorted by key for
|
|
109
|
+
* deterministic ordering (Requirement 4.9). Honors optional `cursor`
|
|
110
|
+
* (exclusive resume point), `limit`, and `delimiter` (collapse to immediate
|
|
111
|
+
* children on `/`) when supplied.
|
|
112
|
+
*/
|
|
113
|
+
async list(prefix, options) {
|
|
114
|
+
const cursor = options?.cursor;
|
|
115
|
+
const useDelimiter = options?.delimiter === true;
|
|
116
|
+
let matchedKeys = [...this.store.keys()]
|
|
117
|
+
.filter((key) => key.startsWith(prefix))
|
|
118
|
+
.sort();
|
|
119
|
+
if (cursor !== undefined) {
|
|
120
|
+
matchedKeys = matchedKeys.filter((key) => key > cursor);
|
|
121
|
+
}
|
|
122
|
+
const items = [];
|
|
123
|
+
const seenChildren = new Set();
|
|
124
|
+
for (const key of matchedKeys) {
|
|
125
|
+
if (useDelimiter) {
|
|
126
|
+
// Collapse to the immediate child segment under the prefix.
|
|
127
|
+
const rest = key.slice(prefix.length);
|
|
128
|
+
const slashIndex = rest.indexOf("/");
|
|
129
|
+
if (slashIndex !== -1) {
|
|
130
|
+
const child = prefix + rest.slice(0, slashIndex + 1);
|
|
131
|
+
if (seenChildren.has(child)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
seenChildren.add(child);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const entry = this.store.get(key);
|
|
138
|
+
if (entry === undefined) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
items.push({
|
|
142
|
+
key,
|
|
143
|
+
size: entry.metadata.size,
|
|
144
|
+
updatedAt: entry.metadata.updatedAt,
|
|
145
|
+
});
|
|
146
|
+
if (options?.limit !== undefined && items.length >= options.limit) {
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return items;
|
|
151
|
+
}
|
|
152
|
+
// ── Streaming ───────────────────────────────────────────────────────────────
|
|
153
|
+
/**
|
|
154
|
+
* Consume a Node {@link Readable} and persist the assembled bytes under `key`
|
|
155
|
+
* with the same computed metadata as {@link put} (Requirement 5.1).
|
|
156
|
+
*
|
|
157
|
+
* The stream is drained through a `pipeline` into a collecting `Writable`,
|
|
158
|
+
* which gives correct backpressure and propagates read/abort errors (a failed
|
|
159
|
+
* source stream rejects the returned promise and stores nothing). Each chunk
|
|
160
|
+
* is normalized to a `Buffer`; the concatenation is handed to {@link put} so
|
|
161
|
+
* checksum/etag/size/timestamps are computed identically to a buffered write.
|
|
162
|
+
*/
|
|
163
|
+
async putStream(key, stream, metadata) {
|
|
164
|
+
const chunks = [];
|
|
165
|
+
const collector = new Writable({
|
|
166
|
+
write(chunk, _encoding, callback) {
|
|
167
|
+
chunks.push(Buffer.isBuffer(chunk)
|
|
168
|
+
? chunk
|
|
169
|
+
: Buffer.from(chunk));
|
|
170
|
+
callback();
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
await pipeline(stream, collector);
|
|
174
|
+
return this.put(key, new Uint8Array(Buffer.concat(chunks)), metadata);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Return a Node {@link Readable} of the stored bytes at `key` (Requirement
|
|
178
|
+
* 5.2). Throws {@link NotFoundError} for a missing key (Requirement 5.5).
|
|
179
|
+
*
|
|
180
|
+
* The stored bytes are copied and wrapped in a single-element array so
|
|
181
|
+
* `Readable.from` emits them as one intact chunk (passing a `Buffer` directly
|
|
182
|
+
* would iterate it byte-by-byte). The copy ensures a consumer draining the
|
|
183
|
+
* stream can never observe or mutate the backing store.
|
|
184
|
+
*/
|
|
185
|
+
async getStream(key) {
|
|
186
|
+
const entry = this.store.get(key);
|
|
187
|
+
if (entry === undefined) {
|
|
188
|
+
throw new NotFoundError(key);
|
|
189
|
+
}
|
|
190
|
+
return Readable.from([Buffer.from(entry.bytes)]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Compute the lowercase sha-256 hex digest of `bytes`. */
|
|
194
|
+
function sha256Hex(bytes) {
|
|
195
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/drivers/memory.ts"],"names":[],"mappings":"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"}
|