@pelatform/storage 1.0.1 → 1.0.3
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 +253 -5
- package/dist/{chunk-ZTZZCC52.js → chunk-NNFNVWIN.js} +18 -8
- package/dist/{chunk-KJMGBTTL.js → chunk-VZDXZ6EA.js} +35 -23
- package/dist/cloudinary.d.ts +1 -1
- package/dist/cloudinary.js +10 -6
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/s3.d.ts +1 -1
- package/dist/s3.js +25 -22
- package/dist/{storage-interface-UizTndyU.d.ts → storage-interface-CoYx1E3B.d.ts} +8 -8
- package/package.json +28 -15
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -1,10 +1,258 @@
|
|
|
1
1
|
# @pelatform/storage
|
|
2
2
|
|
|
3
|
-
A comprehensive storage utilities for SaaS applications. This package provides a unified interface for working with various storage providers including AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces, Supabase, and Cloudinary Storage.
|
|
4
|
-
|
|
5
3
|
[](https://www.npmjs.com/package/@pelatform/storage)
|
|
6
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A comprehensive storage utilities package for SaaS applications. This package provides a unified interface for working with various storage providers including AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces, Supabase Storage, and Cloudinary.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @pelatform/storage
|
|
12
|
+
# or
|
|
13
|
+
bun add @pelatform/storage
|
|
14
|
+
|
|
15
|
+
# Install peer dependencies for the provider(s) you need
|
|
16
|
+
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner # For S3-compatible providers
|
|
17
|
+
npm install cloudinary # For Cloudinary
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### S3-Compatible Storage (AWS, R2, MinIO, etc.)
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { S3Storage } from "@pelatform/storage/s3";
|
|
26
|
+
|
|
27
|
+
const storage = new S3Storage({
|
|
28
|
+
provider: "aws", // or 'cloudflare-r2', 'minio', 'digitalocean', 'supabase'
|
|
29
|
+
region: "us-east-1",
|
|
30
|
+
bucket: "my-bucket",
|
|
31
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
32
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Upload a file
|
|
36
|
+
await storage.upload({
|
|
37
|
+
key: "documents/file.pdf",
|
|
38
|
+
file: fileBuffer,
|
|
39
|
+
contentType: "application/pdf",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Download a file
|
|
43
|
+
const result = await storage.download({ key: "documents/file.pdf" });
|
|
44
|
+
|
|
45
|
+
// Generate a presigned URL (temporary access)
|
|
46
|
+
const url = await storage.getPresignedUrl({
|
|
47
|
+
key: "documents/file.pdf",
|
|
48
|
+
operation: "get",
|
|
49
|
+
expiresIn: 3600, // 1 hour
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Cloudinary Storage
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { CloudinaryStorage } from "@pelatform/storage/cloudinary";
|
|
57
|
+
|
|
58
|
+
const storage = new CloudinaryStorage({
|
|
59
|
+
provider: "cloudinary",
|
|
60
|
+
cloudName: process.env.CLOUDINARY_CLOUD_NAME,
|
|
61
|
+
apiKey: process.env.CLOUDINARY_API_KEY,
|
|
62
|
+
apiSecret: process.env.CLOUDINARY_API_SECRET,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Upload an image
|
|
66
|
+
await storage.upload({
|
|
67
|
+
key: "images/photo.jpg",
|
|
68
|
+
file: imageBuffer,
|
|
69
|
+
contentType: "image/jpeg",
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Key Features
|
|
74
|
+
|
|
75
|
+
- **Unified Interface**: Single API for multiple storage providers
|
|
76
|
+
- **S3-Compatible Providers**: AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces, Supabase Storage
|
|
77
|
+
- **Cloudinary Support**: Image and media management with built-in optimizations
|
|
78
|
+
- **File Operations**: Upload, download, delete, copy, move, and check existence
|
|
79
|
+
- **Folder Operations**: Create, delete, list, and manage folders
|
|
80
|
+
- **Presigned URLs**: Generate time-limited access URLs for secure sharing
|
|
81
|
+
- **Public URLs**: Get public access URLs with optional CDN support
|
|
82
|
+
- **Type-Safe**: Full TypeScript support with comprehensive types
|
|
83
|
+
- **Flexible Configuration**: Support for custom endpoints and CDN URLs
|
|
84
|
+
|
|
85
|
+
## Supported Providers
|
|
86
|
+
|
|
87
|
+
### S3-Compatible
|
|
88
|
+
|
|
89
|
+
- **AWS S3** - Amazon's object storage service
|
|
90
|
+
- **Cloudflare R2** - Cloudflare's S3-compatible storage (no egress fees)
|
|
91
|
+
- **MinIO** - Self-hosted S3-compatible storage
|
|
92
|
+
- **DigitalOcean Spaces** - DigitalOcean's object storage
|
|
93
|
+
- **Supabase Storage** - Supabase's S3-compatible storage
|
|
94
|
+
|
|
95
|
+
### Other Providers
|
|
96
|
+
|
|
97
|
+
- **Cloudinary** - Image and video management platform
|
|
98
|
+
|
|
99
|
+
## Configuration
|
|
100
|
+
|
|
101
|
+
### AWS S3
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const storage = new S3Storage({
|
|
105
|
+
provider: "aws",
|
|
106
|
+
region: "us-east-1",
|
|
107
|
+
bucket: "my-bucket",
|
|
108
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
109
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
110
|
+
publicUrl: "https://cdn.example.com", // Optional CDN URL
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Cloudflare R2
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const storage = new S3Storage({
|
|
118
|
+
provider: "cloudflare-r2",
|
|
119
|
+
region: "auto",
|
|
120
|
+
bucket: "my-bucket",
|
|
121
|
+
accessKeyId: process.env.R2_ACCESS_KEY_ID,
|
|
122
|
+
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
|
|
123
|
+
endpoint: "https://<account-id>.r2.cloudflarestorage.com",
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### MinIO
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const storage = new S3Storage({
|
|
131
|
+
provider: "minio",
|
|
132
|
+
region: "us-east-1",
|
|
133
|
+
bucket: "my-bucket",
|
|
134
|
+
accessKeyId: "minioadmin",
|
|
135
|
+
secretAccessKey: "minioadmin",
|
|
136
|
+
endpoint: "http://localhost:9000",
|
|
137
|
+
forcePathStyle: true, // Required for MinIO
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Basic Usage
|
|
142
|
+
|
|
143
|
+
### File Operations
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// Upload with options
|
|
147
|
+
await storage.upload({
|
|
148
|
+
key: "uploads/document.pdf",
|
|
149
|
+
file: buffer,
|
|
150
|
+
contentType: "application/pdf",
|
|
151
|
+
metadata: { userId: "123", category: "documents" },
|
|
152
|
+
cacheControl: "max-age=31536000",
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Download file
|
|
156
|
+
const { content, metadata } = await storage.download({
|
|
157
|
+
key: "uploads/document.pdf",
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Check if file exists
|
|
161
|
+
const exists = await storage.exists("uploads/document.pdf");
|
|
162
|
+
|
|
163
|
+
// Delete file
|
|
164
|
+
await storage.delete({ key: "uploads/document.pdf" });
|
|
165
|
+
|
|
166
|
+
// Copy file
|
|
167
|
+
await storage.copy({
|
|
168
|
+
sourceKey: "uploads/old.pdf",
|
|
169
|
+
destinationKey: "uploads/new.pdf",
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Move file
|
|
173
|
+
await storage.move({
|
|
174
|
+
sourceKey: "uploads/temp.pdf",
|
|
175
|
+
destinationKey: "uploads/final.pdf",
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// List files
|
|
179
|
+
const files = await storage.list({
|
|
180
|
+
prefix: "uploads/",
|
|
181
|
+
maxKeys: 100,
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Folder Operations
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// Create folder
|
|
189
|
+
await storage.createFolder({ path: "documents/" });
|
|
190
|
+
|
|
191
|
+
// Delete folder (recursive)
|
|
192
|
+
await storage.deleteFolder({
|
|
193
|
+
path: "documents/",
|
|
194
|
+
recursive: true,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// List folders
|
|
198
|
+
const folders = await storage.listFolders({
|
|
199
|
+
prefix: "uploads/",
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Check if folder exists
|
|
203
|
+
const exists = await storage.folderExists("documents/");
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### URL Generation
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// Get public URL
|
|
210
|
+
const publicUrl = storage.getPublicUrl("images/photo.jpg");
|
|
211
|
+
|
|
212
|
+
// Generate presigned URL for download
|
|
213
|
+
const downloadUrl = await storage.getPresignedUrl({
|
|
214
|
+
key: "documents/private.pdf",
|
|
215
|
+
operation: "get",
|
|
216
|
+
expiresIn: 3600, // 1 hour
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Generate presigned URL for upload
|
|
220
|
+
const uploadUrl = await storage.getPresignedUrl({
|
|
221
|
+
key: "uploads/new-file.pdf",
|
|
222
|
+
operation: "put",
|
|
223
|
+
expiresIn: 900, // 15 minutes
|
|
224
|
+
contentType: "application/pdf",
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## API Reference
|
|
229
|
+
|
|
230
|
+
### File Operations
|
|
231
|
+
|
|
232
|
+
- `upload(options)` - Upload a file
|
|
233
|
+
- `download(options)` - Download a file
|
|
234
|
+
- `delete(options)` - Delete a file
|
|
235
|
+
- `exists(key)` - Check if file exists
|
|
236
|
+
- `copy(options)` - Copy a file
|
|
237
|
+
- `move(options)` - Move a file
|
|
238
|
+
- `list(options)` - List files with prefix
|
|
239
|
+
- `getPresignedUrl(options)` - Generate presigned URL
|
|
240
|
+
- `getPublicUrl(key)` - Get public URL
|
|
241
|
+
|
|
242
|
+
### Folder Operations
|
|
243
|
+
|
|
244
|
+
- `createFolder(options)` - Create a folder
|
|
245
|
+
- `deleteFolder(options)` - Delete a folder
|
|
246
|
+
- `listFolders(options)` - List folders
|
|
247
|
+
- `folderExists(path)` - Check if folder exists
|
|
248
|
+
|
|
249
|
+
## Links
|
|
250
|
+
|
|
251
|
+
- [npm Package](https://www.npmjs.com/package/@pelatform/storage)
|
|
252
|
+
- [Contributing Guide](../../CONTRIBUTING.md)
|
|
253
|
+
- [Code of Conduct](../../CODE_OF_CONDUCT.md)
|
|
254
|
+
- [License](../../LICENSE)
|
|
7
255
|
|
|
8
|
-
##
|
|
256
|
+
## License
|
|
9
257
|
|
|
10
|
-
|
|
258
|
+
MIT © [Pelatform Inc.](../../LICENSE)
|
|
@@ -31,11 +31,15 @@ function getRequiredEnvVar(key) {
|
|
|
31
31
|
}
|
|
32
32
|
function getBooleanEnvVar(key, defaultValue = false) {
|
|
33
33
|
const value = getEnvVar(key);
|
|
34
|
-
if (!value)
|
|
34
|
+
if (!value) {
|
|
35
|
+
return defaultValue;
|
|
36
|
+
}
|
|
35
37
|
return value.toLowerCase() === "true" || value === "1";
|
|
36
38
|
}
|
|
37
39
|
function loadS3Config() {
|
|
38
|
-
const provider = getRequiredEnvVar(
|
|
40
|
+
const provider = getRequiredEnvVar(
|
|
41
|
+
ENV_VARS.PELATFORM_S3_PROVIDER
|
|
42
|
+
);
|
|
39
43
|
const validProviders = [
|
|
40
44
|
"aws",
|
|
41
45
|
"cloudflare-r2",
|
|
@@ -75,15 +79,15 @@ function loadStorageConfig() {
|
|
|
75
79
|
if (provider) {
|
|
76
80
|
if (provider === "cloudinary") {
|
|
77
81
|
return loadCloudinaryConfig();
|
|
78
|
-
} else {
|
|
79
|
-
return loadS3Config();
|
|
80
82
|
}
|
|
83
|
+
return loadS3Config();
|
|
81
84
|
}
|
|
82
85
|
const hasCloudinaryVars = getEnvVar(ENV_VARS.PELATFORM_CLOUDINARY_CLOUD_NAME) && getEnvVar(ENV_VARS.PELATFORM_CLOUDINARY_API_KEY) && getEnvVar(ENV_VARS.PELATFORM_CLOUDINARY_API_SECRET);
|
|
83
86
|
const hasS3Vars = getEnvVar(ENV_VARS.PELATFORM_S3_BUCKET) && getEnvVar(ENV_VARS.PELATFORM_S3_ACCESS_KEY_ID) && getEnvVar(ENV_VARS.PELATFORM_S3_SECRET_ACCESS_KEY);
|
|
84
87
|
if (hasCloudinaryVars) {
|
|
85
88
|
return loadCloudinaryConfig();
|
|
86
|
-
}
|
|
89
|
+
}
|
|
90
|
+
if (hasS3Vars) {
|
|
87
91
|
const config = loadS3Config();
|
|
88
92
|
if (!config.provider) {
|
|
89
93
|
config.provider = "aws";
|
|
@@ -107,11 +111,17 @@ function isStorageConfigured() {
|
|
|
107
111
|
}
|
|
108
112
|
function getStorageProvider() {
|
|
109
113
|
const provider = getEnvVar(ENV_VARS.PELATFORM_S3_PROVIDER);
|
|
110
|
-
if (provider)
|
|
114
|
+
if (provider) {
|
|
115
|
+
return provider;
|
|
116
|
+
}
|
|
111
117
|
const hasCloudinaryVars = getEnvVar(ENV_VARS.PELATFORM_CLOUDINARY_CLOUD_NAME);
|
|
112
118
|
const hasS3Vars = getEnvVar(ENV_VARS.PELATFORM_S3_BUCKET);
|
|
113
|
-
if (hasCloudinaryVars)
|
|
114
|
-
|
|
119
|
+
if (hasCloudinaryVars) {
|
|
120
|
+
return "cloudinary";
|
|
121
|
+
}
|
|
122
|
+
if (hasS3Vars) {
|
|
123
|
+
return "aws";
|
|
124
|
+
}
|
|
115
125
|
return void 0;
|
|
116
126
|
}
|
|
117
127
|
function validateS3EnvVars() {
|
|
@@ -26,13 +26,13 @@ function validateFileType(fileName, allowedTypes) {
|
|
|
26
26
|
const extension = fileName.split(".").pop()?.toLowerCase();
|
|
27
27
|
const isValidMime = allowedTypes.some((type) => {
|
|
28
28
|
if (type.includes("*")) {
|
|
29
|
-
const baseType = type.split("/")[0];
|
|
29
|
+
const baseType = type.split("/")[0] ?? "";
|
|
30
30
|
return mimeType.startsWith(baseType);
|
|
31
31
|
}
|
|
32
32
|
return mimeType === type;
|
|
33
33
|
});
|
|
34
34
|
const isValidExtension = extension && allowedTypes.includes(`.${extension}`);
|
|
35
|
-
if (!isValidMime
|
|
35
|
+
if (!(isValidMime || isValidExtension)) {
|
|
36
36
|
return {
|
|
37
37
|
valid: false,
|
|
38
38
|
error: `File type not allowed. Allowed types: ${allowedTypes.join(", ")}`
|
|
@@ -41,11 +41,13 @@ function validateFileType(fileName, allowedTypes) {
|
|
|
41
41
|
return { valid: true };
|
|
42
42
|
}
|
|
43
43
|
function formatFileSize(bytes) {
|
|
44
|
-
if (bytes === 0)
|
|
44
|
+
if (bytes === 0) {
|
|
45
|
+
return "0 Bytes";
|
|
46
|
+
}
|
|
45
47
|
const k = 1024;
|
|
46
48
|
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
47
49
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
48
|
-
return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
|
|
50
|
+
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
|
|
49
51
|
}
|
|
50
52
|
function sanitizeFileName(fileName) {
|
|
51
53
|
return fileName.replace(/[^a-zA-Z0-9.-]/g, "_").replace(/_{2,}/g, "_").replace(/^_|_$/g, "").toLowerCase();
|
|
@@ -68,11 +70,10 @@ function parseS3Url(url) {
|
|
|
68
70
|
bucket: pathParts2[0],
|
|
69
71
|
key: pathParts2.slice(1).join("/")
|
|
70
72
|
};
|
|
71
|
-
} else {
|
|
72
|
-
const bucket = urlObj.hostname.split(".")[0];
|
|
73
|
-
const key = urlObj.pathname.substring(1);
|
|
74
|
-
return { bucket, key };
|
|
75
73
|
}
|
|
74
|
+
const bucket = urlObj.hostname.split(".")[0];
|
|
75
|
+
const key = urlObj.pathname.substring(1);
|
|
76
|
+
return { bucket, key };
|
|
76
77
|
}
|
|
77
78
|
const pathParts = urlObj.pathname.split("/").filter(Boolean);
|
|
78
79
|
return {
|
|
@@ -138,19 +139,18 @@ function extractS3Info(url) {
|
|
|
138
139
|
if (urlObj.hostname.includes("amazonaws.com")) {
|
|
139
140
|
if (urlObj.hostname.startsWith("s3")) {
|
|
140
141
|
const pathParts2 = urlObj.pathname.split("/").filter(Boolean);
|
|
141
|
-
const
|
|
142
|
+
const region2 = urlObj.hostname.split(".")[1];
|
|
142
143
|
return {
|
|
143
144
|
bucket: pathParts2[0],
|
|
144
145
|
key: pathParts2.slice(1).join("/"),
|
|
145
|
-
region
|
|
146
|
+
region: region2
|
|
146
147
|
};
|
|
147
|
-
} else {
|
|
148
|
-
const hostParts = urlObj.hostname.split(".");
|
|
149
|
-
const bucket = hostParts[0];
|
|
150
|
-
const region = hostParts[2];
|
|
151
|
-
const key = urlObj.pathname.substring(1);
|
|
152
|
-
return { bucket, key, region };
|
|
153
148
|
}
|
|
149
|
+
const hostParts = urlObj.hostname.split(".");
|
|
150
|
+
const bucket = hostParts[0];
|
|
151
|
+
const region = hostParts[2];
|
|
152
|
+
const key = urlObj.pathname.substring(1);
|
|
153
|
+
return { bucket, key, region };
|
|
154
154
|
}
|
|
155
155
|
const pathParts = urlObj.pathname.split("/").filter(Boolean);
|
|
156
156
|
return {
|
|
@@ -273,7 +273,7 @@ function getFileName(key) {
|
|
|
273
273
|
return lastSlashIndex === -1 ? normalizedKey : normalizedKey.substring(lastSlashIndex + 1);
|
|
274
274
|
}
|
|
275
275
|
function base64ToBuffer(base64) {
|
|
276
|
-
const cleanBase64 = base64.includes(",") ? base64.split(",")[1] : base64;
|
|
276
|
+
const cleanBase64 = base64.includes(",") ? base64.split(",")[1] ?? "" : base64;
|
|
277
277
|
return Buffer.from(cleanBase64, "base64");
|
|
278
278
|
}
|
|
279
279
|
function bufferToBase64(buffer, includeDataUrl = false, mimeType) {
|
|
@@ -301,7 +301,9 @@ function validateBatchFiles(files, options) {
|
|
|
301
301
|
return files.map((file) => ({
|
|
302
302
|
valid: false,
|
|
303
303
|
fileName: file.name,
|
|
304
|
-
errors: [
|
|
304
|
+
errors: [
|
|
305
|
+
`Maximum ${options.maxFiles} files allowed, but ${files.length} files provided`
|
|
306
|
+
]
|
|
305
307
|
}));
|
|
306
308
|
}
|
|
307
309
|
for (const file of files) {
|
|
@@ -347,25 +349,35 @@ function detectFileTypeFromContent(buffer) {
|
|
|
347
349
|
return "image/webp";
|
|
348
350
|
}
|
|
349
351
|
if (header[0] === 80 && header[1] === 75 && (header[2] === 3 || header[2] === 5)) {
|
|
350
|
-
const zipContent = buffer.toString(
|
|
351
|
-
|
|
352
|
+
const zipContent = buffer.toString(
|
|
353
|
+
"utf8",
|
|
354
|
+
0,
|
|
355
|
+
Math.min(buffer.length, 1e3)
|
|
356
|
+
);
|
|
357
|
+
if (zipContent.includes("word/")) {
|
|
352
358
|
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
353
|
-
|
|
359
|
+
}
|
|
360
|
+
if (zipContent.includes("xl/")) {
|
|
354
361
|
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
355
|
-
|
|
362
|
+
}
|
|
363
|
+
if (zipContent.includes("ppt/")) {
|
|
356
364
|
return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
365
|
+
}
|
|
357
366
|
return "application/zip";
|
|
358
367
|
}
|
|
359
368
|
if (header.subarray(4, 8).toString() === "ftyp") {
|
|
360
369
|
return "video/mp4";
|
|
361
370
|
}
|
|
362
|
-
if (header[0] === 255 && (header[1] & 224) === 224 || header.subarray(0, 3).toString() === "ID3") {
|
|
371
|
+
if (header[0] !== void 0 && header[1] !== void 0 && header[0] === 255 && (header[1] & 224) === 224 || header.subarray(0, 3).toString() === "ID3") {
|
|
363
372
|
return "audio/mpeg";
|
|
364
373
|
}
|
|
365
374
|
let isText = true;
|
|
366
375
|
const sampleSize = Math.min(buffer.length, 512);
|
|
367
376
|
for (let i = 0; i < sampleSize; i++) {
|
|
368
377
|
const byte = buffer[i];
|
|
378
|
+
if (byte === void 0) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
369
381
|
if (byte === 0 || byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) {
|
|
370
382
|
isText = false;
|
|
371
383
|
break;
|
package/dist/cloudinary.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as StorageInterface, C as CloudinaryConfig, U as UploadOptions, a as UploadResult, D as DownloadOptions, b as DownloadResult, c as DeleteOptions, d as DeleteResult, B as BatchDeleteOptions, e as BatchDeleteResult, L as ListOptions, f as ListResult, E as ExistsResult, g as CopyOptions, h as CopyResult, M as MoveOptions, i as MoveResult, j as DuplicateOptions, k as DuplicateResult, P as PresignedUrlOptions, l as PresignedUrlResult, m as CreateFolderOptions, n as CreateFolderResult, o as DeleteFolderOptions, p as DeleteFolderResult, q as ListFoldersOptions, r as ListFoldersResult, F as FolderExistsResult, R as RenameFolderOptions, s as RenameFolderResult, t as CopyFolderOptions, u as CopyFolderResult } from './storage-interface-CoYx1E3B.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Cloudinary storage service implementation
|
package/dist/cloudinary.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadCloudinaryConfig
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NNFNVWIN.js";
|
|
4
4
|
|
|
5
5
|
// src/providers/cloudinary.ts
|
|
6
6
|
import { v2 as cloudinary } from "cloudinary";
|
|
@@ -30,11 +30,13 @@ var CloudinaryProvider = class {
|
|
|
30
30
|
} else if (options.contentType?.startsWith("video/")) {
|
|
31
31
|
resourceType = "video";
|
|
32
32
|
}
|
|
33
|
-
const keyParts = options.key.split("/");
|
|
33
|
+
const keyParts = options.key.split("/").filter(Boolean);
|
|
34
34
|
let folder = "";
|
|
35
35
|
let publicId = options.key;
|
|
36
36
|
if (keyParts.length > 1) {
|
|
37
37
|
folder = keyParts.slice(0, -1).join("/");
|
|
38
|
+
}
|
|
39
|
+
if (keyParts.length > 0) {
|
|
38
40
|
publicId = keyParts[keyParts.length - 1];
|
|
39
41
|
}
|
|
40
42
|
const uploadOptions = {
|
|
@@ -294,10 +296,12 @@ var CloudinaryProvider = class {
|
|
|
294
296
|
async listFolders(_options) {
|
|
295
297
|
try {
|
|
296
298
|
const result = await cloudinary.api.root_folders();
|
|
297
|
-
const folders = (result.folders || []).map(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
299
|
+
const folders = (result.folders || []).map(
|
|
300
|
+
(folder) => ({
|
|
301
|
+
name: folder.name,
|
|
302
|
+
path: folder.path
|
|
303
|
+
})
|
|
304
|
+
);
|
|
301
305
|
return {
|
|
302
306
|
success: true,
|
|
303
307
|
folders,
|
package/dist/helpers.d.ts
CHANGED
|
@@ -420,7 +420,7 @@ declare function validateBucketName(bucketName: string): {
|
|
|
420
420
|
* // Returns: 'attachment; filename="filename.txt"'
|
|
421
421
|
* ```
|
|
422
422
|
*/
|
|
423
|
-
declare function getContentDisposition(fileName: string, disposition?:
|
|
423
|
+
declare function getContentDisposition(fileName: string, disposition?: "inline" | "attachment"): string;
|
|
424
424
|
/**
|
|
425
425
|
* Check if file is an image based on MIME type
|
|
426
426
|
* @param fileName File name or path
|
|
@@ -659,7 +659,7 @@ declare function bufferToBase64(buffer: Buffer, includeDataUrl?: boolean, mimeTy
|
|
|
659
659
|
* }
|
|
660
660
|
* ```
|
|
661
661
|
*/
|
|
662
|
-
declare function generateFileHash(content: Buffer | string, algorithm?:
|
|
662
|
+
declare function generateFileHash(content: Buffer | string, algorithm?: "md5" | "sha1" | "sha256"): Promise<string>;
|
|
663
663
|
/**
|
|
664
664
|
* Generate multiple unique keys at once for batch operations
|
|
665
665
|
* @param fileNames Array of file names
|
package/dist/helpers.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { B as BatchDeleteOptions,
|
|
1
|
+
import { v as S3Config, C as CloudinaryConfig, w as StorageConfig } from './storage-interface-CoYx1E3B.js';
|
|
2
|
+
export { B as BatchDeleteOptions, e as BatchDeleteResult, t as CopyFolderOptions, u as CopyFolderResult, g as CopyOptions, h as CopyResult, m as CreateFolderOptions, n as CreateFolderResult, o as DeleteFolderOptions, p as DeleteFolderResult, c as DeleteOptions, d as DeleteResult, D as DownloadOptions, b as DownloadResult, j as DuplicateOptions, k as DuplicateResult, E as ExistsResult, z as FileInfo, F as FolderExistsResult, A as FolderInfo, q as ListFoldersOptions, r as ListFoldersResult, L as ListOptions, f as ListResult, M as MoveOptions, i as MoveResult, P as PresignedUrlOptions, l as PresignedUrlResult, R as RenameFolderOptions, s as RenameFolderResult, x as S3ProviderType, S as StorageInterface, y as StorageProvider, U as UploadOptions, a as UploadResult } from './storage-interface-CoYx1E3B.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Storage configuration from environment variables
|
package/dist/index.js
CHANGED
package/dist/s3.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as StorageInterface, v as S3Config, U as UploadOptions, a as UploadResult, D as DownloadOptions, b as DownloadResult, c as DeleteOptions, d as DeleteResult, B as BatchDeleteOptions, e as BatchDeleteResult, L as ListOptions, f as ListResult, E as ExistsResult, g as CopyOptions, h as CopyResult, M as MoveOptions, i as MoveResult, j as DuplicateOptions, k as DuplicateResult, P as PresignedUrlOptions, l as PresignedUrlResult, m as CreateFolderOptions, n as CreateFolderResult, o as DeleteFolderOptions, p as DeleteFolderResult, q as ListFoldersOptions, r as ListFoldersResult, F as FolderExistsResult, R as RenameFolderOptions, s as RenameFolderResult, t as CopyFolderOptions, u as CopyFolderResult } from './storage-interface-CoYx1E3B.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* S3 storage service implementation
|
package/dist/s3.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
loadS3Config
|
|
3
|
-
} from "./chunk-ZTZZCC52.js";
|
|
4
1
|
import {
|
|
5
2
|
buildPublicUrl,
|
|
6
3
|
getMimeType
|
|
7
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VZDXZ6EA.js";
|
|
5
|
+
import {
|
|
6
|
+
loadS3Config
|
|
7
|
+
} from "./chunk-NNFNVWIN.js";
|
|
8
8
|
|
|
9
9
|
// src/providers/s3.ts
|
|
10
10
|
import { S3Client } from "@aws-sdk/client-s3";
|
|
@@ -74,7 +74,9 @@ var FileOperations = class {
|
|
|
74
74
|
const reader = result.Body.transformToWebStream().getReader();
|
|
75
75
|
while (true) {
|
|
76
76
|
const { done, value } = await reader.read();
|
|
77
|
-
if (done)
|
|
77
|
+
if (done) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
78
80
|
chunks.push(value);
|
|
79
81
|
}
|
|
80
82
|
const data = Buffer.concat(chunks);
|
|
@@ -124,7 +126,9 @@ var FileOperations = class {
|
|
|
124
126
|
}
|
|
125
127
|
});
|
|
126
128
|
const result = await this.client.send(command);
|
|
127
|
-
const deleted = result.Deleted?.map((obj) => obj.Key).filter(
|
|
129
|
+
const deleted = result.Deleted?.map((obj) => obj.Key).filter(
|
|
130
|
+
Boolean
|
|
131
|
+
);
|
|
128
132
|
const errors = result.Errors?.map((err) => ({
|
|
129
133
|
key: err.Key || "",
|
|
130
134
|
error: err.Message || "Unknown error"
|
|
@@ -349,33 +353,32 @@ var FolderOperations = class {
|
|
|
349
353
|
deletedFiles: []
|
|
350
354
|
};
|
|
351
355
|
}
|
|
352
|
-
const
|
|
356
|
+
const deleteCommand2 = new DeleteObjectsCommand2({
|
|
353
357
|
Bucket: this.config.bucket,
|
|
354
358
|
Delete: {
|
|
355
359
|
Objects: listResult.Contents.map((obj) => ({ Key: obj.Key })),
|
|
356
360
|
Quiet: false
|
|
357
361
|
}
|
|
358
362
|
});
|
|
359
|
-
const deleteResult = await this.client.send(
|
|
363
|
+
const deleteResult = await this.client.send(deleteCommand2);
|
|
360
364
|
const deletedFiles = deleteResult.Deleted?.map((obj) => obj.Key).filter(Boolean) || [];
|
|
361
365
|
return {
|
|
362
366
|
success: true,
|
|
363
367
|
deletedFiles
|
|
364
368
|
};
|
|
365
|
-
} else {
|
|
366
|
-
const deleteCommand = new DeleteObjectsCommand2({
|
|
367
|
-
Bucket: this.config.bucket,
|
|
368
|
-
Delete: {
|
|
369
|
-
Objects: [{ Key: folderPath }],
|
|
370
|
-
Quiet: false
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
await this.client.send(deleteCommand);
|
|
374
|
-
return {
|
|
375
|
-
success: true,
|
|
376
|
-
deletedFiles: [folderPath]
|
|
377
|
-
};
|
|
378
369
|
}
|
|
370
|
+
const deleteCommand = new DeleteObjectsCommand2({
|
|
371
|
+
Bucket: this.config.bucket,
|
|
372
|
+
Delete: {
|
|
373
|
+
Objects: [{ Key: folderPath }],
|
|
374
|
+
Quiet: false
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
await this.client.send(deleteCommand);
|
|
378
|
+
return {
|
|
379
|
+
success: true,
|
|
380
|
+
deletedFiles: [folderPath]
|
|
381
|
+
};
|
|
379
382
|
} catch (error) {
|
|
380
383
|
return {
|
|
381
384
|
success: false,
|
|
@@ -559,7 +562,7 @@ var S3Provider = class {
|
|
|
559
562
|
secretAccessKey: config.secretAccessKey
|
|
560
563
|
},
|
|
561
564
|
endpoint: config.endpoint,
|
|
562
|
-
forcePathStyle: config.forcePathStyle
|
|
565
|
+
forcePathStyle: config.forcePathStyle
|
|
563
566
|
});
|
|
564
567
|
this.fileOps = new FileOperations(this.client, this.config);
|
|
565
568
|
this.folderOps = new FolderOperations(this.client, this.config);
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* S3-compatible provider types
|
|
7
7
|
* @public
|
|
8
8
|
*/
|
|
9
|
-
type S3ProviderType =
|
|
9
|
+
type S3ProviderType = "aws" | "cloudflare-r2" | "minio" | "digitalocean" | "supabase" | "custom";
|
|
10
10
|
/**
|
|
11
11
|
* All supported storage provider types
|
|
12
12
|
* @public
|
|
13
13
|
*/
|
|
14
|
-
type StorageProvider = S3ProviderType |
|
|
14
|
+
type StorageProvider = S3ProviderType | "cloudinary";
|
|
15
15
|
/**
|
|
16
16
|
* Union type for all storage configurations
|
|
17
17
|
* @public
|
|
@@ -45,7 +45,7 @@ interface S3Config {
|
|
|
45
45
|
*/
|
|
46
46
|
interface CloudinaryConfig {
|
|
47
47
|
/** Provider type (always 'cloudinary') */
|
|
48
|
-
provider:
|
|
48
|
+
provider: "cloudinary";
|
|
49
49
|
/** Cloudinary cloud name */
|
|
50
50
|
cloudName: string;
|
|
51
51
|
/** Cloudinary API key */
|
|
@@ -104,7 +104,7 @@ interface UploadOptions {
|
|
|
104
104
|
metadata?: Record<string, string>;
|
|
105
105
|
cacheControl?: string;
|
|
106
106
|
contentDisposition?: string;
|
|
107
|
-
acl?:
|
|
107
|
+
acl?: "private" | "public-read" | "public-read-write";
|
|
108
108
|
expires?: Date;
|
|
109
109
|
}
|
|
110
110
|
interface UploadResult {
|
|
@@ -153,7 +153,7 @@ interface CopyOptions {
|
|
|
153
153
|
sourceKey: string;
|
|
154
154
|
destinationKey: string;
|
|
155
155
|
metadata?: Record<string, string>;
|
|
156
|
-
metadataDirective?:
|
|
156
|
+
metadataDirective?: "COPY" | "REPLACE";
|
|
157
157
|
}
|
|
158
158
|
interface CopyResult {
|
|
159
159
|
success: boolean;
|
|
@@ -164,7 +164,7 @@ interface MoveOptions {
|
|
|
164
164
|
sourceKey: string;
|
|
165
165
|
destinationKey: string;
|
|
166
166
|
metadata?: Record<string, string>;
|
|
167
|
-
metadataDirective?:
|
|
167
|
+
metadataDirective?: "COPY" | "REPLACE";
|
|
168
168
|
}
|
|
169
169
|
interface MoveResult {
|
|
170
170
|
success: boolean;
|
|
@@ -194,7 +194,7 @@ interface ListResult {
|
|
|
194
194
|
}
|
|
195
195
|
interface PresignedUrlOptions {
|
|
196
196
|
key: string;
|
|
197
|
-
operation:
|
|
197
|
+
operation: "get" | "put";
|
|
198
198
|
expiresIn?: number;
|
|
199
199
|
contentType?: string;
|
|
200
200
|
}
|
|
@@ -313,4 +313,4 @@ interface StorageInterface {
|
|
|
313
313
|
copyFolder(options: CopyFolderOptions): Promise<CopyFolderResult>;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
export type {
|
|
316
|
+
export type { FolderInfo as A, BatchDeleteOptions as B, CloudinaryConfig as C, DownloadOptions as D, ExistsResult as E, FolderExistsResult as F, ListOptions as L, MoveOptions as M, PresignedUrlOptions as P, RenameFolderOptions as R, StorageInterface as S, UploadOptions as U, UploadResult as a, DownloadResult as b, DeleteOptions as c, DeleteResult as d, BatchDeleteResult as e, ListResult as f, CopyOptions as g, CopyResult as h, MoveResult as i, DuplicateOptions as j, DuplicateResult as k, PresignedUrlResult as l, CreateFolderOptions as m, CreateFolderResult as n, DeleteFolderOptions as o, DeleteFolderResult as p, ListFoldersOptions as q, ListFoldersResult as r, RenameFolderResult as s, CopyFolderOptions as t, CopyFolderResult as u, S3Config as v, StorageConfig as w, S3ProviderType as x, StorageProvider as y, FileInfo as z };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pelatform/storage",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Storage utilities for SaaS applications.",
|
|
5
|
-
"author": "Pelatform
|
|
5
|
+
"author": "Pelatform",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./dist/index.js",
|
|
@@ -26,10 +26,20 @@
|
|
|
26
26
|
"default": "./dist/helpers.js"
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
|
-
"
|
|
29
|
+
"scripts": {
|
|
30
|
+
"clean": "rimraf dist",
|
|
31
|
+
"clean:all": "rimraf .turbo dist node_modules",
|
|
32
|
+
"dev": "tsup --watch",
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"types:check": "tsc --noEmit"
|
|
35
|
+
},
|
|
36
|
+
"repository": "github:devpelatform/toolkits",
|
|
37
|
+
"homepage": "https://github.com/devpelatform/toolkits",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/devpelatform/toolkits/issues"
|
|
40
|
+
},
|
|
30
41
|
"files": [
|
|
31
|
-
"dist
|
|
32
|
-
"README.md"
|
|
42
|
+
"dist"
|
|
33
43
|
],
|
|
34
44
|
"keywords": [
|
|
35
45
|
"pelatform",
|
|
@@ -44,15 +54,19 @@
|
|
|
44
54
|
"mime-types": "^3.0.2"
|
|
45
55
|
},
|
|
46
56
|
"devDependencies": {
|
|
57
|
+
"@aws-sdk/client-s3": "^3.943.0",
|
|
58
|
+
"@aws-sdk/s3-request-presigner": "^3.943.0",
|
|
59
|
+
"@pelatform/tsconfig": "0.1.0",
|
|
47
60
|
"@types/mime-types": "^3.0.1",
|
|
48
61
|
"@types/node": "^24.10.1",
|
|
62
|
+
"cloudinary": "^2.8.0",
|
|
49
63
|
"tsup": "^8.5.1",
|
|
50
64
|
"typescript": "^5.9.3"
|
|
51
65
|
},
|
|
52
66
|
"peerDependencies": {
|
|
53
|
-
"@aws-sdk/client-s3": ">=3.
|
|
54
|
-
"@aws-sdk/s3-request-presigner": ">=3.
|
|
55
|
-
"cloudinary": ">=2.
|
|
67
|
+
"@aws-sdk/client-s3": ">=3.8.0",
|
|
68
|
+
"@aws-sdk/s3-request-presigner": ">=3.8.0",
|
|
69
|
+
"cloudinary": ">=2.8.0"
|
|
56
70
|
},
|
|
57
71
|
"peerDependenciesMeta": {
|
|
58
72
|
"@aws-sdk/client-s3": {
|
|
@@ -66,13 +80,12 @@
|
|
|
66
80
|
}
|
|
67
81
|
},
|
|
68
82
|
"publishConfig": {
|
|
83
|
+
"registry": "https://registry.npmjs.org/",
|
|
69
84
|
"access": "public"
|
|
70
85
|
},
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"build": "tsup --clean",
|
|
76
|
-
"types:check": "tsc --noEmit"
|
|
86
|
+
"lint-staged": {
|
|
87
|
+
"*.{js,jsx,ts,tsx,cjs,mjs,cts,mts}": "biome check --write --no-errors-on-unmatched",
|
|
88
|
+
"*.{md,yml,yaml}": "prettier --write",
|
|
89
|
+
"*.{json,jsonc,html}": "biome format --write --no-errors-on-unmatched"
|
|
77
90
|
}
|
|
78
|
-
}
|
|
91
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Pelatform
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|