@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,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @streetjs/storage — the multipart upload manager.
|
|
3
|
+
*
|
|
4
|
+
* A {@link Multipart_Upload} splits a large object into independently uploaded
|
|
5
|
+
* parts that are later assembled into a single object whose content equals the
|
|
6
|
+
* concatenation of those parts in order (Requirement 6). {@link MultipartManager}
|
|
7
|
+
* provides this capability in a **provider-agnostic** way:
|
|
8
|
+
*
|
|
9
|
+
* - When the backing {@link StorageDriver} implements the optional native
|
|
10
|
+
* `multipart` capability, the manager delegates each operation to the driver
|
|
11
|
+
* so a provider that has first-class multipart support (e.g. S3) is used
|
|
12
|
+
* directly.
|
|
13
|
+
* - Otherwise the manager **simulates** multipart over the mandatory driver
|
|
14
|
+
* primitives (`put`/`get`/`putStream`/`delete`). Each `uploadPart` persists
|
|
15
|
+
* the part bytes under a reserved key derived from `(uploadId, partNumber)`
|
|
16
|
+
* (mirroring the design's `.multipart/<uploadId>/<partNumber>` layout), and
|
|
17
|
+
* `complete` streams the persisted parts back in the supplied order into the
|
|
18
|
+
* final object. Because completion assembles the object by reading one part at
|
|
19
|
+
* a time into a `Readable` and piping it through `driver.putStream`, the whole
|
|
20
|
+
* object is never required to be materialized in a single buffer by this
|
|
21
|
+
* layer — the design streams the concatenation and therefore imposes **no
|
|
22
|
+
* artificial size cap** (objects of a gigabyte or more are supported,
|
|
23
|
+
* Requirement 6.5).
|
|
24
|
+
*
|
|
25
|
+
* Semantics (Requirement 6):
|
|
26
|
+
* - {@link MultipartManager.create} mints and returns an upload identifier.
|
|
27
|
+
* - {@link MultipartManager.uploadPart} persists a part keyed by
|
|
28
|
+
* `(uploadId, partNumber)` and returns a {@link StoredPart} descriptor.
|
|
29
|
+
* - {@link MultipartManager.complete} concatenates the parts in the supplied
|
|
30
|
+
* order into the final object — equivalent to a single `put` of the
|
|
31
|
+
* concatenation — and returns the resulting metadata.
|
|
32
|
+
* - {@link MultipartManager.abort} discards every persisted part and creates no
|
|
33
|
+
* object.
|
|
34
|
+
*
|
|
35
|
+
* The module depends only on the driver contract, the shared type surface, the
|
|
36
|
+
* error hierarchy, and the metadata layer, keeping the dependency direction
|
|
37
|
+
* acyclic.
|
|
38
|
+
*
|
|
39
|
+
* _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_
|
|
40
|
+
*/
|
|
41
|
+
import { Buffer } from "node:buffer";
|
|
42
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
43
|
+
import { Readable } from "node:stream";
|
|
44
|
+
import { NotFoundError, StorageError } from "./errors.js";
|
|
45
|
+
import { normalizeMetadata } from "./metadata.js";
|
|
46
|
+
/**
|
|
47
|
+
* Reserved key prefix under which simulated multipart parts are persisted via
|
|
48
|
+
* the driver primitives. Parts live at `${PART_KEY_PREFIX}<uploadId>/<partNumber>`
|
|
49
|
+
* and are removed on `complete`/`abort`, so they never remain visible after a
|
|
50
|
+
* multipart upload finalizes.
|
|
51
|
+
*/
|
|
52
|
+
const PART_KEY_PREFIX = ".multipart/";
|
|
53
|
+
/** Write metadata used when persisting an individual (transient) part. */
|
|
54
|
+
const PART_WRITE_METADATA = { contentType: "application/octet-stream" };
|
|
55
|
+
/**
|
|
56
|
+
* Provider-agnostic multipart upload manager built on the driver contract.
|
|
57
|
+
*
|
|
58
|
+
* A single instance is held by the facade and bound to one {@link StorageDriver}
|
|
59
|
+
* for its lifetime, so the native-vs-simulated decision (based on whether the
|
|
60
|
+
* driver exposes a `multipart` capability) is stable across every call for a
|
|
61
|
+
* given upload identifier.
|
|
62
|
+
*/
|
|
63
|
+
export class MultipartManager {
|
|
64
|
+
/** The driver every operation is delegated to or simulated over. */
|
|
65
|
+
driver;
|
|
66
|
+
/**
|
|
67
|
+
* In-progress simulated sessions keyed by upload id. Empty when the driver
|
|
68
|
+
* provides a native `multipart` capability (that path keeps no local state).
|
|
69
|
+
*/
|
|
70
|
+
sessions = new Map();
|
|
71
|
+
constructor(driver) {
|
|
72
|
+
this.driver = driver;
|
|
73
|
+
}
|
|
74
|
+
/** The driver's native multipart capability, when present. */
|
|
75
|
+
get native() {
|
|
76
|
+
return this.driver.multipart;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Begin a multipart upload for `key` and return its upload identifier
|
|
80
|
+
* (Requirement 6.1). When the driver has a native capability the driver mints
|
|
81
|
+
* the id; otherwise a fresh id is generated and a local session records the
|
|
82
|
+
* target key and its write-time metadata for assembly at completion.
|
|
83
|
+
*/
|
|
84
|
+
async create(key, metadata) {
|
|
85
|
+
if (this.native !== undefined) {
|
|
86
|
+
return this.native.create(key, metadata);
|
|
87
|
+
}
|
|
88
|
+
const uploadId = randomUUID();
|
|
89
|
+
this.sessions.set(uploadId, { key, metadata, partNumbers: new Set() });
|
|
90
|
+
return uploadId;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Persist a single part for `uploadId` and return its {@link StoredPart}
|
|
94
|
+
* descriptor (Requirement 6.2). The part bytes are copied defensively before
|
|
95
|
+
* persistence so later mutation of the caller's buffer cannot corrupt a stored
|
|
96
|
+
* part. The returned `etag` is the sha-256 hex digest of the part bytes,
|
|
97
|
+
* matching the checksum scheme the drivers use elsewhere.
|
|
98
|
+
*/
|
|
99
|
+
async uploadPart(uploadId, partNumber, bytes) {
|
|
100
|
+
if (this.native !== undefined) {
|
|
101
|
+
return this.native.uploadPart(uploadId, partNumber, bytes);
|
|
102
|
+
}
|
|
103
|
+
const session = this.requireSession(uploadId);
|
|
104
|
+
assertValidPartNumber(partNumber);
|
|
105
|
+
const stored = bytes.slice();
|
|
106
|
+
await this.driver.put(this.partKey(uploadId, partNumber), stored, PART_WRITE_METADATA);
|
|
107
|
+
session.partNumbers.add(partNumber);
|
|
108
|
+
return { partNumber, etag: sha256Hex(stored), size: stored.byteLength };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Assemble the supplied `parts` in order into the final object and return its
|
|
112
|
+
* metadata (Requirement 6.3). The assembled content equals the concatenation
|
|
113
|
+
* of the parts in the given order — equivalent to a single `put` of that
|
|
114
|
+
* concatenation.
|
|
115
|
+
*
|
|
116
|
+
* For the simulated path the parts are streamed back one at a time from the
|
|
117
|
+
* driver into a `Readable` piped through `driver.putStream`, so this layer
|
|
118
|
+
* never buffers the full object at once (Requirement 6.5). Every persisted
|
|
119
|
+
* part (whether or not it appears in `parts`) is discarded afterward and the
|
|
120
|
+
* session is closed.
|
|
121
|
+
*/
|
|
122
|
+
async complete(uploadId, parts) {
|
|
123
|
+
if (this.native !== undefined) {
|
|
124
|
+
return normalizeMetadata(await this.native.complete(uploadId, parts));
|
|
125
|
+
}
|
|
126
|
+
const session = this.requireSession(uploadId);
|
|
127
|
+
const driver = this.driver;
|
|
128
|
+
const partKey = (partNumber) => this.partKey(uploadId, partNumber);
|
|
129
|
+
// Stream the parts in the supplied order so the concatenation is assembled
|
|
130
|
+
// incrementally rather than materialized in a single buffer here.
|
|
131
|
+
async function* assembleParts() {
|
|
132
|
+
for (const part of parts) {
|
|
133
|
+
const result = await driver.get(partKey(part.partNumber));
|
|
134
|
+
if (!result.found) {
|
|
135
|
+
throw new NotFoundError(partKey(part.partNumber), `Multipart part ${part.partNumber} for upload "${uploadId}" was not found.`);
|
|
136
|
+
}
|
|
137
|
+
yield Buffer.from(result.bytes);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const metadata = await driver.putStream(session.key, Readable.from(assembleParts()), session.metadata);
|
|
141
|
+
await this.discardParts(uploadId, session.partNumbers);
|
|
142
|
+
this.sessions.delete(uploadId);
|
|
143
|
+
return normalizeMetadata(metadata);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Discard every persisted part for `uploadId` and create no completed object
|
|
147
|
+
* (Requirement 6.4). Delegates to the native capability when present; for the
|
|
148
|
+
* simulated path it deletes all recorded part keys and closes the session. An
|
|
149
|
+
* unknown/already-finalized upload id is treated as an idempotent no-op.
|
|
150
|
+
*/
|
|
151
|
+
async abort(uploadId) {
|
|
152
|
+
if (this.native !== undefined) {
|
|
153
|
+
await this.native.abort(uploadId);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const session = this.sessions.get(uploadId);
|
|
157
|
+
if (session === undefined) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
await this.discardParts(uploadId, session.partNumbers);
|
|
161
|
+
this.sessions.delete(uploadId);
|
|
162
|
+
}
|
|
163
|
+
/** Delete every persisted part for a simulated upload id. */
|
|
164
|
+
async discardParts(uploadId, partNumbers) {
|
|
165
|
+
for (const partNumber of partNumbers) {
|
|
166
|
+
await this.driver.delete(this.partKey(uploadId, partNumber));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** The reserved driver key a simulated part is persisted under. */
|
|
170
|
+
partKey(uploadId, partNumber) {
|
|
171
|
+
return `${PART_KEY_PREFIX}${uploadId}/${partNumber}`;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Resolve the simulated session for `uploadId`, throwing a descriptive
|
|
175
|
+
* {@link StorageError} when the id is unknown or the upload has already been
|
|
176
|
+
* finalized.
|
|
177
|
+
*/
|
|
178
|
+
requireSession(uploadId) {
|
|
179
|
+
const session = this.sessions.get(uploadId);
|
|
180
|
+
if (session === undefined) {
|
|
181
|
+
throw new StorageError(`Unknown or already-finalized multipart upload id "${uploadId}".`);
|
|
182
|
+
}
|
|
183
|
+
return session;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Compute the lowercase sha-256 hex digest of `bytes`. */
|
|
187
|
+
function sha256Hex(bytes) {
|
|
188
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Guard that a part number is a positive integer. Part numbers identify the
|
|
192
|
+
* ordinal position of a part and must be well-formed for the reserved part key
|
|
193
|
+
* to be stable and for ordering to be meaningful.
|
|
194
|
+
*/
|
|
195
|
+
function assertValidPartNumber(partNumber) {
|
|
196
|
+
if (!Number.isInteger(partNumber) || partNumber < 1) {
|
|
197
|
+
throw new StorageError(`Invalid multipart part number ${partNumber}; expected a positive integer.`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=multipart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multipart.js","sourceRoot":"","sources":["../src/multipart.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { HealthCheckRegistry, MetricsRegistry } from "streetjs";
|
|
2
|
+
import type { StorageStats, DriverProbe } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The introspection slice of a {@link Storage} facade the observability layer
|
|
5
|
+
* reads: a synchronous stats snapshot and the best-effort driver probe.
|
|
6
|
+
*/
|
|
7
|
+
export interface StorageIntrospect {
|
|
8
|
+
stats(): StorageStats;
|
|
9
|
+
probe(): Promise<DriverProbe>;
|
|
10
|
+
}
|
|
11
|
+
/** The name the storage health check is registered under. */
|
|
12
|
+
export declare const STORAGE_HEALTH_CHECK_NAME = "storage";
|
|
13
|
+
export declare const STORAGE_UPLOADS_METRIC = "storage_uploads_total";
|
|
14
|
+
export declare const STORAGE_DOWNLOADS_METRIC = "storage_downloads_total";
|
|
15
|
+
export declare const STORAGE_BYTES_UPLOADED_METRIC = "storage_bytes_uploaded_total";
|
|
16
|
+
export declare const STORAGE_BYTES_DOWNLOADED_METRIC = "storage_bytes_downloaded_total";
|
|
17
|
+
export declare const STORAGE_ACTIVE_UPLOADS_METRIC = "storage_active_uploads";
|
|
18
|
+
export declare const STORAGE_FAILED_UPLOADS_METRIC = "storage_failed_uploads_total";
|
|
19
|
+
export declare const STORAGE_USAGE_METRIC = "storage_usage_bytes";
|
|
20
|
+
export declare const STORAGE_LATENCY_METRIC = "storage_operation_latency_seconds";
|
|
21
|
+
export declare const STORAGE_MULTIPART_METRIC = "storage_multipart_uploads_total";
|
|
22
|
+
export declare const STORAGE_RESUMABLE_METRIC = "storage_resumable_sessions_total";
|
|
23
|
+
/**
|
|
24
|
+
* The live telemetry sink the facade feeds as operations occur. Every hook is
|
|
25
|
+
* optional so an inert sink (no metrics registry) is a complete no-op. All
|
|
26
|
+
* hooks are isolated by the facade so observability never destabilizes an
|
|
27
|
+
* operation.
|
|
28
|
+
*/
|
|
29
|
+
export interface StorageTelemetry {
|
|
30
|
+
/** A successful upload persisted `bytes` in `latencySeconds`. */
|
|
31
|
+
onUpload?(bytes: number, latencySeconds: number): void;
|
|
32
|
+
/** A successful download returned `bytes` in `latencySeconds`. */
|
|
33
|
+
onDownload?(bytes: number, latencySeconds: number): void;
|
|
34
|
+
/** An upload failed (validation, authorization, or driver error). */
|
|
35
|
+
onUploadFailed?(): void;
|
|
36
|
+
/** The number of in-flight (streamed/resumable) uploads changed. */
|
|
37
|
+
onActiveUploadsChange?(active: number): void;
|
|
38
|
+
/** The tracked storage usage (bytes) changed. */
|
|
39
|
+
onStorageUsage?(bytes: number): void;
|
|
40
|
+
/** A multipart upload was created. */
|
|
41
|
+
onMultipartUpload?(): void;
|
|
42
|
+
/** A resumable upload session was started. */
|
|
43
|
+
onResumableSession?(): void;
|
|
44
|
+
}
|
|
45
|
+
/** Options for {@link registerStorageObservability}. */
|
|
46
|
+
export interface StorageObservabilityOptions {
|
|
47
|
+
/** Registry the `storage` health check is registered with (Req 23.3). */
|
|
48
|
+
health?: HealthCheckRegistry;
|
|
49
|
+
/** Registry the storage metrics are exported through (Req 23.1, 23.2). */
|
|
50
|
+
metrics?: MetricsRegistry;
|
|
51
|
+
/** Refresh cadence for gauges when `autoRefresh` is enabled. */
|
|
52
|
+
refreshIntervalMs?: number;
|
|
53
|
+
/**
|
|
54
|
+
* When `true`, an unref'd interval keeps the gauges primed. Default `false`
|
|
55
|
+
* (pull-based: the caller drives {@link StorageObservabilityHandle.refresh}).
|
|
56
|
+
*/
|
|
57
|
+
autoRefresh?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/** Handle returned by {@link registerStorageObservability}. */
|
|
60
|
+
export interface StorageObservabilityHandle {
|
|
61
|
+
/**
|
|
62
|
+
* The telemetry sink the facade feeds live. Drives the counters, gauges, and
|
|
63
|
+
* latency histogram as operations occur.
|
|
64
|
+
*/
|
|
65
|
+
readonly telemetry: StorageTelemetry;
|
|
66
|
+
/**
|
|
67
|
+
* Register the `storage` health check against the created facade and prime the
|
|
68
|
+
* gauges from `storage.stats()`. Call once after `createStorage`.
|
|
69
|
+
*/
|
|
70
|
+
attach(storage: StorageIntrospect): void;
|
|
71
|
+
/** Recompute the gauges from `storage.stats()` (best-effort; never throws). */
|
|
72
|
+
refresh(): void;
|
|
73
|
+
/** Stop any refresh timer and release resources. Safe to call once. */
|
|
74
|
+
close(): void;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Register storage observability. Returns a {@link StorageObservabilityHandle}
|
|
78
|
+
* whose `telemetry` the facade feeds live and whose `attach` wires the health
|
|
79
|
+
* check + primes the gauges from the created facade.
|
|
80
|
+
*/
|
|
81
|
+
export declare function registerStorageObservability(options?: StorageObservabilityOptions): StorageObservabilityHandle;
|
|
82
|
+
//# sourceMappingURL=observability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observability.d.ts","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EAKhB,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE5D;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,IAAI,YAAY,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;CAC/B;AAED,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB,YAAY,CAAC;AAEnD,eAAO,MAAM,sBAAsB,0BAA0B,CAAC;AAC9D,eAAO,MAAM,wBAAwB,4BAA4B,CAAC;AAClE,eAAO,MAAM,6BAA6B,iCAAiC,CAAC;AAC5E,eAAO,MAAM,+BAA+B,mCAAmC,CAAC;AAChF,eAAO,MAAM,6BAA6B,2BAA2B,CAAC;AACtE,eAAO,MAAM,6BAA6B,iCAAiC,CAAC;AAC5E,eAAO,MAAM,oBAAoB,wBAAwB,CAAC;AAC1D,eAAO,MAAM,sBAAsB,sCAAsC,CAAC;AAC1E,eAAO,MAAM,wBAAwB,oCAAoC,CAAC;AAC1E,eAAO,MAAM,wBAAwB,qCAAqC,CAAC;AAI3E;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvD,kEAAkE;IAClE,UAAU,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACzD,qEAAqE;IACrE,cAAc,CAAC,IAAI,IAAI,CAAC;IACxB,oEAAoE;IACpE,qBAAqB,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,iDAAiD;IACjD,cAAc,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,sCAAsC;IACtC,iBAAiB,CAAC,IAAI,IAAI,CAAC;IAC3B,8CAA8C;IAC9C,kBAAkB,CAAC,IAAI,IAAI,CAAC;CAC7B;AAED,wDAAwD;AACxD,MAAM,WAAW,2BAA2B;IAC1C,yEAAyE;IACzE,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,gEAAgE;IAChE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,+DAA+D;AAC/D,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC;;;OAGG;IACH,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACzC,+EAA+E;IAC/E,OAAO,IAAI,IAAI,CAAC;IAChB,uEAAuE;IACvE,KAAK,IAAI,IAAI,CAAC;CACf;AAKD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,GAAE,2BAAgC,GACxC,0BAA0B,CAoJ5B"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// src/observability.ts
|
|
2
|
+
// @streetjs/storage — health check + metrics wiring over the reused core
|
|
3
|
+
// `HealthCheckRegistry` and `MetricsRegistry`.
|
|
4
|
+
//
|
|
5
|
+
// Following the events package's `observability.ts` pattern, this module
|
|
6
|
+
// exposes a single entry point, `registerStorageObservability`, that returns a
|
|
7
|
+
// handle with:
|
|
8
|
+
//
|
|
9
|
+
// 1. a `telemetry` sink the facade feeds live as operations occur — it drives
|
|
10
|
+
// the upload / download / bytes / failed-upload counters, the active-uploads
|
|
11
|
+
// and storage-usage gauges, the operation-latency histogram, and the
|
|
12
|
+
// multipart / resumable counters (Requirement 23.2);
|
|
13
|
+
// 2. an `attach(storage)` method that registers the `storage` health check
|
|
14
|
+
// against the `HealthCheckRegistry`, sourced from `storage.probe()` for
|
|
15
|
+
// provider connectivity, writability, readability, and quota availability
|
|
16
|
+
// (Requirement 23.3), and primes the gauges from `storage.stats()`.
|
|
17
|
+
//
|
|
18
|
+
// Everything is opt-in: with no `metrics` registry the telemetry is inert; with
|
|
19
|
+
// no `health` registry no check is registered. Registration is idempotent
|
|
20
|
+
// against a shared registry (reuses an existing metric rather than throwing —
|
|
21
|
+
// `reg.has(name) ? reg.get(name) : reg.counter(...)`), so several storage
|
|
22
|
+
// instances can share one registry (Requirement 23.1).
|
|
23
|
+
//
|
|
24
|
+
// Only the existing core primitives (`MetricsRegistry`, `HealthCheckRegistry`)
|
|
25
|
+
// are reused; no separate metrics / health / scheduling / retry / event system
|
|
26
|
+
// is introduced (Requirement 23.4).
|
|
27
|
+
/** The name the storage health check is registered under. */
|
|
28
|
+
export const STORAGE_HEALTH_CHECK_NAME = "storage";
|
|
29
|
+
export const STORAGE_UPLOADS_METRIC = "storage_uploads_total";
|
|
30
|
+
export const STORAGE_DOWNLOADS_METRIC = "storage_downloads_total";
|
|
31
|
+
export const STORAGE_BYTES_UPLOADED_METRIC = "storage_bytes_uploaded_total";
|
|
32
|
+
export const STORAGE_BYTES_DOWNLOADED_METRIC = "storage_bytes_downloaded_total";
|
|
33
|
+
export const STORAGE_ACTIVE_UPLOADS_METRIC = "storage_active_uploads";
|
|
34
|
+
export const STORAGE_FAILED_UPLOADS_METRIC = "storage_failed_uploads_total";
|
|
35
|
+
export const STORAGE_USAGE_METRIC = "storage_usage_bytes";
|
|
36
|
+
export const STORAGE_LATENCY_METRIC = "storage_operation_latency_seconds";
|
|
37
|
+
export const STORAGE_MULTIPART_METRIC = "storage_multipart_uploads_total";
|
|
38
|
+
export const STORAGE_RESUMABLE_METRIC = "storage_resumable_sessions_total";
|
|
39
|
+
const DEFAULT_REFRESH_INTERVAL_MS = 5_000;
|
|
40
|
+
/** An inert telemetry sink used when no metrics registry is provided. */
|
|
41
|
+
const NOOP_TELEMETRY = {};
|
|
42
|
+
/**
|
|
43
|
+
* Register storage observability. Returns a {@link StorageObservabilityHandle}
|
|
44
|
+
* whose `telemetry` the facade feeds live and whose `attach` wires the health
|
|
45
|
+
* check + primes the gauges from the created facade.
|
|
46
|
+
*/
|
|
47
|
+
export function registerStorageObservability(options = {}) {
|
|
48
|
+
const { metrics, health } = options;
|
|
49
|
+
let storage;
|
|
50
|
+
let timer;
|
|
51
|
+
let closed = false;
|
|
52
|
+
// ── Metrics (idempotent registration) ─────────────────────────────────────
|
|
53
|
+
let uploadsCounter;
|
|
54
|
+
let downloadsCounter;
|
|
55
|
+
let bytesUploadedCounter;
|
|
56
|
+
let bytesDownloadedCounter;
|
|
57
|
+
let failedUploadsCounter;
|
|
58
|
+
let multipartCounter;
|
|
59
|
+
let resumableCounter;
|
|
60
|
+
let activeUploadsGauge;
|
|
61
|
+
let usageGauge;
|
|
62
|
+
let latencyHistogram;
|
|
63
|
+
if (metrics) {
|
|
64
|
+
uploadsCounter = counter(metrics, STORAGE_UPLOADS_METRIC, "Total objects uploaded.");
|
|
65
|
+
downloadsCounter = counter(metrics, STORAGE_DOWNLOADS_METRIC, "Total objects downloaded.");
|
|
66
|
+
bytesUploadedCounter = counter(metrics, STORAGE_BYTES_UPLOADED_METRIC, "Total bytes uploaded.");
|
|
67
|
+
bytesDownloadedCounter = counter(metrics, STORAGE_BYTES_DOWNLOADED_METRIC, "Total bytes downloaded.");
|
|
68
|
+
failedUploadsCounter = counter(metrics, STORAGE_FAILED_UPLOADS_METRIC, "Total failed uploads.");
|
|
69
|
+
multipartCounter = counter(metrics, STORAGE_MULTIPART_METRIC, "Total multipart uploads created.");
|
|
70
|
+
resumableCounter = counter(metrics, STORAGE_RESUMABLE_METRIC, "Total resumable upload sessions started.");
|
|
71
|
+
activeUploadsGauge = gauge(metrics, STORAGE_ACTIVE_UPLOADS_METRIC, "Uploads currently in flight.");
|
|
72
|
+
usageGauge = gauge(metrics, STORAGE_USAGE_METRIC, "Tracked storage usage in bytes.");
|
|
73
|
+
latencyHistogram = histogram(metrics, STORAGE_LATENCY_METRIC, "Storage operation latency in seconds.");
|
|
74
|
+
}
|
|
75
|
+
const telemetry = metrics
|
|
76
|
+
? {
|
|
77
|
+
onUpload: (bytes, latencySeconds) => safe(() => {
|
|
78
|
+
uploadsCounter?.inc();
|
|
79
|
+
bytesUploadedCounter?.inc({}, bytes);
|
|
80
|
+
latencyHistogram?.observe(latencySeconds);
|
|
81
|
+
}),
|
|
82
|
+
onDownload: (bytes, latencySeconds) => safe(() => {
|
|
83
|
+
downloadsCounter?.inc();
|
|
84
|
+
bytesDownloadedCounter?.inc({}, bytes);
|
|
85
|
+
latencyHistogram?.observe(latencySeconds);
|
|
86
|
+
}),
|
|
87
|
+
onUploadFailed: () => safe(() => failedUploadsCounter?.inc()),
|
|
88
|
+
onActiveUploadsChange: (active) => safe(() => activeUploadsGauge?.set(active)),
|
|
89
|
+
onStorageUsage: (bytes) => safe(() => usageGauge?.set(bytes)),
|
|
90
|
+
onMultipartUpload: () => safe(() => multipartCounter?.inc()),
|
|
91
|
+
onResumableSession: () => safe(() => resumableCounter?.inc()),
|
|
92
|
+
}
|
|
93
|
+
: NOOP_TELEMETRY;
|
|
94
|
+
const refresh = () => {
|
|
95
|
+
if (!storage) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
safe(() => {
|
|
99
|
+
const stats = storage.stats();
|
|
100
|
+
activeUploadsGauge?.set(stats.activeUploads);
|
|
101
|
+
usageGauge?.set(stats.storageUsage);
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
const attach = (created) => {
|
|
105
|
+
storage = created;
|
|
106
|
+
if (health) {
|
|
107
|
+
health.addCheck(STORAGE_HEALTH_CHECK_NAME, async () => {
|
|
108
|
+
try {
|
|
109
|
+
const probe = await created.probe();
|
|
110
|
+
// The provider is healthy only when it is reachable and all of the
|
|
111
|
+
// connectivity/writability/readability/quota dimensions hold
|
|
112
|
+
// (Requirement 23.3).
|
|
113
|
+
const status = probe.connectivity && probe.writable && probe.readable && probe.quotaAvailable
|
|
114
|
+
? "up"
|
|
115
|
+
: "down";
|
|
116
|
+
return {
|
|
117
|
+
status,
|
|
118
|
+
details: {
|
|
119
|
+
connectivity: probe.connectivity,
|
|
120
|
+
writable: probe.writable,
|
|
121
|
+
readable: probe.readable,
|
|
122
|
+
quotaAvailable: probe.quotaAvailable,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
return {
|
|
128
|
+
status: "down",
|
|
129
|
+
details: { error: err instanceof Error ? err.message : String(err) },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// Prime the gauges immediately; optionally keep them fresh via an unref'd timer.
|
|
135
|
+
refresh();
|
|
136
|
+
if (options.autoRefresh) {
|
|
137
|
+
timer = setInterval(refresh, options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS);
|
|
138
|
+
timer.unref?.();
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
telemetry,
|
|
143
|
+
attach,
|
|
144
|
+
refresh,
|
|
145
|
+
close: () => {
|
|
146
|
+
if (closed) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
closed = true;
|
|
150
|
+
if (timer !== undefined) {
|
|
151
|
+
clearInterval(timer);
|
|
152
|
+
timer = undefined;
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// ── Idempotent metric helpers ────────────────────────────────────────────────
|
|
158
|
+
function counter(reg, name, help) {
|
|
159
|
+
return reg.has(name) ? reg.get(name) : reg.counter(name, help);
|
|
160
|
+
}
|
|
161
|
+
function gauge(reg, name, help, labels = []) {
|
|
162
|
+
return reg.has(name) ? reg.get(name) : reg.gauge(name, help, labels);
|
|
163
|
+
}
|
|
164
|
+
function histogram(reg, name, help) {
|
|
165
|
+
return reg.has(name) ? reg.get(name) : reg.histogram(name, help);
|
|
166
|
+
}
|
|
167
|
+
function safe(fn) {
|
|
168
|
+
try {
|
|
169
|
+
fn();
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// Observability must never destabilize a storage operation or a scrape.
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=observability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observability.js","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,yEAAyE;AACzE,+CAA+C;AAC/C,EAAE;AACF,yEAAyE;AACzE,+EAA+E;AAC/E,eAAe;AACf,EAAE;AACF,gFAAgF;AAChF,kFAAkF;AAClF,0EAA0E;AAC1E,0DAA0D;AAC1D,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAC/E,yEAAyE;AACzE,EAAE;AACF,gFAAgF;AAChF,0EAA0E;AAC1E,8EAA8E;AAC9E,0EAA0E;AAC1E,uDAAuD;AACvD,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,oCAAoC;AAsBpC,6DAA6D;AAC7D,MAAM,CAAC,MAAM,yBAAyB,GAAG,SAAS,CAAC;AAEnD,MAAM,CAAC,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AAC9D,MAAM,CAAC,MAAM,wBAAwB,GAAG,yBAAyB,CAAC;AAClE,MAAM,CAAC,MAAM,6BAA6B,GAAG,8BAA8B,CAAC;AAC5E,MAAM,CAAC,MAAM,+BAA+B,GAAG,gCAAgC,CAAC;AAChF,MAAM,CAAC,MAAM,6BAA6B,GAAG,wBAAwB,CAAC;AACtE,MAAM,CAAC,MAAM,6BAA6B,GAAG,8BAA8B,CAAC;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,qBAAqB,CAAC;AAC1D,MAAM,CAAC,MAAM,sBAAsB,GAAG,mCAAmC,CAAC;AAC1E,MAAM,CAAC,MAAM,wBAAwB,GAAG,iCAAiC,CAAC;AAC1E,MAAM,CAAC,MAAM,wBAAwB,GAAG,kCAAkC,CAAC;AAE3E,MAAM,2BAA2B,GAAG,KAAK,CAAC;AA0D1C,yEAAyE;AACzE,MAAM,cAAc,GAAqB,EAAE,CAAC;AAE5C;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAC1C,UAAuC,EAAE;IAEzC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAEpC,IAAI,OAAsC,CAAC;IAC3C,IAAI,KAAiD,CAAC;IACtD,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,6EAA6E;IAC7E,IAAI,cAAmC,CAAC;IACxC,IAAI,gBAAqC,CAAC;IAC1C,IAAI,oBAAyC,CAAC;IAC9C,IAAI,sBAA2C,CAAC;IAChD,IAAI,oBAAyC,CAAC;IAC9C,IAAI,gBAAqC,CAAC;IAC1C,IAAI,gBAAqC,CAAC;IAC1C,IAAI,kBAAqC,CAAC;IAC1C,IAAI,UAA6B,CAAC;IAClC,IAAI,gBAAuC,CAAC;IAE5C,IAAI,OAAO,EAAE,CAAC;QACZ,cAAc,GAAG,OAAO,CAAC,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,CAAC,CAAC;QACrF,gBAAgB,GAAG,OAAO,CAAC,OAAO,EAAE,wBAAwB,EAAE,2BAA2B,CAAC,CAAC;QAC3F,oBAAoB,GAAG,OAAO,CAC5B,OAAO,EACP,6BAA6B,EAC7B,uBAAuB,CACxB,CAAC;QACF,sBAAsB,GAAG,OAAO,CAC9B,OAAO,EACP,+BAA+B,EAC/B,yBAAyB,CAC1B,CAAC;QACF,oBAAoB,GAAG,OAAO,CAC5B,OAAO,EACP,6BAA6B,EAC7B,uBAAuB,CACxB,CAAC;QACF,gBAAgB,GAAG,OAAO,CACxB,OAAO,EACP,wBAAwB,EACxB,kCAAkC,CACnC,CAAC;QACF,gBAAgB,GAAG,OAAO,CACxB,OAAO,EACP,wBAAwB,EACxB,0CAA0C,CAC3C,CAAC;QACF,kBAAkB,GAAG,KAAK,CACxB,OAAO,EACP,6BAA6B,EAC7B,8BAA8B,CAC/B,CAAC;QACF,UAAU,GAAG,KAAK,CAAC,OAAO,EAAE,oBAAoB,EAAE,iCAAiC,CAAC,CAAC;QACrF,gBAAgB,GAAG,SAAS,CAC1B,OAAO,EACP,sBAAsB,EACtB,uCAAuC,CACxC,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAqB,OAAO;QACzC,CAAC,CAAC;YACE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,CAClC,IAAI,CAAC,GAAG,EAAE;gBACR,cAAc,EAAE,GAAG,EAAE,CAAC;gBACtB,oBAAoB,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;gBACrC,gBAAgB,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;YAC5C,CAAC,CAAC;YACJ,UAAU,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,CACpC,IAAI,CAAC,GAAG,EAAE;gBACR,gBAAgB,EAAE,GAAG,EAAE,CAAC;gBACxB,sBAAsB,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;gBACvC,gBAAgB,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;YAC5C,CAAC,CAAC;YACJ,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE,CAAC;YAC7D,qBAAqB,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YAC9E,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;YAC7D,iBAAiB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAAC;YAC5D,kBAAkB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAAC;SAC9D;QACH,CAAC,CAAC,cAAc,CAAC;IAEnB,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QACD,IAAI,CAAC,GAAG,EAAE;YACR,MAAM,KAAK,GAAiB,OAAQ,CAAC,KAAK,EAAE,CAAC;YAC7C,kBAAkB,EAAE,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;YAC7C,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,OAA0B,EAAQ,EAAE;QAClD,OAAO,GAAG,OAAO,CAAC;QAElB,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,QAAQ,CAAC,yBAAyB,EAAE,KAAK,IAA0B,EAAE;gBAC1E,IAAI,CAAC;oBACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;oBACpC,mEAAmE;oBACnE,6DAA6D;oBAC7D,sBAAsB;oBACtB,MAAM,MAAM,GACV,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,cAAc;wBAC5E,CAAC,CAAC,IAAI;wBACN,CAAC,CAAC,MAAM,CAAC;oBACb,OAAO;wBACL,MAAM;wBACN,OAAO,EAAE;4BACP,YAAY,EAAE,KAAK,CAAC,YAAY;4BAChC,QAAQ,EAAE,KAAK,CAAC,QAAQ;4BACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;4BACxB,cAAc,EAAE,KAAK,CAAC,cAAc;yBACrC;qBACF,CAAC;gBACJ,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,OAAO;wBACL,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;qBACrE,CAAC;gBACJ,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAED,iFAAiF;QACjF,OAAO,EAAE,CAAC;QACV,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACxB,KAAK,GAAG,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,IAAI,2BAA2B,CAAC,CAAC;YACvF,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;QAClB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,SAAS;QACT,MAAM;QACN,OAAO;QACP,KAAK,EAAE,GAAG,EAAE;YACV,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO;YACT,CAAC;YACD,MAAM,GAAG,IAAI,CAAC;YACd,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,aAAa,CAAC,KAAK,CAAC,CAAC;gBACrB,KAAK,GAAG,SAAS,CAAC;YACpB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,gFAAgF;AAEhF,SAAS,OAAO,CAAC,GAAoB,EAAE,IAAY,EAAE,IAAY;IAC/D,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAa,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC9E,CAAC;AACD,SAAS,KAAK,CAAC,GAAoB,EAAE,IAAY,EAAE,IAAY,EAAE,SAAmB,EAAE;IACpF,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAW,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AAClF,CAAC;AACD,SAAS,SAAS,CAAC,GAAoB,EAAE,IAAY,EAAE,IAAY;IACjE,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAe,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAClF,CAAC;AACD,SAAS,IAAI,CAAC,EAAc;IAC1B,IAAI,CAAC;QACH,EAAE,EAAE,CAAC;IACP,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;IAC1E,CAAC;AACH,CAAC"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { PluginModule } from "streetjs";
|
|
2
|
+
import type { SandboxedApp } from "streetjs";
|
|
3
|
+
import { type Storage } from "./facade.js";
|
|
4
|
+
import type { StorageConfig, StorageMetadataMap } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Options for {@link StoragePlugin}.
|
|
7
|
+
*
|
|
8
|
+
* Extends {@link StorageConfig} directly, so every configuration value the
|
|
9
|
+
* facade accepts — provider, driver, validation, versioning, signing secret,
|
|
10
|
+
* the `metrics` / `health` registries the observability layer registers
|
|
11
|
+
* against, the structural auth / image codec, and the structural
|
|
12
|
+
* events / queue / realtime `bridges` the facade wires internally — is supplied
|
|
13
|
+
* through the plugin options. Because a `SandboxedApp` exposes no registry, the
|
|
14
|
+
* registries are resolved from these options rather than from the app.
|
|
15
|
+
*
|
|
16
|
+
* @typeParam T - Optional per-application custom metadata map.
|
|
17
|
+
*/
|
|
18
|
+
export interface StoragePluginOptions<T extends StorageMetadataMap = StorageMetadataMap> extends StorageConfig {
|
|
19
|
+
/**
|
|
20
|
+
* Declarative, imperative bridge wiring composed on top of the constructed
|
|
21
|
+
* facade. Each entry is an attach function called with the live
|
|
22
|
+
* {@link Storage} facade; if it returns a detach function, that detach is
|
|
23
|
+
* invoked on `onUnload`. This keeps the plugin decoupled from any specific
|
|
24
|
+
* integration instance — compose custom bridges without the plugin depending
|
|
25
|
+
* on them, and have them torn down deterministically on unload:
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* new StoragePlugin({
|
|
29
|
+
* provider: "memory",
|
|
30
|
+
* metrics,
|
|
31
|
+
* health,
|
|
32
|
+
* wireBridges: [
|
|
33
|
+
* (storage) => attachAuditLog(storage, auditSink), // returns () => void
|
|
34
|
+
* ],
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* Distinct from the inherited structural `bridges`
|
|
39
|
+
* (events / queue / realtime), which the facade wires internally at
|
|
40
|
+
* construction time.
|
|
41
|
+
*/
|
|
42
|
+
wireBridges?: Array<(storage: Storage<T>) => (() => void) | void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Plugin entry point that constructs and wires the application storage layer.
|
|
46
|
+
*
|
|
47
|
+
* Compatible with the core {@link PluginModule} and safe to register with a
|
|
48
|
+
* `SandboxedApp` (Requirements 20.1, 20.2). Once loaded, the application
|
|
49
|
+
* retrieves the live {@link Storage} facade through the {@link storage}
|
|
50
|
+
* accessor without any existing public API being modified (Requirement 20.3).
|
|
51
|
+
*
|
|
52
|
+
* @typeParam T - Optional per-application custom metadata map.
|
|
53
|
+
*/
|
|
54
|
+
export declare class StoragePlugin<T extends StorageMetadataMap = StorageMetadataMap> extends PluginModule {
|
|
55
|
+
readonly name = "@streetjs/storage";
|
|
56
|
+
readonly version = "1.0.0";
|
|
57
|
+
protected readonly options: StoragePluginOptions<T>;
|
|
58
|
+
private storageInstance?;
|
|
59
|
+
private bridgeDetachers;
|
|
60
|
+
constructor(options: StoragePluginOptions<T>);
|
|
61
|
+
/**
|
|
62
|
+
* The constructed facade, or `undefined` before load / after unload. Because
|
|
63
|
+
* the `SandboxedApp` has no registry to attach to, the application retrieves
|
|
64
|
+
* the live {@link Storage} facade through this accessor after the plugin
|
|
65
|
+
* loads (Requirement 20.3).
|
|
66
|
+
*/
|
|
67
|
+
get storage(): Storage<T> | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Construct the facade (which wires observability from the configured
|
|
70
|
+
* `metrics` / `health` registries and the structural bridges), apply the
|
|
71
|
+
* declarative bridge wiring collecting any detach functions, and expose the
|
|
72
|
+
* facade. Idempotent per load: a second `onLoad` without an intervening
|
|
73
|
+
* `onUnload` reuses the already-constructed facade.
|
|
74
|
+
*/
|
|
75
|
+
onLoad(_app: SandboxedApp): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Detach the declarative bridges and close the facade gracefully (which stops
|
|
78
|
+
* the observability refresh timer). Safe if never loaded.
|
|
79
|
+
*/
|
|
80
|
+
onUnload(_app: SandboxedApp): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEpE;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,kBAAkB,GAAG,kBAAkB,CACrF,SAAQ,aAAa;IACrB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;CACnE;AAED;;;;;;;;;GASG;AACH,qBAAa,aAAa,CACxB,CAAC,SAAS,kBAAkB,GAAG,kBAAkB,CACjD,SAAQ,YAAY;IACpB,QAAQ,CAAC,IAAI,uBAAuB;IACpC,QAAQ,CAAC,OAAO,WAAW;IAE3B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;IAEpD,OAAO,CAAC,eAAe,CAAC,CAAa;IACrC,OAAO,CAAC,eAAe,CAAyB;gBAEpC,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAK5C;;;;;OAKG;IACH,IAAI,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,CAEpC;IAED;;;;;;OAMG;IACY,MAAM,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBxD;;;OAGG;IACY,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAiB3D"}
|