@cibule/storage-s3 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +242 -0
- package/dist/index.cjs +318 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +300 -0
- package/dist/index.js.map +7 -0
- package/dist/lib/is-not-found-error.d.ts +2 -0
- package/dist/lib/is-not-found-error.d.ts.map +1 -0
- package/dist/lib/s3-download-range.d.ts +4 -0
- package/dist/lib/s3-download-range.d.ts.map +1 -0
- package/dist/lib/s3-file-storage-config.d.ts +8 -0
- package/dist/lib/s3-file-storage-config.d.ts.map +1 -0
- package/dist/lib/s3-file-storage.d.ts +26 -0
- package/dist/lib/s3-file-storage.d.ts.map +1 -0
- package/dist/lib/s3-multipart.d.ts +7 -0
- package/dist/lib/s3-multipart.d.ts.map +1 -0
- package/dist/lib/s3-presigned.d.ts +7 -0
- package/dist/lib/s3-presigned.d.ts.map +1 -0
- package/dist/lib/s3-response-to-head-result.d.ts +4 -0
- package/dist/lib/s3-response-to-head-result.d.ts.map +1 -0
- package/dist/lib/s3-response-to-storage-object.d.ts +4 -0
- package/dist/lib/s3-response-to-storage-object.d.ts.map +1 -0
- package/dist/lib/s3-test-helpers.d.ts +14 -0
- package/dist/lib/s3-test-helpers.d.ts.map +1 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# @cibule/storage-s3
|
|
2
|
+
|
|
3
|
+
AWS S3 storage driver for `@cibule/storage`. Implements the full `FileStorage` contract using `@aws-sdk/client-s3`, with presigned URL and multipart upload support. Works in Node.js, Cloudflare Workers, and any runtime with AWS SDK v3.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @cibule/storage-s3 @cibule/storage
|
|
9
|
+
# or
|
|
10
|
+
bun add @cibule/storage-s3 @cibule/storage
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @cibule/storage-s3 @cibule/storage
|
|
13
|
+
# or
|
|
14
|
+
yarn add @cibule/storage-s3 @cibule/storage
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
> **Peer dependency:** `@cibule/storage` provides the abstract `FileStorage` class and shared types.
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
23
|
+
import { S3FileStorage } from '@cibule/storage-s3';
|
|
24
|
+
|
|
25
|
+
const client = new S3Client({ region: 'eu-central-1' });
|
|
26
|
+
const storage = new S3FileStorage({
|
|
27
|
+
client,
|
|
28
|
+
bucket: 'my-bucket',
|
|
29
|
+
publicUrlBase: 'https://cdn.example.com',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await storage.upload('photos/cat.jpg', new Uint8Array([1, 2, 3]), {
|
|
33
|
+
contentType: 'image/jpeg',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const file = await storage.download('photos/cat.jpg');
|
|
37
|
+
console.log(file.contentType); // 'image/jpeg'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API Reference
|
|
41
|
+
|
|
42
|
+
### Constructor
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
new S3FileStorage(config: S3FileStorageConfig)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Option | Type | Required | Description |
|
|
49
|
+
| ------------------------- | ---------- | -------- | ------------------------------------------------------------ |
|
|
50
|
+
| `client` | `S3Client` | Yes | AWS SDK v3 S3 client instance |
|
|
51
|
+
| `bucket` | `string` | Yes | S3 bucket name |
|
|
52
|
+
| `publicUrlBase` | `string` | No | Base URL for public access (trailing slashes are normalized) |
|
|
53
|
+
| `defaultPresignExpiresIn` | `number` | No | Default presigned URL expiry in seconds (default: `3600`) |
|
|
54
|
+
|
|
55
|
+
### Methods
|
|
56
|
+
|
|
57
|
+
All methods implement the `FileStorage` contract from `@cibule/storage`.
|
|
58
|
+
|
|
59
|
+
| Method | Signature | Description |
|
|
60
|
+
| -------------------------- | ------------------------------------------------------ | ------------------------------------------------ |
|
|
61
|
+
| `upload` | `(key, data, options?) → Promise<void>` | Store a file with optional metadata |
|
|
62
|
+
| `download` | `(key) → Promise<FileStorageObject>` | Retrieve a file with metadata and body stream |
|
|
63
|
+
| `head` | `(key) → Promise<HeadResult \| null>` | Get file metadata without body (null if missing) |
|
|
64
|
+
| `delete` | `(key) → Promise<void>` | Remove a file |
|
|
65
|
+
| `listKeys` | `(prefix?) → Promise<string[]>` | List all keys, with optional prefix filter |
|
|
66
|
+
| `getPublicUrl` | `(key) → string` | Build a public URL (requires `publicUrlBase`) |
|
|
67
|
+
| `getProtectedUrl` | `(key) → string` | Always throws (use presigned URLs instead) |
|
|
68
|
+
| `downloadRange` | `(key, offset, length) → Promise<ArrayBuffer>` | Read a fixed-length byte range |
|
|
69
|
+
| `downloadPartial` | `(key, start, end) → Promise<ReadableStream>` | Stream a byte range (exclusive end) |
|
|
70
|
+
| `createPresignedUploadUrl` | `(key, options?) → Promise<PresignedUploadUrl>` | Generate a presigned PUT URL for direct upload |
|
|
71
|
+
| `initiateMultipartUpload` | `(key) → Promise<MultipartUploadInit>` | Start a multipart upload |
|
|
72
|
+
| `createPresignedPartUrls` | `(key, uploadId, parts) → Promise<PresignedPartUrl[]>` | Generate presigned PUT URLs for each part |
|
|
73
|
+
| `uploadPart` | `(key, uploadId, partNumber, data) → Promise<string>` | Upload one part, returns ETag |
|
|
74
|
+
| `completeMultipartUpload` | `(key, uploadId, parts) → Promise<void>` | Finalize a multipart upload |
|
|
75
|
+
| `abortMultipartUpload` | `(key, uploadId) → Promise<void>` | Cancel and clean up a multipart upload |
|
|
76
|
+
|
|
77
|
+
### Upload Options
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
await storage.upload('file.pdf', data, {
|
|
81
|
+
contentType: 'application/pdf',
|
|
82
|
+
cacheControl: 'public, max-age=31536000',
|
|
83
|
+
contentDisposition: 'attachment; filename="file.pdf"',
|
|
84
|
+
metadata: { userId: '123', version: '2' },
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Advanced Patterns
|
|
89
|
+
|
|
90
|
+
### Presigned Upload URLs
|
|
91
|
+
|
|
92
|
+
Generate presigned PUT URLs for browser-direct uploads without proxying through your server:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
const { url, headers } = await storage.createPresignedUploadUrl('uploads/photo.jpg', {
|
|
96
|
+
expiresInSeconds: 600,
|
|
97
|
+
contentType: 'image/jpeg',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Return url + headers to the client for a direct PUT request
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Presigned Download URLs
|
|
104
|
+
|
|
105
|
+
Use the standalone `createS3PresignedGetUrl` function for time-limited download links:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { createS3PresignedGetUrl, DEFAULT_PRESIGN_EXPIRES_IN } from '@cibule/storage-s3';
|
|
109
|
+
|
|
110
|
+
// Uses DEFAULT_PRESIGN_EXPIRES_IN (3600s) when omitted
|
|
111
|
+
const url = await createS3PresignedGetUrl(client, 'my-bucket', 'private/report.pdf');
|
|
112
|
+
|
|
113
|
+
// Or specify a custom expiry
|
|
114
|
+
const shortUrl = await createS3PresignedGetUrl(client, 'my-bucket', 'private/report.pdf', 900);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Multipart Uploads
|
|
118
|
+
|
|
119
|
+
For large files, use S3's multipart upload API:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// 1. Initiate
|
|
123
|
+
const { uploadId, key } = await storage.initiateMultipartUpload('large-file.zip');
|
|
124
|
+
|
|
125
|
+
// 2. Upload parts (minimum 5 MB per part except the last)
|
|
126
|
+
const etag1 = await storage.uploadPart(key, uploadId, 1, chunk1);
|
|
127
|
+
const etag2 = await storage.uploadPart(key, uploadId, 2, chunk2);
|
|
128
|
+
|
|
129
|
+
// 3. Complete
|
|
130
|
+
await storage.completeMultipartUpload(key, uploadId, [
|
|
131
|
+
{ partNumber: 1, etag: etag1 },
|
|
132
|
+
{ partNumber: 2, etag: etag2 },
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
// Or abort if something goes wrong
|
|
136
|
+
// await storage.abortMultipartUpload(key, uploadId);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Browser-Direct Multipart Uploads
|
|
140
|
+
|
|
141
|
+
Generate presigned part URLs so the browser uploads directly to S3:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const { uploadId, key } = await storage.initiateMultipartUpload('large-file.zip');
|
|
145
|
+
|
|
146
|
+
const partUrls = await storage.createPresignedPartUrls(key, uploadId, [1, 2, 3]);
|
|
147
|
+
// Returns [{ partNumber: 1, url: '...' }, { partNumber: 2, url: '...' }, ...]
|
|
148
|
+
|
|
149
|
+
// Client PUTs each chunk to the corresponding URL, collects ETags
|
|
150
|
+
// Then server calls completeMultipartUpload with the ETags
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Byte Range Reads
|
|
154
|
+
|
|
155
|
+
Read specific byte ranges for resumable downloads or media streaming:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// Fixed-length range (returns ArrayBuffer)
|
|
159
|
+
const chunk = await storage.downloadRange('video.mp4', 0, 1024);
|
|
160
|
+
|
|
161
|
+
// Start-end range, exclusive end (returns ReadableStream of bytes [1024, 2048))
|
|
162
|
+
const stream = await storage.downloadPartial('video.mp4', 1024, 2048);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### DI Integration
|
|
166
|
+
|
|
167
|
+
Use the accessor pattern with `@cibule/di` for per-request storage instances:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { Injector } from '@cibule/di';
|
|
171
|
+
import { FILE_STORAGE_ACCESSOR } from '@cibule/storage';
|
|
172
|
+
import { S3FileStorage } from '@cibule/storage-s3';
|
|
173
|
+
|
|
174
|
+
const storage = new S3FileStorage({ client, bucket: 'my-bucket' });
|
|
175
|
+
|
|
176
|
+
const injector = Injector.create({
|
|
177
|
+
providers: [{ provide: FILE_STORAGE_ACCESSOR, useValue: () => storage }],
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const accessor = injector.get(FILE_STORAGE_ACCESSOR);
|
|
181
|
+
const fs = accessor(); // S3FileStorage instance
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Paginated Listing
|
|
185
|
+
|
|
186
|
+
`listKeys()` handles S3's 1000-object pagination limit automatically, fetching all pages via `ContinuationToken`:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
const allKeys = await storage.listKeys('uploads/');
|
|
190
|
+
// Returns all matching keys, regardless of count
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Key Behaviors
|
|
194
|
+
|
|
195
|
+
| Behavior | S3FileStorage |
|
|
196
|
+
| ------------------- | ---------------------------------------------------------------------------------------- |
|
|
197
|
+
| **Platform** | Node.js, Cloudflare Workers, any runtime with AWS SDK v3 |
|
|
198
|
+
| **Persistence** | Durable (AWS S3 object storage) |
|
|
199
|
+
| **Content type** | From `options.contentType` (mapped to S3 `ContentType`) |
|
|
200
|
+
| **Public URLs** | Requires `publicUrlBase` in config; throws without it |
|
|
201
|
+
| **Protected URLs** | Always throws (use `createS3PresignedGetUrl()` for download links) |
|
|
202
|
+
| **Presigned URLs** | Full support: upload URLs via `createPresignedUploadUrl`, part URLs, download via helper |
|
|
203
|
+
| **Multipart** | S3 native multipart API with presigned part URL support for browser-direct uploads |
|
|
204
|
+
| **List pagination** | Automatic `ContinuationToken`-based pagination (fetches all pages) |
|
|
205
|
+
| **Range reads** | S3 `Range` header (`bytes=offset-end`) |
|
|
206
|
+
| **Custom metadata** | Stored via S3's `Metadata` field |
|
|
207
|
+
| **Not found** | `download`, `downloadRange`, `downloadPartial` throw; `head` returns `null` |
|
|
208
|
+
|
|
209
|
+
## Type Exports
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import {
|
|
213
|
+
S3FileStorage,
|
|
214
|
+
createS3PresignedGetUrl,
|
|
215
|
+
DEFAULT_PRESIGN_EXPIRES_IN,
|
|
216
|
+
} from '@cibule/storage-s3';
|
|
217
|
+
import type { S3FileStorageConfig } from '@cibule/storage-s3';
|
|
218
|
+
|
|
219
|
+
// Shared types from @cibule/storage
|
|
220
|
+
import type {
|
|
221
|
+
CompletedPart,
|
|
222
|
+
FileStorageAccessor,
|
|
223
|
+
FileStorageObject,
|
|
224
|
+
FileUploadOptions,
|
|
225
|
+
HeadResult,
|
|
226
|
+
MultipartUploadInit,
|
|
227
|
+
PresignedPartUrl,
|
|
228
|
+
PresignedUploadUrl,
|
|
229
|
+
PresignOptions,
|
|
230
|
+
} from '@cibule/storage';
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Related Packages
|
|
234
|
+
|
|
235
|
+
| Package | Description |
|
|
236
|
+
| ------------------------------------- | ---------------------------------------------- |
|
|
237
|
+
| [`@cibule/storage`](../storage) | Core abstraction + in-memory and local drivers |
|
|
238
|
+
| [`@cibule/storage-r2`](../storage-r2) | Cloudflare R2 storage driver |
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_PRESIGN_EXPIRES_IN: () => DEFAULT_PRESIGN_EXPIRES_IN,
|
|
24
|
+
S3FileStorage: () => S3FileStorage,
|
|
25
|
+
createS3PresignedGetUrl: () => createS3PresignedGetUrl
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
|
|
29
|
+
// src/lib/s3-file-storage.ts
|
|
30
|
+
var import_client_s34 = require("@aws-sdk/client-s3");
|
|
31
|
+
|
|
32
|
+
// ../storage/src/lib/file-storage.ts
|
|
33
|
+
var FileStorage = class {
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/lib/is-not-found-error.ts
|
|
37
|
+
function hasNotFoundMetadata(error) {
|
|
38
|
+
if (!("$metadata" in error)) return false;
|
|
39
|
+
const metadata = error.$metadata;
|
|
40
|
+
if (typeof metadata !== "object" || metadata === null) return false;
|
|
41
|
+
return metadata.httpStatusCode === 404;
|
|
42
|
+
}
|
|
43
|
+
function isNotFoundError(error) {
|
|
44
|
+
if (!(error instanceof Error)) return false;
|
|
45
|
+
const { name } = error;
|
|
46
|
+
if (name === "NotFound" || name === "NoSuchKey" || name === "404") return true;
|
|
47
|
+
return hasNotFoundMetadata(error);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/lib/s3-download-range.ts
|
|
51
|
+
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
52
|
+
async function downloadS3Range(client, bucket, key, offset, length) {
|
|
53
|
+
if (offset < 0) {
|
|
54
|
+
throw new Error("offset must be non-negative");
|
|
55
|
+
}
|
|
56
|
+
if (length <= 0) {
|
|
57
|
+
throw new Error("length must be positive");
|
|
58
|
+
}
|
|
59
|
+
const response = await client.send(
|
|
60
|
+
new import_client_s3.GetObjectCommand({
|
|
61
|
+
Bucket: bucket,
|
|
62
|
+
Key: key,
|
|
63
|
+
Range: `bytes=${String(offset)}-${String(offset + length - 1)}`
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
if (!response.Body) {
|
|
67
|
+
throw new Error(`File not found: ${key}`);
|
|
68
|
+
}
|
|
69
|
+
const bytes = await response.Body.transformToByteArray();
|
|
70
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
71
|
+
}
|
|
72
|
+
async function downloadS3Partial(client, bucket, key, start, end) {
|
|
73
|
+
if (start >= end) {
|
|
74
|
+
throw new Error("start must be less than end");
|
|
75
|
+
}
|
|
76
|
+
const response = await client.send(
|
|
77
|
+
new import_client_s3.GetObjectCommand({
|
|
78
|
+
Bucket: bucket,
|
|
79
|
+
Key: key,
|
|
80
|
+
Range: `bytes=${String(start)}-${String(end - 1)}`
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
if (!response.Body) {
|
|
84
|
+
throw new Error(`File not found: ${key}`);
|
|
85
|
+
}
|
|
86
|
+
return response.Body.transformToWebStream();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/lib/s3-multipart.ts
|
|
90
|
+
var import_client_s32 = require("@aws-sdk/client-s3");
|
|
91
|
+
async function initiateS3MultipartUpload(client, bucket, key) {
|
|
92
|
+
const response = await client.send(
|
|
93
|
+
new import_client_s32.CreateMultipartUploadCommand({ Bucket: bucket, Key: key })
|
|
94
|
+
);
|
|
95
|
+
if (!response.UploadId) {
|
|
96
|
+
throw new Error(`Failed to initiate multipart upload for key: ${key}`);
|
|
97
|
+
}
|
|
98
|
+
return { uploadId: response.UploadId, key };
|
|
99
|
+
}
|
|
100
|
+
async function uploadS3Part(client, bucket, key, uploadId, partNumber, data) {
|
|
101
|
+
const response = await client.send(
|
|
102
|
+
new import_client_s32.UploadPartCommand({
|
|
103
|
+
Bucket: bucket,
|
|
104
|
+
Key: key,
|
|
105
|
+
UploadId: uploadId,
|
|
106
|
+
PartNumber: partNumber,
|
|
107
|
+
Body: data
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
if (!response.ETag) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Missing ETag in upload part response for key: ${key}, part: ${String(partNumber)}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return response.ETag;
|
|
116
|
+
}
|
|
117
|
+
async function completeS3MultipartUpload(client, bucket, key, uploadId, parts) {
|
|
118
|
+
await client.send(
|
|
119
|
+
new import_client_s32.CompleteMultipartUploadCommand({
|
|
120
|
+
Bucket: bucket,
|
|
121
|
+
Key: key,
|
|
122
|
+
UploadId: uploadId,
|
|
123
|
+
MultipartUpload: {
|
|
124
|
+
Parts: parts.map((p) => ({ PartNumber: p.partNumber, ETag: p.etag }))
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
async function abortS3MultipartUpload(client, bucket, key, uploadId) {
|
|
130
|
+
await client.send(
|
|
131
|
+
new import_client_s32.AbortMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId: uploadId })
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/lib/s3-presigned.ts
|
|
136
|
+
var import_client_s33 = require("@aws-sdk/client-s3");
|
|
137
|
+
var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
|
|
138
|
+
var DEFAULT_PRESIGN_EXPIRES_IN = 3600;
|
|
139
|
+
async function createS3PresignedGetUrl(client, bucket, key, expiresIn = DEFAULT_PRESIGN_EXPIRES_IN) {
|
|
140
|
+
return (0, import_s3_request_presigner.getSignedUrl)(client, new import_client_s33.GetObjectCommand({ Bucket: bucket, Key: key }), { expiresIn });
|
|
141
|
+
}
|
|
142
|
+
async function createS3PresignedPutUrl(client, bucket, key, options, defaultExpiresIn) {
|
|
143
|
+
const expiresIn = options?.expiresInSeconds ?? defaultExpiresIn ?? DEFAULT_PRESIGN_EXPIRES_IN;
|
|
144
|
+
const command = new import_client_s33.PutObjectCommand({
|
|
145
|
+
Bucket: bucket,
|
|
146
|
+
Key: key,
|
|
147
|
+
ContentType: options?.contentType
|
|
148
|
+
});
|
|
149
|
+
const url = await (0, import_s3_request_presigner.getSignedUrl)(client, command, { expiresIn });
|
|
150
|
+
const headers = options?.contentType ? { "Content-Type": options.contentType } : void 0;
|
|
151
|
+
return { url, headers };
|
|
152
|
+
}
|
|
153
|
+
async function createS3PresignedPartUrls(client, bucket, key, uploadId, parts, expiresIn = DEFAULT_PRESIGN_EXPIRES_IN) {
|
|
154
|
+
return Promise.all(
|
|
155
|
+
parts.map(async (partNumber) => {
|
|
156
|
+
const command = new import_client_s33.UploadPartCommand({
|
|
157
|
+
Bucket: bucket,
|
|
158
|
+
Key: key,
|
|
159
|
+
UploadId: uploadId,
|
|
160
|
+
PartNumber: partNumber
|
|
161
|
+
});
|
|
162
|
+
const url = await (0, import_s3_request_presigner.getSignedUrl)(client, command, { expiresIn });
|
|
163
|
+
return { partNumber, url };
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/lib/s3-response-to-head-result.ts
|
|
169
|
+
function s3ResponseToHeadResult(response) {
|
|
170
|
+
return {
|
|
171
|
+
size: response.ContentLength ?? 0,
|
|
172
|
+
contentType: response.ContentType,
|
|
173
|
+
etag: response.ETag,
|
|
174
|
+
lastModified: response.LastModified
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/lib/s3-response-to-storage-object.ts
|
|
179
|
+
function s3ResponseToStorageObject(key, response) {
|
|
180
|
+
if (!response.Body) {
|
|
181
|
+
throw new Error(`File not found: ${key}`);
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
key,
|
|
185
|
+
body: response.Body.transformToWebStream(),
|
|
186
|
+
size: response.ContentLength ?? 0,
|
|
187
|
+
contentType: response.ContentType,
|
|
188
|
+
etag: response.ETag,
|
|
189
|
+
lastModified: response.LastModified
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/lib/s3-file-storage.ts
|
|
194
|
+
var S3FileStorage = class extends FileStorage {
|
|
195
|
+
client;
|
|
196
|
+
bucket;
|
|
197
|
+
publicUrlBase;
|
|
198
|
+
defaultPresignExpiresIn;
|
|
199
|
+
constructor(config) {
|
|
200
|
+
super();
|
|
201
|
+
this.client = config.client;
|
|
202
|
+
this.bucket = config.bucket;
|
|
203
|
+
this.publicUrlBase = config.publicUrlBase ? `${config.publicUrlBase.replace(/\/+$/, "")}/` : void 0;
|
|
204
|
+
this.defaultPresignExpiresIn = config.defaultPresignExpiresIn ?? 3600;
|
|
205
|
+
}
|
|
206
|
+
async upload(key, data, options) {
|
|
207
|
+
await this.client.send(
|
|
208
|
+
new import_client_s34.PutObjectCommand({
|
|
209
|
+
Bucket: this.bucket,
|
|
210
|
+
Key: key,
|
|
211
|
+
Body: data,
|
|
212
|
+
ContentType: options?.contentType,
|
|
213
|
+
CacheControl: options?.cacheControl,
|
|
214
|
+
ContentDisposition: options?.contentDisposition,
|
|
215
|
+
Metadata: options?.metadata
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
async download(key) {
|
|
220
|
+
const response = await this.client.send(
|
|
221
|
+
new import_client_s34.GetObjectCommand({ Bucket: this.bucket, Key: key })
|
|
222
|
+
);
|
|
223
|
+
return s3ResponseToStorageObject(key, response);
|
|
224
|
+
}
|
|
225
|
+
async head(key) {
|
|
226
|
+
try {
|
|
227
|
+
const response = await this.client.send(
|
|
228
|
+
new import_client_s34.HeadObjectCommand({ Bucket: this.bucket, Key: key })
|
|
229
|
+
);
|
|
230
|
+
return s3ResponseToHeadResult(response);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (isNotFoundError(error)) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async delete(key) {
|
|
239
|
+
await this.client.send(new import_client_s34.DeleteObjectCommand({ Bucket: this.bucket, Key: key }));
|
|
240
|
+
}
|
|
241
|
+
async listKeys(prefix) {
|
|
242
|
+
const keys = [];
|
|
243
|
+
let continuationToken;
|
|
244
|
+
do {
|
|
245
|
+
const response = await this.client.send(
|
|
246
|
+
new import_client_s34.ListObjectsV2Command({
|
|
247
|
+
Bucket: this.bucket,
|
|
248
|
+
Prefix: prefix,
|
|
249
|
+
ContinuationToken: continuationToken
|
|
250
|
+
})
|
|
251
|
+
);
|
|
252
|
+
if (response.Contents) {
|
|
253
|
+
for (const obj of response.Contents) {
|
|
254
|
+
if (obj.Key) {
|
|
255
|
+
keys.push(obj.Key);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
continuationToken = response.IsTruncated ? response.NextContinuationToken : void 0;
|
|
260
|
+
} while (continuationToken);
|
|
261
|
+
return keys;
|
|
262
|
+
}
|
|
263
|
+
getPublicUrl(key) {
|
|
264
|
+
if (!this.publicUrlBase) {
|
|
265
|
+
throw new Error("publicUrlBase is required for getPublicUrl");
|
|
266
|
+
}
|
|
267
|
+
return `${this.publicUrlBase}${key}`;
|
|
268
|
+
}
|
|
269
|
+
getProtectedUrl(_key) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
"Use createPresignedUploadUrl() or createS3PresignedGetUrl() for presigned URLs"
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
async downloadRange(key, offset, length) {
|
|
275
|
+
return downloadS3Range(this.client, this.bucket, key, offset, length);
|
|
276
|
+
}
|
|
277
|
+
async downloadPartial(key, start, end) {
|
|
278
|
+
return downloadS3Partial(this.client, this.bucket, key, start, end);
|
|
279
|
+
}
|
|
280
|
+
async createPresignedUploadUrl(key, options) {
|
|
281
|
+
return createS3PresignedPutUrl(
|
|
282
|
+
this.client,
|
|
283
|
+
this.bucket,
|
|
284
|
+
key,
|
|
285
|
+
options,
|
|
286
|
+
this.defaultPresignExpiresIn
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
async initiateMultipartUpload(key) {
|
|
290
|
+
return initiateS3MultipartUpload(this.client, this.bucket, key);
|
|
291
|
+
}
|
|
292
|
+
async createPresignedPartUrls(key, uploadId, parts) {
|
|
293
|
+
return createS3PresignedPartUrls(
|
|
294
|
+
this.client,
|
|
295
|
+
this.bucket,
|
|
296
|
+
key,
|
|
297
|
+
uploadId,
|
|
298
|
+
parts,
|
|
299
|
+
this.defaultPresignExpiresIn
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
async uploadPart(key, uploadId, partNumber, data) {
|
|
303
|
+
return uploadS3Part(this.client, this.bucket, key, uploadId, partNumber, data);
|
|
304
|
+
}
|
|
305
|
+
async completeMultipartUpload(key, uploadId, parts) {
|
|
306
|
+
return completeS3MultipartUpload(this.client, this.bucket, key, uploadId, parts);
|
|
307
|
+
}
|
|
308
|
+
async abortMultipartUpload(key, uploadId) {
|
|
309
|
+
return abortS3MultipartUpload(this.client, this.bucket, key, uploadId);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
313
|
+
0 && (module.exports = {
|
|
314
|
+
DEFAULT_PRESIGN_EXPIRES_IN,
|
|
315
|
+
S3FileStorage,
|
|
316
|
+
createS3PresignedGetUrl
|
|
317
|
+
});
|
|
318
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/lib/s3-file-storage.ts", "../../storage/src/lib/file-storage.ts", "../src/lib/is-not-found-error.ts", "../src/lib/s3-download-range.ts", "../src/lib/s3-multipart.ts", "../src/lib/s3-presigned.ts", "../src/lib/s3-response-to-head-result.ts", "../src/lib/s3-response-to-storage-object.ts"],
|
|
4
|
+
"sourcesContent": ["export { S3FileStorage } from './lib/s3-file-storage';\nexport type { S3FileStorageConfig } from './lib/s3-file-storage-config';\nexport { createS3PresignedGetUrl, DEFAULT_PRESIGN_EXPIRES_IN } from './lib/s3-presigned';\n", "import type { S3Client } from '@aws-sdk/client-s3';\nimport {\n DeleteObjectCommand,\n GetObjectCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n} from '@aws-sdk/client-s3';\nimport type {\n CompletedPart,\n FileStorageObject,\n FileUploadOptions,\n HeadResult,\n MultipartUploadInit,\n PresignedPartUrl,\n PresignedUploadUrl,\n PresignOptions,\n} from '@cibule/storage';\nimport { FileStorage } from '@cibule/storage';\n\nimport { isNotFoundError } from './is-not-found-error';\nimport { downloadS3Partial, downloadS3Range } from './s3-download-range';\nimport type { S3FileStorageConfig } from './s3-file-storage-config';\nimport {\n abortS3MultipartUpload,\n completeS3MultipartUpload,\n initiateS3MultipartUpload,\n uploadS3Part,\n} from './s3-multipart';\nimport { createS3PresignedPartUrls, createS3PresignedPutUrl } from './s3-presigned';\nimport { s3ResponseToHeadResult } from './s3-response-to-head-result';\nimport { s3ResponseToStorageObject } from './s3-response-to-storage-object';\n\nexport class S3FileStorage extends FileStorage {\n private readonly client: S3Client;\n private readonly bucket: string;\n private readonly publicUrlBase: string | undefined;\n private readonly defaultPresignExpiresIn: number;\n\n public constructor(config: S3FileStorageConfig) {\n super();\n this.client = config.client;\n this.bucket = config.bucket;\n this.publicUrlBase = config.publicUrlBase\n ? `${config.publicUrlBase.replace(/\\/+$/, '')}/`\n : undefined;\n this.defaultPresignExpiresIn = config.defaultPresignExpiresIn ?? 3600;\n }\n\n public async upload(\n key: string,\n data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array,\n options?: FileUploadOptions,\n ): Promise<void> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: data as PutObjectCommand['input']['Body'],\n ContentType: options?.contentType,\n CacheControl: options?.cacheControl,\n ContentDisposition: options?.contentDisposition,\n Metadata: options?.metadata,\n }),\n );\n }\n\n public async download(key: string): Promise<FileStorageObject> {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: key }),\n );\n return s3ResponseToStorageObject(key, response);\n }\n\n public async head(key: string): Promise<HeadResult | null> {\n try {\n const response = await this.client.send(\n new HeadObjectCommand({ Bucket: this.bucket, Key: key }),\n );\n return s3ResponseToHeadResult(response);\n } catch (error: unknown) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n public async delete(key: string): Promise<void> {\n await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: key }));\n }\n\n public async listKeys(prefix?: string): Promise<string[]> {\n const keys: string[] = [];\n let continuationToken: string | undefined;\n\n do {\n const response = await this.client.send(\n new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: prefix,\n ContinuationToken: continuationToken,\n }),\n );\n\n if (response.Contents) {\n for (const obj of response.Contents) {\n if (obj.Key) {\n keys.push(obj.Key);\n }\n }\n }\n\n continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined;\n } while (continuationToken);\n\n return keys;\n }\n\n public getPublicUrl(key: string): string {\n if (!this.publicUrlBase) {\n throw new Error('publicUrlBase is required for getPublicUrl');\n }\n return `${this.publicUrlBase}${key}`;\n }\n\n public getProtectedUrl(_key: string): string {\n throw new Error(\n 'Use createPresignedUploadUrl() or createS3PresignedGetUrl() for presigned URLs',\n );\n }\n\n public async downloadRange(key: string, offset: number, length: number): Promise<ArrayBuffer> {\n return downloadS3Range(this.client, this.bucket, key, offset, length);\n }\n\n public async downloadPartial(\n key: string,\n start: number,\n end: number,\n ): Promise<ReadableStream<Uint8Array>> {\n return downloadS3Partial(this.client, this.bucket, key, start, end);\n }\n\n public async createPresignedUploadUrl(\n key: string,\n options?: PresignOptions,\n ): Promise<PresignedUploadUrl> {\n return createS3PresignedPutUrl(\n this.client,\n this.bucket,\n key,\n options,\n this.defaultPresignExpiresIn,\n );\n }\n\n public async initiateMultipartUpload(key: string): Promise<MultipartUploadInit> {\n return initiateS3MultipartUpload(this.client, this.bucket, key);\n }\n\n public async createPresignedPartUrls(\n key: string,\n uploadId: string,\n parts: number[],\n ): Promise<PresignedPartUrl[]> {\n return createS3PresignedPartUrls(\n this.client,\n this.bucket,\n key,\n uploadId,\n parts,\n this.defaultPresignExpiresIn,\n );\n }\n\n public async uploadPart(\n key: string,\n uploadId: string,\n partNumber: number,\n data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array,\n ): Promise<string> {\n return uploadS3Part(this.client, this.bucket, key, uploadId, partNumber, data);\n }\n\n public async completeMultipartUpload(\n key: string,\n uploadId: string,\n parts: CompletedPart[],\n ): Promise<void> {\n return completeS3MultipartUpload(this.client, this.bucket, key, uploadId, parts);\n }\n\n public async abortMultipartUpload(key: string, uploadId: string): Promise<void> {\n return abortS3MultipartUpload(this.client, this.bucket, key, uploadId);\n }\n}\n", "import type {\n CompletedPart,\n FileStorageObject,\n FileUploadOptions,\n HeadResult,\n MultipartUploadInit,\n PresignedPartUrl,\n PresignedUploadUrl,\n PresignOptions,\n} from './file-storage-types';\n\nexport abstract class FileStorage {\n abstract upload(\n key: string,\n data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array,\n options?: FileUploadOptions,\n ): Promise<void>;\n\n abstract download(key: string): Promise<FileStorageObject>;\n\n abstract head(key: string): Promise<HeadResult | null>;\n\n abstract delete(key: string): Promise<void>;\n\n abstract listKeys(prefix?: string): Promise<string[]>;\n\n abstract getPublicUrl(key: string): string;\n\n abstract getProtectedUrl(key: string): string;\n\n abstract downloadRange(key: string, offset: number, length: number): Promise<ArrayBuffer>;\n\n abstract downloadPartial(\n key: string,\n start: number,\n end: number,\n ): Promise<ReadableStream<Uint8Array>>;\n\n abstract createPresignedUploadUrl(\n key: string,\n options?: PresignOptions,\n ): Promise<PresignedUploadUrl | null>;\n\n abstract initiateMultipartUpload(key: string): Promise<MultipartUploadInit>;\n\n abstract createPresignedPartUrls(\n key: string,\n uploadId: string,\n parts: number[],\n ): Promise<PresignedPartUrl[]>;\n\n abstract uploadPart(\n key: string,\n uploadId: string,\n partNumber: number,\n data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array,\n ): Promise<string>;\n\n abstract completeMultipartUpload(\n key: string,\n uploadId: string,\n parts: CompletedPart[],\n ): Promise<void>;\n\n abstract abortMultipartUpload(key: string, uploadId: string): Promise<void>;\n}\n", "interface S3ErrorMetadata {\n readonly httpStatusCode?: number;\n}\n\nfunction hasNotFoundMetadata(error: Error): boolean {\n if (!('$metadata' in error)) return false;\n\n const metadata = error.$metadata;\n if (typeof metadata !== 'object' || metadata === null) return false;\n\n return (metadata as S3ErrorMetadata).httpStatusCode === 404;\n}\n\nexport function isNotFoundError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n\n const { name } = error;\n if (name === 'NotFound' || name === 'NoSuchKey' || name === '404') return true;\n\n return hasNotFoundMetadata(error);\n}\n", "import type { S3Client } from '@aws-sdk/client-s3';\nimport { GetObjectCommand } from '@aws-sdk/client-s3';\n\nexport async function downloadS3Range(\n client: S3Client,\n bucket: string,\n key: string,\n offset: number,\n length: number,\n): Promise<ArrayBuffer> {\n if (offset < 0) {\n throw new Error('offset must be non-negative');\n }\n if (length <= 0) {\n throw new Error('length must be positive');\n }\n\n const response = await client.send(\n new GetObjectCommand({\n Bucket: bucket,\n Key: key,\n Range: `bytes=${String(offset)}-${String(offset + length - 1)}`,\n }),\n );\n\n if (!response.Body) {\n throw new Error(`File not found: ${key}`);\n }\n\n const bytes = await response.Body.transformToByteArray();\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);\n}\n\nexport async function downloadS3Partial(\n client: S3Client,\n bucket: string,\n key: string,\n start: number,\n end: number,\n): Promise<ReadableStream<Uint8Array>> {\n if (start >= end) {\n throw new Error('start must be less than end');\n }\n\n const response = await client.send(\n new GetObjectCommand({\n Bucket: bucket,\n Key: key,\n Range: `bytes=${String(start)}-${String(end - 1)}`,\n }),\n );\n\n if (!response.Body) {\n throw new Error(`File not found: ${key}`);\n }\n\n return response.Body.transformToWebStream() as ReadableStream<Uint8Array>;\n}\n", "import type { S3Client } from '@aws-sdk/client-s3';\nimport {\n AbortMultipartUploadCommand,\n CompleteMultipartUploadCommand,\n CreateMultipartUploadCommand,\n UploadPartCommand,\n} from '@aws-sdk/client-s3';\nimport type { CompletedPart, MultipartUploadInit } from '@cibule/storage';\n\nexport async function initiateS3MultipartUpload(\n client: S3Client,\n bucket: string,\n key: string,\n): Promise<MultipartUploadInit> {\n const response = await client.send(\n new CreateMultipartUploadCommand({ Bucket: bucket, Key: key }),\n );\n\n if (!response.UploadId) {\n throw new Error(`Failed to initiate multipart upload for key: ${key}`);\n }\n\n return { uploadId: response.UploadId, key };\n}\n\nexport async function uploadS3Part(\n client: S3Client,\n bucket: string,\n key: string,\n uploadId: string,\n partNumber: number,\n data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array,\n): Promise<string> {\n const response = await client.send(\n new UploadPartCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n Body: data as UploadPartCommand['input']['Body'],\n }),\n );\n\n if (!response.ETag) {\n throw new Error(\n `Missing ETag in upload part response for key: ${key}, part: ${String(partNumber)}`,\n );\n }\n\n return response.ETag;\n}\n\nexport async function completeS3MultipartUpload(\n client: S3Client,\n bucket: string,\n key: string,\n uploadId: string,\n parts: CompletedPart[],\n): Promise<void> {\n await client.send(\n new CompleteMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: {\n Parts: parts.map(p => ({ PartNumber: p.partNumber, ETag: p.etag })),\n },\n }),\n );\n}\n\nexport async function abortS3MultipartUpload(\n client: S3Client,\n bucket: string,\n key: string,\n uploadId: string,\n): Promise<void> {\n await client.send(\n new AbortMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId: uploadId }),\n );\n}\n", "import type { S3Client } from '@aws-sdk/client-s3';\nimport { GetObjectCommand, PutObjectCommand, UploadPartCommand } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport type { PresignedPartUrl, PresignedUploadUrl, PresignOptions } from '@cibule/storage';\n\nexport const DEFAULT_PRESIGN_EXPIRES_IN = 3600;\n\nexport async function createS3PresignedGetUrl(\n client: S3Client,\n bucket: string,\n key: string,\n expiresIn: number = DEFAULT_PRESIGN_EXPIRES_IN,\n): Promise<string> {\n return getSignedUrl(client, new GetObjectCommand({ Bucket: bucket, Key: key }), { expiresIn });\n}\n\nexport async function createS3PresignedPutUrl(\n client: S3Client,\n bucket: string,\n key: string,\n options?: PresignOptions,\n defaultExpiresIn?: number,\n): Promise<PresignedUploadUrl> {\n const expiresIn = options?.expiresInSeconds ?? defaultExpiresIn ?? DEFAULT_PRESIGN_EXPIRES_IN;\n const command = new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n ContentType: options?.contentType,\n });\n\n const url = await getSignedUrl(client, command, { expiresIn });\n const headers = options?.contentType ? { 'Content-Type': options.contentType } : undefined;\n\n return { url, headers };\n}\n\nexport async function createS3PresignedPartUrls(\n client: S3Client,\n bucket: string,\n key: string,\n uploadId: string,\n parts: number[],\n expiresIn: number = DEFAULT_PRESIGN_EXPIRES_IN,\n): Promise<PresignedPartUrl[]> {\n return Promise.all(\n parts.map(async partNumber => {\n const command = new UploadPartCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n });\n const url = await getSignedUrl(client, command, { expiresIn });\n return { partNumber, url };\n }),\n );\n}\n", "import type { HeadObjectCommandOutput } from '@aws-sdk/client-s3';\nimport type { HeadResult } from '@cibule/storage';\n\nexport function s3ResponseToHeadResult(response: HeadObjectCommandOutput): HeadResult {\n return {\n size: response.ContentLength ?? 0,\n contentType: response.ContentType,\n etag: response.ETag,\n lastModified: response.LastModified,\n };\n}\n", "import type { GetObjectCommandOutput } from '@aws-sdk/client-s3';\nimport type { FileStorageObject } from '@cibule/storage';\n\nexport function s3ResponseToStorageObject(\n key: string,\n response: GetObjectCommandOutput,\n): FileStorageObject {\n if (!response.Body) {\n throw new Error(`File not found: ${key}`);\n }\n\n return {\n key,\n body: response.Body.transformToWebStream() as ReadableStream<Uint8Array>,\n size: response.ContentLength ?? 0,\n contentType: response.ContentType,\n etag: response.ETag,\n lastModified: response.LastModified,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_client_s3", "import_client_s3", "import_client_s3"]
|
|
7
|
+
}
|
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,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// src/lib/s3-file-storage.ts
|
|
2
|
+
import {
|
|
3
|
+
DeleteObjectCommand,
|
|
4
|
+
GetObjectCommand as GetObjectCommand3,
|
|
5
|
+
HeadObjectCommand,
|
|
6
|
+
ListObjectsV2Command,
|
|
7
|
+
PutObjectCommand as PutObjectCommand2
|
|
8
|
+
} from "@aws-sdk/client-s3";
|
|
9
|
+
|
|
10
|
+
// ../storage/src/lib/file-storage.ts
|
|
11
|
+
var FileStorage = class {
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/lib/is-not-found-error.ts
|
|
15
|
+
function hasNotFoundMetadata(error) {
|
|
16
|
+
if (!("$metadata" in error)) return false;
|
|
17
|
+
const metadata = error.$metadata;
|
|
18
|
+
if (typeof metadata !== "object" || metadata === null) return false;
|
|
19
|
+
return metadata.httpStatusCode === 404;
|
|
20
|
+
}
|
|
21
|
+
function isNotFoundError(error) {
|
|
22
|
+
if (!(error instanceof Error)) return false;
|
|
23
|
+
const { name } = error;
|
|
24
|
+
if (name === "NotFound" || name === "NoSuchKey" || name === "404") return true;
|
|
25
|
+
return hasNotFoundMetadata(error);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/lib/s3-download-range.ts
|
|
29
|
+
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
|
30
|
+
async function downloadS3Range(client, bucket, key, offset, length) {
|
|
31
|
+
if (offset < 0) {
|
|
32
|
+
throw new Error("offset must be non-negative");
|
|
33
|
+
}
|
|
34
|
+
if (length <= 0) {
|
|
35
|
+
throw new Error("length must be positive");
|
|
36
|
+
}
|
|
37
|
+
const response = await client.send(
|
|
38
|
+
new GetObjectCommand({
|
|
39
|
+
Bucket: bucket,
|
|
40
|
+
Key: key,
|
|
41
|
+
Range: `bytes=${String(offset)}-${String(offset + length - 1)}`
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
if (!response.Body) {
|
|
45
|
+
throw new Error(`File not found: ${key}`);
|
|
46
|
+
}
|
|
47
|
+
const bytes = await response.Body.transformToByteArray();
|
|
48
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
49
|
+
}
|
|
50
|
+
async function downloadS3Partial(client, bucket, key, start, end) {
|
|
51
|
+
if (start >= end) {
|
|
52
|
+
throw new Error("start must be less than end");
|
|
53
|
+
}
|
|
54
|
+
const response = await client.send(
|
|
55
|
+
new GetObjectCommand({
|
|
56
|
+
Bucket: bucket,
|
|
57
|
+
Key: key,
|
|
58
|
+
Range: `bytes=${String(start)}-${String(end - 1)}`
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
if (!response.Body) {
|
|
62
|
+
throw new Error(`File not found: ${key}`);
|
|
63
|
+
}
|
|
64
|
+
return response.Body.transformToWebStream();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/lib/s3-multipart.ts
|
|
68
|
+
import {
|
|
69
|
+
AbortMultipartUploadCommand,
|
|
70
|
+
CompleteMultipartUploadCommand,
|
|
71
|
+
CreateMultipartUploadCommand,
|
|
72
|
+
UploadPartCommand
|
|
73
|
+
} from "@aws-sdk/client-s3";
|
|
74
|
+
async function initiateS3MultipartUpload(client, bucket, key) {
|
|
75
|
+
const response = await client.send(
|
|
76
|
+
new CreateMultipartUploadCommand({ Bucket: bucket, Key: key })
|
|
77
|
+
);
|
|
78
|
+
if (!response.UploadId) {
|
|
79
|
+
throw new Error(`Failed to initiate multipart upload for key: ${key}`);
|
|
80
|
+
}
|
|
81
|
+
return { uploadId: response.UploadId, key };
|
|
82
|
+
}
|
|
83
|
+
async function uploadS3Part(client, bucket, key, uploadId, partNumber, data) {
|
|
84
|
+
const response = await client.send(
|
|
85
|
+
new UploadPartCommand({
|
|
86
|
+
Bucket: bucket,
|
|
87
|
+
Key: key,
|
|
88
|
+
UploadId: uploadId,
|
|
89
|
+
PartNumber: partNumber,
|
|
90
|
+
Body: data
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
if (!response.ETag) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Missing ETag in upload part response for key: ${key}, part: ${String(partNumber)}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return response.ETag;
|
|
99
|
+
}
|
|
100
|
+
async function completeS3MultipartUpload(client, bucket, key, uploadId, parts) {
|
|
101
|
+
await client.send(
|
|
102
|
+
new CompleteMultipartUploadCommand({
|
|
103
|
+
Bucket: bucket,
|
|
104
|
+
Key: key,
|
|
105
|
+
UploadId: uploadId,
|
|
106
|
+
MultipartUpload: {
|
|
107
|
+
Parts: parts.map((p) => ({ PartNumber: p.partNumber, ETag: p.etag }))
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
async function abortS3MultipartUpload(client, bucket, key, uploadId) {
|
|
113
|
+
await client.send(
|
|
114
|
+
new AbortMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId: uploadId })
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/lib/s3-presigned.ts
|
|
119
|
+
import { GetObjectCommand as GetObjectCommand2, PutObjectCommand, UploadPartCommand as UploadPartCommand2 } from "@aws-sdk/client-s3";
|
|
120
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
121
|
+
var DEFAULT_PRESIGN_EXPIRES_IN = 3600;
|
|
122
|
+
async function createS3PresignedGetUrl(client, bucket, key, expiresIn = DEFAULT_PRESIGN_EXPIRES_IN) {
|
|
123
|
+
return getSignedUrl(client, new GetObjectCommand2({ Bucket: bucket, Key: key }), { expiresIn });
|
|
124
|
+
}
|
|
125
|
+
async function createS3PresignedPutUrl(client, bucket, key, options, defaultExpiresIn) {
|
|
126
|
+
const expiresIn = options?.expiresInSeconds ?? defaultExpiresIn ?? DEFAULT_PRESIGN_EXPIRES_IN;
|
|
127
|
+
const command = new PutObjectCommand({
|
|
128
|
+
Bucket: bucket,
|
|
129
|
+
Key: key,
|
|
130
|
+
ContentType: options?.contentType
|
|
131
|
+
});
|
|
132
|
+
const url = await getSignedUrl(client, command, { expiresIn });
|
|
133
|
+
const headers = options?.contentType ? { "Content-Type": options.contentType } : void 0;
|
|
134
|
+
return { url, headers };
|
|
135
|
+
}
|
|
136
|
+
async function createS3PresignedPartUrls(client, bucket, key, uploadId, parts, expiresIn = DEFAULT_PRESIGN_EXPIRES_IN) {
|
|
137
|
+
return Promise.all(
|
|
138
|
+
parts.map(async (partNumber) => {
|
|
139
|
+
const command = new UploadPartCommand2({
|
|
140
|
+
Bucket: bucket,
|
|
141
|
+
Key: key,
|
|
142
|
+
UploadId: uploadId,
|
|
143
|
+
PartNumber: partNumber
|
|
144
|
+
});
|
|
145
|
+
const url = await getSignedUrl(client, command, { expiresIn });
|
|
146
|
+
return { partNumber, url };
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/lib/s3-response-to-head-result.ts
|
|
152
|
+
function s3ResponseToHeadResult(response) {
|
|
153
|
+
return {
|
|
154
|
+
size: response.ContentLength ?? 0,
|
|
155
|
+
contentType: response.ContentType,
|
|
156
|
+
etag: response.ETag,
|
|
157
|
+
lastModified: response.LastModified
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/lib/s3-response-to-storage-object.ts
|
|
162
|
+
function s3ResponseToStorageObject(key, response) {
|
|
163
|
+
if (!response.Body) {
|
|
164
|
+
throw new Error(`File not found: ${key}`);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
key,
|
|
168
|
+
body: response.Body.transformToWebStream(),
|
|
169
|
+
size: response.ContentLength ?? 0,
|
|
170
|
+
contentType: response.ContentType,
|
|
171
|
+
etag: response.ETag,
|
|
172
|
+
lastModified: response.LastModified
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/lib/s3-file-storage.ts
|
|
177
|
+
var S3FileStorage = class extends FileStorage {
|
|
178
|
+
client;
|
|
179
|
+
bucket;
|
|
180
|
+
publicUrlBase;
|
|
181
|
+
defaultPresignExpiresIn;
|
|
182
|
+
constructor(config) {
|
|
183
|
+
super();
|
|
184
|
+
this.client = config.client;
|
|
185
|
+
this.bucket = config.bucket;
|
|
186
|
+
this.publicUrlBase = config.publicUrlBase ? `${config.publicUrlBase.replace(/\/+$/, "")}/` : void 0;
|
|
187
|
+
this.defaultPresignExpiresIn = config.defaultPresignExpiresIn ?? 3600;
|
|
188
|
+
}
|
|
189
|
+
async upload(key, data, options) {
|
|
190
|
+
await this.client.send(
|
|
191
|
+
new PutObjectCommand2({
|
|
192
|
+
Bucket: this.bucket,
|
|
193
|
+
Key: key,
|
|
194
|
+
Body: data,
|
|
195
|
+
ContentType: options?.contentType,
|
|
196
|
+
CacheControl: options?.cacheControl,
|
|
197
|
+
ContentDisposition: options?.contentDisposition,
|
|
198
|
+
Metadata: options?.metadata
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
async download(key) {
|
|
203
|
+
const response = await this.client.send(
|
|
204
|
+
new GetObjectCommand3({ Bucket: this.bucket, Key: key })
|
|
205
|
+
);
|
|
206
|
+
return s3ResponseToStorageObject(key, response);
|
|
207
|
+
}
|
|
208
|
+
async head(key) {
|
|
209
|
+
try {
|
|
210
|
+
const response = await this.client.send(
|
|
211
|
+
new HeadObjectCommand({ Bucket: this.bucket, Key: key })
|
|
212
|
+
);
|
|
213
|
+
return s3ResponseToHeadResult(response);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (isNotFoundError(error)) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async delete(key) {
|
|
222
|
+
await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: key }));
|
|
223
|
+
}
|
|
224
|
+
async listKeys(prefix) {
|
|
225
|
+
const keys = [];
|
|
226
|
+
let continuationToken;
|
|
227
|
+
do {
|
|
228
|
+
const response = await this.client.send(
|
|
229
|
+
new ListObjectsV2Command({
|
|
230
|
+
Bucket: this.bucket,
|
|
231
|
+
Prefix: prefix,
|
|
232
|
+
ContinuationToken: continuationToken
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
if (response.Contents) {
|
|
236
|
+
for (const obj of response.Contents) {
|
|
237
|
+
if (obj.Key) {
|
|
238
|
+
keys.push(obj.Key);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
continuationToken = response.IsTruncated ? response.NextContinuationToken : void 0;
|
|
243
|
+
} while (continuationToken);
|
|
244
|
+
return keys;
|
|
245
|
+
}
|
|
246
|
+
getPublicUrl(key) {
|
|
247
|
+
if (!this.publicUrlBase) {
|
|
248
|
+
throw new Error("publicUrlBase is required for getPublicUrl");
|
|
249
|
+
}
|
|
250
|
+
return `${this.publicUrlBase}${key}`;
|
|
251
|
+
}
|
|
252
|
+
getProtectedUrl(_key) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
"Use createPresignedUploadUrl() or createS3PresignedGetUrl() for presigned URLs"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
async downloadRange(key, offset, length) {
|
|
258
|
+
return downloadS3Range(this.client, this.bucket, key, offset, length);
|
|
259
|
+
}
|
|
260
|
+
async downloadPartial(key, start, end) {
|
|
261
|
+
return downloadS3Partial(this.client, this.bucket, key, start, end);
|
|
262
|
+
}
|
|
263
|
+
async createPresignedUploadUrl(key, options) {
|
|
264
|
+
return createS3PresignedPutUrl(
|
|
265
|
+
this.client,
|
|
266
|
+
this.bucket,
|
|
267
|
+
key,
|
|
268
|
+
options,
|
|
269
|
+
this.defaultPresignExpiresIn
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
async initiateMultipartUpload(key) {
|
|
273
|
+
return initiateS3MultipartUpload(this.client, this.bucket, key);
|
|
274
|
+
}
|
|
275
|
+
async createPresignedPartUrls(key, uploadId, parts) {
|
|
276
|
+
return createS3PresignedPartUrls(
|
|
277
|
+
this.client,
|
|
278
|
+
this.bucket,
|
|
279
|
+
key,
|
|
280
|
+
uploadId,
|
|
281
|
+
parts,
|
|
282
|
+
this.defaultPresignExpiresIn
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
async uploadPart(key, uploadId, partNumber, data) {
|
|
286
|
+
return uploadS3Part(this.client, this.bucket, key, uploadId, partNumber, data);
|
|
287
|
+
}
|
|
288
|
+
async completeMultipartUpload(key, uploadId, parts) {
|
|
289
|
+
return completeS3MultipartUpload(this.client, this.bucket, key, uploadId, parts);
|
|
290
|
+
}
|
|
291
|
+
async abortMultipartUpload(key, uploadId) {
|
|
292
|
+
return abortS3MultipartUpload(this.client, this.bucket, key, uploadId);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
export {
|
|
296
|
+
DEFAULT_PRESIGN_EXPIRES_IN,
|
|
297
|
+
S3FileStorage,
|
|
298
|
+
createS3PresignedGetUrl
|
|
299
|
+
};
|
|
300
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/lib/s3-file-storage.ts", "../../storage/src/lib/file-storage.ts", "../src/lib/is-not-found-error.ts", "../src/lib/s3-download-range.ts", "../src/lib/s3-multipart.ts", "../src/lib/s3-presigned.ts", "../src/lib/s3-response-to-head-result.ts", "../src/lib/s3-response-to-storage-object.ts"],
|
|
4
|
+
"sourcesContent": ["import type { S3Client } from '@aws-sdk/client-s3';\nimport {\n DeleteObjectCommand,\n GetObjectCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n} from '@aws-sdk/client-s3';\nimport type {\n CompletedPart,\n FileStorageObject,\n FileUploadOptions,\n HeadResult,\n MultipartUploadInit,\n PresignedPartUrl,\n PresignedUploadUrl,\n PresignOptions,\n} from '@cibule/storage';\nimport { FileStorage } from '@cibule/storage';\n\nimport { isNotFoundError } from './is-not-found-error';\nimport { downloadS3Partial, downloadS3Range } from './s3-download-range';\nimport type { S3FileStorageConfig } from './s3-file-storage-config';\nimport {\n abortS3MultipartUpload,\n completeS3MultipartUpload,\n initiateS3MultipartUpload,\n uploadS3Part,\n} from './s3-multipart';\nimport { createS3PresignedPartUrls, createS3PresignedPutUrl } from './s3-presigned';\nimport { s3ResponseToHeadResult } from './s3-response-to-head-result';\nimport { s3ResponseToStorageObject } from './s3-response-to-storage-object';\n\nexport class S3FileStorage extends FileStorage {\n private readonly client: S3Client;\n private readonly bucket: string;\n private readonly publicUrlBase: string | undefined;\n private readonly defaultPresignExpiresIn: number;\n\n public constructor(config: S3FileStorageConfig) {\n super();\n this.client = config.client;\n this.bucket = config.bucket;\n this.publicUrlBase = config.publicUrlBase\n ? `${config.publicUrlBase.replace(/\\/+$/, '')}/`\n : undefined;\n this.defaultPresignExpiresIn = config.defaultPresignExpiresIn ?? 3600;\n }\n\n public async upload(\n key: string,\n data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array,\n options?: FileUploadOptions,\n ): Promise<void> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: data as PutObjectCommand['input']['Body'],\n ContentType: options?.contentType,\n CacheControl: options?.cacheControl,\n ContentDisposition: options?.contentDisposition,\n Metadata: options?.metadata,\n }),\n );\n }\n\n public async download(key: string): Promise<FileStorageObject> {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: key }),\n );\n return s3ResponseToStorageObject(key, response);\n }\n\n public async head(key: string): Promise<HeadResult | null> {\n try {\n const response = await this.client.send(\n new HeadObjectCommand({ Bucket: this.bucket, Key: key }),\n );\n return s3ResponseToHeadResult(response);\n } catch (error: unknown) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n public async delete(key: string): Promise<void> {\n await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: key }));\n }\n\n public async listKeys(prefix?: string): Promise<string[]> {\n const keys: string[] = [];\n let continuationToken: string | undefined;\n\n do {\n const response = await this.client.send(\n new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: prefix,\n ContinuationToken: continuationToken,\n }),\n );\n\n if (response.Contents) {\n for (const obj of response.Contents) {\n if (obj.Key) {\n keys.push(obj.Key);\n }\n }\n }\n\n continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined;\n } while (continuationToken);\n\n return keys;\n }\n\n public getPublicUrl(key: string): string {\n if (!this.publicUrlBase) {\n throw new Error('publicUrlBase is required for getPublicUrl');\n }\n return `${this.publicUrlBase}${key}`;\n }\n\n public getProtectedUrl(_key: string): string {\n throw new Error(\n 'Use createPresignedUploadUrl() or createS3PresignedGetUrl() for presigned URLs',\n );\n }\n\n public async downloadRange(key: string, offset: number, length: number): Promise<ArrayBuffer> {\n return downloadS3Range(this.client, this.bucket, key, offset, length);\n }\n\n public async downloadPartial(\n key: string,\n start: number,\n end: number,\n ): Promise<ReadableStream<Uint8Array>> {\n return downloadS3Partial(this.client, this.bucket, key, start, end);\n }\n\n public async createPresignedUploadUrl(\n key: string,\n options?: PresignOptions,\n ): Promise<PresignedUploadUrl> {\n return createS3PresignedPutUrl(\n this.client,\n this.bucket,\n key,\n options,\n this.defaultPresignExpiresIn,\n );\n }\n\n public async initiateMultipartUpload(key: string): Promise<MultipartUploadInit> {\n return initiateS3MultipartUpload(this.client, this.bucket, key);\n }\n\n public async createPresignedPartUrls(\n key: string,\n uploadId: string,\n parts: number[],\n ): Promise<PresignedPartUrl[]> {\n return createS3PresignedPartUrls(\n this.client,\n this.bucket,\n key,\n uploadId,\n parts,\n this.defaultPresignExpiresIn,\n );\n }\n\n public async uploadPart(\n key: string,\n uploadId: string,\n partNumber: number,\n data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array,\n ): Promise<string> {\n return uploadS3Part(this.client, this.bucket, key, uploadId, partNumber, data);\n }\n\n public async completeMultipartUpload(\n key: string,\n uploadId: string,\n parts: CompletedPart[],\n ): Promise<void> {\n return completeS3MultipartUpload(this.client, this.bucket, key, uploadId, parts);\n }\n\n public async abortMultipartUpload(key: string, uploadId: string): Promise<void> {\n return abortS3MultipartUpload(this.client, this.bucket, key, uploadId);\n }\n}\n", "import type {\n CompletedPart,\n FileStorageObject,\n FileUploadOptions,\n HeadResult,\n MultipartUploadInit,\n PresignedPartUrl,\n PresignedUploadUrl,\n PresignOptions,\n} from './file-storage-types';\n\nexport abstract class FileStorage {\n abstract upload(\n key: string,\n data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array,\n options?: FileUploadOptions,\n ): Promise<void>;\n\n abstract download(key: string): Promise<FileStorageObject>;\n\n abstract head(key: string): Promise<HeadResult | null>;\n\n abstract delete(key: string): Promise<void>;\n\n abstract listKeys(prefix?: string): Promise<string[]>;\n\n abstract getPublicUrl(key: string): string;\n\n abstract getProtectedUrl(key: string): string;\n\n abstract downloadRange(key: string, offset: number, length: number): Promise<ArrayBuffer>;\n\n abstract downloadPartial(\n key: string,\n start: number,\n end: number,\n ): Promise<ReadableStream<Uint8Array>>;\n\n abstract createPresignedUploadUrl(\n key: string,\n options?: PresignOptions,\n ): Promise<PresignedUploadUrl | null>;\n\n abstract initiateMultipartUpload(key: string): Promise<MultipartUploadInit>;\n\n abstract createPresignedPartUrls(\n key: string,\n uploadId: string,\n parts: number[],\n ): Promise<PresignedPartUrl[]>;\n\n abstract uploadPart(\n key: string,\n uploadId: string,\n partNumber: number,\n data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array,\n ): Promise<string>;\n\n abstract completeMultipartUpload(\n key: string,\n uploadId: string,\n parts: CompletedPart[],\n ): Promise<void>;\n\n abstract abortMultipartUpload(key: string, uploadId: string): Promise<void>;\n}\n", "interface S3ErrorMetadata {\n readonly httpStatusCode?: number;\n}\n\nfunction hasNotFoundMetadata(error: Error): boolean {\n if (!('$metadata' in error)) return false;\n\n const metadata = error.$metadata;\n if (typeof metadata !== 'object' || metadata === null) return false;\n\n return (metadata as S3ErrorMetadata).httpStatusCode === 404;\n}\n\nexport function isNotFoundError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n\n const { name } = error;\n if (name === 'NotFound' || name === 'NoSuchKey' || name === '404') return true;\n\n return hasNotFoundMetadata(error);\n}\n", "import type { S3Client } from '@aws-sdk/client-s3';\nimport { GetObjectCommand } from '@aws-sdk/client-s3';\n\nexport async function downloadS3Range(\n client: S3Client,\n bucket: string,\n key: string,\n offset: number,\n length: number,\n): Promise<ArrayBuffer> {\n if (offset < 0) {\n throw new Error('offset must be non-negative');\n }\n if (length <= 0) {\n throw new Error('length must be positive');\n }\n\n const response = await client.send(\n new GetObjectCommand({\n Bucket: bucket,\n Key: key,\n Range: `bytes=${String(offset)}-${String(offset + length - 1)}`,\n }),\n );\n\n if (!response.Body) {\n throw new Error(`File not found: ${key}`);\n }\n\n const bytes = await response.Body.transformToByteArray();\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);\n}\n\nexport async function downloadS3Partial(\n client: S3Client,\n bucket: string,\n key: string,\n start: number,\n end: number,\n): Promise<ReadableStream<Uint8Array>> {\n if (start >= end) {\n throw new Error('start must be less than end');\n }\n\n const response = await client.send(\n new GetObjectCommand({\n Bucket: bucket,\n Key: key,\n Range: `bytes=${String(start)}-${String(end - 1)}`,\n }),\n );\n\n if (!response.Body) {\n throw new Error(`File not found: ${key}`);\n }\n\n return response.Body.transformToWebStream() as ReadableStream<Uint8Array>;\n}\n", "import type { S3Client } from '@aws-sdk/client-s3';\nimport {\n AbortMultipartUploadCommand,\n CompleteMultipartUploadCommand,\n CreateMultipartUploadCommand,\n UploadPartCommand,\n} from '@aws-sdk/client-s3';\nimport type { CompletedPart, MultipartUploadInit } from '@cibule/storage';\n\nexport async function initiateS3MultipartUpload(\n client: S3Client,\n bucket: string,\n key: string,\n): Promise<MultipartUploadInit> {\n const response = await client.send(\n new CreateMultipartUploadCommand({ Bucket: bucket, Key: key }),\n );\n\n if (!response.UploadId) {\n throw new Error(`Failed to initiate multipart upload for key: ${key}`);\n }\n\n return { uploadId: response.UploadId, key };\n}\n\nexport async function uploadS3Part(\n client: S3Client,\n bucket: string,\n key: string,\n uploadId: string,\n partNumber: number,\n data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array,\n): Promise<string> {\n const response = await client.send(\n new UploadPartCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n Body: data as UploadPartCommand['input']['Body'],\n }),\n );\n\n if (!response.ETag) {\n throw new Error(\n `Missing ETag in upload part response for key: ${key}, part: ${String(partNumber)}`,\n );\n }\n\n return response.ETag;\n}\n\nexport async function completeS3MultipartUpload(\n client: S3Client,\n bucket: string,\n key: string,\n uploadId: string,\n parts: CompletedPart[],\n): Promise<void> {\n await client.send(\n new CompleteMultipartUploadCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: {\n Parts: parts.map(p => ({ PartNumber: p.partNumber, ETag: p.etag })),\n },\n }),\n );\n}\n\nexport async function abortS3MultipartUpload(\n client: S3Client,\n bucket: string,\n key: string,\n uploadId: string,\n): Promise<void> {\n await client.send(\n new AbortMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId: uploadId }),\n );\n}\n", "import type { S3Client } from '@aws-sdk/client-s3';\nimport { GetObjectCommand, PutObjectCommand, UploadPartCommand } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport type { PresignedPartUrl, PresignedUploadUrl, PresignOptions } from '@cibule/storage';\n\nexport const DEFAULT_PRESIGN_EXPIRES_IN = 3600;\n\nexport async function createS3PresignedGetUrl(\n client: S3Client,\n bucket: string,\n key: string,\n expiresIn: number = DEFAULT_PRESIGN_EXPIRES_IN,\n): Promise<string> {\n return getSignedUrl(client, new GetObjectCommand({ Bucket: bucket, Key: key }), { expiresIn });\n}\n\nexport async function createS3PresignedPutUrl(\n client: S3Client,\n bucket: string,\n key: string,\n options?: PresignOptions,\n defaultExpiresIn?: number,\n): Promise<PresignedUploadUrl> {\n const expiresIn = options?.expiresInSeconds ?? defaultExpiresIn ?? DEFAULT_PRESIGN_EXPIRES_IN;\n const command = new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n ContentType: options?.contentType,\n });\n\n const url = await getSignedUrl(client, command, { expiresIn });\n const headers = options?.contentType ? { 'Content-Type': options.contentType } : undefined;\n\n return { url, headers };\n}\n\nexport async function createS3PresignedPartUrls(\n client: S3Client,\n bucket: string,\n key: string,\n uploadId: string,\n parts: number[],\n expiresIn: number = DEFAULT_PRESIGN_EXPIRES_IN,\n): Promise<PresignedPartUrl[]> {\n return Promise.all(\n parts.map(async partNumber => {\n const command = new UploadPartCommand({\n Bucket: bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n });\n const url = await getSignedUrl(client, command, { expiresIn });\n return { partNumber, url };\n }),\n );\n}\n", "import type { HeadObjectCommandOutput } from '@aws-sdk/client-s3';\nimport type { HeadResult } from '@cibule/storage';\n\nexport function s3ResponseToHeadResult(response: HeadObjectCommandOutput): HeadResult {\n return {\n size: response.ContentLength ?? 0,\n contentType: response.ContentType,\n etag: response.ETag,\n lastModified: response.LastModified,\n };\n}\n", "import type { GetObjectCommandOutput } from '@aws-sdk/client-s3';\nimport type { FileStorageObject } from '@cibule/storage';\n\nexport function s3ResponseToStorageObject(\n key: string,\n response: GetObjectCommandOutput,\n): FileStorageObject {\n if (!response.Body) {\n throw new Error(`File not found: ${key}`);\n }\n\n return {\n key,\n body: response.Body.transformToWebStream() as ReadableStream<Uint8Array>,\n size: response.ContentLength ?? 0,\n contentType: response.ContentType,\n etag: response.ETag,\n lastModified: response.LastModified,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["GetObjectCommand", "PutObjectCommand", "GetObjectCommand", "UploadPartCommand", "PutObjectCommand", "GetObjectCommand"]
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-not-found-error.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/is-not-found-error.ts"],"names":[],"mappings":"AAaA,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOvD"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
export declare function downloadS3Range(client: S3Client, bucket: string, key: string, offset: number, length: number): Promise<ArrayBuffer>;
|
|
3
|
+
export declare function downloadS3Partial(client: S3Client, bucket: string, key: string, start: number, end: number): Promise<ReadableStream<Uint8Array>>;
|
|
4
|
+
//# sourceMappingURL=s3-download-range.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-download-range.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/s3-download-range.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAGnD,wBAAsB,eAAe,CACnC,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,WAAW,CAAC,CAsBtB;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAkBrC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
export interface S3FileStorageConfig {
|
|
3
|
+
readonly client: S3Client;
|
|
4
|
+
readonly bucket: string;
|
|
5
|
+
readonly publicUrlBase?: string;
|
|
6
|
+
readonly defaultPresignExpiresIn?: number;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=s3-file-storage-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-file-storage-config.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/s3-file-storage-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAC3C"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CompletedPart, FileStorageObject, FileUploadOptions, HeadResult, MultipartUploadInit, PresignedPartUrl, PresignedUploadUrl, PresignOptions } from '@cibule/storage';
|
|
2
|
+
import { FileStorage } from '@cibule/storage';
|
|
3
|
+
import type { S3FileStorageConfig } from './s3-file-storage-config';
|
|
4
|
+
export declare class S3FileStorage extends FileStorage {
|
|
5
|
+
private readonly client;
|
|
6
|
+
private readonly bucket;
|
|
7
|
+
private readonly publicUrlBase;
|
|
8
|
+
private readonly defaultPresignExpiresIn;
|
|
9
|
+
constructor(config: S3FileStorageConfig);
|
|
10
|
+
upload(key: string, data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array, options?: FileUploadOptions): Promise<void>;
|
|
11
|
+
download(key: string): Promise<FileStorageObject>;
|
|
12
|
+
head(key: string): Promise<HeadResult | null>;
|
|
13
|
+
delete(key: string): Promise<void>;
|
|
14
|
+
listKeys(prefix?: string): Promise<string[]>;
|
|
15
|
+
getPublicUrl(key: string): string;
|
|
16
|
+
getProtectedUrl(_key: string): string;
|
|
17
|
+
downloadRange(key: string, offset: number, length: number): Promise<ArrayBuffer>;
|
|
18
|
+
downloadPartial(key: string, start: number, end: number): Promise<ReadableStream<Uint8Array>>;
|
|
19
|
+
createPresignedUploadUrl(key: string, options?: PresignOptions): Promise<PresignedUploadUrl>;
|
|
20
|
+
initiateMultipartUpload(key: string): Promise<MultipartUploadInit>;
|
|
21
|
+
createPresignedPartUrls(key: string, uploadId: string, parts: number[]): Promise<PresignedPartUrl[]>;
|
|
22
|
+
uploadPart(key: string, uploadId: string, partNumber: number, data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array): Promise<string>;
|
|
23
|
+
completeMultipartUpload(key: string, uploadId: string, parts: CompletedPart[]): Promise<void>;
|
|
24
|
+
abortMultipartUpload(key: string, uploadId: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=s3-file-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-file-storage.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/s3-file-storage.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAI9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAWpE,qBAAa,aAAc,SAAQ,WAAW;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;gBAE9B,MAAM,EAAE,mBAAmB;IAUjC,MAAM,CACjB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,UAAU,EAC3D,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC;IAcH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAOjD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAc7C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IA2BlD,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAOjC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAM/B,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAIhF,eAAe,CAC1B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IAIzB,wBAAwB,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,kBAAkB,CAAC;IAUjB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAIlE,uBAAuB,CAClC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAWjB,UAAU,CACrB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,UAAU,GAC1D,OAAO,CAAC,MAAM,CAAC;IAIL,uBAAuB,CAClC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,EAAE,GACrB,OAAO,CAAC,IAAI,CAAC;IAIH,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGhF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import type { CompletedPart, MultipartUploadInit } from '@cibule/storage';
|
|
3
|
+
export declare function initiateS3MultipartUpload(client: S3Client, bucket: string, key: string): Promise<MultipartUploadInit>;
|
|
4
|
+
export declare function uploadS3Part(client: S3Client, bucket: string, key: string, uploadId: string, partNumber: number, data: ReadableStream<Uint8Array> | ArrayBuffer | Uint8Array): Promise<string>;
|
|
5
|
+
export declare function completeS3MultipartUpload(client: S3Client, bucket: string, key: string, uploadId: string, parts: CompletedPart[]): Promise<void>;
|
|
6
|
+
export declare function abortS3MultipartUpload(client: S3Client, bucket: string, key: string, uploadId: string): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=s3-multipart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-multipart.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/s3-multipart.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAOnD,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE1E,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,mBAAmB,CAAC,CAU9B;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,UAAU,GAC1D,OAAO,CAAC,MAAM,CAAC,CAkBjB;AAED,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,EAAE,GACrB,OAAO,CAAC,IAAI,CAAC,CAWf;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAIf"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import type { PresignedPartUrl, PresignedUploadUrl, PresignOptions } from '@cibule/storage';
|
|
3
|
+
export declare const DEFAULT_PRESIGN_EXPIRES_IN = 3600;
|
|
4
|
+
export declare function createS3PresignedGetUrl(client: S3Client, bucket: string, key: string, expiresIn?: number): Promise<string>;
|
|
5
|
+
export declare function createS3PresignedPutUrl(client: S3Client, bucket: string, key: string, options?: PresignOptions, defaultExpiresIn?: number): Promise<PresignedUploadUrl>;
|
|
6
|
+
export declare function createS3PresignedPartUrls(client: S3Client, bucket: string, key: string, uploadId: string, parts: number[], expiresIn?: number): Promise<PresignedPartUrl[]>;
|
|
7
|
+
//# sourceMappingURL=s3-presigned.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-presigned.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/s3-presigned.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAGnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE5F,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,MAAmC,GAC7C,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,cAAc,EACxB,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,kBAAkB,CAAC,CAY7B;AAED,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EAAE,EACf,SAAS,GAAE,MAAmC,GAC7C,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAa7B"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { HeadObjectCommandOutput } from '@aws-sdk/client-s3';
|
|
2
|
+
import type { HeadResult } from '@cibule/storage';
|
|
3
|
+
export declare function s3ResponseToHeadResult(response: HeadObjectCommandOutput): HeadResult;
|
|
4
|
+
//# sourceMappingURL=s3-response-to-head-result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-response-to-head-result.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/s3-response-to-head-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,UAAU,CAOpF"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { GetObjectCommandOutput } from '@aws-sdk/client-s3';
|
|
2
|
+
import type { FileStorageObject } from '@cibule/storage';
|
|
3
|
+
export declare function s3ResponseToStorageObject(key: string, response: GetObjectCommandOutput): FileStorageObject;
|
|
4
|
+
//# sourceMappingURL=s3-response-to-storage-object.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-response-to-storage-object.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/s3-response-to-storage-object.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,sBAAsB,GAC/B,iBAAiB,CAanB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import { vi } from 'vitest';
|
|
3
|
+
export interface MockS3Client {
|
|
4
|
+
readonly client: S3Client;
|
|
5
|
+
readonly sendFn: ReturnType<typeof vi.fn>;
|
|
6
|
+
}
|
|
7
|
+
export declare function createMockClient(sendReturnValue?: unknown): MockS3Client;
|
|
8
|
+
export declare function makeStream(): ReadableStream<Uint8Array>;
|
|
9
|
+
export declare function makeBody(stream?: ReadableStream<Uint8Array>): {
|
|
10
|
+
transformToWebStream: ReturnType<typeof vi.fn>;
|
|
11
|
+
transformToByteArray: ReturnType<typeof vi.fn>;
|
|
12
|
+
transformToString: ReturnType<typeof vi.fn>;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=s3-test-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-test-helpers.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/s3-test-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE5B,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;CAC3C;AAED,wBAAgB,gBAAgB,CAAC,eAAe,GAAE,OAAY,GAAG,YAAY,CAG5E;AAED,wBAAgB,UAAU,IAAI,cAAc,CAAC,UAAU,CAAC,CAEvD;AAED,wBAAgB,QAAQ,CAAC,MAAM,GAAE,cAAc,CAAC,UAAU,CAAgB,GAAG;IAC3E,oBAAoB,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/C,oBAAoB,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/C,iBAAiB,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;CAC7C,CAMA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cibule/storage-s3",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://gitlab.com/LadaBr/cibule",
|
|
28
|
+
"directory": "packages/storage-s3"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@aws-sdk/client-s3": "^3.1004.0",
|
|
35
|
+
"@aws-sdk/s3-request-presigner": "^3.1004.0",
|
|
36
|
+
"@cibule/storage": "workspace:*"
|
|
37
|
+
}
|
|
38
|
+
}
|