s3mini 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -60,6 +60,7 @@ The library supports a subset of S3 operations, focusing on essential features,
60
60
  - ✅ GetObject (getObject, getObjectResponse, getObjectWithETag, getObjectRaw, getObjectArrayBuffer, getObjectJSON)
61
61
  - ✅ PutObject (putObject)
62
62
  - ✅ DeleteObject (deleteObject)
63
+ - ✅ DeleteObjects (deleteObjects)
63
64
  - ✅ HeadObject (objectExists, getEtag, getContentLength)
64
65
  - ✅ listMultipartUploads
65
66
  - ✅ CreateMultipartUpload (getMultipartUploadId)
@@ -68,6 +69,8 @@ The library supports a subset of S3 operations, focusing on essential features,
68
69
  - ✅ uploadPart
69
70
  - ❌ CopyObject: Not implemented (tbd)
70
71
 
72
+ Put/Get objects with SSE-C (server-side encryption with customer-provided keys) is supported, but only tested on Cloudflare R2!
73
+
71
74
  ## Installation
72
75
 
73
76
  ```bash
@@ -99,10 +102,14 @@ mv example.env .env
99
102
 
100
103
  ## Usage
101
104
 
105
+ > [!WARNING]
106
+ > `s3mini` is a deprecated alias retained solely for backward compatibility.
107
+ > It is scheduled for removal in a future release. Please migrate to the new `S3mini` class.
108
+
102
109
  ```typescript
103
- import { s3mini, sanitizeETag } from 's3mini';
110
+ import { S3mini, sanitizeETag } from 's3mini';
104
111
 
105
- const s3client = new s3mini({
112
+ const s3client = new S3mini({
106
113
  accessKeyId: config.accessKeyId,
107
114
  secretAccessKey: config.secretAccessKey,
108
115
  endpoint: config.endpoint,
@@ -149,7 +156,7 @@ const objectData: string | null = await s3client.getObject(smallObjectKey);
149
156
  console.log('Object data:', objectData);
150
157
 
151
158
  // get the object with ETag, null if not found
152
- const response2: Response = await s3mini.getObject(smallObjectKey, { 'if-none-match': etag });
159
+ const response2: Response = await S3mini.getObject(smallObjectKey, { 'if-none-match': etag });
153
160
  if (response2) {
154
161
  // ETag changed so we can get the object data and new ETag
155
162
  // Note: ETag is not guaranteed to be the same as the MD5 hash of the object
@@ -175,6 +182,10 @@ if (list) {
175
182
 
176
183
  // delete the object
177
184
  const wasDeleted: boolean = await s3client.deleteObject(smallObjectKey);
185
+ // to delete multiple objects, use deleteObjects method
186
+ // const keysToDelete: string[] = ['object1.txt', 'object2.txt'];
187
+ // const deletedArray: boolean[] = await s3client.deleteObjects(keysToDelete);
188
+ // Note: deleteObjects returns an array of booleans, one for each key, indicating if the object was deleted or not
178
189
 
179
190
  // Multipart upload
180
191
  const multipartKey = 'multipart-object.txt';
package/dist/s3mini.d.ts CHANGED
@@ -7,6 +7,11 @@ interface S3Config {
7
7
  requestAbortTimeout?: number;
8
8
  logger?: Logger;
9
9
  }
10
+ interface SSECHeaders {
11
+ 'x-amz-server-side-encryption-customer-algorithm': string;
12
+ 'x-amz-server-side-encryption-customer-key': string;
13
+ 'x-amz-server-side-encryption-customer-key-md5': string;
14
+ }
10
15
  interface Logger {
11
16
  info: (message: string, ...args: unknown[]) => void;
12
17
  warn: (message: string, ...args: unknown[]) => void;
@@ -16,6 +21,13 @@ interface UploadPart {
16
21
  partNumber: number;
17
22
  etag: string;
18
23
  }
24
+ interface ListObject {
25
+ Key: string;
26
+ Size: number;
27
+ LastModified: Date;
28
+ ETag: string;
29
+ StorageClass: string;
30
+ }
19
31
  interface CompleteMultipartUploadResult {
20
32
  location: string;
21
33
  bucket: string;
@@ -92,7 +104,7 @@ type ExistResponseCode = false | true | null;
92
104
  * // Delete a file
93
105
  * await s3.deleteObject('example.txt');
94
106
  */
95
- declare class s3mini {
107
+ declare class S3mini {
96
108
  /**
97
109
  * Creates an instance of the S3 class.
98
110
  *
@@ -136,37 +148,281 @@ declare class s3mini {
136
148
  private _calculateSignature;
137
149
  private _buildAuthorizationHeader;
138
150
  private _signedRequest;
151
+ /**
152
+ * Gets the current configuration properties of the S3 instance.
153
+ * @returns {IT.S3Config} The current S3 configuration object containing all settings.
154
+ * @example
155
+ * const config = s3.getProps();
156
+ * console.log(config.endpoint); // 'https://s3.amazonaws.com/my-bucket'
157
+ */
139
158
  getProps(): S3Config;
159
+ /**
160
+ * Updates the configuration properties of the S3 instance.
161
+ * @param {IT.S3Config} props - The new configuration object.
162
+ * @param {string} props.accessKeyId - The access key ID for authentication.
163
+ * @param {string} props.secretAccessKey - The secret access key for authentication.
164
+ * @param {string} props.endpoint - The endpoint URL of the S3-compatible service.
165
+ * @param {string} [props.region='auto'] - The region of the S3 service.
166
+ * @param {number} [props.requestSizeInBytes=8388608] - The request size of a single request in bytes.
167
+ * @param {number} [props.requestAbortTimeout] - The timeout in milliseconds after which a request should be aborted.
168
+ * @param {IT.Logger} [props.logger] - A logger object with methods like info, warn, error.
169
+ * @throws {TypeError} Will throw an error if required parameters are missing or of incorrect type.
170
+ * @example
171
+ * s3.setProps({
172
+ * accessKeyId: 'new-access-key',
173
+ * secretAccessKey: 'new-secret-key',
174
+ * endpoint: 'https://new-endpoint.com/my-bucket',
175
+ * region: 'us-west-2' // by default is auto
176
+ * });
177
+ */
140
178
  setProps(props: S3Config): void;
179
+ /**
180
+ * Sanitizes an ETag value by removing surrounding quotes and whitespace.
181
+ * Still returns RFC compliant ETag. https://www.rfc-editor.org/rfc/rfc9110#section-8.8.3
182
+ * @param {string} etag - The ETag value to sanitize.
183
+ * @returns {string} The sanitized ETag value.
184
+ * @example
185
+ * const cleanEtag = s3.sanitizeETag('"abc123"'); // Returns: 'abc123'
186
+ */
141
187
  sanitizeETag(etag: string): string;
188
+ /**
189
+ * Creates a new bucket.
190
+ * This method sends a request to create a new bucket in the specified in endpoint.
191
+ * @returns A promise that resolves to true if the bucket was created successfully, false otherwise.
192
+ */
142
193
  createBucket(): Promise<boolean>;
194
+ /**
195
+ * Checks if a bucket exists.
196
+ * This method sends a request to check if the specified bucket exists in the S3-compatible service.
197
+ * @returns A promise that resolves to true if the bucket exists, false otherwise.
198
+ */
143
199
  bucketExists(): Promise<boolean>;
144
- listObjects(delimiter?: string, prefix?: string, maxKeys?: number, opts?: Record<string, unknown>): Promise<object[] | null>;
200
+ /**
201
+ * Lists objects in the bucket with optional filtering and no pagination.
202
+ * This method retrieves all objects matching the criteria (not paginated like listObjectsV2).
203
+ * @param {string} [delimiter='/'] - The delimiter to use for grouping objects.
204
+ * @param {string} [prefix=''] - The prefix to filter objects by.
205
+ * @param {number} [maxKeys] - The maximum number of keys to return. If not provided, all keys will be returned.
206
+ * @param {Record<string, unknown>} [opts={}] - Additional options for the request.
207
+ * @returns {Promise<IT.ListObject[] | null>} A promise that resolves to an array of objects or null if the bucket is empty.
208
+ * @example
209
+ * // List all objects
210
+ * const objects = await s3.listObjects();
211
+ *
212
+ * // List objects with prefix
213
+ * const photos = await s3.listObjects('/', 'photos/', 100);
214
+ */
215
+ listObjects(delimiter?: string, prefix?: string, maxKeys?: number, opts?: Record<string, unknown>): Promise<ListObject[] | null>;
216
+ /**
217
+ * Lists multipart uploads in the bucket.
218
+ * This method sends a request to list multipart uploads in the specified bucket.
219
+ * @param {string} [delimiter='/'] - The delimiter to use for grouping uploads.
220
+ * @param {string} [prefix=''] - The prefix to filter uploads by.
221
+ * @param {IT.HttpMethod} [method='GET'] - The HTTP method to use for the request (GET or HEAD).
222
+ * @param {Record<string, string | number | boolean | undefined>} [opts={}] - Additional options for the request.
223
+ * @returns A promise that resolves to a list of multipart uploads or an error.
224
+ */
145
225
  listMultipartUploads(delimiter?: string, prefix?: string, method?: HttpMethod, opts?: Record<string, string | number | boolean | undefined>): Promise<ListMultipartUploadSuccess | MultipartUploadError>;
146
- getObject(key: string, opts?: Record<string, unknown>): Promise<string | null>;
147
- getObjectResponse(key: string, opts?: Record<string, unknown>): Promise<Response | null>;
148
- getObjectArrayBuffer(key: string, opts?: Record<string, unknown>): Promise<ArrayBuffer | null>;
149
- getObjectJSON<T = unknown>(key: string, opts?: Record<string, unknown>): Promise<T | null>;
150
- getObjectWithETag(key: string, opts?: Record<string, unknown>): Promise<{
226
+ /**
227
+ * Get an object from the S3-compatible service.
228
+ * This method sends a request to retrieve the specified object from the S3-compatible service.
229
+ * @param {string} key - The key of the object to retrieve.
230
+ * @param {Record<string, unknown>} [opts] - Additional options for the request.
231
+ * @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
232
+ * @returns A promise that resolves to the object data (string) or null if not found.
233
+ */
234
+ getObject(key: string, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<string | null>;
235
+ /**
236
+ * Get an object response from the S3-compatible service.
237
+ * This method sends a request to retrieve the specified object and returns the full response.
238
+ * @param {string} key - The key of the object to retrieve.
239
+ * @param {Record<string, unknown>} [opts={}] - Additional options for the request.
240
+ * @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
241
+ * @returns A promise that resolves to the Response object or null if not found.
242
+ */
243
+ getObjectResponse(key: string, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<Response | null>;
244
+ /**
245
+ * Get an object as an ArrayBuffer from the S3-compatible service.
246
+ * This method sends a request to retrieve the specified object and returns it as an ArrayBuffer.
247
+ * @param {string} key - The key of the object to retrieve.
248
+ * @param {Record<string, unknown>} [opts={}] - Additional options for the request.
249
+ * @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
250
+ * @returns A promise that resolves to the object data as an ArrayBuffer or null if not found.
251
+ */
252
+ getObjectArrayBuffer(key: string, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<ArrayBuffer | null>;
253
+ /**
254
+ * Get an object as JSON from the S3-compatible service.
255
+ * This method sends a request to retrieve the specified object and returns it as JSON.
256
+ * @param {string} key - The key of the object to retrieve.
257
+ * @param {Record<string, unknown>} [opts={}] - Additional options for the request.
258
+ * @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
259
+ * @returns A promise that resolves to the object data as JSON or null if not found.
260
+ */
261
+ getObjectJSON<T = unknown>(key: string, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<T | null>;
262
+ /**
263
+ * Get an object with its ETag from the S3-compatible service.
264
+ * This method sends a request to retrieve the specified object and its ETag.
265
+ * @param {string} key - The key of the object to retrieve.
266
+ * @param {Record<string, unknown>} [opts={}] - Additional options for the request.
267
+ * @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
268
+ * @returns A promise that resolves to an object containing the ETag and the object data as an ArrayBuffer or null if not found.
269
+ */
270
+ getObjectWithETag(key: string, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<{
151
271
  etag: string | null;
152
272
  data: ArrayBuffer | null;
153
273
  }>;
154
- getObjectRaw(key: string, wholeFile?: boolean, rangeFrom?: number, rangeTo?: number, opts?: Record<string, unknown>): Promise<Response>;
155
- getContentLength(key: string): Promise<number>;
274
+ /**
275
+ * Get an object as a raw response from the S3-compatible service.
276
+ * This method sends a request to retrieve the specified object and returns the raw response.
277
+ * @param {string} key - The key of the object to retrieve.
278
+ * @param {boolean} [wholeFile=true] - Whether to retrieve the whole file or a range.
279
+ * @param {number} [rangeFrom=0] - The starting byte for the range (if not whole file).
280
+ * @param {number} [rangeTo=this.requestSizeInBytes] - The ending byte for the range (if not whole file).
281
+ * @param {Record<string, unknown>} [opts={}] - Additional options for the request.
282
+ * @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
283
+ * @returns A promise that resolves to the Response object.
284
+ */
285
+ getObjectRaw(key: string, wholeFile?: boolean, rangeFrom?: number, rangeTo?: number, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<Response>;
286
+ /**
287
+ * Get the content length of an object.
288
+ * This method sends a HEAD request to retrieve the content length of the specified object.
289
+ * @param {string} key - The key of the object to retrieve the content length for.
290
+ * @returns A promise that resolves to the content length of the object in bytes, or 0 if not found.
291
+ * @throws {Error} If the content length header is not found in the response.
292
+ */
293
+ getContentLength(key: string, ssecHeaders?: SSECHeaders): Promise<number>;
294
+ /**
295
+ * Checks if an object exists in the S3-compatible service.
296
+ * This method sends a HEAD request to check if the specified object exists.
297
+ * @param {string} key - The key of the object to check.
298
+ * @param {Record<string, unknown>} [opts={}] - Additional options for the request.
299
+ * @returns A promise that resolves to true if the object exists, false if not found, or null if ETag mismatch.
300
+ */
156
301
  objectExists(key: string, opts?: Record<string, unknown>): Promise<ExistResponseCode>;
157
- getEtag(key: string, opts?: Record<string, unknown>): Promise<string | null>;
158
- putObject(key: string, data: string | Buffer, fileType?: string): Promise<Response>;
159
- getMultipartUploadId(key: string, fileType?: string): Promise<string>;
160
- uploadPart(key: string, uploadId: string, data: Buffer | string, partNumber: number, opts?: Record<string, unknown>): Promise<UploadPart>;
302
+ /**
303
+ * Retrieves the ETag of an object without downloading its content.
304
+ * @param {string} key - The key of the object to retrieve the ETag for.
305
+ * @param {Record<string, unknown>} [opts={}] - Additional options for the request.
306
+ * @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
307
+ * @returns {Promise<string | null>} A promise that resolves to the ETag value or null if the object is not found.
308
+ * @throws {Error} If the ETag header is not found in the response.
309
+ * @example
310
+ * const etag = await s3.getEtag('path/to/file.txt');
311
+ * if (etag) {
312
+ * console.log(`File ETag: ${etag}`);
313
+ * }
314
+ */
315
+ getEtag(key: string, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<string | null>;
316
+ /**
317
+ * Uploads an object to the S3-compatible service.
318
+ * @param {string} key - The key/path where the object will be stored.
319
+ * @param {string | Buffer} data - The data to upload (string or Buffer).
320
+ * @param {string} [fileType='application/octet-stream'] - The MIME type of the file.
321
+ * @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
322
+ * @returns {Promise<Response>} A promise that resolves to the Response object from the upload request.
323
+ * @throws {TypeError} If data is not a string or Buffer.
324
+ * @example
325
+ * // Upload text file
326
+ * await s3.putObject('hello.txt', 'Hello, World!', 'text/plain');
327
+ *
328
+ * // Upload binary data
329
+ * const buffer = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
330
+ * await s3.putObject('image.png', buffer, 'image/png');
331
+ */
332
+ putObject(key: string, data: string | Buffer, fileType?: string, ssecHeaders?: SSECHeaders): Promise<Response>;
333
+ /**
334
+ * Initiates a multipart upload and returns the upload ID.
335
+ * @param {string} key - The key/path where the object will be stored.
336
+ * @param {string} [fileType='application/octet-stream'] - The MIME type of the file.
337
+ * @param {IT.SSECHeaders?} [ssecHeaders] - Server-Side Encryption headers, if any.
338
+ * @returns {Promise<string>} A promise that resolves to the upload ID for the multipart upload.
339
+ * @throws {TypeError} If key is invalid or fileType is not a string.
340
+ * @throws {Error} If the multipart upload fails to initialize.
341
+ * @example
342
+ * const uploadId = await s3.getMultipartUploadId('large-file.zip', 'application/zip');
343
+ * console.log(`Started multipart upload: ${uploadId}`);
344
+ */
345
+ getMultipartUploadId(key: string, fileType?: string, ssecHeaders?: SSECHeaders): Promise<string>;
346
+ /**
347
+ * Uploads a part in a multipart upload.
348
+ * @param {string} key - The key of the object being uploaded.
349
+ * @param {string} uploadId - The upload ID from getMultipartUploadId.
350
+ * @param {Buffer | string} data - The data for this part.
351
+ * @param {number} partNumber - The part number (must be between 1 and 10,000).
352
+ * @param {Record<string, unknown>} [opts={}] - Additional options for the request.
353
+ * @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
354
+ * @returns {Promise<IT.UploadPart>} A promise that resolves to an object containing the partNumber and etag.
355
+ * @throws {TypeError} If any parameter is invalid.
356
+ * @example
357
+ * const part = await s3.uploadPart(
358
+ * 'large-file.zip',
359
+ * uploadId,
360
+ * partData,
361
+ * 1
362
+ * );
363
+ * console.log(`Part ${part.partNumber} uploaded with ETag: ${part.etag}`);
364
+ */
365
+ uploadPart(key: string, uploadId: string, data: Buffer | string, partNumber: number, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<UploadPart>;
366
+ /**
367
+ * Completes a multipart upload by combining all uploaded parts.
368
+ * @param {string} key - The key of the object being uploaded.
369
+ * @param {string} uploadId - The upload ID from getMultipartUploadId.
370
+ * @param {Array<IT.UploadPart>} parts - Array of uploaded parts with partNumber and etag.
371
+ * @returns {Promise<IT.CompleteMultipartUploadResult>} A promise that resolves to the completion result containing the final ETag.
372
+ * @throws {Error} If the multipart upload fails to complete.
373
+ * @example
374
+ * const result = await s3.completeMultipartUpload(
375
+ * 'large-file.zip',
376
+ * uploadId,
377
+ * [
378
+ * { partNumber: 1, etag: 'abc123' },
379
+ * { partNumber: 2, etag: 'def456' }
380
+ * ]
381
+ * );
382
+ * console.log(`Upload completed with ETag: ${result.etag}`);
383
+ */
161
384
  completeMultipartUpload(key: string, uploadId: string, parts: Array<UploadPart>): Promise<CompleteMultipartUploadResult>;
162
- abortMultipartUpload(key: string, uploadId: string): Promise<object>;
385
+ /**
386
+ * Aborts a multipart upload and removes all uploaded parts.
387
+ * @param {string} key - The key of the object being uploaded.
388
+ * @param {string} uploadId - The upload ID to abort.
389
+ * @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
390
+ * @returns {Promise<object>} A promise that resolves to an object containing the abort status and details.
391
+ * @throws {TypeError} If key or uploadId is invalid.
392
+ * @throws {Error} If the abort operation fails.
393
+ * @example
394
+ * try {
395
+ * const result = await s3.abortMultipartUpload('large-file.zip', uploadId);
396
+ * console.log('Upload aborted:', result.status);
397
+ * } catch (error) {
398
+ * console.error('Failed to abort upload:', error);
399
+ * }
400
+ */
401
+ abortMultipartUpload(key: string, uploadId: string, ssecHeaders?: SSECHeaders): Promise<object>;
163
402
  private _buildCompleteMultipartUploadXml;
403
+ /**
404
+ * Deletes an object from the bucket.
405
+ * This method sends a request to delete the specified object from the bucket.
406
+ * @param {string} key - The key of the object to delete.
407
+ * @returns A promise that resolves to true if the object was deleted successfully, false otherwise.
408
+ */
164
409
  deleteObject(key: string): Promise<boolean>;
410
+ private _deleteObjectsProcess;
411
+ /**
412
+ * Deletes multiple objects from the bucket.
413
+ * @param {string[]} keys - An array of object keys to delete.
414
+ * @returns A promise that resolves to an array of booleans indicating success for each key in order.
415
+ */
416
+ deleteObjects(keys: string[]): Promise<boolean[]>;
165
417
  private _sendRequest;
166
418
  private _handleErrorResponse;
167
419
  private _buildCanonicalQueryString;
168
420
  private _getSignatureKey;
169
421
  }
422
+ /**
423
+ * @deprecated Use `S3mini` instead.
424
+ */
425
+ declare const s3mini: typeof S3mini;
170
426
 
171
427
  /**
172
428
  * Sanitize ETag value by removing quotes and XML entities
@@ -185,4 +441,5 @@ declare const sanitizeETag: (etag: string) => string;
185
441
  */
186
442
  declare const runInBatches: <T = unknown>(tasks: Iterable<() => Promise<T>>, batchSize?: number, minIntervalMs?: number) => Promise<Array<PromiseSettledResult<T>>>;
187
443
 
188
- export { type CompleteMultipartUploadResult, type ErrorWithCode, type ExistResponseCode, type ListBucketResponse, type ListMultipartUploadResponse, type Logger, type S3Config, type UploadPart, s3mini as default, runInBatches, s3mini, sanitizeETag };
444
+ export { S3mini, S3mini as default, runInBatches, s3mini, sanitizeETag };
445
+ export type { CompleteMultipartUploadResult, ErrorWithCode, ExistResponseCode, ListBucketResponse, ListMultipartUploadResponse, Logger, S3Config, UploadPart };