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 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"]}
@@ -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 };
@@ -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":";;;;;;AAAO,IAAM,kBAAA,GAAN,cAAiC,KAAA,CAAM;AAAA,EAC5C,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,oBAAA;AAAA,EACd;AACF;AAEO,IAAM,eAAA,GAAN,cAA8B,KAAA,CAAM;AAAA,EACzC,WAAA,CAAY,SAA0B,KAAA,EAAiB;AACrD,IAAA,KAAA,CAAM,OAAO,CAAA;AADuB,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AAEpC,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AAAA,EACd;AACF;;;ACiBO,IAAM,kBAAN,MAAsB;AAAA,EAI3B,YAAY,MAAA,EAAyB;AACnC,IAAA,IAAI,CAAC,MAAA,CAAO,MAAA,EAAQ,IAAA,EAAK,EAAG;AAC1B,MAAA,MAAM,IAAI,mBAAmB,6BAA6B,CAAA;AAAA,IAC5D;AACA,IAAA,IAAI,CAAC,MAAA,CAAO,MAAA,EAAQ,IAAA,EAAK,EAAG;AAC1B,MAAA,MAAM,IAAI,mBAAmB,6BAA6B,CAAA;AAAA,IAC5D;AAEA,IAAA,IAAA,CAAK,SAAS,MAAA,CAAO,MAAA;AACrB,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,QAAA,CAAS;AAAA,MACzB,QAAQ,MAAA,CAAO,MAAA;AAAA,MACf,GAAI,MAAA,CAAO,WAAA,IAAe,MAAA,CAAO,eAAA,GAC7B;AAAA,QACE,WAAA,EAAa;AAAA,UACX,aAAa,MAAA,CAAO,WAAA;AAAA,UACpB,iBAAiB,MAAA,CAAO;AAAA;AAC1B,UAEF;AAAC,KACN,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,SAAA,CACJ,GAAA,EACA,IAAA,EACA,aACA,eAAA,EACe;AACf,IAAA,MAAM,KAAK,MAAA,CAAO,IAAA;AAAA,MAChB,IAAI,gBAAA,CAAiB;AAAA,QACnB,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,GAAA,EAAK,GAAA;AAAA,QACL,IAAA,EAAM,IAAA;AAAA,QACN,GAAI,WAAA,GAAc,EAAE,WAAA,EAAa,WAAA,KAAgB,EAAC;AAAA,QAClD,GAAI,eAAA,GAAkB,EAAE,eAAA,EAAiB,eAAA,KAAoB;AAAC,OAC/D;AAAA,KACH;AAAA,EACF;AAAA,EAEA,MAAM,gBAAgB,GAAA,EAA8B;AAClD,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,MACjC,IAAI,gBAAA,CAAiB;AAAA,QACnB,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,GAAA,EAAK;AAAA,OACN;AAAA,KACH;AACA,IAAA,IAAI,CAAC,SAAS,IAAA,EAAM;AAClB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,kBAAA,EAAqB,GAAG,CAAA,CAAE,CAAA;AAAA,IAC5C;AACA,IAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,CAAK,oBAAA,EAAqB;AAC3D,IAAA,OAAO,MAAA,CAAO,KAAK,SAAS,CAAA;AAAA,EAC9B;AAAA,EAEA,MAAM,WAAA,CACJ,MAAA,GAAiB,EAAA,EACjB,OAAA,GAAgC,EAAC,EACH;AAC9B,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,MACjC,IAAI,oBAAA,CAAqB;AAAA,QACvB,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,MAAA,EAAQ,MAAA;AAAA,QACR,WAAW,OAAA,CAAQ,SAAA;AAAA,QACnB,OAAA,EAAS,QAAQ,KAAA,IAAS,MAAA;AAAA,QAC1B,GAAI,QAAQ,MAAA,GAAS,EAAE,YAAY,OAAA,CAAQ,MAAA,KAAW;AAAC,OACxD;AAAA,KACH;AAEA,IAAA,OAAO;AAAA,MACL,MAAM,IAAA,CAAK,MAAA;AAAA,MACX,MAAA,EAAQ,MAAA;AAAA,MACR,WAAW,OAAA,CAAQ,SAAA;AAAA,MACnB,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,YAAY,QAAA,CAAS,qBAAA;AAAA,MACrB,aAAa,QAAA,CAAS,WAAA;AAAA,MACtB,QAAA,EAAA,CAAW,QAAA,CAAS,QAAA,IAAY,EAAC,EAAG,GAAA,CAAI,CAAC,GAAA,MAAkB,EAAE,GAAA,EAAK,GAAA,CAAI,GAAA,EAAI,CAAE,CAAA;AAAA,MAC5E,cAAA,EAAA,CAAiB,QAAA,CAAS,cAAA,IAAkB,EAAC,EAAG,GAAA,CAAI,CAAC,EAAA,MAAQ,EAAE,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAO,CAAE;AAAA,KACrF;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,GAAA,EAA4B;AAC7C,IAAA,MAAM,KAAK,MAAA,CAAO,IAAA;AAAA,MAChB,IAAI,mBAAA,CAAoB;AAAA,QACtB,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,GAAA,EAAK;AAAA,OACN;AAAA,KACH;AAAA,EACF;AAAA,EAEA,MAAM,sBAAsB,MAAA,EAA+B;AACzD,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,MAC/B,IAAI,oBAAA,CAAqB;AAAA,QACvB,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,MAAA,EAAQ;AAAA,OACT;AAAA,KACH;AACA,IAAA,MAAM,WAAW,MAAA,CAAO,QAAA,IAAY,EAAC,EAClC,MAAA,CAAO,CAAC,GAAA,KAAQ,GAAA,CAAI,GAAG,CAAA,CACvB,IAAI,CAAC,GAAA,MAAS,EAAE,GAAA,EAAK,GAAA,CAAI,KAAK,CAAE,CAAA;AAEnC,IAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AACxB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,KAAK,MAAA,CAAO,IAAA;AAAA,MAChB,IAAI,oBAAA,CAAqB;AAAA,QACvB,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,MAAA,EAAQ;AAAA,UACN,OAAA,EAAS,OAAA;AAAA,UACT,KAAA,EAAO;AAAA;AACT,OACD;AAAA,KACH;AAAA,EACF;AAAA,EAEA,MAAM,mBAAmB,GAAA,EAA4B;AACnD,IAAA,MAAM,KAAK,SAAA,CAAU,GAAA,EAAK,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,EAC3C;AACF;;;ACnJO,SAAS,iBAAA,CAAkB,WAA+B,YAAA,EAA8B;AAC7F,EAAA,MAAM,cAAA,GAAiB,YAAA,CAAa,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AACtD,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,OAAO,cAAA;AAAA,EACT;AACA,EAAA,MAAM,SAAS,SAAA,CAAU,QAAA,CAAS,GAAG,CAAA,GAAI,SAAA,GAAY,GAAG,SAAS,CAAA,CAAA,CAAA;AACjE,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,EAAG,cAAc,CAAA,CAAA;AACnC;;;ACMO,IAAM,mBAAN,MAAuB;AAAA,EAK5B,YAAY,MAAA,EAA0B;AACpC,IAAA,IAAI,CAAC,MAAA,CAAO,MAAA,EAAQ,IAAA,EAAK,EAAG;AAC1B,MAAA,MAAM,IAAI,mBAAmB,8BAA8B,CAAA;AAAA,IAC7D;AACA,IAAA,IAAA,CAAK,aAAa,MAAA,CAAO,MAAA;AACzB,IAAA,IAAA,CAAK,SAAA,GAAY,OAAO,SAAA,IAAa,EAAA;AACrC,IAAA,IAAA,CAAK,OAAA,GAAU,IAAI,OAAA,CAAQ;AAAA,MACzB,aAAa,MAAA,CAAO,WAAA;AAAA,MACpB,WAAW,MAAA,CAAO;AAAA,KACnB,CAAA;AAAA,EACH;AAAA,EAEQ,MAAA,GAAS;AACf,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,MAAA,CAAO,IAAA,CAAK,UAAU,CAAA;AAAA,EAC5C;AAAA,EAEQ,QAAQ,YAAA,EAA8B;AAC5C,IAAA,OAAO,iBAAA,CAAkB,IAAA,CAAK,SAAA,EAAW,YAAY,CAAA;AAAA,EACvD;AAAA,EAEA,MAAM,MAAA,CAAO,GAAA,EAAa,MAAA,EAAgB,WAAA,EAA+D;AACvG,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AACjC,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,MAAA,EAAO,CAAE,KAAK,QAAQ,CAAA;AACxC,IAAA,MAAM,IAAA,CAAK,IAAA,CAAK,MAAA,EAAQ,EAAE,aAAa,CAAA;AACvC,IAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,OAAA,EAAS,IAAA,EAAK;AAAA,EACzC;AAAA,EAEA,MAAM,UAAA,CAAW,GAAA,EAAa,MAAA,EAAgB,WAAA,EAA+D;AAC3G,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,GAAA,EAAK,MAAA,EAAQ,WAAW,CAAA;AAAA,EAC7C;AAAA,EAEA,MAAM,SAAS,YAAA,EAAuC;AACpD,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,OAAA,CAAQ,YAAY,CAAA;AACxC,IAAA,MAAM,CAAC,IAAI,CAAA,GAAI,MAAM,IAAA,CAAK,QAAO,CAAE,IAAA,CAAK,MAAM,CAAA,CAAE,QAAA,EAAS;AACzD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAM,IAAA,CAAK,MAAA,EAAgB,OAAA,EAAgC;AACzD,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,MAAM,CAAA;AAC/B,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AACjC,IAAA,MAAM,IAAA,CAAK,MAAA,EAAO,CAAE,IAAA,CAAK,GAAG,CAAA,CAAE,IAAA,CAAK,IAAA,CAAK,MAAA,EAAO,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,EAC7D;AAAA,EAEA,MAAM,IAAA,CACJ,UAAA,GAAqB,IACrB,KAAA,GAAgB,CAAA,EAChB,SAAiB,EAAA,EACY;AAC7B,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,OAAA,CAAQ,UAAU,CAAA;AACtC,IAAA,MAAM,CAAC,KAAK,CAAA,GAAI,MAAM,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS;AAAA,MAC3C,MAAA;AAAA,MACA,YAAY,KAAA,IAAS;AAAA,KACtB,CAAA;AAED,IAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,SAAA,GAAY,CAAA,EAAG,IAAA,CAAK,UAAU,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,CAAA,CAAA,GAAM,EAAA;AAElF,IAAA,MAAM,QAAA,GAAW,MACd,MAAA,CAAO,CAAC,MAAM,EAAE,CAAA,CAAE,KAAK,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA,CAAE,MAAA,GAAS,EAAE,CAAA,CACjE,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,MACX,GAAA,EAAK,cAAA,GAAiB,CAAA,CAAE,IAAA,CAAK,MAAM,cAAc,CAAA,CAAE,CAAC,CAAA,IAAK,EAAE,IAAA,GAAO,CAAA,CAAE,IAAA,CAAK,OAAA,CAAQ,QAAQ,EAAE;AAAA,KAC7F,CAAE,CAAA;AAEJ,IAAA,MAAM,YAAA,GAAe,MAClB,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,IAAA,CAAK,QAAQ,MAAA,EAAQ,EAAE,EAAE,KAAA,CAAM,GAAG,EAAE,MAAA,GAAS,CAAC,EAC9D,MAAA,CAAkC,CAAC,KAAK,GAAA,KAAQ;AAC/C,MAAA,MAAM,QAAA,GAAW,GAAA,CAAI,IAAA,CAAK,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAC5C,MAAA,MAAM,eAAe,CAAA,EAAG,QAAA,CAAS,MAAM,GAAG,CAAA,CAAE,CAAC,CAAC,CAAA,CAAA,CAAA;AAC9C,MAAA,IAAI,CAAC,IAAI,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,YAAY,CAAA,EAAG;AAC/C,QAAA,GAAA,CAAI,IAAA,CAAK,EAAE,MAAA,EAAQ,YAAA,EAAc,CAAA;AAAA,MACnC;AACA,MAAA,OAAO,GAAA;AAAA,IACT,CAAA,EAAG,EAAE,CAAA,CACJ,IAAI,CAAC,CAAA,MAAO,EAAE,MAAA,EAAQ,GAAG,UAAU,CAAA,EAAG,CAAA,CAAE,MAAM,IAAG,CAAE,CAAA;AAEtD,IAAA,OAAO;AAAA,MACL,MAAM,IAAA,CAAK,SAAA,CAAU,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,IAAK,SAAA;AAAA,MAC3C,MAAA,EAAQ,UAAA;AAAA,MACR,SAAA,EAAW,KAAA;AAAA,MACX,MAAA,EAAQ,MAAA;AAAA,MACR,UAAA,EAAY,EAAA;AAAA,MACZ,WAAA,EAAa,KAAA;AAAA,MACb,QAAA,EAAU,QAAA;AAAA,MACV,cAAA,EAAgB;AAAA,KAClB;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,YAAA,EAAqC;AACpD,IAAA,MAAM,IAAA,CAAK,QAAO,CAAE,IAAA,CAAK,KAAK,OAAA,CAAQ,YAAY,CAAC,CAAA,CAAE,MAAA,EAAO;AAAA,EAC9D;AAAA,EAEA,MAAM,eAAe,cAAA,EAAuC;AAC1D,IAAA,MAAM,IAAA,CAAK,MAAA,EAAO,CAAE,WAAA,CAAY,EAAE,QAAQ,IAAA,CAAK,OAAA,CAAQ,cAAc,CAAA,EAAG,CAAA;AAAA,EAC1E;AAAA;AAAA,EAGA,YAAA,GAAe;AACb,IAAA,OAAO,KAAK,MAAA,EAAO;AAAA,EACrB;AACF;;;ACjHO,SAAS,oBAAA,CAAqB,cAAsB,MAAA,EAAiC;AAC1F,EAAA,MAAM,GAAA,GAAM,YAAA,CAAa,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAE3C,EAAA,IAAI,MAAA,CAAO,aAAa,IAAA,EAAM;AAC5B,IAAA,MAAM,MAAA,GAAS,OAAO,MAAA,IAAU,EAAA;AAChC,IAAA,MAAM,MAAA,GAAS,OAAO,MAAA,IAAU,WAAA;AAChC,IAAA,MAAMA,KAAAA,GAAO,CAAA,QAAA,EAAW,MAAM,CAAA,IAAA,EAAO,MAAM,CAAA,cAAA,CAAA;AAC3C,IAAA,OAAO,CAAA,EAAGA,KAAI,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA;AAAA,EACvB;AAEA,EAAA,IAAI,MAAA,CAAO,aAAa,KAAA,EAAO;AAC7B,IAAA,MAAM,SAAA,GAAY,OAAO,SAAA,IAAa,EAAA;AACtC,IAAA,MAAM,SAAS,iBAAA,CAAkB,SAAA,CAAU,QAAQ,KAAA,EAAO,EAAE,GAAG,GAAG,CAAA;AAClE,IAAA,MAAMA,SAAQ,MAAA,CAAO,OAAA,IAAW,EAAA,EAAI,OAAA,CAAQ,OAAO,EAAE,CAAA;AACrD,IAAA,IAAIA,KAAAA,EAAM;AACR,MAAA,OAAO,CAAA,EAAGA,KAAI,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AAAA,IAC1B;AACA,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,SAAA,IAAa,MAAA,CAAO,MAAA,IAAU,EAAA;AACpD,IAAA,OAAO,CAAA,+BAAA,EAAkC,MAAM,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AAAA,EAC3D;AAEA,EAAA,IAAI,UAAU,GAAA,CAAI,UAAA,CAAW,UAAU,CAAA,GAAI,GAAA,GAAM,WAAW,GAAG,CAAA,CAAA;AAC/D,EAAA,MAAM,QAAQ,MAAA,CAAO,OAAA,IAAW,EAAA,EAAI,OAAA,CAAQ,OAAO,EAAE,CAAA;AACrD,EAAA,IAAI,KAAK,QAAA,CAAS,UAAU,KAAK,OAAA,CAAQ,UAAA,CAAW,UAAU,CAAA,EAAG;AAC/D,IAAA,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,YAAA,EAAc,EAAE,CAAA;AAAA,EAC5C;AACA,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA;AAC3B","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
+ }