multivendor-storage 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/index.cjs +245 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +112 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +238 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# multivendor-storage
|
|
2
|
+
|
|
3
|
+
Framework-agnostic S3 and Google Cloud Storage clients with public URL helpers.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install multivendor-storage
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { S3StorageClient, GcpStorageClient, buildPublicUploadUrl } from 'multivendor-storage';
|
|
15
|
+
|
|
16
|
+
const s3 = new S3StorageClient({
|
|
17
|
+
region: 'ap-south-1',
|
|
18
|
+
bucket: 'my-bucket',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await s3.putObject('products/photo.jpg', buffer, 'image/jpeg');
|
|
22
|
+
|
|
23
|
+
const url = buildPublicUploadUrl('products/photo.jpg', {
|
|
24
|
+
provider: 's3',
|
|
25
|
+
bucket: 'my-bucket',
|
|
26
|
+
region: 'ap-south-1',
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## License
|
|
31
|
+
|
|
32
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var clientS3 = require('@aws-sdk/client-s3');
|
|
4
|
+
var storage = require('@google-cloud/storage');
|
|
5
|
+
|
|
6
|
+
// src/s3Client.ts
|
|
7
|
+
|
|
8
|
+
// src/errors.ts
|
|
9
|
+
var StorageConfigError = class extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "StorageConfigError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var StorageApiError = class extends Error {
|
|
16
|
+
constructor(message, cause) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.cause = cause;
|
|
19
|
+
this.name = "StorageApiError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/s3Client.ts
|
|
24
|
+
var S3StorageClient = class {
|
|
25
|
+
constructor(config) {
|
|
26
|
+
if (!config.bucket?.trim()) {
|
|
27
|
+
throw new StorageConfigError("S3 bucket is not configured");
|
|
28
|
+
}
|
|
29
|
+
if (!config.region?.trim()) {
|
|
30
|
+
throw new StorageConfigError("S3 region is not configured");
|
|
31
|
+
}
|
|
32
|
+
this.bucket = config.bucket;
|
|
33
|
+
this.client = new clientS3.S3Client({
|
|
34
|
+
region: config.region,
|
|
35
|
+
...config.accessKeyId && config.secretAccessKey ? {
|
|
36
|
+
credentials: {
|
|
37
|
+
accessKeyId: config.accessKeyId,
|
|
38
|
+
secretAccessKey: config.secretAccessKey
|
|
39
|
+
}
|
|
40
|
+
} : {}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async putObject(key, body, contentType, contentEncoding) {
|
|
44
|
+
await this.client.send(
|
|
45
|
+
new clientS3.PutObjectCommand({
|
|
46
|
+
Bucket: this.bucket,
|
|
47
|
+
Key: key,
|
|
48
|
+
Body: body,
|
|
49
|
+
...contentType ? { ContentType: contentType } : {},
|
|
50
|
+
...contentEncoding ? { ContentEncoding: contentEncoding } : {}
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
async getObjectBuffer(key) {
|
|
55
|
+
const response = await this.client.send(
|
|
56
|
+
new clientS3.GetObjectCommand({
|
|
57
|
+
Bucket: this.bucket,
|
|
58
|
+
Key: key
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
if (!response.Body) {
|
|
62
|
+
throw new Error(`Object not found: ${key}`);
|
|
63
|
+
}
|
|
64
|
+
const byteArray = await response.Body.transformToByteArray();
|
|
65
|
+
return Buffer.from(byteArray);
|
|
66
|
+
}
|
|
67
|
+
async listObjects(prefix = "", options = {}) {
|
|
68
|
+
const response = await this.client.send(
|
|
69
|
+
new clientS3.ListObjectsV2Command({
|
|
70
|
+
Bucket: this.bucket,
|
|
71
|
+
Prefix: prefix,
|
|
72
|
+
Delimiter: options.delimiter,
|
|
73
|
+
MaxKeys: options.limit || void 0,
|
|
74
|
+
...options.marker ? { StartAfter: options.marker } : {}
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
return {
|
|
78
|
+
Name: this.bucket,
|
|
79
|
+
Prefix: prefix,
|
|
80
|
+
Delimiter: options.delimiter,
|
|
81
|
+
Marker: options.marker,
|
|
82
|
+
NextMarker: response.NextContinuationToken,
|
|
83
|
+
IsTruncated: response.IsTruncated,
|
|
84
|
+
Contents: (response.Contents || []).map((obj) => ({ Key: obj.Key })),
|
|
85
|
+
CommonPrefixes: (response.CommonPrefixes || []).map((cp) => ({ Prefix: cp.Prefix }))
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
async deleteObject(key) {
|
|
89
|
+
await this.client.send(
|
|
90
|
+
new clientS3.DeleteObjectCommand({
|
|
91
|
+
Bucket: this.bucket,
|
|
92
|
+
Key: key
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
async deleteObjectsByPrefix(prefix) {
|
|
97
|
+
const listed = await this.client.send(
|
|
98
|
+
new clientS3.ListObjectsV2Command({
|
|
99
|
+
Bucket: this.bucket,
|
|
100
|
+
Prefix: prefix
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
const objects = (listed.Contents || []).filter((obj) => obj.Key).map((obj) => ({ Key: obj.Key }));
|
|
104
|
+
if (objects.length === 0) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await this.client.send(
|
|
108
|
+
new clientS3.DeleteObjectsCommand({
|
|
109
|
+
Bucket: this.bucket,
|
|
110
|
+
Delete: {
|
|
111
|
+
Objects: objects,
|
|
112
|
+
Quiet: true
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
async createFolderPrefix(key) {
|
|
118
|
+
await this.putObject(key, Buffer.alloc(0));
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/keys.ts
|
|
123
|
+
function resolveStorageKey(keyPrefix, relativePath) {
|
|
124
|
+
const normalizedPath = relativePath.replace(/^\/+/, "");
|
|
125
|
+
if (!keyPrefix) {
|
|
126
|
+
return normalizedPath;
|
|
127
|
+
}
|
|
128
|
+
const prefix = keyPrefix.endsWith("/") ? keyPrefix : `${keyPrefix}/`;
|
|
129
|
+
return `${prefix}${normalizedPath}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/gcpClient.ts
|
|
133
|
+
var GcpStorageClient = class {
|
|
134
|
+
constructor(config) {
|
|
135
|
+
if (!config.bucket?.trim()) {
|
|
136
|
+
throw new StorageConfigError("GCP bucket is not configured");
|
|
137
|
+
}
|
|
138
|
+
this.bucketName = config.bucket;
|
|
139
|
+
this.keyPrefix = config.keyPrefix || "";
|
|
140
|
+
this.storage = new storage.Storage({
|
|
141
|
+
keyFilename: config.keyFilename,
|
|
142
|
+
projectId: config.projectId
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
bucket() {
|
|
146
|
+
return this.storage.bucket(this.bucketName);
|
|
147
|
+
}
|
|
148
|
+
fullKey(relativePath) {
|
|
149
|
+
return resolveStorageKey(this.keyPrefix, relativePath);
|
|
150
|
+
}
|
|
151
|
+
async upload(key, buffer, contentType) {
|
|
152
|
+
const filePath = this.fullKey(key);
|
|
153
|
+
const file = this.bucket().file(filePath);
|
|
154
|
+
await file.save(buffer, { contentType });
|
|
155
|
+
return { path: filePath, success: true };
|
|
156
|
+
}
|
|
157
|
+
async uploadFile(key, buffer, contentType) {
|
|
158
|
+
return this.upload(key, buffer, contentType);
|
|
159
|
+
}
|
|
160
|
+
async download(relativePath) {
|
|
161
|
+
const prefix = this.fullKey(relativePath);
|
|
162
|
+
const [data] = await this.bucket().file(prefix).download();
|
|
163
|
+
return data;
|
|
164
|
+
}
|
|
165
|
+
async copy(srcKey, destKey) {
|
|
166
|
+
const src = this.fullKey(srcKey);
|
|
167
|
+
const dest = this.fullKey(destKey);
|
|
168
|
+
await this.bucket().file(src).copy(this.bucket().file(dest));
|
|
169
|
+
}
|
|
170
|
+
async list(folderName = "", limit = 0, marker = "") {
|
|
171
|
+
const prefix = this.fullKey(folderName);
|
|
172
|
+
const [files] = await this.bucket().getFiles({
|
|
173
|
+
prefix,
|
|
174
|
+
maxResults: limit || void 0
|
|
175
|
+
});
|
|
176
|
+
const prefixForSplit = this.keyPrefix ? `${this.keyPrefix.replace(/\/$/, "")}/` : "";
|
|
177
|
+
const contents = files.filter((t) => !(t.name.replace(prefix, "").split("/").length - 1)).map((t) => ({
|
|
178
|
+
Key: prefixForSplit ? t.name.split(prefixForSplit)[1] || t.name : t.name.replace(prefix, "")
|
|
179
|
+
}));
|
|
180
|
+
const commonPrefix = files.filter((t) => t.name.replace(prefix, "").split("/").length - 1).reduce((acc, val) => {
|
|
181
|
+
const relative = val.name.replace(prefix, "");
|
|
182
|
+
const folderPrefix = `${relative.split("/")[0]}/`;
|
|
183
|
+
if (!acc.some((t) => t.Prefix === folderPrefix)) {
|
|
184
|
+
acc.push({ Prefix: folderPrefix });
|
|
185
|
+
}
|
|
186
|
+
return acc;
|
|
187
|
+
}, []).map((t) => ({ Prefix: `${folderName}${t.Prefix}` }));
|
|
188
|
+
return {
|
|
189
|
+
Name: this.keyPrefix.replace(/\/$/, "") || "storage",
|
|
190
|
+
Prefix: folderName,
|
|
191
|
+
Delimiter: limit,
|
|
192
|
+
Marker: marker,
|
|
193
|
+
NextMarker: "",
|
|
194
|
+
IsTruncated: false,
|
|
195
|
+
Contents: contents,
|
|
196
|
+
CommonPrefixes: commonPrefix
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async deleteFile(relativePath) {
|
|
200
|
+
await this.bucket().file(this.fullKey(relativePath)).delete();
|
|
201
|
+
}
|
|
202
|
+
async deleteByPrefix(relativePrefix) {
|
|
203
|
+
await this.bucket().deleteFiles({ prefix: this.fullKey(relativePrefix) });
|
|
204
|
+
}
|
|
205
|
+
/** Raw bucket access for app-specific paths outside keyPrefix. */
|
|
206
|
+
getRawBucket() {
|
|
207
|
+
return this.bucket();
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// src/publicUrl.ts
|
|
212
|
+
function buildPublicUploadUrl(relativePath, config) {
|
|
213
|
+
const key = relativePath.replace(/^\/+/, "");
|
|
214
|
+
if (config.provider === "s3") {
|
|
215
|
+
const bucket = config.bucket || "";
|
|
216
|
+
const region = config.region || "us-east-1";
|
|
217
|
+
const base2 = `https://${bucket}.s3.${region}.amazonaws.com`;
|
|
218
|
+
return `${base2}/${key}`;
|
|
219
|
+
}
|
|
220
|
+
if (config.provider === "gcp") {
|
|
221
|
+
const gcpPrefix = config.gcpPrefix || "";
|
|
222
|
+
const gcpKey = resolveStorageKey(gcpPrefix.replace(/\/$/, ""), key);
|
|
223
|
+
const base2 = (config.baseUrl || "").replace(/\/$/, "");
|
|
224
|
+
if (base2) {
|
|
225
|
+
return `${base2}/${gcpKey}`;
|
|
226
|
+
}
|
|
227
|
+
const bucket = config.gcpBucket || config.bucket || "";
|
|
228
|
+
return `https://storage.googleapis.com/${bucket}/${gcpKey}`;
|
|
229
|
+
}
|
|
230
|
+
let urlPath = key.startsWith("uploads/") ? key : `uploads/${key}`;
|
|
231
|
+
const base = (config.baseUrl || "").replace(/\/$/, "");
|
|
232
|
+
if (base.endsWith("/uploads") && urlPath.startsWith("uploads/")) {
|
|
233
|
+
urlPath = urlPath.replace(/^uploads\//, "");
|
|
234
|
+
}
|
|
235
|
+
return `${base}/${urlPath}`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
exports.GcpStorageClient = GcpStorageClient;
|
|
239
|
+
exports.S3StorageClient = S3StorageClient;
|
|
240
|
+
exports.StorageApiError = StorageApiError;
|
|
241
|
+
exports.StorageConfigError = StorageConfigError;
|
|
242
|
+
exports.buildPublicUploadUrl = buildPublicUploadUrl;
|
|
243
|
+
exports.resolveStorageKey = resolveStorageKey;
|
|
244
|
+
//# sourceMappingURL=index.cjs.map
|
|
245
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/s3Client.ts","../src/keys.ts","../src/gcpClient.ts","../src/publicUrl.ts"],"names":["S3Client","PutObjectCommand","GetObjectCommand","ListObjectsV2Command","DeleteObjectCommand","DeleteObjectsCommand","Storage","base"],"mappings":"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","file":"index.cjs","sourcesContent":["export class StorageConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'StorageConfigError';\n }\n}\n\nexport class StorageApiError extends Error {\n constructor(message: string, readonly cause?: unknown) {\n super(message);\n this.name = 'StorageApiError';\n }\n}\n","import {\n DeleteObjectCommand,\n DeleteObjectsCommand,\n GetObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n S3Client,\n type _Object,\n} from '@aws-sdk/client-s3';\nimport type { S3StorageConfig } from './config';\nimport { StorageConfigError } from './errors';\n\nexport interface S3ListObjectsOptions {\n limit?: number;\n marker?: string;\n delimiter?: string;\n}\n\nexport interface S3ListObjectsResult {\n Name?: string;\n Prefix?: string;\n Delimiter?: string;\n Marker?: string;\n NextMarker?: string;\n IsTruncated?: boolean;\n Contents?: Array<{ Key?: string }>;\n CommonPrefixes?: Array<{ Prefix?: string }>;\n}\n\nexport class S3StorageClient {\n private readonly client: S3Client;\n private readonly bucket: string;\n\n constructor(config: S3StorageConfig) {\n if (!config.bucket?.trim()) {\n throw new StorageConfigError('S3 bucket is not configured');\n }\n if (!config.region?.trim()) {\n throw new StorageConfigError('S3 region is not configured');\n }\n\n this.bucket = config.bucket;\n this.client = new S3Client({\n region: config.region,\n ...(config.accessKeyId && config.secretAccessKey\n ? {\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n }\n : {}),\n });\n }\n\n async putObject(\n key: string,\n body: Buffer | Uint8Array | string,\n contentType?: string,\n contentEncoding?: string\n ): Promise<void> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ...(contentType ? { ContentType: contentType } : {}),\n ...(contentEncoding ? { ContentEncoding: contentEncoding } : {}),\n })\n );\n }\n\n async getObjectBuffer(key: string): Promise<Buffer> {\n const response = await this.client.send(\n new GetObjectCommand({\n Bucket: this.bucket,\n Key: key,\n })\n );\n if (!response.Body) {\n throw new Error(`Object not found: ${key}`);\n }\n const byteArray = await response.Body.transformToByteArray();\n return Buffer.from(byteArray);\n }\n\n async listObjects(\n prefix: string = '',\n options: S3ListObjectsOptions = {}\n ): Promise<S3ListObjectsResult> {\n const response = await this.client.send(\n new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: prefix,\n Delimiter: options.delimiter,\n MaxKeys: options.limit || undefined,\n ...(options.marker ? { StartAfter: options.marker } : {}),\n })\n );\n\n return {\n Name: this.bucket,\n Prefix: prefix,\n Delimiter: options.delimiter,\n Marker: options.marker,\n NextMarker: response.NextContinuationToken,\n IsTruncated: response.IsTruncated,\n Contents: (response.Contents || []).map((obj: _Object) => ({ Key: obj.Key })),\n CommonPrefixes: (response.CommonPrefixes || []).map((cp) => ({ Prefix: cp.Prefix })),\n };\n }\n\n async deleteObject(key: string): Promise<void> {\n await this.client.send(\n new DeleteObjectCommand({\n Bucket: this.bucket,\n Key: key,\n })\n );\n }\n\n async deleteObjectsByPrefix(prefix: string): Promise<void> {\n const listed = await this.client.send(\n new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: prefix,\n })\n );\n const objects = (listed.Contents || [])\n .filter((obj) => obj.Key)\n .map((obj) => ({ Key: obj.Key! }));\n\n if (objects.length === 0) {\n return;\n }\n\n await this.client.send(\n new DeleteObjectsCommand({\n Bucket: this.bucket,\n Delete: {\n Objects: objects,\n Quiet: true,\n },\n })\n );\n }\n\n async createFolderPrefix(key: string): Promise<void> {\n await this.putObject(key, Buffer.alloc(0));\n }\n}\n","/**\n * Join a configurable key prefix with a relative object path.\n */\nexport function resolveStorageKey(keyPrefix: string | undefined, relativePath: string): string {\n const normalizedPath = relativePath.replace(/^\\/+/, '');\n if (!keyPrefix) {\n return normalizedPath;\n }\n const prefix = keyPrefix.endsWith('/') ? keyPrefix : `${keyPrefix}/`;\n return `${prefix}${normalizedPath}`;\n}\n","import { Storage } from '@google-cloud/storage';\nimport type { GcpStorageConfig } from './config';\nimport { StorageConfigError } from './errors';\nimport { resolveStorageKey } from './keys';\n\nexport interface GcpListBlobsResult {\n Name: string;\n Prefix: string;\n Delimiter: number;\n Marker: string;\n NextMarker: string;\n IsTruncated: boolean;\n Contents: Array<{ Key: string }>;\n CommonPrefixes: Array<{ Prefix: string }>;\n}\n\nexport class GcpStorageClient {\n private readonly storage: Storage;\n private readonly bucketName: string;\n private readonly keyPrefix: string;\n\n constructor(config: GcpStorageConfig) {\n if (!config.bucket?.trim()) {\n throw new StorageConfigError('GCP bucket is not configured');\n }\n this.bucketName = config.bucket;\n this.keyPrefix = config.keyPrefix || '';\n this.storage = new Storage({\n keyFilename: config.keyFilename,\n projectId: config.projectId,\n });\n }\n\n private bucket() {\n return this.storage.bucket(this.bucketName);\n }\n\n private fullKey(relativePath: string): string {\n return resolveStorageKey(this.keyPrefix, relativePath);\n }\n\n async upload(key: string, buffer: Buffer, contentType: string): Promise<{ path: string; success: true }> {\n const filePath = this.fullKey(key);\n const file = this.bucket().file(filePath);\n await file.save(buffer, { contentType });\n return { path: filePath, success: true };\n }\n\n async uploadFile(key: string, buffer: Buffer, contentType: string): Promise<{ path: string; success: true }> {\n return this.upload(key, buffer, contentType);\n }\n\n async download(relativePath: string): Promise<Buffer> {\n const prefix = this.fullKey(relativePath);\n const [data] = await this.bucket().file(prefix).download();\n return data;\n }\n\n async copy(srcKey: string, destKey: string): Promise<void> {\n const src = this.fullKey(srcKey);\n const dest = this.fullKey(destKey);\n await this.bucket().file(src).copy(this.bucket().file(dest));\n }\n\n async list(\n folderName: string = '',\n limit: number = 0,\n marker: string = ''\n ): Promise<GcpListBlobsResult> {\n const prefix = this.fullKey(folderName);\n const [files] = await this.bucket().getFiles({\n prefix,\n maxResults: limit || undefined,\n });\n\n const prefixForSplit = this.keyPrefix ? `${this.keyPrefix.replace(/\\/$/, '')}/` : '';\n\n const contents = files\n .filter((t) => !(t.name.replace(prefix, '').split('/').length - 1))\n .map((t) => ({\n Key: prefixForSplit ? t.name.split(prefixForSplit)[1] || t.name : t.name.replace(prefix, ''),\n }));\n\n const commonPrefix = files\n .filter((t) => t.name.replace(prefix, '').split('/').length - 1)\n .reduce<Array<{ Prefix: string }>>((acc, val) => {\n const relative = val.name.replace(prefix, '');\n const folderPrefix = `${relative.split('/')[0]}/`;\n if (!acc.some((t) => t.Prefix === folderPrefix)) {\n acc.push({ Prefix: folderPrefix });\n }\n return acc;\n }, [])\n .map((t) => ({ Prefix: `${folderName}${t.Prefix}` }));\n\n return {\n Name: this.keyPrefix.replace(/\\/$/, '') || 'storage',\n Prefix: folderName,\n Delimiter: limit,\n Marker: marker,\n NextMarker: '',\n IsTruncated: false,\n Contents: contents,\n CommonPrefixes: commonPrefix,\n };\n }\n\n async deleteFile(relativePath: string): Promise<void> {\n await this.bucket().file(this.fullKey(relativePath)).delete();\n }\n\n async deleteByPrefix(relativePrefix: string): Promise<void> {\n await this.bucket().deleteFiles({ prefix: this.fullKey(relativePrefix) });\n }\n\n /** Raw bucket access for app-specific paths outside keyPrefix. */\n getRawBucket() {\n return this.bucket();\n }\n}\n","import type { PublicUrlConfig } from './config';\nimport { resolveStorageKey } from './keys';\n\n/**\n * Build a public URL for an object key after upload.\n */\nexport function buildPublicUploadUrl(relativePath: string, config: PublicUrlConfig): string {\n const key = relativePath.replace(/^\\/+/, '');\n\n if (config.provider === 's3') {\n const bucket = config.bucket || '';\n const region = config.region || 'us-east-1';\n const base = `https://${bucket}.s3.${region}.amazonaws.com`;\n return `${base}/${key}`;\n }\n\n if (config.provider === 'gcp') {\n const gcpPrefix = config.gcpPrefix || '';\n const gcpKey = resolveStorageKey(gcpPrefix.replace(/\\/$/, ''), key);\n const base = (config.baseUrl || '').replace(/\\/$/, '');\n if (base) {\n return `${base}/${gcpKey}`;\n }\n const bucket = config.gcpBucket || config.bucket || '';\n return `https://storage.googleapis.com/${bucket}/${gcpKey}`;\n }\n\n let urlPath = key.startsWith('uploads/') ? key : `uploads/${key}`;\n const base = (config.baseUrl || '').replace(/\\/$/, '');\n if (base.endsWith('/uploads') && urlPath.startsWith('uploads/')) {\n urlPath = urlPath.replace(/^uploads\\//, '');\n }\n return `${base}/${urlPath}`;\n}\n"]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as _google_cloud_storage from '@google-cloud/storage';
|
|
2
|
+
|
|
3
|
+
interface S3StorageConfig {
|
|
4
|
+
accessKeyId?: string;
|
|
5
|
+
secretAccessKey?: string;
|
|
6
|
+
region: string;
|
|
7
|
+
bucket: string;
|
|
8
|
+
}
|
|
9
|
+
interface GcpStorageConfig {
|
|
10
|
+
keyFilename: string;
|
|
11
|
+
projectId: string;
|
|
12
|
+
bucket: string;
|
|
13
|
+
keyPrefix?: string;
|
|
14
|
+
}
|
|
15
|
+
type StorageProvider = 's3' | 'gcp' | 'local';
|
|
16
|
+
interface PublicUrlConfig {
|
|
17
|
+
provider: StorageProvider;
|
|
18
|
+
bucket?: string;
|
|
19
|
+
region?: string;
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
gcpPrefix?: string;
|
|
22
|
+
gcpBucket?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface S3ListObjectsOptions {
|
|
26
|
+
limit?: number;
|
|
27
|
+
marker?: string;
|
|
28
|
+
delimiter?: string;
|
|
29
|
+
}
|
|
30
|
+
interface S3ListObjectsResult {
|
|
31
|
+
Name?: string;
|
|
32
|
+
Prefix?: string;
|
|
33
|
+
Delimiter?: string;
|
|
34
|
+
Marker?: string;
|
|
35
|
+
NextMarker?: string;
|
|
36
|
+
IsTruncated?: boolean;
|
|
37
|
+
Contents?: Array<{
|
|
38
|
+
Key?: string;
|
|
39
|
+
}>;
|
|
40
|
+
CommonPrefixes?: Array<{
|
|
41
|
+
Prefix?: string;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
declare class S3StorageClient {
|
|
45
|
+
private readonly client;
|
|
46
|
+
private readonly bucket;
|
|
47
|
+
constructor(config: S3StorageConfig);
|
|
48
|
+
putObject(key: string, body: Buffer | Uint8Array | string, contentType?: string, contentEncoding?: string): Promise<void>;
|
|
49
|
+
getObjectBuffer(key: string): Promise<Buffer>;
|
|
50
|
+
listObjects(prefix?: string, options?: S3ListObjectsOptions): Promise<S3ListObjectsResult>;
|
|
51
|
+
deleteObject(key: string): Promise<void>;
|
|
52
|
+
deleteObjectsByPrefix(prefix: string): Promise<void>;
|
|
53
|
+
createFolderPrefix(key: string): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface GcpListBlobsResult {
|
|
57
|
+
Name: string;
|
|
58
|
+
Prefix: string;
|
|
59
|
+
Delimiter: number;
|
|
60
|
+
Marker: string;
|
|
61
|
+
NextMarker: string;
|
|
62
|
+
IsTruncated: boolean;
|
|
63
|
+
Contents: Array<{
|
|
64
|
+
Key: string;
|
|
65
|
+
}>;
|
|
66
|
+
CommonPrefixes: Array<{
|
|
67
|
+
Prefix: string;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
declare class GcpStorageClient {
|
|
71
|
+
private readonly storage;
|
|
72
|
+
private readonly bucketName;
|
|
73
|
+
private readonly keyPrefix;
|
|
74
|
+
constructor(config: GcpStorageConfig);
|
|
75
|
+
private bucket;
|
|
76
|
+
private fullKey;
|
|
77
|
+
upload(key: string, buffer: Buffer, contentType: string): Promise<{
|
|
78
|
+
path: string;
|
|
79
|
+
success: true;
|
|
80
|
+
}>;
|
|
81
|
+
uploadFile(key: string, buffer: Buffer, contentType: string): Promise<{
|
|
82
|
+
path: string;
|
|
83
|
+
success: true;
|
|
84
|
+
}>;
|
|
85
|
+
download(relativePath: string): Promise<Buffer>;
|
|
86
|
+
copy(srcKey: string, destKey: string): Promise<void>;
|
|
87
|
+
list(folderName?: string, limit?: number, marker?: string): Promise<GcpListBlobsResult>;
|
|
88
|
+
deleteFile(relativePath: string): Promise<void>;
|
|
89
|
+
deleteByPrefix(relativePrefix: string): Promise<void>;
|
|
90
|
+
/** Raw bucket access for app-specific paths outside keyPrefix. */
|
|
91
|
+
getRawBucket(): _google_cloud_storage.Bucket;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
declare class StorageConfigError extends Error {
|
|
95
|
+
constructor(message: string);
|
|
96
|
+
}
|
|
97
|
+
declare class StorageApiError extends Error {
|
|
98
|
+
readonly cause?: unknown | undefined;
|
|
99
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Join a configurable key prefix with a relative object path.
|
|
104
|
+
*/
|
|
105
|
+
declare function resolveStorageKey(keyPrefix: string | undefined, relativePath: string): string;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Build a public URL for an object key after upload.
|
|
109
|
+
*/
|
|
110
|
+
declare function buildPublicUploadUrl(relativePath: string, config: PublicUrlConfig): string;
|
|
111
|
+
|
|
112
|
+
export { type GcpListBlobsResult, GcpStorageClient, type GcpStorageConfig, type PublicUrlConfig, type S3ListObjectsOptions, type S3ListObjectsResult, S3StorageClient, type S3StorageConfig, StorageApiError, StorageConfigError, type StorageProvider, buildPublicUploadUrl, resolveStorageKey };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as _google_cloud_storage from '@google-cloud/storage';
|
|
2
|
+
|
|
3
|
+
interface S3StorageConfig {
|
|
4
|
+
accessKeyId?: string;
|
|
5
|
+
secretAccessKey?: string;
|
|
6
|
+
region: string;
|
|
7
|
+
bucket: string;
|
|
8
|
+
}
|
|
9
|
+
interface GcpStorageConfig {
|
|
10
|
+
keyFilename: string;
|
|
11
|
+
projectId: string;
|
|
12
|
+
bucket: string;
|
|
13
|
+
keyPrefix?: string;
|
|
14
|
+
}
|
|
15
|
+
type StorageProvider = 's3' | 'gcp' | 'local';
|
|
16
|
+
interface PublicUrlConfig {
|
|
17
|
+
provider: StorageProvider;
|
|
18
|
+
bucket?: string;
|
|
19
|
+
region?: string;
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
gcpPrefix?: string;
|
|
22
|
+
gcpBucket?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface S3ListObjectsOptions {
|
|
26
|
+
limit?: number;
|
|
27
|
+
marker?: string;
|
|
28
|
+
delimiter?: string;
|
|
29
|
+
}
|
|
30
|
+
interface S3ListObjectsResult {
|
|
31
|
+
Name?: string;
|
|
32
|
+
Prefix?: string;
|
|
33
|
+
Delimiter?: string;
|
|
34
|
+
Marker?: string;
|
|
35
|
+
NextMarker?: string;
|
|
36
|
+
IsTruncated?: boolean;
|
|
37
|
+
Contents?: Array<{
|
|
38
|
+
Key?: string;
|
|
39
|
+
}>;
|
|
40
|
+
CommonPrefixes?: Array<{
|
|
41
|
+
Prefix?: string;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
declare class S3StorageClient {
|
|
45
|
+
private readonly client;
|
|
46
|
+
private readonly bucket;
|
|
47
|
+
constructor(config: S3StorageConfig);
|
|
48
|
+
putObject(key: string, body: Buffer | Uint8Array | string, contentType?: string, contentEncoding?: string): Promise<void>;
|
|
49
|
+
getObjectBuffer(key: string): Promise<Buffer>;
|
|
50
|
+
listObjects(prefix?: string, options?: S3ListObjectsOptions): Promise<S3ListObjectsResult>;
|
|
51
|
+
deleteObject(key: string): Promise<void>;
|
|
52
|
+
deleteObjectsByPrefix(prefix: string): Promise<void>;
|
|
53
|
+
createFolderPrefix(key: string): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface GcpListBlobsResult {
|
|
57
|
+
Name: string;
|
|
58
|
+
Prefix: string;
|
|
59
|
+
Delimiter: number;
|
|
60
|
+
Marker: string;
|
|
61
|
+
NextMarker: string;
|
|
62
|
+
IsTruncated: boolean;
|
|
63
|
+
Contents: Array<{
|
|
64
|
+
Key: string;
|
|
65
|
+
}>;
|
|
66
|
+
CommonPrefixes: Array<{
|
|
67
|
+
Prefix: string;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
declare class GcpStorageClient {
|
|
71
|
+
private readonly storage;
|
|
72
|
+
private readonly bucketName;
|
|
73
|
+
private readonly keyPrefix;
|
|
74
|
+
constructor(config: GcpStorageConfig);
|
|
75
|
+
private bucket;
|
|
76
|
+
private fullKey;
|
|
77
|
+
upload(key: string, buffer: Buffer, contentType: string): Promise<{
|
|
78
|
+
path: string;
|
|
79
|
+
success: true;
|
|
80
|
+
}>;
|
|
81
|
+
uploadFile(key: string, buffer: Buffer, contentType: string): Promise<{
|
|
82
|
+
path: string;
|
|
83
|
+
success: true;
|
|
84
|
+
}>;
|
|
85
|
+
download(relativePath: string): Promise<Buffer>;
|
|
86
|
+
copy(srcKey: string, destKey: string): Promise<void>;
|
|
87
|
+
list(folderName?: string, limit?: number, marker?: string): Promise<GcpListBlobsResult>;
|
|
88
|
+
deleteFile(relativePath: string): Promise<void>;
|
|
89
|
+
deleteByPrefix(relativePrefix: string): Promise<void>;
|
|
90
|
+
/** Raw bucket access for app-specific paths outside keyPrefix. */
|
|
91
|
+
getRawBucket(): _google_cloud_storage.Bucket;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
declare class StorageConfigError extends Error {
|
|
95
|
+
constructor(message: string);
|
|
96
|
+
}
|
|
97
|
+
declare class StorageApiError extends Error {
|
|
98
|
+
readonly cause?: unknown | undefined;
|
|
99
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Join a configurable key prefix with a relative object path.
|
|
104
|
+
*/
|
|
105
|
+
declare function resolveStorageKey(keyPrefix: string | undefined, relativePath: string): string;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Build a public URL for an object key after upload.
|
|
109
|
+
*/
|
|
110
|
+
declare function buildPublicUploadUrl(relativePath: string, config: PublicUrlConfig): string;
|
|
111
|
+
|
|
112
|
+
export { type GcpListBlobsResult, GcpStorageClient, type GcpStorageConfig, type PublicUrlConfig, type S3ListObjectsOptions, type S3ListObjectsResult, S3StorageClient, type S3StorageConfig, StorageApiError, StorageConfigError, type StorageProvider, buildPublicUploadUrl, resolveStorageKey };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { S3Client, PutObjectCommand, GetObjectCommand, ListObjectsV2Command, DeleteObjectCommand, DeleteObjectsCommand } from '@aws-sdk/client-s3';
|
|
2
|
+
import { Storage } from '@google-cloud/storage';
|
|
3
|
+
|
|
4
|
+
// src/s3Client.ts
|
|
5
|
+
|
|
6
|
+
// src/errors.ts
|
|
7
|
+
var StorageConfigError = class extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "StorageConfigError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var StorageApiError = class extends Error {
|
|
14
|
+
constructor(message, cause) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.cause = cause;
|
|
17
|
+
this.name = "StorageApiError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/s3Client.ts
|
|
22
|
+
var S3StorageClient = class {
|
|
23
|
+
constructor(config) {
|
|
24
|
+
if (!config.bucket?.trim()) {
|
|
25
|
+
throw new StorageConfigError("S3 bucket is not configured");
|
|
26
|
+
}
|
|
27
|
+
if (!config.region?.trim()) {
|
|
28
|
+
throw new StorageConfigError("S3 region is not configured");
|
|
29
|
+
}
|
|
30
|
+
this.bucket = config.bucket;
|
|
31
|
+
this.client = new S3Client({
|
|
32
|
+
region: config.region,
|
|
33
|
+
...config.accessKeyId && config.secretAccessKey ? {
|
|
34
|
+
credentials: {
|
|
35
|
+
accessKeyId: config.accessKeyId,
|
|
36
|
+
secretAccessKey: config.secretAccessKey
|
|
37
|
+
}
|
|
38
|
+
} : {}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async putObject(key, body, contentType, contentEncoding) {
|
|
42
|
+
await this.client.send(
|
|
43
|
+
new PutObjectCommand({
|
|
44
|
+
Bucket: this.bucket,
|
|
45
|
+
Key: key,
|
|
46
|
+
Body: body,
|
|
47
|
+
...contentType ? { ContentType: contentType } : {},
|
|
48
|
+
...contentEncoding ? { ContentEncoding: contentEncoding } : {}
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
async getObjectBuffer(key) {
|
|
53
|
+
const response = await this.client.send(
|
|
54
|
+
new GetObjectCommand({
|
|
55
|
+
Bucket: this.bucket,
|
|
56
|
+
Key: key
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
if (!response.Body) {
|
|
60
|
+
throw new Error(`Object not found: ${key}`);
|
|
61
|
+
}
|
|
62
|
+
const byteArray = await response.Body.transformToByteArray();
|
|
63
|
+
return Buffer.from(byteArray);
|
|
64
|
+
}
|
|
65
|
+
async listObjects(prefix = "", options = {}) {
|
|
66
|
+
const response = await this.client.send(
|
|
67
|
+
new ListObjectsV2Command({
|
|
68
|
+
Bucket: this.bucket,
|
|
69
|
+
Prefix: prefix,
|
|
70
|
+
Delimiter: options.delimiter,
|
|
71
|
+
MaxKeys: options.limit || void 0,
|
|
72
|
+
...options.marker ? { StartAfter: options.marker } : {}
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
return {
|
|
76
|
+
Name: this.bucket,
|
|
77
|
+
Prefix: prefix,
|
|
78
|
+
Delimiter: options.delimiter,
|
|
79
|
+
Marker: options.marker,
|
|
80
|
+
NextMarker: response.NextContinuationToken,
|
|
81
|
+
IsTruncated: response.IsTruncated,
|
|
82
|
+
Contents: (response.Contents || []).map((obj) => ({ Key: obj.Key })),
|
|
83
|
+
CommonPrefixes: (response.CommonPrefixes || []).map((cp) => ({ Prefix: cp.Prefix }))
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async deleteObject(key) {
|
|
87
|
+
await this.client.send(
|
|
88
|
+
new DeleteObjectCommand({
|
|
89
|
+
Bucket: this.bucket,
|
|
90
|
+
Key: key
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
async deleteObjectsByPrefix(prefix) {
|
|
95
|
+
const listed = await this.client.send(
|
|
96
|
+
new ListObjectsV2Command({
|
|
97
|
+
Bucket: this.bucket,
|
|
98
|
+
Prefix: prefix
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
const objects = (listed.Contents || []).filter((obj) => obj.Key).map((obj) => ({ Key: obj.Key }));
|
|
102
|
+
if (objects.length === 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
await this.client.send(
|
|
106
|
+
new DeleteObjectsCommand({
|
|
107
|
+
Bucket: this.bucket,
|
|
108
|
+
Delete: {
|
|
109
|
+
Objects: objects,
|
|
110
|
+
Quiet: true
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
async createFolderPrefix(key) {
|
|
116
|
+
await this.putObject(key, Buffer.alloc(0));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// src/keys.ts
|
|
121
|
+
function resolveStorageKey(keyPrefix, relativePath) {
|
|
122
|
+
const normalizedPath = relativePath.replace(/^\/+/, "");
|
|
123
|
+
if (!keyPrefix) {
|
|
124
|
+
return normalizedPath;
|
|
125
|
+
}
|
|
126
|
+
const prefix = keyPrefix.endsWith("/") ? keyPrefix : `${keyPrefix}/`;
|
|
127
|
+
return `${prefix}${normalizedPath}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/gcpClient.ts
|
|
131
|
+
var GcpStorageClient = class {
|
|
132
|
+
constructor(config) {
|
|
133
|
+
if (!config.bucket?.trim()) {
|
|
134
|
+
throw new StorageConfigError("GCP bucket is not configured");
|
|
135
|
+
}
|
|
136
|
+
this.bucketName = config.bucket;
|
|
137
|
+
this.keyPrefix = config.keyPrefix || "";
|
|
138
|
+
this.storage = new Storage({
|
|
139
|
+
keyFilename: config.keyFilename,
|
|
140
|
+
projectId: config.projectId
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
bucket() {
|
|
144
|
+
return this.storage.bucket(this.bucketName);
|
|
145
|
+
}
|
|
146
|
+
fullKey(relativePath) {
|
|
147
|
+
return resolveStorageKey(this.keyPrefix, relativePath);
|
|
148
|
+
}
|
|
149
|
+
async upload(key, buffer, contentType) {
|
|
150
|
+
const filePath = this.fullKey(key);
|
|
151
|
+
const file = this.bucket().file(filePath);
|
|
152
|
+
await file.save(buffer, { contentType });
|
|
153
|
+
return { path: filePath, success: true };
|
|
154
|
+
}
|
|
155
|
+
async uploadFile(key, buffer, contentType) {
|
|
156
|
+
return this.upload(key, buffer, contentType);
|
|
157
|
+
}
|
|
158
|
+
async download(relativePath) {
|
|
159
|
+
const prefix = this.fullKey(relativePath);
|
|
160
|
+
const [data] = await this.bucket().file(prefix).download();
|
|
161
|
+
return data;
|
|
162
|
+
}
|
|
163
|
+
async copy(srcKey, destKey) {
|
|
164
|
+
const src = this.fullKey(srcKey);
|
|
165
|
+
const dest = this.fullKey(destKey);
|
|
166
|
+
await this.bucket().file(src).copy(this.bucket().file(dest));
|
|
167
|
+
}
|
|
168
|
+
async list(folderName = "", limit = 0, marker = "") {
|
|
169
|
+
const prefix = this.fullKey(folderName);
|
|
170
|
+
const [files] = await this.bucket().getFiles({
|
|
171
|
+
prefix,
|
|
172
|
+
maxResults: limit || void 0
|
|
173
|
+
});
|
|
174
|
+
const prefixForSplit = this.keyPrefix ? `${this.keyPrefix.replace(/\/$/, "")}/` : "";
|
|
175
|
+
const contents = files.filter((t) => !(t.name.replace(prefix, "").split("/").length - 1)).map((t) => ({
|
|
176
|
+
Key: prefixForSplit ? t.name.split(prefixForSplit)[1] || t.name : t.name.replace(prefix, "")
|
|
177
|
+
}));
|
|
178
|
+
const commonPrefix = files.filter((t) => t.name.replace(prefix, "").split("/").length - 1).reduce((acc, val) => {
|
|
179
|
+
const relative = val.name.replace(prefix, "");
|
|
180
|
+
const folderPrefix = `${relative.split("/")[0]}/`;
|
|
181
|
+
if (!acc.some((t) => t.Prefix === folderPrefix)) {
|
|
182
|
+
acc.push({ Prefix: folderPrefix });
|
|
183
|
+
}
|
|
184
|
+
return acc;
|
|
185
|
+
}, []).map((t) => ({ Prefix: `${folderName}${t.Prefix}` }));
|
|
186
|
+
return {
|
|
187
|
+
Name: this.keyPrefix.replace(/\/$/, "") || "storage",
|
|
188
|
+
Prefix: folderName,
|
|
189
|
+
Delimiter: limit,
|
|
190
|
+
Marker: marker,
|
|
191
|
+
NextMarker: "",
|
|
192
|
+
IsTruncated: false,
|
|
193
|
+
Contents: contents,
|
|
194
|
+
CommonPrefixes: commonPrefix
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async deleteFile(relativePath) {
|
|
198
|
+
await this.bucket().file(this.fullKey(relativePath)).delete();
|
|
199
|
+
}
|
|
200
|
+
async deleteByPrefix(relativePrefix) {
|
|
201
|
+
await this.bucket().deleteFiles({ prefix: this.fullKey(relativePrefix) });
|
|
202
|
+
}
|
|
203
|
+
/** Raw bucket access for app-specific paths outside keyPrefix. */
|
|
204
|
+
getRawBucket() {
|
|
205
|
+
return this.bucket();
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// src/publicUrl.ts
|
|
210
|
+
function buildPublicUploadUrl(relativePath, config) {
|
|
211
|
+
const key = relativePath.replace(/^\/+/, "");
|
|
212
|
+
if (config.provider === "s3") {
|
|
213
|
+
const bucket = config.bucket || "";
|
|
214
|
+
const region = config.region || "us-east-1";
|
|
215
|
+
const base2 = `https://${bucket}.s3.${region}.amazonaws.com`;
|
|
216
|
+
return `${base2}/${key}`;
|
|
217
|
+
}
|
|
218
|
+
if (config.provider === "gcp") {
|
|
219
|
+
const gcpPrefix = config.gcpPrefix || "";
|
|
220
|
+
const gcpKey = resolveStorageKey(gcpPrefix.replace(/\/$/, ""), key);
|
|
221
|
+
const base2 = (config.baseUrl || "").replace(/\/$/, "");
|
|
222
|
+
if (base2) {
|
|
223
|
+
return `${base2}/${gcpKey}`;
|
|
224
|
+
}
|
|
225
|
+
const bucket = config.gcpBucket || config.bucket || "";
|
|
226
|
+
return `https://storage.googleapis.com/${bucket}/${gcpKey}`;
|
|
227
|
+
}
|
|
228
|
+
let urlPath = key.startsWith("uploads/") ? key : `uploads/${key}`;
|
|
229
|
+
const base = (config.baseUrl || "").replace(/\/$/, "");
|
|
230
|
+
if (base.endsWith("/uploads") && urlPath.startsWith("uploads/")) {
|
|
231
|
+
urlPath = urlPath.replace(/^uploads\//, "");
|
|
232
|
+
}
|
|
233
|
+
return `${base}/${urlPath}`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export { GcpStorageClient, S3StorageClient, StorageApiError, StorageConfigError, buildPublicUploadUrl, resolveStorageKey };
|
|
237
|
+
//# sourceMappingURL=index.js.map
|
|
238
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/s3Client.ts","../src/keys.ts","../src/gcpClient.ts","../src/publicUrl.ts"],"names":["base"],"mappings":"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","file":"index.js","sourcesContent":["export class StorageConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'StorageConfigError';\n }\n}\n\nexport class StorageApiError extends Error {\n constructor(message: string, readonly cause?: unknown) {\n super(message);\n this.name = 'StorageApiError';\n }\n}\n","import {\n DeleteObjectCommand,\n DeleteObjectsCommand,\n GetObjectCommand,\n ListObjectsV2Command,\n PutObjectCommand,\n S3Client,\n type _Object,\n} from '@aws-sdk/client-s3';\nimport type { S3StorageConfig } from './config';\nimport { StorageConfigError } from './errors';\n\nexport interface S3ListObjectsOptions {\n limit?: number;\n marker?: string;\n delimiter?: string;\n}\n\nexport interface S3ListObjectsResult {\n Name?: string;\n Prefix?: string;\n Delimiter?: string;\n Marker?: string;\n NextMarker?: string;\n IsTruncated?: boolean;\n Contents?: Array<{ Key?: string }>;\n CommonPrefixes?: Array<{ Prefix?: string }>;\n}\n\nexport class S3StorageClient {\n private readonly client: S3Client;\n private readonly bucket: string;\n\n constructor(config: S3StorageConfig) {\n if (!config.bucket?.trim()) {\n throw new StorageConfigError('S3 bucket is not configured');\n }\n if (!config.region?.trim()) {\n throw new StorageConfigError('S3 region is not configured');\n }\n\n this.bucket = config.bucket;\n this.client = new S3Client({\n region: config.region,\n ...(config.accessKeyId && config.secretAccessKey\n ? {\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n }\n : {}),\n });\n }\n\n async putObject(\n key: string,\n body: Buffer | Uint8Array | string,\n contentType?: string,\n contentEncoding?: string\n ): Promise<void> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ...(contentType ? { ContentType: contentType } : {}),\n ...(contentEncoding ? { ContentEncoding: contentEncoding } : {}),\n })\n );\n }\n\n async getObjectBuffer(key: string): Promise<Buffer> {\n const response = await this.client.send(\n new GetObjectCommand({\n Bucket: this.bucket,\n Key: key,\n })\n );\n if (!response.Body) {\n throw new Error(`Object not found: ${key}`);\n }\n const byteArray = await response.Body.transformToByteArray();\n return Buffer.from(byteArray);\n }\n\n async listObjects(\n prefix: string = '',\n options: S3ListObjectsOptions = {}\n ): Promise<S3ListObjectsResult> {\n const response = await this.client.send(\n new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: prefix,\n Delimiter: options.delimiter,\n MaxKeys: options.limit || undefined,\n ...(options.marker ? { StartAfter: options.marker } : {}),\n })\n );\n\n return {\n Name: this.bucket,\n Prefix: prefix,\n Delimiter: options.delimiter,\n Marker: options.marker,\n NextMarker: response.NextContinuationToken,\n IsTruncated: response.IsTruncated,\n Contents: (response.Contents || []).map((obj: _Object) => ({ Key: obj.Key })),\n CommonPrefixes: (response.CommonPrefixes || []).map((cp) => ({ Prefix: cp.Prefix })),\n };\n }\n\n async deleteObject(key: string): Promise<void> {\n await this.client.send(\n new DeleteObjectCommand({\n Bucket: this.bucket,\n Key: key,\n })\n );\n }\n\n async deleteObjectsByPrefix(prefix: string): Promise<void> {\n const listed = await this.client.send(\n new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: prefix,\n })\n );\n const objects = (listed.Contents || [])\n .filter((obj) => obj.Key)\n .map((obj) => ({ Key: obj.Key! }));\n\n if (objects.length === 0) {\n return;\n }\n\n await this.client.send(\n new DeleteObjectsCommand({\n Bucket: this.bucket,\n Delete: {\n Objects: objects,\n Quiet: true,\n },\n })\n );\n }\n\n async createFolderPrefix(key: string): Promise<void> {\n await this.putObject(key, Buffer.alloc(0));\n }\n}\n","/**\n * Join a configurable key prefix with a relative object path.\n */\nexport function resolveStorageKey(keyPrefix: string | undefined, relativePath: string): string {\n const normalizedPath = relativePath.replace(/^\\/+/, '');\n if (!keyPrefix) {\n return normalizedPath;\n }\n const prefix = keyPrefix.endsWith('/') ? keyPrefix : `${keyPrefix}/`;\n return `${prefix}${normalizedPath}`;\n}\n","import { Storage } from '@google-cloud/storage';\nimport type { GcpStorageConfig } from './config';\nimport { StorageConfigError } from './errors';\nimport { resolveStorageKey } from './keys';\n\nexport interface GcpListBlobsResult {\n Name: string;\n Prefix: string;\n Delimiter: number;\n Marker: string;\n NextMarker: string;\n IsTruncated: boolean;\n Contents: Array<{ Key: string }>;\n CommonPrefixes: Array<{ Prefix: string }>;\n}\n\nexport class GcpStorageClient {\n private readonly storage: Storage;\n private readonly bucketName: string;\n private readonly keyPrefix: string;\n\n constructor(config: GcpStorageConfig) {\n if (!config.bucket?.trim()) {\n throw new StorageConfigError('GCP bucket is not configured');\n }\n this.bucketName = config.bucket;\n this.keyPrefix = config.keyPrefix || '';\n this.storage = new Storage({\n keyFilename: config.keyFilename,\n projectId: config.projectId,\n });\n }\n\n private bucket() {\n return this.storage.bucket(this.bucketName);\n }\n\n private fullKey(relativePath: string): string {\n return resolveStorageKey(this.keyPrefix, relativePath);\n }\n\n async upload(key: string, buffer: Buffer, contentType: string): Promise<{ path: string; success: true }> {\n const filePath = this.fullKey(key);\n const file = this.bucket().file(filePath);\n await file.save(buffer, { contentType });\n return { path: filePath, success: true };\n }\n\n async uploadFile(key: string, buffer: Buffer, contentType: string): Promise<{ path: string; success: true }> {\n return this.upload(key, buffer, contentType);\n }\n\n async download(relativePath: string): Promise<Buffer> {\n const prefix = this.fullKey(relativePath);\n const [data] = await this.bucket().file(prefix).download();\n return data;\n }\n\n async copy(srcKey: string, destKey: string): Promise<void> {\n const src = this.fullKey(srcKey);\n const dest = this.fullKey(destKey);\n await this.bucket().file(src).copy(this.bucket().file(dest));\n }\n\n async list(\n folderName: string = '',\n limit: number = 0,\n marker: string = ''\n ): Promise<GcpListBlobsResult> {\n const prefix = this.fullKey(folderName);\n const [files] = await this.bucket().getFiles({\n prefix,\n maxResults: limit || undefined,\n });\n\n const prefixForSplit = this.keyPrefix ? `${this.keyPrefix.replace(/\\/$/, '')}/` : '';\n\n const contents = files\n .filter((t) => !(t.name.replace(prefix, '').split('/').length - 1))\n .map((t) => ({\n Key: prefixForSplit ? t.name.split(prefixForSplit)[1] || t.name : t.name.replace(prefix, ''),\n }));\n\n const commonPrefix = files\n .filter((t) => t.name.replace(prefix, '').split('/').length - 1)\n .reduce<Array<{ Prefix: string }>>((acc, val) => {\n const relative = val.name.replace(prefix, '');\n const folderPrefix = `${relative.split('/')[0]}/`;\n if (!acc.some((t) => t.Prefix === folderPrefix)) {\n acc.push({ Prefix: folderPrefix });\n }\n return acc;\n }, [])\n .map((t) => ({ Prefix: `${folderName}${t.Prefix}` }));\n\n return {\n Name: this.keyPrefix.replace(/\\/$/, '') || 'storage',\n Prefix: folderName,\n Delimiter: limit,\n Marker: marker,\n NextMarker: '',\n IsTruncated: false,\n Contents: contents,\n CommonPrefixes: commonPrefix,\n };\n }\n\n async deleteFile(relativePath: string): Promise<void> {\n await this.bucket().file(this.fullKey(relativePath)).delete();\n }\n\n async deleteByPrefix(relativePrefix: string): Promise<void> {\n await this.bucket().deleteFiles({ prefix: this.fullKey(relativePrefix) });\n }\n\n /** Raw bucket access for app-specific paths outside keyPrefix. */\n getRawBucket() {\n return this.bucket();\n }\n}\n","import type { PublicUrlConfig } from './config';\nimport { resolveStorageKey } from './keys';\n\n/**\n * Build a public URL for an object key after upload.\n */\nexport function buildPublicUploadUrl(relativePath: string, config: PublicUrlConfig): string {\n const key = relativePath.replace(/^\\/+/, '');\n\n if (config.provider === 's3') {\n const bucket = config.bucket || '';\n const region = config.region || 'us-east-1';\n const base = `https://${bucket}.s3.${region}.amazonaws.com`;\n return `${base}/${key}`;\n }\n\n if (config.provider === 'gcp') {\n const gcpPrefix = config.gcpPrefix || '';\n const gcpKey = resolveStorageKey(gcpPrefix.replace(/\\/$/, ''), key);\n const base = (config.baseUrl || '').replace(/\\/$/, '');\n if (base) {\n return `${base}/${gcpKey}`;\n }\n const bucket = config.gcpBucket || config.bucket || '';\n return `https://storage.googleapis.com/${bucket}/${gcpKey}`;\n }\n\n let urlPath = key.startsWith('uploads/') ? key : `uploads/${key}`;\n const base = (config.baseUrl || '').replace(/\\/$/, '');\n if (base.endsWith('/uploads') && urlPath.startsWith('uploads/')) {\n urlPath = urlPath.replace(/^uploads\\//, '');\n }\n return `${base}/${urlPath}`;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "multivendor-storage",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Framework-agnostic S3 and GCP storage clients with public URL helpers",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "SmartByteLabs",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/SmartByteLabs/multivendor-saas-components.git",
|
|
10
|
+
"directory": "multivendor-storage"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.cjs",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/index.d.cts",
|
|
26
|
+
"default": "./dist/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@aws-sdk/client-s3": "^3.525.0",
|
|
35
|
+
"@google-cloud/storage": "^7.12.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.13.4",
|
|
39
|
+
"tsup": "^8.4.0",
|
|
40
|
+
"typescript": "^5.7.3",
|
|
41
|
+
"vitest": "^3.0.5"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"s3",
|
|
45
|
+
"gcp",
|
|
46
|
+
"storage",
|
|
47
|
+
"upload",
|
|
48
|
+
"aws",
|
|
49
|
+
"google-cloud"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest"
|
|
55
|
+
}
|
|
56
|
+
}
|