@wrelik/storage 0.1.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/CHANGELOG.md +12 -0
- package/package.json +23 -0
- package/src/index.ts +84 -0
- package/tsconfig.json +7 -0
package/CHANGELOG.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wrelik/storage",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@aws-sdk/client-s3": "^3.500.0",
|
|
8
|
+
"@aws-sdk/s3-request-presigner": "^3.500.0",
|
|
9
|
+
"@wrelik/errors": "0.1.0"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@types/node": "^25.2.0",
|
|
13
|
+
"tsup": "^8.0.1",
|
|
14
|
+
"vitest": "^1.2.2",
|
|
15
|
+
"@wrelik/eslint-config": "0.1.0",
|
|
16
|
+
"@wrelik/tsconfig": "0.1.0"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
20
|
+
"lint": "eslint src/",
|
|
21
|
+
"test": "vitest run --passWithNoTests"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
2
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
3
|
+
import { ValidationError } from '@wrelik/errors';
|
|
4
|
+
|
|
5
|
+
export interface StorageConfig {
|
|
6
|
+
accountId: string;
|
|
7
|
+
accessKeyId: string;
|
|
8
|
+
secretAccessKey: string;
|
|
9
|
+
bucketName: string;
|
|
10
|
+
region?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let client: S3Client;
|
|
14
|
+
let bucket: string;
|
|
15
|
+
|
|
16
|
+
export function initStorage(config: StorageConfig) {
|
|
17
|
+
// For R2, endpoint: https://<accountid>.r2.cloudflarestorage.com
|
|
18
|
+
const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
|
|
19
|
+
|
|
20
|
+
client = new S3Client({
|
|
21
|
+
region: config.region || 'auto',
|
|
22
|
+
endpoint,
|
|
23
|
+
credentials: {
|
|
24
|
+
accessKeyId: config.accessKeyId,
|
|
25
|
+
secretAccessKey: config.secretAccessKey,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
bucket = config.bucketName;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getClient() {
|
|
33
|
+
if (!client) throw new Error('Storage not initialized. Call initStorage first.');
|
|
34
|
+
return client;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function putObject(
|
|
38
|
+
body: Buffer | Uint8Array | Blob | string,
|
|
39
|
+
key: string,
|
|
40
|
+
contentType: string,
|
|
41
|
+
metadata?: Record<string, string>,
|
|
42
|
+
) {
|
|
43
|
+
const command = new PutObjectCommand({
|
|
44
|
+
Bucket: bucket,
|
|
45
|
+
Key: key,
|
|
46
|
+
Body: body,
|
|
47
|
+
ContentType: contentType,
|
|
48
|
+
Metadata: metadata,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return getClient().send(command);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function getSignedUploadUrl(key: string, contentType: string, expiresIn = 3600) {
|
|
55
|
+
const command = new PutObjectCommand({
|
|
56
|
+
Bucket: bucket,
|
|
57
|
+
Key: key,
|
|
58
|
+
ContentType: contentType,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return getSignedUrl(getClient(), command, { expiresIn });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function getSignedDownloadUrl(key: string, expiresIn = 3600) {
|
|
65
|
+
const command = new GetObjectCommand({
|
|
66
|
+
Bucket: bucket,
|
|
67
|
+
Key: key,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return getSignedUrl(getClient(), command, { expiresIn });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function validateUpload(
|
|
74
|
+
file: { contentType: string; sizeBytes: number },
|
|
75
|
+
policy: { maxSizeBytes: number; allowedTypes: string[] },
|
|
76
|
+
) {
|
|
77
|
+
if (file.sizeBytes > policy.maxSizeBytes) {
|
|
78
|
+
throw new ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
|
|
79
|
+
}
|
|
80
|
+
if (!policy.allowedTypes.includes(file.contentType)) {
|
|
81
|
+
throw new ValidationError(`Invalid file type ${file.contentType}`);
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}
|