@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 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
+ }
@@ -0,0 +1,4 @@
1
+ export { S3FileStorage } from './lib/s3-file-storage';
2
+ export type { S3FileStorageConfig } from './lib/s3-file-storage-config';
3
+ export { createS3PresignedGetUrl, DEFAULT_PRESIGN_EXPIRES_IN } from './lib/s3-presigned';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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": ";AACA;AAAA,EACE;AAAA,EACA,oBAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAAC;AAAA,OACK;;;ACIA,IAAe,cAAf,MAA2B;AAsDlC;;;AC7DA,SAAS,oBAAoB,OAAuB;AAClD,MAAI,EAAE,eAAe,OAAQ,QAAO;AAEpC,QAAM,WAAW,MAAM;AACvB,MAAI,OAAO,aAAa,YAAY,aAAa,KAAM,QAAO;AAE9D,SAAQ,SAA6B,mBAAmB;AAC1D;AAEO,SAAS,gBAAgB,OAAyB;AACvD,MAAI,EAAE,iBAAiB,OAAQ,QAAO;AAEtC,QAAM,EAAE,KAAK,IAAI;AACjB,MAAI,SAAS,cAAc,SAAS,eAAe,SAAS,MAAO,QAAO;AAE1E,SAAO,oBAAoB,KAAK;AAClC;;;ACnBA,SAAS,wBAAwB;AAEjC,eAAsB,gBACpB,QACA,QACA,KACA,QACA,QACsB;AACtB,MAAI,SAAS,GAAG;AACd,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AACA,MAAI,UAAU,GAAG;AACf,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAEA,QAAM,WAAW,MAAM,OAAO;AAAA,IAC5B,IAAI,iBAAiB;AAAA,MACnB,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,OAAO,SAAS,OAAO,MAAM,CAAC,IAAI,OAAO,SAAS,SAAS,CAAC,CAAC;AAAA,IAC/D,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,SAAS,MAAM;AAClB,UAAM,IAAI,MAAM,mBAAmB,GAAG,EAAE;AAAA,EAC1C;AAEA,QAAM,QAAQ,MAAM,SAAS,KAAK,qBAAqB;AACvD,SAAO,MAAM,OAAO,MAAM,MAAM,YAAY,MAAM,aAAa,MAAM,UAAU;AACjF;AAEA,eAAsB,kBACpB,QACA,QACA,KACA,OACA,KACqC;AACrC,MAAI,SAAS,KAAK;AAChB,UAAM,IAAI,MAAM,6BAA6B;AAAA,EAC/C;AAEA,QAAM,WAAW,MAAM,OAAO;AAAA,IAC5B,IAAI,iBAAiB;AAAA,MACnB,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,OAAO,SAAS,OAAO,KAAK,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC;AAAA,IAClD,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,SAAS,MAAM;AAClB,UAAM,IAAI,MAAM,mBAAmB,GAAG,EAAE;AAAA,EAC1C;AAEA,SAAO,SAAS,KAAK,qBAAqB;AAC5C;;;ACxDA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,eAAsB,0BACpB,QACA,QACA,KAC8B;AAC9B,QAAM,WAAW,MAAM,OAAO;AAAA,IAC5B,IAAI,6BAA6B,EAAE,QAAQ,QAAQ,KAAK,IAAI,CAAC;AAAA,EAC/D;AAEA,MAAI,CAAC,SAAS,UAAU;AACtB,UAAM,IAAI,MAAM,gDAAgD,GAAG,EAAE;AAAA,EACvE;AAEA,SAAO,EAAE,UAAU,SAAS,UAAU,IAAI;AAC5C;AAEA,eAAsB,aACpB,QACA,QACA,KACA,UACA,YACA,MACiB;AACjB,QAAM,WAAW,MAAM,OAAO;AAAA,IAC5B,IAAI,kBAAkB;AAAA,MACpB,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,SAAS,MAAM;AAClB,UAAM,IAAI;AAAA,MACR,iDAAiD,GAAG,WAAW,OAAO,UAAU,CAAC;AAAA,IACnF;AAAA,EACF;AAEA,SAAO,SAAS;AAClB;AAEA,eAAsB,0BACpB,QACA,QACA,KACA,UACA,OACe;AACf,QAAM,OAAO;AAAA,IACX,IAAI,+BAA+B;AAAA,MACjC,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,UAAU;AAAA,MACV,iBAAiB;AAAA,QACf,OAAO,MAAM,IAAI,QAAM,EAAE,YAAY,EAAE,YAAY,MAAM,EAAE,KAAK,EAAE;AAAA,MACpE;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,eAAsB,uBACpB,QACA,QACA,KACA,UACe;AACf,QAAM,OAAO;AAAA,IACX,IAAI,4BAA4B,EAAE,QAAQ,QAAQ,KAAK,KAAK,UAAU,SAAS,CAAC;AAAA,EAClF;AACF;;;AC/EA,SAAS,oBAAAC,mBAAkB,kBAAkB,qBAAAC,0BAAyB;AACtE,SAAS,oBAAoB;AAGtB,IAAM,6BAA6B;AAE1C,eAAsB,wBACpB,QACA,QACA,KACA,YAAoB,4BACH;AACjB,SAAO,aAAa,QAAQ,IAAID,kBAAiB,EAAE,QAAQ,QAAQ,KAAK,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC;AAC/F;AAEA,eAAsB,wBACpB,QACA,QACA,KACA,SACA,kBAC6B;AAC7B,QAAM,YAAY,SAAS,oBAAoB,oBAAoB;AACnE,QAAM,UAAU,IAAI,iBAAiB;AAAA,IACnC,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,aAAa,SAAS;AAAA,EACxB,CAAC;AAED,QAAM,MAAM,MAAM,aAAa,QAAQ,SAAS,EAAE,UAAU,CAAC;AAC7D,QAAM,UAAU,SAAS,cAAc,EAAE,gBAAgB,QAAQ,YAAY,IAAI;AAEjF,SAAO,EAAE,KAAK,QAAQ;AACxB;AAEA,eAAsB,0BACpB,QACA,QACA,KACA,UACA,OACA,YAAoB,4BACS;AAC7B,SAAO,QAAQ;AAAA,IACb,MAAM,IAAI,OAAM,eAAc;AAC5B,YAAM,UAAU,IAAIC,mBAAkB;AAAA,QACpC,QAAQ;AAAA,QACR,KAAK;AAAA,QACL,UAAU;AAAA,QACV,YAAY;AAAA,MACd,CAAC;AACD,YAAM,MAAM,MAAM,aAAa,QAAQ,SAAS,EAAE,UAAU,CAAC;AAC7D,aAAO,EAAE,YAAY,IAAI;AAAA,IAC3B,CAAC;AAAA,EACH;AACF;;;ACrDO,SAAS,uBAAuB,UAA+C;AACpF,SAAO;AAAA,IACL,MAAM,SAAS,iBAAiB;AAAA,IAChC,aAAa,SAAS;AAAA,IACtB,MAAM,SAAS;AAAA,IACf,cAAc,SAAS;AAAA,EACzB;AACF;;;ACPO,SAAS,0BACd,KACA,UACmB;AACnB,MAAI,CAAC,SAAS,MAAM;AAClB,UAAM,IAAI,MAAM,mBAAmB,GAAG,EAAE;AAAA,EAC1C;AAEA,SAAO;AAAA,IACL;AAAA,IACA,MAAM,SAAS,KAAK,qBAAqB;AAAA,IACzC,MAAM,SAAS,iBAAiB;AAAA,IAChC,aAAa,SAAS;AAAA,IACtB,MAAM,SAAS;AAAA,IACf,cAAc,SAAS;AAAA,EACzB;AACF;;;APcO,IAAM,gBAAN,cAA4B,YAAY;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEV,YAAY,QAA6B;AAC9C,UAAM;AACN,SAAK,SAAS,OAAO;AACrB,SAAK,SAAS,OAAO;AACrB,SAAK,gBAAgB,OAAO,gBACxB,GAAG,OAAO,cAAc,QAAQ,QAAQ,EAAE,CAAC,MAC3C;AACJ,SAAK,0BAA0B,OAAO,2BAA2B;AAAA,EACnE;AAAA,EAEA,MAAa,OACX,KACA,MACA,SACe;AACf,UAAM,KAAK,OAAO;AAAA,MAChB,IAAIC,kBAAiB;AAAA,QACnB,QAAQ,KAAK;AAAA,QACb,KAAK;AAAA,QACL,MAAM;AAAA,QACN,aAAa,SAAS;AAAA,QACtB,cAAc,SAAS;AAAA,QACvB,oBAAoB,SAAS;AAAA,QAC7B,UAAU,SAAS;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAa,SAAS,KAAyC;AAC7D,UAAM,WAAW,MAAM,KAAK,OAAO;AAAA,MACjC,IAAIC,kBAAiB,EAAE,QAAQ,KAAK,QAAQ,KAAK,IAAI,CAAC;AAAA,IACxD;AACA,WAAO,0BAA0B,KAAK,QAAQ;AAAA,EAChD;AAAA,EAEA,MAAa,KAAK,KAAyC;AACzD,QAAI;AACF,YAAM,WAAW,MAAM,KAAK,OAAO;AAAA,QACjC,IAAI,kBAAkB,EAAE,QAAQ,KAAK,QAAQ,KAAK,IAAI,CAAC;AAAA,MACzD;AACA,aAAO,uBAAuB,QAAQ;AAAA,IACxC,SAAS,OAAgB;AACvB,UAAI,gBAAgB,KAAK,GAAG;AAC1B,eAAO;AAAA,MACT;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAa,OAAO,KAA4B;AAC9C,UAAM,KAAK,OAAO,KAAK,IAAI,oBAAoB,EAAE,QAAQ,KAAK,QAAQ,KAAK,IAAI,CAAC,CAAC;AAAA,EACnF;AAAA,EAEA,MAAa,SAAS,QAAoC;AACxD,UAAM,OAAiB,CAAC;AACxB,QAAI;AAEJ,OAAG;AACD,YAAM,WAAW,MAAM,KAAK,OAAO;AAAA,QACjC,IAAI,qBAAqB;AAAA,UACvB,QAAQ,KAAK;AAAA,UACb,QAAQ;AAAA,UACR,mBAAmB;AAAA,QACrB,CAAC;AAAA,MACH;AAEA,UAAI,SAAS,UAAU;AACrB,mBAAW,OAAO,SAAS,UAAU;AACnC,cAAI,IAAI,KAAK;AACX,iBAAK,KAAK,IAAI,GAAG;AAAA,UACnB;AAAA,QACF;AAAA,MACF;AAEA,0BAAoB,SAAS,cAAc,SAAS,wBAAwB;AAAA,IAC9E,SAAS;AAET,WAAO;AAAA,EACT;AAAA,EAEO,aAAa,KAAqB;AACvC,QAAI,CAAC,KAAK,eAAe;AACvB,YAAM,IAAI,MAAM,4CAA4C;AAAA,IAC9D;AACA,WAAO,GAAG,KAAK,aAAa,GAAG,GAAG;AAAA,EACpC;AAAA,EAEO,gBAAgB,MAAsB;AAC3C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAa,cAAc,KAAa,QAAgB,QAAsC;AAC5F,WAAO,gBAAgB,KAAK,QAAQ,KAAK,QAAQ,KAAK,QAAQ,MAAM;AAAA,EACtE;AAAA,EAEA,MAAa,gBACX,KACA,OACA,KACqC;AACrC,WAAO,kBAAkB,KAAK,QAAQ,KAAK,QAAQ,KAAK,OAAO,GAAG;AAAA,EACpE;AAAA,EAEA,MAAa,yBACX,KACA,SAC6B;AAC7B,WAAO;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,KAAK;AAAA,IACP;AAAA,EACF;AAAA,EAEA,MAAa,wBAAwB,KAA2C;AAC9E,WAAO,0BAA0B,KAAK,QAAQ,KAAK,QAAQ,GAAG;AAAA,EAChE;AAAA,EAEA,MAAa,wBACX,KACA,UACA,OAC6B;AAC7B,WAAO;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK;AAAA,IACP;AAAA,EACF;AAAA,EAEA,MAAa,WACX,KACA,UACA,YACA,MACiB;AACjB,WAAO,aAAa,KAAK,QAAQ,KAAK,QAAQ,KAAK,UAAU,YAAY,IAAI;AAAA,EAC/E;AAAA,EAEA,MAAa,wBACX,KACA,UACA,OACe;AACf,WAAO,0BAA0B,KAAK,QAAQ,KAAK,QAAQ,KAAK,UAAU,KAAK;AAAA,EACjF;AAAA,EAEA,MAAa,qBAAqB,KAAa,UAAiC;AAC9E,WAAO,uBAAuB,KAAK,QAAQ,KAAK,QAAQ,KAAK,QAAQ;AAAA,EACvE;AACF;",
6
+ "names": ["GetObjectCommand", "PutObjectCommand", "GetObjectCommand", "UploadPartCommand", "PutObjectCommand", "GetObjectCommand"]
7
+ }
@@ -0,0 +1,2 @@
1
+ export declare function isNotFoundError(error: unknown): boolean;
2
+ //# sourceMappingURL=is-not-found-error.d.ts.map
@@ -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
+ }