@wrelik/storage 0.1.0 → 0.2.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/.turbo/turbo-build.log +26 -0
- package/CHANGELOG.md +19 -0
- package/dist/chunk-6VKCOC6I.mjs +15 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +96 -0
- package/dist/index.mjs +57 -0
- package/dist/react-native.d.mts +14 -0
- package/dist/react-native.d.ts +14 -0
- package/dist/react-native.js +73 -0
- package/dist/react-native.mjs +36 -0
- package/dist/shared-DqbBUqiW.d.mts +9 -0
- package/dist/shared-DqbBUqiW.d.ts +9 -0
- package/package.json +17 -7
- package/src/index.ts +2 -13
- package/src/react-native.ts +36 -0
- package/src/shared.test.ts +17 -0
- package/src/shared.ts +14 -0
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
> @wrelik/storage@0.2.0 build /home/runner/work/wrelik-kit/wrelik-kit/packages/storage
|
|
3
|
+
> tsup src/index.ts src/react-native.ts --format cjs,esm --dts --clean
|
|
4
|
+
|
|
5
|
+
[34mCLI[39m Building entry: src/index.ts, src/react-native.ts
|
|
6
|
+
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
+
[34mCLI[39m tsup v8.5.1
|
|
8
|
+
[34mCLI[39m Target: es2022
|
|
9
|
+
[34mCLI[39m Cleaning output folder
|
|
10
|
+
[34mCJS[39m Build start
|
|
11
|
+
[34mESM[39m Build start
|
|
12
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m1.46 KB[39m
|
|
13
|
+
[32mESM[39m [1mdist/react-native.mjs [22m[32m701.00 B[39m
|
|
14
|
+
[32mESM[39m [1mdist/chunk-6VKCOC6I.mjs [22m[32m421.00 B[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 61ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.js [22m[32m3.10 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/react-native.js [22m[32m2.17 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 63ms
|
|
19
|
+
[34mDTS[39m Build start
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 4031ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m815.00 B[39m
|
|
22
|
+
[32mDTS[39m [1mdist/react-native.d.ts [22m[32m457.00 B[39m
|
|
23
|
+
[32mDTS[39m [1mdist/shared-DqbBUqiW.d.ts [22m[32m201.00 B[39m
|
|
24
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m816.00 B[39m
|
|
25
|
+
[32mDTS[39m [1mdist/react-native.d.mts [22m[32m458.00 B[39m
|
|
26
|
+
[32mDTS[39m [1mdist/shared-DqbBUqiW.d.mts [22m[32m201.00 B[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @wrelik/storage
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a38ebcb: Add React Native / Expo support and enforce server-only boundaries.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [a38ebcb]
|
|
12
|
+
- @wrelik/errors@0.2.0
|
|
13
|
+
|
|
14
|
+
## 0.1.1
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Update publishConfig to access:public for all packages.
|
|
19
|
+
- Updated dependencies
|
|
20
|
+
- @wrelik/errors@0.1.1
|
|
21
|
+
|
|
3
22
|
## 0.1.0
|
|
4
23
|
|
|
5
24
|
### Minor Changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/shared.ts
|
|
2
|
+
import { ValidationError } from "@wrelik/errors";
|
|
3
|
+
function validateUpload(file, policy) {
|
|
4
|
+
if (file.sizeBytes > policy.maxSizeBytes) {
|
|
5
|
+
throw new ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
|
|
6
|
+
}
|
|
7
|
+
if (!policy.allowedTypes.includes(file.contentType)) {
|
|
8
|
+
throw new ValidationError(`Invalid file type ${file.contentType}`);
|
|
9
|
+
}
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
validateUpload
|
|
15
|
+
};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as _aws_sdk_client_s3 from '@aws-sdk/client-s3';
|
|
2
|
+
export { v as validateUpload } from './shared-DqbBUqiW.mjs';
|
|
3
|
+
|
|
4
|
+
interface StorageConfig {
|
|
5
|
+
accountId: string;
|
|
6
|
+
accessKeyId: string;
|
|
7
|
+
secretAccessKey: string;
|
|
8
|
+
bucketName: string;
|
|
9
|
+
region?: string;
|
|
10
|
+
}
|
|
11
|
+
declare function initStorage(config: StorageConfig): void;
|
|
12
|
+
declare function putObject(body: Buffer | Uint8Array | Blob | string, key: string, contentType: string, metadata?: Record<string, string>): Promise<_aws_sdk_client_s3.PutObjectCommandOutput>;
|
|
13
|
+
declare function getSignedUploadUrl(key: string, contentType: string, expiresIn?: number): Promise<string>;
|
|
14
|
+
declare function getSignedDownloadUrl(key: string, expiresIn?: number): Promise<string>;
|
|
15
|
+
|
|
16
|
+
export { type StorageConfig, getSignedDownloadUrl, getSignedUploadUrl, initStorage, putObject };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as _aws_sdk_client_s3 from '@aws-sdk/client-s3';
|
|
2
|
+
export { v as validateUpload } from './shared-DqbBUqiW.js';
|
|
3
|
+
|
|
4
|
+
interface StorageConfig {
|
|
5
|
+
accountId: string;
|
|
6
|
+
accessKeyId: string;
|
|
7
|
+
secretAccessKey: string;
|
|
8
|
+
bucketName: string;
|
|
9
|
+
region?: string;
|
|
10
|
+
}
|
|
11
|
+
declare function initStorage(config: StorageConfig): void;
|
|
12
|
+
declare function putObject(body: Buffer | Uint8Array | Blob | string, key: string, contentType: string, metadata?: Record<string, string>): Promise<_aws_sdk_client_s3.PutObjectCommandOutput>;
|
|
13
|
+
declare function getSignedUploadUrl(key: string, contentType: string, expiresIn?: number): Promise<string>;
|
|
14
|
+
declare function getSignedDownloadUrl(key: string, expiresIn?: number): Promise<string>;
|
|
15
|
+
|
|
16
|
+
export { type StorageConfig, getSignedDownloadUrl, getSignedUploadUrl, initStorage, putObject };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
getSignedDownloadUrl: () => getSignedDownloadUrl,
|
|
24
|
+
getSignedUploadUrl: () => getSignedUploadUrl,
|
|
25
|
+
initStorage: () => initStorage,
|
|
26
|
+
putObject: () => putObject,
|
|
27
|
+
validateUpload: () => validateUpload
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
31
|
+
var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
|
|
32
|
+
|
|
33
|
+
// src/shared.ts
|
|
34
|
+
var import_errors = require("@wrelik/errors");
|
|
35
|
+
function validateUpload(file, policy) {
|
|
36
|
+
if (file.sizeBytes > policy.maxSizeBytes) {
|
|
37
|
+
throw new import_errors.ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
|
|
38
|
+
}
|
|
39
|
+
if (!policy.allowedTypes.includes(file.contentType)) {
|
|
40
|
+
throw new import_errors.ValidationError(`Invalid file type ${file.contentType}`);
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/index.ts
|
|
46
|
+
var client;
|
|
47
|
+
var bucket;
|
|
48
|
+
function initStorage(config) {
|
|
49
|
+
const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
|
|
50
|
+
client = new import_client_s3.S3Client({
|
|
51
|
+
region: config.region || "auto",
|
|
52
|
+
endpoint,
|
|
53
|
+
credentials: {
|
|
54
|
+
accessKeyId: config.accessKeyId,
|
|
55
|
+
secretAccessKey: config.secretAccessKey
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
bucket = config.bucketName;
|
|
59
|
+
}
|
|
60
|
+
function getClient() {
|
|
61
|
+
if (!client) throw new Error("Storage not initialized. Call initStorage first.");
|
|
62
|
+
return client;
|
|
63
|
+
}
|
|
64
|
+
async function putObject(body, key, contentType, metadata) {
|
|
65
|
+
const command = new import_client_s3.PutObjectCommand({
|
|
66
|
+
Bucket: bucket,
|
|
67
|
+
Key: key,
|
|
68
|
+
Body: body,
|
|
69
|
+
ContentType: contentType,
|
|
70
|
+
Metadata: metadata
|
|
71
|
+
});
|
|
72
|
+
return getClient().send(command);
|
|
73
|
+
}
|
|
74
|
+
async function getSignedUploadUrl(key, contentType, expiresIn = 3600) {
|
|
75
|
+
const command = new import_client_s3.PutObjectCommand({
|
|
76
|
+
Bucket: bucket,
|
|
77
|
+
Key: key,
|
|
78
|
+
ContentType: contentType
|
|
79
|
+
});
|
|
80
|
+
return (0, import_s3_request_presigner.getSignedUrl)(getClient(), command, { expiresIn });
|
|
81
|
+
}
|
|
82
|
+
async function getSignedDownloadUrl(key, expiresIn = 3600) {
|
|
83
|
+
const command = new import_client_s3.GetObjectCommand({
|
|
84
|
+
Bucket: bucket,
|
|
85
|
+
Key: key
|
|
86
|
+
});
|
|
87
|
+
return (0, import_s3_request_presigner.getSignedUrl)(getClient(), command, { expiresIn });
|
|
88
|
+
}
|
|
89
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
90
|
+
0 && (module.exports = {
|
|
91
|
+
getSignedDownloadUrl,
|
|
92
|
+
getSignedUploadUrl,
|
|
93
|
+
initStorage,
|
|
94
|
+
putObject,
|
|
95
|
+
validateUpload
|
|
96
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateUpload
|
|
3
|
+
} from "./chunk-6VKCOC6I.mjs";
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
|
|
7
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
8
|
+
var client;
|
|
9
|
+
var bucket;
|
|
10
|
+
function initStorage(config) {
|
|
11
|
+
const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
|
|
12
|
+
client = new S3Client({
|
|
13
|
+
region: config.region || "auto",
|
|
14
|
+
endpoint,
|
|
15
|
+
credentials: {
|
|
16
|
+
accessKeyId: config.accessKeyId,
|
|
17
|
+
secretAccessKey: config.secretAccessKey
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
bucket = config.bucketName;
|
|
21
|
+
}
|
|
22
|
+
function getClient() {
|
|
23
|
+
if (!client) throw new Error("Storage not initialized. Call initStorage first.");
|
|
24
|
+
return client;
|
|
25
|
+
}
|
|
26
|
+
async function putObject(body, key, contentType, metadata) {
|
|
27
|
+
const command = new PutObjectCommand({
|
|
28
|
+
Bucket: bucket,
|
|
29
|
+
Key: key,
|
|
30
|
+
Body: body,
|
|
31
|
+
ContentType: contentType,
|
|
32
|
+
Metadata: metadata
|
|
33
|
+
});
|
|
34
|
+
return getClient().send(command);
|
|
35
|
+
}
|
|
36
|
+
async function getSignedUploadUrl(key, contentType, expiresIn = 3600) {
|
|
37
|
+
const command = new PutObjectCommand({
|
|
38
|
+
Bucket: bucket,
|
|
39
|
+
Key: key,
|
|
40
|
+
ContentType: contentType
|
|
41
|
+
});
|
|
42
|
+
return getSignedUrl(getClient(), command, { expiresIn });
|
|
43
|
+
}
|
|
44
|
+
async function getSignedDownloadUrl(key, expiresIn = 3600) {
|
|
45
|
+
const command = new GetObjectCommand({
|
|
46
|
+
Bucket: bucket,
|
|
47
|
+
Key: key
|
|
48
|
+
});
|
|
49
|
+
return getSignedUrl(getClient(), command, { expiresIn });
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
getSignedDownloadUrl,
|
|
53
|
+
getSignedUploadUrl,
|
|
54
|
+
initStorage,
|
|
55
|
+
putObject,
|
|
56
|
+
validateUpload
|
|
57
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as node_buffer from 'node:buffer';
|
|
2
|
+
export { v as validateUpload } from './shared-DqbBUqiW.mjs';
|
|
3
|
+
|
|
4
|
+
declare function uploadToSignedUrl({ url, file, contentType, headers, }: {
|
|
5
|
+
url: string;
|
|
6
|
+
file: File | Blob;
|
|
7
|
+
contentType: string;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
}): Promise<boolean>;
|
|
10
|
+
declare function downloadFromSignedUrl({ url }: {
|
|
11
|
+
url: string;
|
|
12
|
+
}): Promise<node_buffer.Blob>;
|
|
13
|
+
|
|
14
|
+
export { downloadFromSignedUrl, uploadToSignedUrl };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as node_buffer from 'node:buffer';
|
|
2
|
+
export { v as validateUpload } from './shared-DqbBUqiW.js';
|
|
3
|
+
|
|
4
|
+
declare function uploadToSignedUrl({ url, file, contentType, headers, }: {
|
|
5
|
+
url: string;
|
|
6
|
+
file: File | Blob;
|
|
7
|
+
contentType: string;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
}): Promise<boolean>;
|
|
10
|
+
declare function downloadFromSignedUrl({ url }: {
|
|
11
|
+
url: string;
|
|
12
|
+
}): Promise<node_buffer.Blob>;
|
|
13
|
+
|
|
14
|
+
export { downloadFromSignedUrl, uploadToSignedUrl };
|
|
@@ -0,0 +1,73 @@
|
|
|
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/react-native.ts
|
|
21
|
+
var react_native_exports = {};
|
|
22
|
+
__export(react_native_exports, {
|
|
23
|
+
downloadFromSignedUrl: () => downloadFromSignedUrl,
|
|
24
|
+
uploadToSignedUrl: () => uploadToSignedUrl,
|
|
25
|
+
validateUpload: () => validateUpload
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(react_native_exports);
|
|
28
|
+
|
|
29
|
+
// src/shared.ts
|
|
30
|
+
var import_errors = require("@wrelik/errors");
|
|
31
|
+
function validateUpload(file, policy) {
|
|
32
|
+
if (file.sizeBytes > policy.maxSizeBytes) {
|
|
33
|
+
throw new import_errors.ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
|
|
34
|
+
}
|
|
35
|
+
if (!policy.allowedTypes.includes(file.contentType)) {
|
|
36
|
+
throw new import_errors.ValidationError(`Invalid file type ${file.contentType}`);
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/react-native.ts
|
|
42
|
+
async function uploadToSignedUrl({
|
|
43
|
+
url,
|
|
44
|
+
file,
|
|
45
|
+
contentType,
|
|
46
|
+
headers = {}
|
|
47
|
+
}) {
|
|
48
|
+
const result = await fetch(url, {
|
|
49
|
+
method: "PUT",
|
|
50
|
+
body: file,
|
|
51
|
+
headers: {
|
|
52
|
+
"Content-Type": contentType,
|
|
53
|
+
...headers
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
if (!result.ok) {
|
|
57
|
+
throw new Error(`Upload failed with status ${result.status}`);
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
async function downloadFromSignedUrl({ url }) {
|
|
62
|
+
const result = await fetch(url);
|
|
63
|
+
if (!result.ok) {
|
|
64
|
+
throw new Error(`Download failed with status ${result.status}`);
|
|
65
|
+
}
|
|
66
|
+
return result.blob();
|
|
67
|
+
}
|
|
68
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
69
|
+
0 && (module.exports = {
|
|
70
|
+
downloadFromSignedUrl,
|
|
71
|
+
uploadToSignedUrl,
|
|
72
|
+
validateUpload
|
|
73
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateUpload
|
|
3
|
+
} from "./chunk-6VKCOC6I.mjs";
|
|
4
|
+
|
|
5
|
+
// src/react-native.ts
|
|
6
|
+
async function uploadToSignedUrl({
|
|
7
|
+
url,
|
|
8
|
+
file,
|
|
9
|
+
contentType,
|
|
10
|
+
headers = {}
|
|
11
|
+
}) {
|
|
12
|
+
const result = await fetch(url, {
|
|
13
|
+
method: "PUT",
|
|
14
|
+
body: file,
|
|
15
|
+
headers: {
|
|
16
|
+
"Content-Type": contentType,
|
|
17
|
+
...headers
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
if (!result.ok) {
|
|
21
|
+
throw new Error(`Upload failed with status ${result.status}`);
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
async function downloadFromSignedUrl({ url }) {
|
|
26
|
+
const result = await fetch(url);
|
|
27
|
+
if (!result.ok) {
|
|
28
|
+
throw new Error(`Download failed with status ${result.status}`);
|
|
29
|
+
}
|
|
30
|
+
return result.blob();
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
downloadFromSignedUrl,
|
|
34
|
+
uploadToSignedUrl,
|
|
35
|
+
validateUpload
|
|
36
|
+
};
|
package/package.json
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrelik/storage",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/lwhite702/wrelik-kit.git",
|
|
10
|
+
"directory": "packages/storage"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js",
|
|
14
|
+
"./react-native": "./dist/react-native.js"
|
|
15
|
+
},
|
|
6
16
|
"dependencies": {
|
|
7
17
|
"@aws-sdk/client-s3": "^3.500.0",
|
|
8
18
|
"@aws-sdk/s3-request-presigner": "^3.500.0",
|
|
9
|
-
"@wrelik/errors": "0.
|
|
19
|
+
"@wrelik/errors": "0.2.0"
|
|
10
20
|
},
|
|
11
21
|
"devDependencies": {
|
|
12
22
|
"@types/node": "^25.2.0",
|
|
13
23
|
"tsup": "^8.0.1",
|
|
14
24
|
"vitest": "^1.2.2",
|
|
15
|
-
"@wrelik/eslint-config": "0.1.
|
|
16
|
-
"@wrelik/tsconfig": "0.1.
|
|
25
|
+
"@wrelik/eslint-config": "0.1.1",
|
|
26
|
+
"@wrelik/tsconfig": "0.1.1"
|
|
17
27
|
},
|
|
18
28
|
"scripts": {
|
|
19
|
-
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
29
|
+
"build": "tsup src/index.ts src/react-native.ts --format cjs,esm --dts --clean",
|
|
20
30
|
"lint": "eslint src/",
|
|
21
31
|
"test": "vitest run --passWithNoTests"
|
|
22
32
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
/* eslint-disable no-restricted-imports */
|
|
1
2
|
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
2
3
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
3
|
-
import { ValidationError } from '@wrelik/errors';
|
|
4
4
|
|
|
5
5
|
export interface StorageConfig {
|
|
6
6
|
accountId: string;
|
|
@@ -70,15 +70,4 @@ export async function getSignedDownloadUrl(key: string, expiresIn = 3600) {
|
|
|
70
70
|
return getSignedUrl(getClient(), command, { expiresIn });
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
export
|
|
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
|
-
}
|
|
73
|
+
export * from './shared';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export * from './shared';
|
|
2
|
+
|
|
3
|
+
export async function uploadToSignedUrl({
|
|
4
|
+
url,
|
|
5
|
+
file,
|
|
6
|
+
contentType,
|
|
7
|
+
headers = {},
|
|
8
|
+
}: {
|
|
9
|
+
url: string;
|
|
10
|
+
file: File | Blob;
|
|
11
|
+
contentType: string;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
}) {
|
|
14
|
+
const result = await fetch(url, {
|
|
15
|
+
method: 'PUT',
|
|
16
|
+
body: file,
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': contentType,
|
|
19
|
+
...headers,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!result.ok) {
|
|
24
|
+
throw new Error(`Upload failed with status ${result.status}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function downloadFromSignedUrl({ url }: { url: string }) {
|
|
31
|
+
const result = await fetch(url);
|
|
32
|
+
if (!result.ok) {
|
|
33
|
+
throw new Error(`Download failed with status ${result.status}`);
|
|
34
|
+
}
|
|
35
|
+
return result.blob();
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { validateUpload } from './shared';
|
|
3
|
+
|
|
4
|
+
describe('Storage Shared', () => {
|
|
5
|
+
it('validates upload policy', () => {
|
|
6
|
+
const policy = { maxSizeBytes: 100, allowedTypes: ['image/png'] };
|
|
7
|
+
|
|
8
|
+
expect(() => validateUpload({ contentType: 'image/png', sizeBytes: 50 }, policy)).not.toThrow();
|
|
9
|
+
|
|
10
|
+
expect(() => validateUpload({ contentType: 'image/jpeg', sizeBytes: 50 }, policy)).toThrow(
|
|
11
|
+
/Invalid file type/,
|
|
12
|
+
);
|
|
13
|
+
expect(() => validateUpload({ contentType: 'image/png', sizeBytes: 150 }, policy)).toThrow(
|
|
14
|
+
/File too large/,
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
});
|
package/src/shared.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ValidationError } from '@wrelik/errors';
|
|
2
|
+
|
|
3
|
+
export function validateUpload(
|
|
4
|
+
file: { contentType: string; sizeBytes: number },
|
|
5
|
+
policy: { maxSizeBytes: number; allowedTypes: string[] },
|
|
6
|
+
) {
|
|
7
|
+
if (file.sizeBytes > policy.maxSizeBytes) {
|
|
8
|
+
throw new ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
|
|
9
|
+
}
|
|
10
|
+
if (!policy.allowedTypes.includes(file.contentType)) {
|
|
11
|
+
throw new ValidationError(`Invalid file type ${file.contentType}`);
|
|
12
|
+
}
|
|
13
|
+
return true;
|
|
14
|
+
}
|