@ddd-ts-kit/s3 0.0.0 → 0.9.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 +81 -1
- package/dist/bucket-keys.d.ts +11 -0
- package/dist/bucket-keys.d.ts.map +1 -0
- package/dist/bucket-keys.js +23 -0
- package/dist/bucket-keys.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/is-missing.d.ts +3 -0
- package/dist/is-missing.d.ts.map +1 -0
- package/dist/is-missing.js +7 -0
- package/dist/is-missing.js.map +1 -0
- package/dist/list-objects.d.ts +5 -0
- package/dist/list-objects.d.ts.map +1 -0
- package/dist/list-objects.js +17 -0
- package/dist/list-objects.js.map +1 -0
- package/dist/s3-client-of.d.ts +9 -0
- package/dist/s3-client-of.d.ts.map +1 -0
- package/dist/s3-client-of.js +18 -0
- package/dist/s3-client-of.js.map +1 -0
- package/dist/s3-file-storage-options.d.ts +15 -0
- package/dist/s3-file-storage-options.d.ts.map +1 -0
- package/dist/s3-file-storage-options.js +2 -0
- package/dist/s3-file-storage-options.js.map +1 -0
- package/dist/s3-file-storage.d.ts +29 -0
- package/dist/s3-file-storage.d.ts.map +1 -0
- package/dist/s3-file-storage.js +125 -0
- package/dist/s3-file-storage.js.map +1 -0
- package/dist/s3-settings.d.ts +12 -0
- package/dist/s3-settings.d.ts.map +1 -0
- package/dist/s3-settings.js +2 -0
- package/dist/s3-settings.js.map +1 -0
- package/dist/settled-first.d.ts +6 -0
- package/dist/settled-first.d.ts.map +1 -0
- package/dist/settled-first.js +14 -0
- package/dist/settled-first.js.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +2 -0
- package/dist/testing.js.map +1 -0
- package/package.json +57 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gary Bourgeat
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1 +1,81 @@
|
|
|
1
|
-
|
|
1
|
+
# @ddd-ts-kit/s3
|
|
2
|
+
|
|
3
|
+
The kernel `FileStorage` on any provider of the S3 protocol: AWS S3, MinIO, OVHcloud Object
|
|
4
|
+
Storage, Scaleway, Cloudflare R2, Garage. The content of a file streams to the bucket in parts,
|
|
5
|
+
never whole in memory and never in the database; the aggregate keeps the `StoredFile` that `put`
|
|
6
|
+
answers. Nothing in `domain/` or `application/` imports this package: handlers depend on the
|
|
7
|
+
`FileStorage` port, and the platform binds this adapter.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npm i @ddd-ts-kit/s3 @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The three packages of the official AWS SDK v3 are optional peers, written against 3.1150.
|
|
14
|
+
|
|
15
|
+
## Composition root
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { S3FileStorage, s3ClientOf } from '@ddd-ts-kit/s3';
|
|
19
|
+
|
|
20
|
+
const files = new S3FileStorage({
|
|
21
|
+
client: s3ClientOf({
|
|
22
|
+
endpoint: config.storage.endpoint, // left out for AWS; http://localhost:9000 for MinIO
|
|
23
|
+
region: config.storage.region, // 'auto' for R2
|
|
24
|
+
accessKeyId: config.storage.accessKeyId,
|
|
25
|
+
secretAccessKey: config.storage.secretAccessKey,
|
|
26
|
+
}),
|
|
27
|
+
bucket: config.storage.bucket, // created by the infrastructure code, never by the application
|
|
28
|
+
prefix: 'scheduling/', // optional: several applications in one bucket
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`s3ClientOf` sets path-style addressing when an endpoint is given (MinIO and most self-hosted
|
|
33
|
+
servers need it) and computes checksums only when an operation requires one: the default of the
|
|
34
|
+
SDK sends a checksum with every request, which several providers that are not AWS refuse, and
|
|
35
|
+
which would bind a signed upload link to an empty body. A client built by hand works too.
|
|
36
|
+
|
|
37
|
+
## What it does
|
|
38
|
+
|
|
39
|
+
| Port method | On the bucket |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `put(key, stream, { mediaType, maxBytes })` | a multipart upload of `partSize` parts (8 MiB by default, one in memory at a time); the content is counted and hashed on its way, and refused with `FILE_TOO_LARGE` beyond `maxBytes`, which aborts the upload and leaves the previous object |
|
|
42
|
+
| `get`, `stat` | `GetObject` as a stream, `HeadObject`; `undefined` when the object does not exist, a rejection when the service cannot be reached |
|
|
43
|
+
| `delete` | `DeleteObject`, idempotent |
|
|
44
|
+
| `copy`, `move` | `CopyObject` done by the service (and `DeleteObject` for a move), up to 5 GiB per object |
|
|
45
|
+
| `list(prefix)` | `ListObjectsV2`, page after page |
|
|
46
|
+
| `signedDownloadUrl` | a presigned `GetObject` answered with `Content-Disposition: attachment`, with the file name when one is given |
|
|
47
|
+
| `signedUploadUrl` | a presigned `PutObject` whose signature covers the `content-type`: another media type is refused by the service |
|
|
48
|
+
|
|
49
|
+
The checksum (`sha256:<hex>`) is answered by `put`, computed while the content streams; the
|
|
50
|
+
bucket does not keep it, so `stat` does not answer one. A file uploaded through a signed link
|
|
51
|
+
is measured, checked and hashed by `FileStaging.confirm` of the kernel, which reads it back.
|
|
52
|
+
|
|
53
|
+
## Limits
|
|
54
|
+
|
|
55
|
+
- A signed upload link cannot cap the size of what is sent (only a presigned POST policy can):
|
|
56
|
+
its `maxBytes` is not enforced by the service, and `FileStaging.confirm` measures the file
|
|
57
|
+
against the cap of the grant the server kept and discards it beyond.
|
|
58
|
+
- Copy and move go through `CopyObject`, limited to 5 GiB per object by the protocol.
|
|
59
|
+
- Lifecycle rules, versioning, object lock and the encryption of the bucket are settings of the
|
|
60
|
+
bucket, made by the infrastructure code of the deployment, not by this adapter.
|
|
61
|
+
|
|
62
|
+
## Tests
|
|
63
|
+
|
|
64
|
+
The contract of the port is held against a real server: `fileStorageContract` of
|
|
65
|
+
`@ddd-ts-kit/testing/contracts` (re-exported by `@ddd-ts-kit/s3/testing`) runs on MinIO in the
|
|
66
|
+
integration job of the pipeline, with `DDD_TS_KIT_TEST_S3_ENDPOINT` set; without it the specs
|
|
67
|
+
that need a server are skipped. The upstream MinIO project stopped publishing images in 2025:
|
|
68
|
+
the pipeline runs `pgsty/minio`, built from the same sources, pinned by digest
|
|
69
|
+
(`pipeline/github/scripts/start-minio.sh`).
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
docker run -d --name minio -p 127.0.0.1:9000:9000 -e MINIO_ROOT_USER=ddd-ts-kit -e MINIO_ROOT_PASSWORD=ddd-ts-kit-secret pgsty/minio:RELEASE.2026-08-04T00-00-00Z server /data
|
|
73
|
+
DDD_TS_KIT_TEST_S3_ENDPOINT=http://localhost:9000 npx vitest run packages/s3
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Another provider
|
|
77
|
+
|
|
78
|
+
Google Cloud Storage and Azure Blob Storage speak their own protocols. A connector for one of
|
|
79
|
+
them is a class extending `FileStorage` of the kernel that passes `fileStorageContract` against
|
|
80
|
+
the real service or its emulator (fake-gcs-server, Azurite): see
|
|
81
|
+
[docs/tools/file-storage.md](../../docs/tools/file-storage.md).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Turns the keys of the port into the keys of the bucket and back, under the prefix of the connector. */
|
|
2
|
+
export declare class BucketKeys {
|
|
3
|
+
private readonly prefix;
|
|
4
|
+
constructor(prefix: string);
|
|
5
|
+
of(key: string): string;
|
|
6
|
+
/** The prefix of the bucket a listing of the port reads. */
|
|
7
|
+
under(prefix: string): string;
|
|
8
|
+
/** The key of the port of an object listed in the bucket. */
|
|
9
|
+
back(objectKey: string): string;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=bucket-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bucket-keys.d.ts","sourceRoot":"","sources":["../src/bucket-keys.ts"],"names":[],"mappings":"AAEA,0GAA0G;AAC1G,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAnC,YAA6B,MAAM,EAAE,MAAM,EAI1C;IAED,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtB;IAED,4DAA4D;IAC5D,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE5B;IAED,6DAA6D;IAC7D,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE9B;CACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { assertFileKey, isFileKey } from '@ddd-ts-kit/kernel';
|
|
2
|
+
/** Turns the keys of the port into the keys of the bucket and back, under the prefix of the connector. */
|
|
3
|
+
export class BucketKeys {
|
|
4
|
+
prefix;
|
|
5
|
+
constructor(prefix) {
|
|
6
|
+
this.prefix = prefix;
|
|
7
|
+
if (prefix !== '' && !(prefix.endsWith('/') && isFileKey(prefix.slice(0, -1)))) {
|
|
8
|
+
throw new Error(`The prefix of an S3 file storage is a valid key ending with "/", not "${prefix}".`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
of(key) {
|
|
12
|
+
return `${this.prefix}${assertFileKey(key)}`;
|
|
13
|
+
}
|
|
14
|
+
/** The prefix of the bucket a listing of the port reads. */
|
|
15
|
+
under(prefix) {
|
|
16
|
+
return `${this.prefix}${prefix}`;
|
|
17
|
+
}
|
|
18
|
+
/** The key of the port of an object listed in the bucket. */
|
|
19
|
+
back(objectKey) {
|
|
20
|
+
return objectKey.slice(this.prefix.length);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=bucket-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bucket-keys.js","sourceRoot":"","sources":["../src/bucket-keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE9D,0GAA0G;AAC1G,MAAM,OAAO,UAAU;IACQ,MAAM;IAAnC,YAA6B,MAAc;sBAAd,MAAM;QACjC,IAAI,MAAM,KAAK,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/E,MAAM,IAAI,KAAK,CAAC,yEAAyE,MAAM,IAAI,CAAC,CAAC;QACvG,CAAC;IACH,CAAC;IAED,EAAE,CAAC,GAAW;QACZ,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;IAC/C,CAAC;IAED,4DAA4D;IAC5D,KAAK,CAAC,MAAc;QAClB,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC;IACnC,CAAC;IAED,6DAA6D;IAC7D,IAAI,CAAC,SAAiB;QACpB,OAAO,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACzE,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-missing.d.ts","sourceRoot":"","sources":["../src/is-missing.ts"],"names":[],"mappings":"AAEA,gHAAgH;AAChH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKjD"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { S3ServiceException } from '@aws-sdk/client-s3';
|
|
2
|
+
/** Whether the service answered that the object does not exist: `NoSuchKey` on a read, `NotFound` on a head. */
|
|
3
|
+
export function isMissing(error) {
|
|
4
|
+
return (error instanceof S3ServiceException &&
|
|
5
|
+
(error.name === 'NoSuchKey' || error.name === 'NotFound' || error.$metadata.httpStatusCode === 404));
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=is-missing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-missing.js","sourceRoot":"","sources":["../src/is-missing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,gHAAgH;AAChH,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,OAAO,CACL,KAAK,YAAY,kBAAkB;QACnC,CAAC,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,KAAK,CAAC,SAAS,CAAC,cAAc,KAAK,GAAG,CAAC,CACpG,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import type { ListedFile } from '@ddd-ts-kit/kernel';
|
|
3
|
+
/** Every object under the prefix, page after page, with the key of the bucket. */
|
|
4
|
+
export declare function listObjects(client: S3Client, bucket: string, prefix: string): AsyncIterable<ListedFile>;
|
|
5
|
+
//# sourceMappingURL=list-objects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-objects.d.ts","sourceRoot":"","sources":["../src/list-objects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,kFAAkF;AAClF,wBAAuB,WAAW,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAkB9G"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ListObjectsV2Command } from '@aws-sdk/client-s3';
|
|
2
|
+
/** Every object under the prefix, page after page, with the key of the bucket. */
|
|
3
|
+
export async function* listObjects(client, bucket, prefix) {
|
|
4
|
+
let token;
|
|
5
|
+
do {
|
|
6
|
+
const page = await client.send(new ListObjectsV2Command({
|
|
7
|
+
Bucket: bucket,
|
|
8
|
+
Prefix: prefix,
|
|
9
|
+
...(token === undefined ? {} : { ContinuationToken: token }),
|
|
10
|
+
}));
|
|
11
|
+
for (const object of page.Contents ?? []) {
|
|
12
|
+
yield { key: object.Key ?? '', size: object.Size ?? 0, lastModified: object.LastModified ?? new Date(0) };
|
|
13
|
+
}
|
|
14
|
+
token = page.IsTruncated === true ? page.NextContinuationToken : undefined;
|
|
15
|
+
} while (token !== undefined);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=list-objects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-objects.js","sourceRoot":"","sources":["../src/list-objects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAiB,MAAM,oBAAoB,CAAC;AAGzE,kFAAkF;AAClF,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,WAAW,CAAC,MAAgB,EAAE,MAAc,EAAE,MAAc;IACjF,IAAI,KAAyB,CAAC;IAE9B,GAAG,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAC5B,IAAI,oBAAoB,CAAC;YACvB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,MAAM;YACd,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC;SAC7D,CAAC,CACH,CAAC;QAEF,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;YACzC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5G,CAAC;QAED,KAAK,GAAG,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7E,CAAC,QAAQ,KAAK,KAAK,SAAS,EAAE;AAChC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import type { S3Settings } from './s3-settings.js';
|
|
3
|
+
/**
|
|
4
|
+
* A client for any provider of the S3 protocol. Checksums are computed only when an operation
|
|
5
|
+
* requires one: the default of the SDK (a checksum on every request) is refused by several
|
|
6
|
+
* providers that are not AWS, and would bind a signed upload link to an empty body.
|
|
7
|
+
*/
|
|
8
|
+
export declare function s3ClientOf(settings: S3Settings): S3Client;
|
|
9
|
+
//# sourceMappingURL=s3-client-of.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-client-of.d.ts","sourceRoot":"","sources":["../src/s3-client-of.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,UAAU,GAAG,QAAQ,CAWzD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
/**
|
|
3
|
+
* A client for any provider of the S3 protocol. Checksums are computed only when an operation
|
|
4
|
+
* requires one: the default of the SDK (a checksum on every request) is refused by several
|
|
5
|
+
* providers that are not AWS, and would bind a signed upload link to an empty body.
|
|
6
|
+
*/
|
|
7
|
+
export function s3ClientOf(settings) {
|
|
8
|
+
const { endpoint, region, accessKeyId, secretAccessKey } = settings;
|
|
9
|
+
return new S3Client({
|
|
10
|
+
region,
|
|
11
|
+
credentials: { accessKeyId, secretAccessKey },
|
|
12
|
+
...(endpoint === undefined ? {} : { endpoint }),
|
|
13
|
+
forcePathStyle: settings.forcePathStyle ?? endpoint !== undefined,
|
|
14
|
+
requestChecksumCalculation: 'WHEN_REQUIRED',
|
|
15
|
+
responseChecksumValidation: 'WHEN_REQUIRED',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=s3-client-of.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-client-of.js","sourceRoot":"","sources":["../src/s3-client-of.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAG9C;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,QAAoB;IAC7C,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,QAAQ,CAAC;IAEpE,OAAO,IAAI,QAAQ,CAAC;QAClB,MAAM;QACN,WAAW,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE;QAC7C,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;QAC/C,cAAc,EAAE,QAAQ,CAAC,cAAc,IAAI,QAAQ,KAAK,SAAS;QACjE,0BAA0B,EAAE,eAAe;QAC3C,0BAA0B,EAAE,eAAe;KAC5C,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import type { Clock } from '@ddd-ts-kit/kernel';
|
|
3
|
+
export interface S3FileStorageOptions {
|
|
4
|
+
/** `s3ClientOf(settings)` builds one for AWS, MinIO, OVHcloud, Scaleway or Cloudflare R2. */
|
|
5
|
+
readonly client: S3Client;
|
|
6
|
+
/** Created beforehand, by the infrastructure code of the deployment: the connector never creates one. */
|
|
7
|
+
readonly bucket: string;
|
|
8
|
+
/** Put before every key, ending with `/`: several applications or test runs then share a bucket. Default: none. */
|
|
9
|
+
readonly prefix?: string;
|
|
10
|
+
/** The size of each part of a multipart upload, at least 5 MiB, the most held in memory per upload. Default 8 MiB. */
|
|
11
|
+
readonly partSize?: number;
|
|
12
|
+
/** Reads the expiry the signed links answer. Default: the system clock. */
|
|
13
|
+
readonly clock?: Clock;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=s3-file-storage-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-file-storage-options.d.ts","sourceRoot":"","sources":["../src/s3-file-storage-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEhD,MAAM,WAAW,oBAAoB;IACnC,6FAA6F;IAC7F,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,yGAAyG;IACzG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mHAAmH;IACnH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,sHAAsH;IACtH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-file-storage-options.js","sourceRoot":"","sources":["../src/s3-file-storage-options.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FileStorage, type DownloadUrlOptions, type FileContent, type FileMetadata, type FileSource, type ListedFile, type PutOptions, type SignedUpload, type SignedUrl, type StoredFile, type UploadUrlOptions } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import type { S3FileStorageOptions } from './s3-file-storage-options.js';
|
|
3
|
+
/**
|
|
4
|
+
* The `FileStorage` of a deployment: one bucket of any provider of the S3 protocol. An upload
|
|
5
|
+
* streams in parts of `partSize`, so at most one part is held in memory; a content refused on
|
|
6
|
+
* its way (`FILE_TOO_LARGE`) aborts the upload and leaves the previous object. The checksum is
|
|
7
|
+
* computed while the content streams and answered by `put`, not kept by the bucket: `stat` does
|
|
8
|
+
* not know it. Copy and move are done by the service, up to 5 GiB per object.
|
|
9
|
+
*/
|
|
10
|
+
export declare class S3FileStorage extends FileStorage {
|
|
11
|
+
private readonly client;
|
|
12
|
+
private readonly bucket;
|
|
13
|
+
private readonly keys;
|
|
14
|
+
private readonly partSize;
|
|
15
|
+
private readonly clock;
|
|
16
|
+
constructor(options: S3FileStorageOptions);
|
|
17
|
+
put(key: string, content: FileSource, options: PutOptions): Promise<StoredFile>;
|
|
18
|
+
get(key: string): Promise<FileContent | undefined>;
|
|
19
|
+
stat(key: string): Promise<FileMetadata | undefined>;
|
|
20
|
+
delete(key: string): Promise<void>;
|
|
21
|
+
copy(from: string, to: string): Promise<void>;
|
|
22
|
+
move(from: string, to: string): Promise<void>;
|
|
23
|
+
list(prefix: string): AsyncIterable<ListedFile>;
|
|
24
|
+
signedDownloadUrl(key: string, options: DownloadUrlOptions): Promise<SignedUrl>;
|
|
25
|
+
signedUploadUrl(key: string, options: UploadUrlOptions): Promise<SignedUpload>;
|
|
26
|
+
private expiry;
|
|
27
|
+
private missing;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=s3-file-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-file-storage.d.ts","sourceRoot":"","sources":["../src/s3-file-storage.ts"],"names":[],"mappings":"AAYA,OAAO,EAGL,WAAW,EAMX,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAKzE;;;;;;GAMG;AACH,qBAAa,aAAc,SAAQ,WAAW;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAElC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAElC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAE9B,YAAY,OAAO,EAAE,oBAAoB,EAOxC;IAEK,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAgBpF;IAEK,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAavD;IAEK,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAKzD;IAEK,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvC;IAEK,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOlD;IAEK,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASlD;IAEM,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAIrD;IAEK,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CAUpF;IAEK,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAOnF;IAED,OAAO,CAAC,MAAM;YAIA,OAAO;CAKtB"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { pipeline } from 'node:stream/promises';
|
|
3
|
+
import { CopyObjectCommand, DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand, } from '@aws-sdk/client-s3';
|
|
4
|
+
import { Upload } from '@aws-sdk/lib-storage';
|
|
5
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
6
|
+
import { FileError, FileInspection, FileStorage, SystemClock, assertExpiry, contentDisposition, mediaTypeOf, } from '@ddd-ts-kit/kernel';
|
|
7
|
+
import { BucketKeys } from './bucket-keys.js';
|
|
8
|
+
import { isMissing } from './is-missing.js';
|
|
9
|
+
import { listObjects } from './list-objects.js';
|
|
10
|
+
import { settledFirst } from './settled-first.js';
|
|
11
|
+
const MEBIBYTE = 1_024 * 1_024;
|
|
12
|
+
/**
|
|
13
|
+
* The `FileStorage` of a deployment: one bucket of any provider of the S3 protocol. An upload
|
|
14
|
+
* streams in parts of `partSize`, so at most one part is held in memory; a content refused on
|
|
15
|
+
* its way (`FILE_TOO_LARGE`) aborts the upload and leaves the previous object. The checksum is
|
|
16
|
+
* computed while the content streams and answered by `put`, not kept by the bucket: `stat` does
|
|
17
|
+
* not know it. Copy and move are done by the service, up to 5 GiB per object.
|
|
18
|
+
*/
|
|
19
|
+
export class S3FileStorage extends FileStorage {
|
|
20
|
+
client;
|
|
21
|
+
bucket;
|
|
22
|
+
keys;
|
|
23
|
+
partSize;
|
|
24
|
+
clock;
|
|
25
|
+
constructor(options) {
|
|
26
|
+
super();
|
|
27
|
+
this.client = options.client;
|
|
28
|
+
this.bucket = options.bucket;
|
|
29
|
+
this.keys = new BucketKeys(options.prefix ?? '');
|
|
30
|
+
this.partSize = Math.max(5 * MEBIBYTE, options.partSize ?? 8 * MEBIBYTE);
|
|
31
|
+
this.clock = options.clock ?? new SystemClock();
|
|
32
|
+
}
|
|
33
|
+
async put(key, content, options) {
|
|
34
|
+
const objectKey = this.keys.of(key);
|
|
35
|
+
const mediaType = mediaTypeOf(options.mediaType);
|
|
36
|
+
const inspection = new FileInspection(options.maxBytes === undefined ? {} : { maxBytes: options.maxBytes });
|
|
37
|
+
const upload = new Upload({
|
|
38
|
+
client: this.client,
|
|
39
|
+
params: { Bucket: this.bucket, Key: objectKey, Body: inspection, ContentType: mediaType },
|
|
40
|
+
partSize: this.partSize,
|
|
41
|
+
queueSize: 1,
|
|
42
|
+
leavePartsOnError: false,
|
|
43
|
+
});
|
|
44
|
+
await settledFirst(pipeline(content, inspection), upload.done());
|
|
45
|
+
return { key, size: inspection.size, mediaType, checksum: inspection.checksum };
|
|
46
|
+
}
|
|
47
|
+
async get(key) {
|
|
48
|
+
const command = new GetObjectCommand({ Bucket: this.bucket, Key: this.keys.of(key) });
|
|
49
|
+
const response = await this.client.send(command).catch(absentAsUndefined);
|
|
50
|
+
if (response === undefined) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
if (!(response.Body instanceof Readable)) {
|
|
54
|
+
throw new Error('The S3 client answered a body that is not a Node.js stream: use the Node.js build of the SDK.');
|
|
55
|
+
}
|
|
56
|
+
return { metadata: metadataOf(key, response), stream: response.Body };
|
|
57
|
+
}
|
|
58
|
+
async stat(key) {
|
|
59
|
+
const command = new HeadObjectCommand({ Bucket: this.bucket, Key: this.keys.of(key) });
|
|
60
|
+
const response = await this.client.send(command).catch(absentAsUndefined);
|
|
61
|
+
return response === undefined ? undefined : metadataOf(key, response);
|
|
62
|
+
}
|
|
63
|
+
async delete(key) {
|
|
64
|
+
await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: this.keys.of(key) }));
|
|
65
|
+
}
|
|
66
|
+
async copy(from, to) {
|
|
67
|
+
const source = `${this.bucket}/${this.keys.of(from)}`;
|
|
68
|
+
const command = new CopyObjectCommand({ Bucket: this.bucket, Key: this.keys.of(to), CopySource: source });
|
|
69
|
+
await this.client.send(command).catch((error) => {
|
|
70
|
+
throw isMissing(error) ? FileError.notFound(from) : error;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async move(from, to) {
|
|
74
|
+
if (from === to) {
|
|
75
|
+
await this.missing(from);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
await this.copy(from, to);
|
|
79
|
+
await this.delete(from);
|
|
80
|
+
}
|
|
81
|
+
async *list(prefix) {
|
|
82
|
+
for await (const object of listObjects(this.client, this.bucket, this.keys.under(prefix))) {
|
|
83
|
+
yield { ...object, key: this.keys.back(object.key) };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async signedDownloadUrl(key, options) {
|
|
87
|
+
const expiresIn = assertExpiry(options.expiresInSeconds);
|
|
88
|
+
const command = new GetObjectCommand({
|
|
89
|
+
Bucket: this.bucket,
|
|
90
|
+
Key: this.keys.of(key),
|
|
91
|
+
ResponseContentDisposition: contentDisposition(options.fileName),
|
|
92
|
+
});
|
|
93
|
+
return { url: await getSignedUrl(this.client, command, { expiresIn }), expiresAt: this.expiry(expiresIn) };
|
|
94
|
+
}
|
|
95
|
+
async signedUploadUrl(key, options) {
|
|
96
|
+
const expiresIn = assertExpiry(options.expiresInSeconds);
|
|
97
|
+
const mediaType = mediaTypeOf(options.mediaType);
|
|
98
|
+
const command = new PutObjectCommand({ Bucket: this.bucket, Key: this.keys.of(key), ContentType: mediaType });
|
|
99
|
+
const url = await getSignedUrl(this.client, command, { expiresIn, signableHeaders: new Set(['content-type']) });
|
|
100
|
+
return { url, method: 'PUT', headers: { 'content-type': mediaType }, expiresAt: this.expiry(expiresIn) };
|
|
101
|
+
}
|
|
102
|
+
expiry(seconds) {
|
|
103
|
+
return new Date(this.clock.now().getTime() + seconds * 1_000);
|
|
104
|
+
}
|
|
105
|
+
async missing(key) {
|
|
106
|
+
if ((await this.stat(key)) === undefined) {
|
|
107
|
+
throw FileError.notFound(key);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function absentAsUndefined(error) {
|
|
112
|
+
if (isMissing(error)) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
function metadataOf(key, head) {
|
|
118
|
+
return {
|
|
119
|
+
key,
|
|
120
|
+
size: head.ContentLength ?? 0,
|
|
121
|
+
mediaType: mediaTypeOf(head.ContentType ?? 'application/octet-stream'),
|
|
122
|
+
lastModified: head.LastModified ?? new Date(0),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=s3-file-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-file-storage.js","sourceRoot":"","sources":["../src/s3-file-storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** What reaches a bucket: the same five values for every provider of the S3 protocol. */
|
|
2
|
+
export interface S3Settings {
|
|
3
|
+
/** Left out for AWS. MinIO `http://localhost:9000`, OVHcloud `https://s3.gra.io.cloud.ovh.net`, R2 `https://<account>.r2.cloudflarestorage.com`. */
|
|
4
|
+
readonly endpoint?: string;
|
|
5
|
+
/** `auto` for R2; the region of the bucket elsewhere. */
|
|
6
|
+
readonly region: string;
|
|
7
|
+
readonly accessKeyId: string;
|
|
8
|
+
readonly secretAccessKey: string;
|
|
9
|
+
/** `bucket` in the path instead of the host name: MinIO and most self-hosted servers need it. Default: true with an endpoint. */
|
|
10
|
+
readonly forcePathStyle?: boolean;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=s3-settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-settings.d.ts","sourceRoot":"","sources":["../src/s3-settings.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,MAAM,WAAW,UAAU;IACzB,oJAAoJ;IACpJ,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,iIAAiI;IACjI,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;CACnC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-settings.js","sourceRoot":"","sources":["../src/s3-settings.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waits for the stream that feeds an upload and for the upload, and rejects with the failure of
|
|
3
|
+
* the stream first: when the content was refused on its way, the upload only says it was cut.
|
|
4
|
+
*/
|
|
5
|
+
export declare function settledFirst(feeding: Promise<void>, uploading: Promise<unknown>): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=settled-first.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settled-first.d.ts","sourceRoot":"","sources":["../src/settled-first.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAUrG"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waits for the stream that feeds an upload and for the upload, and rejects with the failure of
|
|
3
|
+
* the stream first: when the content was refused on its way, the upload only says it was cut.
|
|
4
|
+
*/
|
|
5
|
+
export async function settledFirst(feeding, uploading) {
|
|
6
|
+
const [fed, uploaded] = await Promise.allSettled([feeding, uploading]);
|
|
7
|
+
if (fed.status === 'rejected') {
|
|
8
|
+
throw fed.reason;
|
|
9
|
+
}
|
|
10
|
+
if (uploaded.status === 'rejected') {
|
|
11
|
+
throw uploaded.reason;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=settled-first.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settled-first.js","sourceRoot":"","sources":["../src/settled-first.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAsB,EAAE,SAA2B;IACpF,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;IAEvE,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QAC9B,MAAM,GAAG,CAAC,MAAM,CAAC;IACnB,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACnC,MAAM,QAAQ,CAAC,MAAM,CAAC;IACxB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,KAAK,0BAA0B,EAC/B,KAAK,gBAAgB,EACrB,KAAK,aAAa,GACnB,MAAM,+BAA+B,CAAC"}
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,eAAe,GAIhB,MAAM,+BAA+B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ddd-ts-kit/s3",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Adapter of the kernel FileStorage port on the S3 protocol (AWS S3, MinIO, OVHcloud, Scaleway, Cloudflare R2): streamed uploads, signed links, copy, move, listing.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"repository": {
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/gbourgeat/ddd-ts-kit.git",
|
|
9
|
+
"directory": "packages/s3"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/gbourgeat/ddd-ts-kit/tree/main/packages/s3#readme",
|
|
12
|
+
"bugs": "https://github.com/gbourgeat/ddd-ts-kit/issues",
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public",
|
|
15
|
+
"provenance": true
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./testing": {
|
|
25
|
+
"types": "./dist/testing.d.ts",
|
|
26
|
+
"default": "./dist/testing.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@ddd-ts-kit/kernel": "0.9.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@aws-sdk/client-s3": "^3.1150.0",
|
|
37
|
+
"@aws-sdk/lib-storage": "^3.1150.0",
|
|
38
|
+
"@aws-sdk/s3-request-presigner": "^3.1150.0",
|
|
39
|
+
"@ddd-ts-kit/testing": "0.9.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"@aws-sdk/client-s3": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"@aws-sdk/lib-storage": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"@aws-sdk/s3-request-presigner": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"@ddd-ts-kit/testing": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@aws-sdk/client-s3": "3.1150.0",
|
|
57
|
+
"@aws-sdk/lib-storage": "3.1150.0",
|
|
58
|
+
"@aws-sdk/s3-request-presigner": "3.1150.0",
|
|
59
|
+
"@ddd-ts-kit/testing": "0.9.0"
|
|
60
|
+
}
|
|
7
61
|
}
|