@constructive-io/s3-utils 2.10.2 → 2.12.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/client.d.ts +74 -0
- package/client.js +80 -0
- package/esm/client.js +75 -0
- package/esm/index.js +2 -0
- package/esm/presigned.js +84 -0
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +5 -4
- package/presigned.d.ts +73 -0
- package/presigned.js +89 -0
package/client.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified S3 client factory.
|
|
3
|
+
*
|
|
4
|
+
* Provides a single, canonical way to create an S3Client across the
|
|
5
|
+
* Constructive ecosystem. Consumers import from @constructive-io/s3-utils
|
|
6
|
+
* instead of wiring up @aws-sdk/client-s3 directly.
|
|
7
|
+
*
|
|
8
|
+
* Handles provider-specific defaults:
|
|
9
|
+
* - minio: forces path-style URLs, requires endpoint
|
|
10
|
+
* - r2: forces path-style URLs, requires endpoint
|
|
11
|
+
* - gcs: forces path-style URLs, requires endpoint
|
|
12
|
+
* - s3: virtual-hosted style (AWS default)
|
|
13
|
+
* - spaces: virtual-hosted style (DigitalOcean default)
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { createS3Client } from '@constructive-io/s3-utils';
|
|
18
|
+
*
|
|
19
|
+
* const client = createS3Client({
|
|
20
|
+
* provider: 'minio',
|
|
21
|
+
* region: 'us-east-1',
|
|
22
|
+
* endpoint: 'http://minio:9000',
|
|
23
|
+
* accessKeyId: 'minioadmin',
|
|
24
|
+
* secretAccessKey: 'minioadmin',
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
29
|
+
/**
|
|
30
|
+
* Supported storage provider identifiers.
|
|
31
|
+
*
|
|
32
|
+
* Used to select provider-specific behavior (e.g., path-style URLs for MinIO,
|
|
33
|
+
* jurisdiction headers for R2).
|
|
34
|
+
*/
|
|
35
|
+
export type StorageProvider = 's3' | 'minio' | 'r2' | 'gcs' | 'spaces';
|
|
36
|
+
/**
|
|
37
|
+
* Connection configuration for an S3-compatible storage backend.
|
|
38
|
+
*
|
|
39
|
+
* This is the input you provide to connect to your storage provider.
|
|
40
|
+
* For AWS S3, only `region` and credentials are needed.
|
|
41
|
+
* For MinIO/R2/etc., also provide `endpoint`.
|
|
42
|
+
*/
|
|
43
|
+
export interface StorageConnectionConfig {
|
|
44
|
+
/** Storage provider type */
|
|
45
|
+
provider: StorageProvider;
|
|
46
|
+
/** S3 region (e.g., "us-east-1"). Required for AWS S3. */
|
|
47
|
+
region: string;
|
|
48
|
+
/** S3-compatible endpoint URL (e.g., "http://minio:9000"). Required for non-AWS providers. */
|
|
49
|
+
endpoint?: string;
|
|
50
|
+
/** AWS access key ID */
|
|
51
|
+
accessKeyId: string;
|
|
52
|
+
/** AWS secret access key */
|
|
53
|
+
secretAccessKey: string;
|
|
54
|
+
/** Use path-style URLs (required for MinIO, optional for others) */
|
|
55
|
+
forcePathStyle?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Structured error for S3 client configuration issues.
|
|
59
|
+
*/
|
|
60
|
+
export declare class S3ConfigError extends Error {
|
|
61
|
+
readonly code: string;
|
|
62
|
+
constructor(code: string, message: string);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create an S3Client from a storage connection config.
|
|
66
|
+
*
|
|
67
|
+
* Provider-specific defaults:
|
|
68
|
+
* - `minio`: forces path-style URLs (required by MinIO)
|
|
69
|
+
* - `r2`: forces path-style URLs (required by Cloudflare R2)
|
|
70
|
+
* - `s3`: uses virtual-hosted style (AWS default)
|
|
71
|
+
* - `gcs`: forces path-style URLs (GCS S3-compatible API)
|
|
72
|
+
* - `spaces`: uses virtual-hosted style (DigitalOcean default)
|
|
73
|
+
*/
|
|
74
|
+
export declare function createS3Client(config: StorageConnectionConfig): S3Client;
|
package/client.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unified S3 client factory.
|
|
4
|
+
*
|
|
5
|
+
* Provides a single, canonical way to create an S3Client across the
|
|
6
|
+
* Constructive ecosystem. Consumers import from @constructive-io/s3-utils
|
|
7
|
+
* instead of wiring up @aws-sdk/client-s3 directly.
|
|
8
|
+
*
|
|
9
|
+
* Handles provider-specific defaults:
|
|
10
|
+
* - minio: forces path-style URLs, requires endpoint
|
|
11
|
+
* - r2: forces path-style URLs, requires endpoint
|
|
12
|
+
* - gcs: forces path-style URLs, requires endpoint
|
|
13
|
+
* - s3: virtual-hosted style (AWS default)
|
|
14
|
+
* - spaces: virtual-hosted style (DigitalOcean default)
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { createS3Client } from '@constructive-io/s3-utils';
|
|
19
|
+
*
|
|
20
|
+
* const client = createS3Client({
|
|
21
|
+
* provider: 'minio',
|
|
22
|
+
* region: 'us-east-1',
|
|
23
|
+
* endpoint: 'http://minio:9000',
|
|
24
|
+
* accessKeyId: 'minioadmin',
|
|
25
|
+
* secretAccessKey: 'minioadmin',
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.S3ConfigError = void 0;
|
|
31
|
+
exports.createS3Client = createS3Client;
|
|
32
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
33
|
+
// --- Error ---
|
|
34
|
+
/**
|
|
35
|
+
* Structured error for S3 client configuration issues.
|
|
36
|
+
*/
|
|
37
|
+
class S3ConfigError extends Error {
|
|
38
|
+
code;
|
|
39
|
+
constructor(code, message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = 'S3ConfigError';
|
|
42
|
+
this.code = code;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.S3ConfigError = S3ConfigError;
|
|
46
|
+
// --- Factory ---
|
|
47
|
+
/**
|
|
48
|
+
* Create an S3Client from a storage connection config.
|
|
49
|
+
*
|
|
50
|
+
* Provider-specific defaults:
|
|
51
|
+
* - `minio`: forces path-style URLs (required by MinIO)
|
|
52
|
+
* - `r2`: forces path-style URLs (required by Cloudflare R2)
|
|
53
|
+
* - `s3`: uses virtual-hosted style (AWS default)
|
|
54
|
+
* - `gcs`: forces path-style URLs (GCS S3-compatible API)
|
|
55
|
+
* - `spaces`: uses virtual-hosted style (DigitalOcean default)
|
|
56
|
+
*/
|
|
57
|
+
function createS3Client(config) {
|
|
58
|
+
if (!config.accessKeyId || !config.secretAccessKey) {
|
|
59
|
+
throw new S3ConfigError('INVALID_CONFIG', 'accessKeyId and secretAccessKey are required');
|
|
60
|
+
}
|
|
61
|
+
if (!config.region) {
|
|
62
|
+
throw new S3ConfigError('INVALID_CONFIG', 'region is required');
|
|
63
|
+
}
|
|
64
|
+
// Providers that require path-style URLs
|
|
65
|
+
const pathStyleProviders = new Set(['minio', 'r2', 'gcs']);
|
|
66
|
+
const forcePathStyle = config.forcePathStyle ?? pathStyleProviders.has(config.provider);
|
|
67
|
+
// Non-AWS providers require an endpoint
|
|
68
|
+
if (config.provider !== 's3' && !config.endpoint) {
|
|
69
|
+
throw new S3ConfigError('INVALID_CONFIG', `endpoint is required for provider '${config.provider}'`);
|
|
70
|
+
}
|
|
71
|
+
return new client_s3_1.S3Client({
|
|
72
|
+
region: config.region,
|
|
73
|
+
endpoint: config.endpoint,
|
|
74
|
+
forcePathStyle,
|
|
75
|
+
credentials: {
|
|
76
|
+
accessKeyId: config.accessKeyId,
|
|
77
|
+
secretAccessKey: config.secretAccessKey,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
package/esm/client.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified S3 client factory.
|
|
3
|
+
*
|
|
4
|
+
* Provides a single, canonical way to create an S3Client across the
|
|
5
|
+
* Constructive ecosystem. Consumers import from @constructive-io/s3-utils
|
|
6
|
+
* instead of wiring up @aws-sdk/client-s3 directly.
|
|
7
|
+
*
|
|
8
|
+
* Handles provider-specific defaults:
|
|
9
|
+
* - minio: forces path-style URLs, requires endpoint
|
|
10
|
+
* - r2: forces path-style URLs, requires endpoint
|
|
11
|
+
* - gcs: forces path-style URLs, requires endpoint
|
|
12
|
+
* - s3: virtual-hosted style (AWS default)
|
|
13
|
+
* - spaces: virtual-hosted style (DigitalOcean default)
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { createS3Client } from '@constructive-io/s3-utils';
|
|
18
|
+
*
|
|
19
|
+
* const client = createS3Client({
|
|
20
|
+
* provider: 'minio',
|
|
21
|
+
* region: 'us-east-1',
|
|
22
|
+
* endpoint: 'http://minio:9000',
|
|
23
|
+
* accessKeyId: 'minioadmin',
|
|
24
|
+
* secretAccessKey: 'minioadmin',
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
29
|
+
// --- Error ---
|
|
30
|
+
/**
|
|
31
|
+
* Structured error for S3 client configuration issues.
|
|
32
|
+
*/
|
|
33
|
+
export class S3ConfigError extends Error {
|
|
34
|
+
code;
|
|
35
|
+
constructor(code, message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'S3ConfigError';
|
|
38
|
+
this.code = code;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// --- Factory ---
|
|
42
|
+
/**
|
|
43
|
+
* Create an S3Client from a storage connection config.
|
|
44
|
+
*
|
|
45
|
+
* Provider-specific defaults:
|
|
46
|
+
* - `minio`: forces path-style URLs (required by MinIO)
|
|
47
|
+
* - `r2`: forces path-style URLs (required by Cloudflare R2)
|
|
48
|
+
* - `s3`: uses virtual-hosted style (AWS default)
|
|
49
|
+
* - `gcs`: forces path-style URLs (GCS S3-compatible API)
|
|
50
|
+
* - `spaces`: uses virtual-hosted style (DigitalOcean default)
|
|
51
|
+
*/
|
|
52
|
+
export function createS3Client(config) {
|
|
53
|
+
if (!config.accessKeyId || !config.secretAccessKey) {
|
|
54
|
+
throw new S3ConfigError('INVALID_CONFIG', 'accessKeyId and secretAccessKey are required');
|
|
55
|
+
}
|
|
56
|
+
if (!config.region) {
|
|
57
|
+
throw new S3ConfigError('INVALID_CONFIG', 'region is required');
|
|
58
|
+
}
|
|
59
|
+
// Providers that require path-style URLs
|
|
60
|
+
const pathStyleProviders = new Set(['minio', 'r2', 'gcs']);
|
|
61
|
+
const forcePathStyle = config.forcePathStyle ?? pathStyleProviders.has(config.provider);
|
|
62
|
+
// Non-AWS providers require an endpoint
|
|
63
|
+
if (config.provider !== 's3' && !config.endpoint) {
|
|
64
|
+
throw new S3ConfigError('INVALID_CONFIG', `endpoint is required for provider '${config.provider}'`);
|
|
65
|
+
}
|
|
66
|
+
return new S3Client({
|
|
67
|
+
region: config.region,
|
|
68
|
+
endpoint: config.endpoint,
|
|
69
|
+
forcePathStyle,
|
|
70
|
+
credentials: {
|
|
71
|
+
accessKeyId: config.accessKeyId,
|
|
72
|
+
secretAccessKey: config.secretAccessKey,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
package/esm/index.js
CHANGED
package/esm/presigned.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presigned URL helpers.
|
|
3
|
+
*
|
|
4
|
+
* Generates presigned PUT and GET URLs for S3-compatible storage.
|
|
5
|
+
* Consumers import from @constructive-io/s3-utils instead of wiring
|
|
6
|
+
* up @aws-sdk/s3-request-presigner directly.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { createS3Client, presignGetUrl, presignPutUrl } from '@constructive-io/s3-utils';
|
|
11
|
+
*
|
|
12
|
+
* const client = createS3Client({ ... });
|
|
13
|
+
*
|
|
14
|
+
* const putUrl = await presignPutUrl(client, {
|
|
15
|
+
* bucket: 'my-bucket',
|
|
16
|
+
* key: 'uploads/photo.jpg',
|
|
17
|
+
* contentType: 'image/jpeg',
|
|
18
|
+
* contentLength: 102400,
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* const getUrl = await presignGetUrl(client, {
|
|
22
|
+
* bucket: 'my-bucket',
|
|
23
|
+
* key: 'uploads/photo.jpg',
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
import { PutObjectCommand, GetObjectCommand, HeadObjectCommand, } from '@aws-sdk/client-s3';
|
|
28
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
29
|
+
// --- Functions ---
|
|
30
|
+
/**
|
|
31
|
+
* Generate a presigned PUT URL for uploading a file to S3.
|
|
32
|
+
*
|
|
33
|
+
* The presigned URL is locked to the specific key, content-type, and
|
|
34
|
+
* content-length via the signature. The client MUST use these exact values
|
|
35
|
+
* when performing the PUT request.
|
|
36
|
+
*/
|
|
37
|
+
export async function presignPutUrl(client, options) {
|
|
38
|
+
const { bucket, key, contentType, contentLength, expiresIn = 900 } = options;
|
|
39
|
+
const command = new PutObjectCommand({
|
|
40
|
+
Bucket: bucket,
|
|
41
|
+
Key: key,
|
|
42
|
+
ContentType: contentType,
|
|
43
|
+
ContentLength: contentLength,
|
|
44
|
+
});
|
|
45
|
+
return getSignedUrl(client, command, { expiresIn });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Generate a presigned GET URL for downloading a file from S3.
|
|
49
|
+
*
|
|
50
|
+
* Used for private files that shouldn't be served through a public CDN.
|
|
51
|
+
*/
|
|
52
|
+
export async function presignGetUrl(client, options) {
|
|
53
|
+
const { bucket, key, expiresIn = 3600, filename } = options;
|
|
54
|
+
const params = {
|
|
55
|
+
Bucket: bucket,
|
|
56
|
+
Key: key,
|
|
57
|
+
};
|
|
58
|
+
if (filename) {
|
|
59
|
+
const sanitized = filename.replace(/["\\\r\n]/g, '_');
|
|
60
|
+
params.ResponseContentDisposition = `attachment; filename="${sanitized}"`;
|
|
61
|
+
}
|
|
62
|
+
const command = new GetObjectCommand(params);
|
|
63
|
+
return getSignedUrl(client, command, { expiresIn });
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if an object exists in S3 and return its metadata.
|
|
67
|
+
*
|
|
68
|
+
* Returns null if the object does not exist.
|
|
69
|
+
*/
|
|
70
|
+
export async function headObject(client, bucket, key) {
|
|
71
|
+
try {
|
|
72
|
+
const response = await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
|
|
73
|
+
return {
|
|
74
|
+
contentType: response.ContentType,
|
|
75
|
+
contentLength: response.ContentLength,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
if (e.name === 'NotFound' || e.$metadata?.httpStatusCode === 404) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
}
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -14,5 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./client"), exports);
|
|
17
18
|
__exportStar(require("./policies"), exports);
|
|
19
|
+
__exportStar(require("./presigned"), exports);
|
|
18
20
|
__exportStar(require("./utils"), exports);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/s3-utils",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Unified S3 utilities — client factory, file operations, presigned URLs",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"module": "esm/index.js",
|
|
8
8
|
"types": "index.d.ts",
|
|
@@ -31,11 +31,12 @@
|
|
|
31
31
|
"keywords": [],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@aws-sdk/client-s3": "^3.1009.0",
|
|
34
|
-
"@aws-sdk/lib-storage": "^3.1009.0"
|
|
34
|
+
"@aws-sdk/lib-storage": "^3.1009.0",
|
|
35
|
+
"@aws-sdk/s3-request-presigner": "^3.1009.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@types/node": "^22.19.11",
|
|
38
39
|
"makage": "^0.3.0"
|
|
39
40
|
},
|
|
40
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "434a578648cb04d3ed25e79a75c654f71573292f"
|
|
41
42
|
}
|
package/presigned.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presigned URL helpers.
|
|
3
|
+
*
|
|
4
|
+
* Generates presigned PUT and GET URLs for S3-compatible storage.
|
|
5
|
+
* Consumers import from @constructive-io/s3-utils instead of wiring
|
|
6
|
+
* up @aws-sdk/s3-request-presigner directly.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { createS3Client, presignGetUrl, presignPutUrl } from '@constructive-io/s3-utils';
|
|
11
|
+
*
|
|
12
|
+
* const client = createS3Client({ ... });
|
|
13
|
+
*
|
|
14
|
+
* const putUrl = await presignPutUrl(client, {
|
|
15
|
+
* bucket: 'my-bucket',
|
|
16
|
+
* key: 'uploads/photo.jpg',
|
|
17
|
+
* contentType: 'image/jpeg',
|
|
18
|
+
* contentLength: 102400,
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* const getUrl = await presignGetUrl(client, {
|
|
22
|
+
* bucket: 'my-bucket',
|
|
23
|
+
* key: 'uploads/photo.jpg',
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
import type { S3Client } from '@aws-sdk/client-s3';
|
|
28
|
+
export interface PresignPutOptions {
|
|
29
|
+
/** S3 bucket name */
|
|
30
|
+
bucket: string;
|
|
31
|
+
/** S3 object key */
|
|
32
|
+
key: string;
|
|
33
|
+
/** MIME type (locked into the signature) */
|
|
34
|
+
contentType: string;
|
|
35
|
+
/** File size in bytes (locked into the signature) */
|
|
36
|
+
contentLength: number;
|
|
37
|
+
/** URL expiry in seconds (default: 900 = 15 minutes) */
|
|
38
|
+
expiresIn?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface PresignGetOptions {
|
|
41
|
+
/** S3 bucket name */
|
|
42
|
+
bucket: string;
|
|
43
|
+
/** S3 object key */
|
|
44
|
+
key: string;
|
|
45
|
+
/** URL expiry in seconds (default: 3600 = 1 hour) */
|
|
46
|
+
expiresIn?: number;
|
|
47
|
+
/** Optional filename for Content-Disposition header */
|
|
48
|
+
filename?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface HeadObjectResult {
|
|
51
|
+
contentType?: string;
|
|
52
|
+
contentLength?: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Generate a presigned PUT URL for uploading a file to S3.
|
|
56
|
+
*
|
|
57
|
+
* The presigned URL is locked to the specific key, content-type, and
|
|
58
|
+
* content-length via the signature. The client MUST use these exact values
|
|
59
|
+
* when performing the PUT request.
|
|
60
|
+
*/
|
|
61
|
+
export declare function presignPutUrl(client: S3Client, options: PresignPutOptions): Promise<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Generate a presigned GET URL for downloading a file from S3.
|
|
64
|
+
*
|
|
65
|
+
* Used for private files that shouldn't be served through a public CDN.
|
|
66
|
+
*/
|
|
67
|
+
export declare function presignGetUrl(client: S3Client, options: PresignGetOptions): Promise<string>;
|
|
68
|
+
/**
|
|
69
|
+
* Check if an object exists in S3 and return its metadata.
|
|
70
|
+
*
|
|
71
|
+
* Returns null if the object does not exist.
|
|
72
|
+
*/
|
|
73
|
+
export declare function headObject(client: S3Client, bucket: string, key: string): Promise<HeadObjectResult | null>;
|
package/presigned.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Presigned URL helpers.
|
|
4
|
+
*
|
|
5
|
+
* Generates presigned PUT and GET URLs for S3-compatible storage.
|
|
6
|
+
* Consumers import from @constructive-io/s3-utils instead of wiring
|
|
7
|
+
* up @aws-sdk/s3-request-presigner directly.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createS3Client, presignGetUrl, presignPutUrl } from '@constructive-io/s3-utils';
|
|
12
|
+
*
|
|
13
|
+
* const client = createS3Client({ ... });
|
|
14
|
+
*
|
|
15
|
+
* const putUrl = await presignPutUrl(client, {
|
|
16
|
+
* bucket: 'my-bucket',
|
|
17
|
+
* key: 'uploads/photo.jpg',
|
|
18
|
+
* contentType: 'image/jpeg',
|
|
19
|
+
* contentLength: 102400,
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* const getUrl = await presignGetUrl(client, {
|
|
23
|
+
* bucket: 'my-bucket',
|
|
24
|
+
* key: 'uploads/photo.jpg',
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.presignPutUrl = presignPutUrl;
|
|
30
|
+
exports.presignGetUrl = presignGetUrl;
|
|
31
|
+
exports.headObject = headObject;
|
|
32
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
33
|
+
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
34
|
+
// --- Functions ---
|
|
35
|
+
/**
|
|
36
|
+
* Generate a presigned PUT URL for uploading a file to S3.
|
|
37
|
+
*
|
|
38
|
+
* The presigned URL is locked to the specific key, content-type, and
|
|
39
|
+
* content-length via the signature. The client MUST use these exact values
|
|
40
|
+
* when performing the PUT request.
|
|
41
|
+
*/
|
|
42
|
+
async function presignPutUrl(client, options) {
|
|
43
|
+
const { bucket, key, contentType, contentLength, expiresIn = 900 } = options;
|
|
44
|
+
const command = new client_s3_1.PutObjectCommand({
|
|
45
|
+
Bucket: bucket,
|
|
46
|
+
Key: key,
|
|
47
|
+
ContentType: contentType,
|
|
48
|
+
ContentLength: contentLength,
|
|
49
|
+
});
|
|
50
|
+
return (0, s3_request_presigner_1.getSignedUrl)(client, command, { expiresIn });
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate a presigned GET URL for downloading a file from S3.
|
|
54
|
+
*
|
|
55
|
+
* Used for private files that shouldn't be served through a public CDN.
|
|
56
|
+
*/
|
|
57
|
+
async function presignGetUrl(client, options) {
|
|
58
|
+
const { bucket, key, expiresIn = 3600, filename } = options;
|
|
59
|
+
const params = {
|
|
60
|
+
Bucket: bucket,
|
|
61
|
+
Key: key,
|
|
62
|
+
};
|
|
63
|
+
if (filename) {
|
|
64
|
+
const sanitized = filename.replace(/["\\\r\n]/g, '_');
|
|
65
|
+
params.ResponseContentDisposition = `attachment; filename="${sanitized}"`;
|
|
66
|
+
}
|
|
67
|
+
const command = new client_s3_1.GetObjectCommand(params);
|
|
68
|
+
return (0, s3_request_presigner_1.getSignedUrl)(client, command, { expiresIn });
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if an object exists in S3 and return its metadata.
|
|
72
|
+
*
|
|
73
|
+
* Returns null if the object does not exist.
|
|
74
|
+
*/
|
|
75
|
+
async function headObject(client, bucket, key) {
|
|
76
|
+
try {
|
|
77
|
+
const response = await client.send(new client_s3_1.HeadObjectCommand({ Bucket: bucket, Key: key }));
|
|
78
|
+
return {
|
|
79
|
+
contentType: response.ContentType,
|
|
80
|
+
contentLength: response.ContentLength,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
if (e.name === 'NotFound' || e.$metadata?.httpStatusCode === 404) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
throw e;
|
|
88
|
+
}
|
|
89
|
+
}
|