@wrelik/storage 0.2.0 → 2.0.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 +18 -17
- package/CHANGELOG.md +24 -0
- package/dist/{react-native.d.ts → client/index.d.mts} +2 -5
- package/dist/{react-native.d.mts → client/index.d.ts} +2 -5
- package/dist/{react-native.js → client/index.js} +6 -22
- package/dist/{react-native.mjs → client/index.mjs} +2 -7
- package/dist/server/index.d.mts +39 -0
- package/dist/server/index.d.ts +39 -0
- package/dist/server/index.js +118 -0
- package/dist/server/index.mjs +89 -0
- package/dist/{shared-DqbBUqiW.d.mts → shared/index.d.mts} +2 -2
- package/dist/{shared-DqbBUqiW.d.ts → shared/index.d.ts} +2 -2
- package/dist/shared/index.js +39 -0
- package/dist/{chunk-6VKCOC6I.mjs → shared/index.mjs} +2 -3
- package/package.json +30 -7
- package/src/{react-native.ts → client/index.ts} +3 -4
- package/src/server/index.test.ts +71 -0
- package/src/server/index.ts +147 -0
- package/src/{shared.test.ts → shared/index.test.ts} +3 -4
- package/src/{shared.ts → shared/index.ts} +4 -2
- package/tsconfig.json +3 -1
- package/dist/index.d.mts +0 -16
- package/dist/index.d.ts +0 -16
- package/dist/index.js +0 -96
- package/dist/index.mjs +0 -57
- package/src/index.ts +0 -73
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
|
|
2
|
-
> @wrelik/storage@
|
|
3
|
-
> tsup src/index.ts src/
|
|
2
|
+
> @wrelik/storage@2.0.0 build /home/runner/work/wrelik-kit/wrelik-kit/packages/storage
|
|
3
|
+
> tsup src/server/index.ts src/client/index.ts src/shared/index.ts --format cjs,esm --dts --clean
|
|
4
4
|
|
|
5
|
-
[34mCLI[39m Building entry: src/index.ts, src/
|
|
5
|
+
[34mCLI[39m Building entry: src/client/index.ts, src/server/index.ts, src/shared/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.5.1
|
|
8
8
|
[34mCLI[39m Target: es2022
|
|
9
9
|
[34mCLI[39m Cleaning output folder
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
12
|
+
[32mCJS[39m [1mdist/server/index.js [22m[32m4.13 KB[39m
|
|
13
|
+
[32mCJS[39m [1mdist/shared/index.js [22m[32m1.44 KB[39m
|
|
14
|
+
[32mCJS[39m [1mdist/client/index.js [22m[32m1.67 KB[39m
|
|
15
|
+
[32mCJS[39m ⚡️ Build success in 82ms
|
|
16
|
+
[32mESM[39m [1mdist/client/index.mjs [22m[32m625.00 B[39m
|
|
17
|
+
[32mESM[39m [1mdist/server/index.mjs [22m[32m2.86 KB[39m
|
|
18
|
+
[32mESM[39m [1mdist/shared/index.mjs [22m[32m433.00 B[39m
|
|
19
|
+
[32mESM[39m ⚡️ Build success in 82ms
|
|
19
20
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.ts
|
|
22
|
-
[32mDTS[39m [1mdist/
|
|
23
|
-
[32mDTS[39m [1mdist/shared
|
|
24
|
-
[32mDTS[39m [1mdist/index.d.mts
|
|
25
|
-
[32mDTS[39m [1mdist/
|
|
26
|
-
[32mDTS[39m [1mdist/shared
|
|
21
|
+
[32mDTS[39m ⚡️ Build success in 6479ms
|
|
22
|
+
[32mDTS[39m [1mdist/client/index.d.ts [22m[32m337.00 B[39m
|
|
23
|
+
[32mDTS[39m [1mdist/server/index.d.ts [22m[32m1.99 KB[39m
|
|
24
|
+
[32mDTS[39m [1mdist/shared/index.d.ts [22m[32m193.00 B[39m
|
|
25
|
+
[32mDTS[39m [1mdist/client/index.d.mts [22m[32m337.00 B[39m
|
|
26
|
+
[32mDTS[39m [1mdist/server/index.d.mts [22m[32m1.99 KB[39m
|
|
27
|
+
[32mDTS[39m [1mdist/shared/index.d.mts [22m[32m193.00 B[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @wrelik/storage
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 818ed58: Refactor runtime packages to strict subpath exports (`/server`, `/client`, `/shared`) with side-effect free entrypoints and hard CI/runtime boundary enforcement.
|
|
8
|
+
|
|
9
|
+
### Minor Changes
|
|
10
|
+
|
|
11
|
+
- Add platform wrapper packages for Clerk (Next/Expo), Next Sentry client instrumentation, and Upstash server SDK access so apps can avoid direct vendor imports while staying inside the `@wrelik/*` boundary.
|
|
12
|
+
|
|
13
|
+
Add temporary deprecated server-side compatibility singleton exports to analytics, email, jobs, and storage to support DRX migration from root-import convenience APIs to runtime subpaths in one cutover.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [818ed58]
|
|
18
|
+
- @wrelik/errors@2.0.0
|
|
19
|
+
|
|
20
|
+
## 0.2.1
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Updated dependencies [2b9cbf7]
|
|
25
|
+
- @wrelik/errors@0.2.1
|
|
26
|
+
|
|
3
27
|
## 0.2.0
|
|
4
28
|
|
|
5
29
|
### Minor Changes
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import * as node_buffer from 'node:buffer';
|
|
2
|
-
export { v as validateUpload } from './shared-DqbBUqiW.js';
|
|
3
|
-
|
|
4
1
|
declare function uploadToSignedUrl({ url, file, contentType, headers, }: {
|
|
5
2
|
url: string;
|
|
6
3
|
file: File | Blob;
|
|
7
4
|
contentType: string;
|
|
8
5
|
headers?: Record<string, string>;
|
|
9
|
-
}): Promise<
|
|
6
|
+
}): Promise<true>;
|
|
10
7
|
declare function downloadFromSignedUrl({ url }: {
|
|
11
8
|
url: string;
|
|
12
|
-
}): Promise<
|
|
9
|
+
}): Promise<Blob>;
|
|
13
10
|
|
|
14
11
|
export { downloadFromSignedUrl, uploadToSignedUrl };
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import * as node_buffer from 'node:buffer';
|
|
2
|
-
export { v as validateUpload } from './shared-DqbBUqiW.mjs';
|
|
3
|
-
|
|
4
1
|
declare function uploadToSignedUrl({ url, file, contentType, headers, }: {
|
|
5
2
|
url: string;
|
|
6
3
|
file: File | Blob;
|
|
7
4
|
contentType: string;
|
|
8
5
|
headers?: Record<string, string>;
|
|
9
|
-
}): Promise<
|
|
6
|
+
}): Promise<true>;
|
|
10
7
|
declare function downloadFromSignedUrl({ url }: {
|
|
11
8
|
url: string;
|
|
12
|
-
}): Promise<
|
|
9
|
+
}): Promise<Blob>;
|
|
13
10
|
|
|
14
11
|
export { downloadFromSignedUrl, uploadToSignedUrl };
|
|
@@ -17,28 +17,13 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
17
|
};
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
|
-
// src/
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
20
|
+
// src/client/index.ts
|
|
21
|
+
var client_exports = {};
|
|
22
|
+
__export(client_exports, {
|
|
23
23
|
downloadFromSignedUrl: () => downloadFromSignedUrl,
|
|
24
|
-
uploadToSignedUrl: () => uploadToSignedUrl
|
|
25
|
-
validateUpload: () => validateUpload
|
|
24
|
+
uploadToSignedUrl: () => uploadToSignedUrl
|
|
26
25
|
});
|
|
27
|
-
module.exports = __toCommonJS(
|
|
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
|
|
26
|
+
module.exports = __toCommonJS(client_exports);
|
|
42
27
|
async function uploadToSignedUrl({
|
|
43
28
|
url,
|
|
44
29
|
file,
|
|
@@ -68,6 +53,5 @@ async function downloadFromSignedUrl({ url }) {
|
|
|
68
53
|
// Annotate the CommonJS export names for ESM import in node:
|
|
69
54
|
0 && (module.exports = {
|
|
70
55
|
downloadFromSignedUrl,
|
|
71
|
-
uploadToSignedUrl
|
|
72
|
-
validateUpload
|
|
56
|
+
uploadToSignedUrl
|
|
73
57
|
});
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
validateUpload
|
|
3
|
-
} from "./chunk-6VKCOC6I.mjs";
|
|
4
|
-
|
|
5
|
-
// src/react-native.ts
|
|
1
|
+
// src/client/index.ts
|
|
6
2
|
async function uploadToSignedUrl({
|
|
7
3
|
url,
|
|
8
4
|
file,
|
|
@@ -31,6 +27,5 @@ async function downloadFromSignedUrl({ url }) {
|
|
|
31
27
|
}
|
|
32
28
|
export {
|
|
33
29
|
downloadFromSignedUrl,
|
|
34
|
-
uploadToSignedUrl
|
|
35
|
-
validateUpload
|
|
30
|
+
uploadToSignedUrl
|
|
36
31
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
interface StorageConfig {
|
|
2
|
+
accountId: string;
|
|
3
|
+
accessKeyId: string;
|
|
4
|
+
secretAccessKey: string;
|
|
5
|
+
bucketName: string;
|
|
6
|
+
region?: string;
|
|
7
|
+
}
|
|
8
|
+
interface StorageServer {
|
|
9
|
+
putObject: (body: Buffer | Uint8Array | Blob | string, key: string, contentType: string, metadata?: Record<string, string>) => Promise<unknown>;
|
|
10
|
+
getSignedUploadUrl: (key: string, contentType: string, expiresIn?: number) => Promise<string>;
|
|
11
|
+
getSignedDownloadUrl: (key: string, expiresIn?: number) => Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
interface StorageServerDependencies {
|
|
14
|
+
client?: {
|
|
15
|
+
send: (command: unknown) => Promise<unknown>;
|
|
16
|
+
};
|
|
17
|
+
signUrl?: (client: unknown, command: unknown, options: {
|
|
18
|
+
expiresIn: number;
|
|
19
|
+
}) => Promise<string>;
|
|
20
|
+
}
|
|
21
|
+
declare function createStorageServer(config: StorageConfig, deps?: StorageServerDependencies): StorageServer;
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...) and explicit dependency wiring.
|
|
24
|
+
*/
|
|
25
|
+
declare function initStorage(config: StorageConfig, deps?: StorageServerDependencies): void;
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).putObject(...)
|
|
28
|
+
*/
|
|
29
|
+
declare function putObject(body: Parameters<StorageServer['putObject']>[0], key: string, contentType: string, metadata?: Record<string, string>): ReturnType<StorageServer['putObject']>;
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).getSignedUploadUrl(...)
|
|
32
|
+
*/
|
|
33
|
+
declare function getSignedUploadUrl(key: string, contentType: string, expiresIn?: number): ReturnType<StorageServer['getSignedUploadUrl']>;
|
|
34
|
+
/**
|
|
35
|
+
* @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).getSignedDownloadUrl(...)
|
|
36
|
+
*/
|
|
37
|
+
declare function getSignedDownloadUrl(key: string, expiresIn?: number): ReturnType<StorageServer['getSignedDownloadUrl']>;
|
|
38
|
+
|
|
39
|
+
export { type StorageConfig, type StorageServer, type StorageServerDependencies, createStorageServer, getSignedDownloadUrl, getSignedUploadUrl, initStorage, putObject };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
interface StorageConfig {
|
|
2
|
+
accountId: string;
|
|
3
|
+
accessKeyId: string;
|
|
4
|
+
secretAccessKey: string;
|
|
5
|
+
bucketName: string;
|
|
6
|
+
region?: string;
|
|
7
|
+
}
|
|
8
|
+
interface StorageServer {
|
|
9
|
+
putObject: (body: Buffer | Uint8Array | Blob | string, key: string, contentType: string, metadata?: Record<string, string>) => Promise<unknown>;
|
|
10
|
+
getSignedUploadUrl: (key: string, contentType: string, expiresIn?: number) => Promise<string>;
|
|
11
|
+
getSignedDownloadUrl: (key: string, expiresIn?: number) => Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
interface StorageServerDependencies {
|
|
14
|
+
client?: {
|
|
15
|
+
send: (command: unknown) => Promise<unknown>;
|
|
16
|
+
};
|
|
17
|
+
signUrl?: (client: unknown, command: unknown, options: {
|
|
18
|
+
expiresIn: number;
|
|
19
|
+
}) => Promise<string>;
|
|
20
|
+
}
|
|
21
|
+
declare function createStorageServer(config: StorageConfig, deps?: StorageServerDependencies): StorageServer;
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...) and explicit dependency wiring.
|
|
24
|
+
*/
|
|
25
|
+
declare function initStorage(config: StorageConfig, deps?: StorageServerDependencies): void;
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).putObject(...)
|
|
28
|
+
*/
|
|
29
|
+
declare function putObject(body: Parameters<StorageServer['putObject']>[0], key: string, contentType: string, metadata?: Record<string, string>): ReturnType<StorageServer['putObject']>;
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).getSignedUploadUrl(...)
|
|
32
|
+
*/
|
|
33
|
+
declare function getSignedUploadUrl(key: string, contentType: string, expiresIn?: number): ReturnType<StorageServer['getSignedUploadUrl']>;
|
|
34
|
+
/**
|
|
35
|
+
* @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).getSignedDownloadUrl(...)
|
|
36
|
+
*/
|
|
37
|
+
declare function getSignedDownloadUrl(key: string, expiresIn?: number): ReturnType<StorageServer['getSignedDownloadUrl']>;
|
|
38
|
+
|
|
39
|
+
export { type StorageConfig, type StorageServer, type StorageServerDependencies, createStorageServer, getSignedDownloadUrl, getSignedUploadUrl, initStorage, putObject };
|
|
@@ -0,0 +1,118 @@
|
|
|
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/server/index.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
createStorageServer: () => createStorageServer,
|
|
24
|
+
getSignedDownloadUrl: () => getSignedDownloadUrl,
|
|
25
|
+
getSignedUploadUrl: () => getSignedUploadUrl,
|
|
26
|
+
initStorage: () => initStorage,
|
|
27
|
+
putObject: () => putObject
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(server_exports);
|
|
30
|
+
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
31
|
+
var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
|
|
32
|
+
var import_shared = require("@wrelik/errors/shared");
|
|
33
|
+
function assertNonEmpty(value, field) {
|
|
34
|
+
if (!value?.trim()) {
|
|
35
|
+
throw new import_shared.ValidationError(`${field} is required`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function assertExpiresIn(expiresIn) {
|
|
39
|
+
if (!Number.isInteger(expiresIn) || expiresIn <= 0) {
|
|
40
|
+
throw new import_shared.ValidationError("expiresIn must be a positive integer");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function createStorageServer(config, deps) {
|
|
44
|
+
const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
|
|
45
|
+
const client = deps?.client ?? new import_client_s3.S3Client({
|
|
46
|
+
region: config.region ?? "auto",
|
|
47
|
+
endpoint,
|
|
48
|
+
credentials: {
|
|
49
|
+
accessKeyId: config.accessKeyId,
|
|
50
|
+
secretAccessKey: config.secretAccessKey
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const signUrl = deps?.signUrl ?? ((client2, command, options) => (0, import_s3_request_presigner.getSignedUrl)(client2, command, options));
|
|
54
|
+
return {
|
|
55
|
+
putObject(body, key, contentType, metadata) {
|
|
56
|
+
assertNonEmpty(key, "key");
|
|
57
|
+
assertNonEmpty(contentType, "contentType");
|
|
58
|
+
return client.send(
|
|
59
|
+
new import_client_s3.PutObjectCommand({
|
|
60
|
+
Bucket: config.bucketName,
|
|
61
|
+
Key: key,
|
|
62
|
+
Body: body,
|
|
63
|
+
ContentType: contentType,
|
|
64
|
+
Metadata: metadata
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
getSignedUploadUrl(key, contentType, expiresIn = 3600) {
|
|
69
|
+
assertNonEmpty(key, "key");
|
|
70
|
+
assertNonEmpty(contentType, "contentType");
|
|
71
|
+
assertExpiresIn(expiresIn);
|
|
72
|
+
const command = new import_client_s3.PutObjectCommand({
|
|
73
|
+
Bucket: config.bucketName,
|
|
74
|
+
Key: key,
|
|
75
|
+
ContentType: contentType
|
|
76
|
+
});
|
|
77
|
+
return signUrl(client, command, { expiresIn });
|
|
78
|
+
},
|
|
79
|
+
getSignedDownloadUrl(key, expiresIn = 3600) {
|
|
80
|
+
assertNonEmpty(key, "key");
|
|
81
|
+
assertExpiresIn(expiresIn);
|
|
82
|
+
const command = new import_client_s3.GetObjectCommand({
|
|
83
|
+
Bucket: config.bucketName,
|
|
84
|
+
Key: key
|
|
85
|
+
});
|
|
86
|
+
return signUrl(client, command, { expiresIn });
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
var storageSingleton = null;
|
|
91
|
+
function requireStorageSingleton() {
|
|
92
|
+
if (!storageSingleton) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
"@wrelik/storage/server compatibility API is not initialized. Call initStorage(config) before putObject/getSignedUploadUrl/getSignedDownloadUrl."
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return storageSingleton;
|
|
98
|
+
}
|
|
99
|
+
function initStorage(config, deps) {
|
|
100
|
+
storageSingleton = createStorageServer(config, deps);
|
|
101
|
+
}
|
|
102
|
+
function putObject(body, key, contentType, metadata) {
|
|
103
|
+
return requireStorageSingleton().putObject(body, key, contentType, metadata);
|
|
104
|
+
}
|
|
105
|
+
function getSignedUploadUrl(key, contentType, expiresIn) {
|
|
106
|
+
return requireStorageSingleton().getSignedUploadUrl(key, contentType, expiresIn);
|
|
107
|
+
}
|
|
108
|
+
function getSignedDownloadUrl(key, expiresIn) {
|
|
109
|
+
return requireStorageSingleton().getSignedDownloadUrl(key, expiresIn);
|
|
110
|
+
}
|
|
111
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
112
|
+
0 && (module.exports = {
|
|
113
|
+
createStorageServer,
|
|
114
|
+
getSignedDownloadUrl,
|
|
115
|
+
getSignedUploadUrl,
|
|
116
|
+
initStorage,
|
|
117
|
+
putObject
|
|
118
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/server/index.ts
|
|
2
|
+
import { GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
3
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
4
|
+
import { ValidationError } from "@wrelik/errors/shared";
|
|
5
|
+
function assertNonEmpty(value, field) {
|
|
6
|
+
if (!value?.trim()) {
|
|
7
|
+
throw new ValidationError(`${field} is required`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function assertExpiresIn(expiresIn) {
|
|
11
|
+
if (!Number.isInteger(expiresIn) || expiresIn <= 0) {
|
|
12
|
+
throw new ValidationError("expiresIn must be a positive integer");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function createStorageServer(config, deps) {
|
|
16
|
+
const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
|
|
17
|
+
const client = deps?.client ?? new S3Client({
|
|
18
|
+
region: config.region ?? "auto",
|
|
19
|
+
endpoint,
|
|
20
|
+
credentials: {
|
|
21
|
+
accessKeyId: config.accessKeyId,
|
|
22
|
+
secretAccessKey: config.secretAccessKey
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const signUrl = deps?.signUrl ?? ((client2, command, options) => getSignedUrl(client2, command, options));
|
|
26
|
+
return {
|
|
27
|
+
putObject(body, key, contentType, metadata) {
|
|
28
|
+
assertNonEmpty(key, "key");
|
|
29
|
+
assertNonEmpty(contentType, "contentType");
|
|
30
|
+
return client.send(
|
|
31
|
+
new PutObjectCommand({
|
|
32
|
+
Bucket: config.bucketName,
|
|
33
|
+
Key: key,
|
|
34
|
+
Body: body,
|
|
35
|
+
ContentType: contentType,
|
|
36
|
+
Metadata: metadata
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
},
|
|
40
|
+
getSignedUploadUrl(key, contentType, expiresIn = 3600) {
|
|
41
|
+
assertNonEmpty(key, "key");
|
|
42
|
+
assertNonEmpty(contentType, "contentType");
|
|
43
|
+
assertExpiresIn(expiresIn);
|
|
44
|
+
const command = new PutObjectCommand({
|
|
45
|
+
Bucket: config.bucketName,
|
|
46
|
+
Key: key,
|
|
47
|
+
ContentType: contentType
|
|
48
|
+
});
|
|
49
|
+
return signUrl(client, command, { expiresIn });
|
|
50
|
+
},
|
|
51
|
+
getSignedDownloadUrl(key, expiresIn = 3600) {
|
|
52
|
+
assertNonEmpty(key, "key");
|
|
53
|
+
assertExpiresIn(expiresIn);
|
|
54
|
+
const command = new GetObjectCommand({
|
|
55
|
+
Bucket: config.bucketName,
|
|
56
|
+
Key: key
|
|
57
|
+
});
|
|
58
|
+
return signUrl(client, command, { expiresIn });
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
var storageSingleton = null;
|
|
63
|
+
function requireStorageSingleton() {
|
|
64
|
+
if (!storageSingleton) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"@wrelik/storage/server compatibility API is not initialized. Call initStorage(config) before putObject/getSignedUploadUrl/getSignedDownloadUrl."
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return storageSingleton;
|
|
70
|
+
}
|
|
71
|
+
function initStorage(config, deps) {
|
|
72
|
+
storageSingleton = createStorageServer(config, deps);
|
|
73
|
+
}
|
|
74
|
+
function putObject(body, key, contentType, metadata) {
|
|
75
|
+
return requireStorageSingleton().putObject(body, key, contentType, metadata);
|
|
76
|
+
}
|
|
77
|
+
function getSignedUploadUrl(key, contentType, expiresIn) {
|
|
78
|
+
return requireStorageSingleton().getSignedUploadUrl(key, contentType, expiresIn);
|
|
79
|
+
}
|
|
80
|
+
function getSignedDownloadUrl(key, expiresIn) {
|
|
81
|
+
return requireStorageSingleton().getSignedDownloadUrl(key, expiresIn);
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
createStorageServer,
|
|
85
|
+
getSignedDownloadUrl,
|
|
86
|
+
getSignedUploadUrl,
|
|
87
|
+
initStorage,
|
|
88
|
+
putObject
|
|
89
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
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/shared/index.ts
|
|
21
|
+
var shared_exports = {};
|
|
22
|
+
__export(shared_exports, {
|
|
23
|
+
validateUpload: () => validateUpload
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(shared_exports);
|
|
26
|
+
var import_shared = require("@wrelik/errors/shared");
|
|
27
|
+
function validateUpload(file, policy) {
|
|
28
|
+
if (file.sizeBytes > policy.maxSizeBytes) {
|
|
29
|
+
throw new import_shared.ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
|
|
30
|
+
}
|
|
31
|
+
if (!policy.allowedTypes.includes(file.contentType)) {
|
|
32
|
+
throw new import_shared.ValidationError(`Invalid file type ${file.contentType}`);
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
37
|
+
0 && (module.exports = {
|
|
38
|
+
validateUpload
|
|
39
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// src/shared.ts
|
|
2
|
-
import { ValidationError } from "@wrelik/errors";
|
|
1
|
+
// src/shared/index.ts
|
|
2
|
+
import { ValidationError } from "@wrelik/errors/shared";
|
|
3
3
|
function validateUpload(file, policy) {
|
|
4
4
|
if (file.sizeBytes > policy.maxSizeBytes) {
|
|
5
5
|
throw new ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
|
|
@@ -9,7 +9,6 @@ function validateUpload(file, policy) {
|
|
|
9
9
|
}
|
|
10
10
|
return true;
|
|
11
11
|
}
|
|
12
|
-
|
|
13
12
|
export {
|
|
14
13
|
validateUpload
|
|
15
14
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrelik/storage",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -9,25 +9,48 @@
|
|
|
9
9
|
"url": "https://github.com/lwhite702/wrelik-kit.git",
|
|
10
10
|
"directory": "packages/storage"
|
|
11
11
|
},
|
|
12
|
+
"sideEffects": false,
|
|
12
13
|
"exports": {
|
|
13
|
-
"
|
|
14
|
-
|
|
14
|
+
"./server": {
|
|
15
|
+
"types": "./dist/server/index.d.ts",
|
|
16
|
+
"import": "./dist/server/index.mjs",
|
|
17
|
+
"require": "./dist/server/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./client": {
|
|
20
|
+
"types": "./dist/client/index.d.ts",
|
|
21
|
+
"import": "./dist/client/index.mjs",
|
|
22
|
+
"require": "./dist/client/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./shared": {
|
|
25
|
+
"types": "./dist/shared/index.d.ts",
|
|
26
|
+
"import": "./dist/shared/index.mjs",
|
|
27
|
+
"require": "./dist/shared/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./package.json": "./package.json"
|
|
15
30
|
},
|
|
16
31
|
"dependencies": {
|
|
17
32
|
"@aws-sdk/client-s3": "^3.500.0",
|
|
18
33
|
"@aws-sdk/s3-request-presigner": "^3.500.0",
|
|
19
|
-
"@wrelik/errors": "
|
|
34
|
+
"@wrelik/errors": "2.0.0"
|
|
20
35
|
},
|
|
21
36
|
"devDependencies": {
|
|
22
37
|
"@types/node": "^25.2.0",
|
|
23
38
|
"tsup": "^8.0.1",
|
|
24
39
|
"vitest": "^1.2.2",
|
|
25
|
-
"@wrelik/
|
|
26
|
-
"@wrelik/
|
|
40
|
+
"@wrelik/tsconfig": "0.1.1",
|
|
41
|
+
"@wrelik/eslint-config": "0.1.1"
|
|
42
|
+
},
|
|
43
|
+
"wrelik": {
|
|
44
|
+
"runtimes": [
|
|
45
|
+
"server",
|
|
46
|
+
"client",
|
|
47
|
+
"shared"
|
|
48
|
+
]
|
|
27
49
|
},
|
|
28
50
|
"scripts": {
|
|
29
|
-
"build": "tsup src/index.ts src/
|
|
51
|
+
"build": "tsup src/server/index.ts src/client/index.ts src/shared/index.ts --format cjs,esm --dts --clean",
|
|
30
52
|
"lint": "eslint src/",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
31
54
|
"test": "vitest run --passWithNoTests"
|
|
32
55
|
}
|
|
33
56
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export * from './shared';
|
|
2
|
-
|
|
3
1
|
export async function uploadToSignedUrl({
|
|
4
2
|
url,
|
|
5
3
|
file,
|
|
@@ -10,7 +8,7 @@ export async function uploadToSignedUrl({
|
|
|
10
8
|
file: File | Blob;
|
|
11
9
|
contentType: string;
|
|
12
10
|
headers?: Record<string, string>;
|
|
13
|
-
}) {
|
|
11
|
+
}): Promise<true> {
|
|
14
12
|
const result = await fetch(url, {
|
|
15
13
|
method: 'PUT',
|
|
16
14
|
body: file,
|
|
@@ -27,10 +25,11 @@ export async function uploadToSignedUrl({
|
|
|
27
25
|
return true;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
export async function downloadFromSignedUrl({ url }: { url: string }) {
|
|
28
|
+
export async function downloadFromSignedUrl({ url }: { url: string }): Promise<Blob> {
|
|
31
29
|
const result = await fetch(url);
|
|
32
30
|
if (!result.ok) {
|
|
33
31
|
throw new Error(`Download failed with status ${result.status}`);
|
|
34
32
|
}
|
|
33
|
+
|
|
35
34
|
return result.blob();
|
|
36
35
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { ValidationError } from '@wrelik/errors/shared';
|
|
3
|
+
import { createStorageServer } from './index';
|
|
4
|
+
|
|
5
|
+
const config = {
|
|
6
|
+
accountId: 'acct',
|
|
7
|
+
accessKeyId: 'key',
|
|
8
|
+
secretAccessKey: 'secret',
|
|
9
|
+
bucketName: 'bucket',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
vi.resetModules();
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('@wrelik/storage/server', () => {
|
|
18
|
+
it('generates signed URL shapes with configured signer', async () => {
|
|
19
|
+
const signUrl = vi.fn(async (_client, command, options: { expiresIn: number }) => {
|
|
20
|
+
const input = (command as { input: { Key: string } }).input;
|
|
21
|
+
return `https://signed.example/${input.Key}?expiresIn=${options.expiresIn}`;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const storage = createStorageServer(config, {
|
|
25
|
+
client: { send: vi.fn() },
|
|
26
|
+
signUrl,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await expect(storage.getSignedUploadUrl('uploads/a.png', 'image/png', 60)).resolves.toBe(
|
|
30
|
+
'https://signed.example/uploads/a.png?expiresIn=60',
|
|
31
|
+
);
|
|
32
|
+
await expect(storage.getSignedDownloadUrl('uploads/a.png', 120)).resolves.toBe(
|
|
33
|
+
'https://signed.example/uploads/a.png?expiresIn=120',
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('validates signed URL input rules', async () => {
|
|
38
|
+
const storage = createStorageServer(config, {
|
|
39
|
+
client: { send: vi.fn() },
|
|
40
|
+
signUrl: vi.fn(async () => 'https://signed.example/ok'),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(() => storage.getSignedUploadUrl('', 'image/png')).toThrow(ValidationError);
|
|
44
|
+
expect(() => storage.getSignedUploadUrl('a.png', '', 60)).toThrow(ValidationError);
|
|
45
|
+
expect(() => storage.getSignedDownloadUrl('a.png', 0)).toThrow(/positive integer/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('compatibility singleton throws before initStorage', async () => {
|
|
49
|
+
const mod = await import('./index.js');
|
|
50
|
+
expect(() => mod.getSignedDownloadUrl('uploads/a.png', 60)).toThrow(/initStorage/i);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('compatibility singleton delegates after initStorage', async () => {
|
|
54
|
+
const send = vi.fn(async () => ({ etag: 'etag_1' }));
|
|
55
|
+
const signUrl = vi.fn(async (_client, command, options: { expiresIn: number }) => {
|
|
56
|
+
const input = (command as { input: { Key: string } }).input;
|
|
57
|
+
return `https://signed.example/${input.Key}?expiresIn=${options.expiresIn}`;
|
|
58
|
+
});
|
|
59
|
+
const mod = await import('./index.js');
|
|
60
|
+
|
|
61
|
+
mod.initStorage(config, { client: { send }, signUrl });
|
|
62
|
+
|
|
63
|
+
await mod.putObject('body', 'uploads/a.png', 'image/png');
|
|
64
|
+
await expect(mod.getSignedUploadUrl('uploads/a.png', 'image/png', 30)).resolves.toBe(
|
|
65
|
+
'https://signed.example/uploads/a.png?expiresIn=30',
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(send).toHaveBeenCalledTimes(1);
|
|
69
|
+
expect(signUrl).toHaveBeenCalledTimes(1);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/* eslint-disable no-restricted-imports */
|
|
2
|
+
import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
3
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
4
|
+
import { ValidationError } from '@wrelik/errors/shared';
|
|
5
|
+
|
|
6
|
+
export interface StorageConfig {
|
|
7
|
+
accountId: string;
|
|
8
|
+
accessKeyId: string;
|
|
9
|
+
secretAccessKey: string;
|
|
10
|
+
bucketName: string;
|
|
11
|
+
region?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface StorageServer {
|
|
15
|
+
putObject: (
|
|
16
|
+
body: Buffer | Uint8Array | Blob | string,
|
|
17
|
+
key: string,
|
|
18
|
+
contentType: string,
|
|
19
|
+
metadata?: Record<string, string>,
|
|
20
|
+
) => Promise<unknown>;
|
|
21
|
+
getSignedUploadUrl: (key: string, contentType: string, expiresIn?: number) => Promise<string>;
|
|
22
|
+
getSignedDownloadUrl: (key: string, expiresIn?: number) => Promise<string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface StorageServerDependencies {
|
|
26
|
+
client?: {
|
|
27
|
+
send: (command: unknown) => Promise<unknown>;
|
|
28
|
+
};
|
|
29
|
+
signUrl?: (client: unknown, command: unknown, options: { expiresIn: number }) => Promise<string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function assertNonEmpty(value: string, field: string): void {
|
|
33
|
+
if (!value?.trim()) {
|
|
34
|
+
throw new ValidationError(`${field} is required`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function assertExpiresIn(expiresIn: number): void {
|
|
39
|
+
if (!Number.isInteger(expiresIn) || expiresIn <= 0) {
|
|
40
|
+
throw new ValidationError('expiresIn must be a positive integer');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createStorageServer(config: StorageConfig, deps?: StorageServerDependencies): StorageServer {
|
|
45
|
+
const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
|
|
46
|
+
const client =
|
|
47
|
+
deps?.client ??
|
|
48
|
+
new S3Client({
|
|
49
|
+
region: config.region ?? 'auto',
|
|
50
|
+
endpoint,
|
|
51
|
+
credentials: {
|
|
52
|
+
accessKeyId: config.accessKeyId,
|
|
53
|
+
secretAccessKey: config.secretAccessKey,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
const signUrl =
|
|
57
|
+
deps?.signUrl ??
|
|
58
|
+
((client: unknown, command: unknown, options: { expiresIn: number }) =>
|
|
59
|
+
getSignedUrl(client as S3Client, command as PutObjectCommand | GetObjectCommand, options));
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
putObject(body, key, contentType, metadata) {
|
|
63
|
+
assertNonEmpty(key, 'key');
|
|
64
|
+
assertNonEmpty(contentType, 'contentType');
|
|
65
|
+
return client.send(
|
|
66
|
+
new PutObjectCommand({
|
|
67
|
+
Bucket: config.bucketName,
|
|
68
|
+
Key: key,
|
|
69
|
+
Body: body,
|
|
70
|
+
ContentType: contentType,
|
|
71
|
+
Metadata: metadata,
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
getSignedUploadUrl(key, contentType, expiresIn = 3600) {
|
|
77
|
+
assertNonEmpty(key, 'key');
|
|
78
|
+
assertNonEmpty(contentType, 'contentType');
|
|
79
|
+
assertExpiresIn(expiresIn);
|
|
80
|
+
const command = new PutObjectCommand({
|
|
81
|
+
Bucket: config.bucketName,
|
|
82
|
+
Key: key,
|
|
83
|
+
ContentType: contentType,
|
|
84
|
+
});
|
|
85
|
+
return signUrl(client, command, { expiresIn });
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
getSignedDownloadUrl(key, expiresIn = 3600) {
|
|
89
|
+
assertNonEmpty(key, 'key');
|
|
90
|
+
assertExpiresIn(expiresIn);
|
|
91
|
+
const command = new GetObjectCommand({
|
|
92
|
+
Bucket: config.bucketName,
|
|
93
|
+
Key: key,
|
|
94
|
+
});
|
|
95
|
+
return signUrl(client, command, { expiresIn });
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let storageSingleton: StorageServer | null = null;
|
|
101
|
+
|
|
102
|
+
function requireStorageSingleton(): StorageServer {
|
|
103
|
+
if (!storageSingleton) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
'@wrelik/storage/server compatibility API is not initialized. Call initStorage(config) before putObject/getSignedUploadUrl/getSignedDownloadUrl.',
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return storageSingleton;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...) and explicit dependency wiring.
|
|
114
|
+
*/
|
|
115
|
+
export function initStorage(config: StorageConfig, deps?: StorageServerDependencies): void {
|
|
116
|
+
storageSingleton = createStorageServer(config, deps);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).putObject(...)
|
|
121
|
+
*/
|
|
122
|
+
export function putObject(
|
|
123
|
+
body: Parameters<StorageServer['putObject']>[0],
|
|
124
|
+
key: string,
|
|
125
|
+
contentType: string,
|
|
126
|
+
metadata?: Record<string, string>,
|
|
127
|
+
): ReturnType<StorageServer['putObject']> {
|
|
128
|
+
return requireStorageSingleton().putObject(body, key, contentType, metadata);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).getSignedUploadUrl(...)
|
|
133
|
+
*/
|
|
134
|
+
export function getSignedUploadUrl(
|
|
135
|
+
key: string,
|
|
136
|
+
contentType: string,
|
|
137
|
+
expiresIn?: number,
|
|
138
|
+
): ReturnType<StorageServer['getSignedUploadUrl']> {
|
|
139
|
+
return requireStorageSingleton().getSignedUploadUrl(key, contentType, expiresIn);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).getSignedDownloadUrl(...)
|
|
144
|
+
*/
|
|
145
|
+
export function getSignedDownloadUrl(key: string, expiresIn?: number): ReturnType<StorageServer['getSignedDownloadUrl']> {
|
|
146
|
+
return requireStorageSingleton().getSignedDownloadUrl(key, expiresIn);
|
|
147
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { describe,
|
|
2
|
-
import { validateUpload } from './
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { validateUpload } from './index';
|
|
3
3
|
|
|
4
|
-
describe('
|
|
4
|
+
describe('@wrelik/storage/shared', () => {
|
|
5
5
|
it('validates upload policy', () => {
|
|
6
6
|
const policy = { maxSizeBytes: 100, allowedTypes: ['image/png'] };
|
|
7
7
|
|
|
8
8
|
expect(() => validateUpload({ contentType: 'image/png', sizeBytes: 50 }, policy)).not.toThrow();
|
|
9
|
-
|
|
10
9
|
expect(() => validateUpload({ contentType: 'image/jpeg', sizeBytes: 50 }, policy)).toThrow(
|
|
11
10
|
/Invalid file type/,
|
|
12
11
|
);
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { ValidationError } from '@wrelik/errors';
|
|
1
|
+
import { ValidationError } from '@wrelik/errors/shared';
|
|
2
2
|
|
|
3
3
|
export function validateUpload(
|
|
4
4
|
file: { contentType: string; sizeBytes: number },
|
|
5
5
|
policy: { maxSizeBytes: number; allowedTypes: string[] },
|
|
6
|
-
) {
|
|
6
|
+
): true {
|
|
7
7
|
if (file.sizeBytes > policy.maxSizeBytes) {
|
|
8
8
|
throw new ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
|
|
9
9
|
}
|
|
10
|
+
|
|
10
11
|
if (!policy.allowedTypes.includes(file.contentType)) {
|
|
11
12
|
throw new ValidationError(`Invalid file type ${file.contentType}`);
|
|
12
13
|
}
|
|
14
|
+
|
|
13
15
|
return true;
|
|
14
16
|
}
|
package/tsconfig.json
CHANGED
package/dist/index.d.mts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
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
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-restricted-imports */
|
|
2
|
-
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
3
|
-
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
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 * from './shared';
|