@pelatform/storage 1.0.3 → 1.0.4
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/dist/{chunk-VZDXZ6EA.js → chunk-KJMGBTTL.js} +23 -35
- package/dist/{chunk-NNFNVWIN.js → chunk-ZTZZCC52.js} +8 -18
- package/dist/cloudinary.js +6 -10
- package/dist/helpers.js +1 -1
- package/dist/index.js +1 -1
- package/dist/s3.js +20 -23
- package/package.json +2 -2
|
@@ -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 (!
|
|
35
|
+
if (!isValidMime && !isValidExtension) {
|
|
36
36
|
return {
|
|
37
37
|
valid: false,
|
|
38
38
|
error: `File type not allowed. Allowed types: ${allowedTypes.join(", ")}`
|
|
@@ -41,13 +41,11 @@ function validateFileType(fileName, allowedTypes) {
|
|
|
41
41
|
return { valid: true };
|
|
42
42
|
}
|
|
43
43
|
function formatFileSize(bytes) {
|
|
44
|
-
if (bytes === 0)
|
|
45
|
-
return "0 Bytes";
|
|
46
|
-
}
|
|
44
|
+
if (bytes === 0) return "0 Bytes";
|
|
47
45
|
const k = 1024;
|
|
48
46
|
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
49
47
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
50
|
-
return `${
|
|
48
|
+
return `${parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
|
|
51
49
|
}
|
|
52
50
|
function sanitizeFileName(fileName) {
|
|
53
51
|
return fileName.replace(/[^a-zA-Z0-9.-]/g, "_").replace(/_{2,}/g, "_").replace(/^_|_$/g, "").toLowerCase();
|
|
@@ -70,10 +68,11 @@ function parseS3Url(url) {
|
|
|
70
68
|
bucket: pathParts2[0],
|
|
71
69
|
key: pathParts2.slice(1).join("/")
|
|
72
70
|
};
|
|
71
|
+
} else {
|
|
72
|
+
const bucket = urlObj.hostname.split(".")[0];
|
|
73
|
+
const key = urlObj.pathname.substring(1);
|
|
74
|
+
return { bucket, key };
|
|
73
75
|
}
|
|
74
|
-
const bucket = urlObj.hostname.split(".")[0];
|
|
75
|
-
const key = urlObj.pathname.substring(1);
|
|
76
|
-
return { bucket, key };
|
|
77
76
|
}
|
|
78
77
|
const pathParts = urlObj.pathname.split("/").filter(Boolean);
|
|
79
78
|
return {
|
|
@@ -139,18 +138,19 @@ function extractS3Info(url) {
|
|
|
139
138
|
if (urlObj.hostname.includes("amazonaws.com")) {
|
|
140
139
|
if (urlObj.hostname.startsWith("s3")) {
|
|
141
140
|
const pathParts2 = urlObj.pathname.split("/").filter(Boolean);
|
|
142
|
-
const
|
|
141
|
+
const region = urlObj.hostname.split(".")[1];
|
|
143
142
|
return {
|
|
144
143
|
bucket: pathParts2[0],
|
|
145
144
|
key: pathParts2.slice(1).join("/"),
|
|
146
|
-
region
|
|
145
|
+
region
|
|
147
146
|
};
|
|
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 };
|
|
148
153
|
}
|
|
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]
|
|
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,9 +301,7 @@ function validateBatchFiles(files, options) {
|
|
|
301
301
|
return files.map((file) => ({
|
|
302
302
|
valid: false,
|
|
303
303
|
fileName: file.name,
|
|
304
|
-
errors: [
|
|
305
|
-
`Maximum ${options.maxFiles} files allowed, but ${files.length} files provided`
|
|
306
|
-
]
|
|
304
|
+
errors: [`Maximum ${options.maxFiles} files allowed, but ${files.length} files provided`]
|
|
307
305
|
}));
|
|
308
306
|
}
|
|
309
307
|
for (const file of files) {
|
|
@@ -349,35 +347,25 @@ function detectFileTypeFromContent(buffer) {
|
|
|
349
347
|
return "image/webp";
|
|
350
348
|
}
|
|
351
349
|
if (header[0] === 80 && header[1] === 75 && (header[2] === 3 || header[2] === 5)) {
|
|
352
|
-
const zipContent = buffer.toString(
|
|
353
|
-
|
|
354
|
-
0,
|
|
355
|
-
Math.min(buffer.length, 1e3)
|
|
356
|
-
);
|
|
357
|
-
if (zipContent.includes("word/")) {
|
|
350
|
+
const zipContent = buffer.toString("utf8", 0, Math.min(buffer.length, 1e3));
|
|
351
|
+
if (zipContent.includes("word/"))
|
|
358
352
|
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
359
|
-
|
|
360
|
-
if (zipContent.includes("xl/")) {
|
|
353
|
+
if (zipContent.includes("xl/"))
|
|
361
354
|
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
362
|
-
|
|
363
|
-
if (zipContent.includes("ppt/")) {
|
|
355
|
+
if (zipContent.includes("ppt/"))
|
|
364
356
|
return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
365
|
-
}
|
|
366
357
|
return "application/zip";
|
|
367
358
|
}
|
|
368
359
|
if (header.subarray(4, 8).toString() === "ftyp") {
|
|
369
360
|
return "video/mp4";
|
|
370
361
|
}
|
|
371
|
-
if (header[0]
|
|
362
|
+
if (header[0] === 255 && (header[1] & 224) === 224 || header.subarray(0, 3).toString() === "ID3") {
|
|
372
363
|
return "audio/mpeg";
|
|
373
364
|
}
|
|
374
365
|
let isText = true;
|
|
375
366
|
const sampleSize = Math.min(buffer.length, 512);
|
|
376
367
|
for (let i = 0; i < sampleSize; i++) {
|
|
377
368
|
const byte = buffer[i];
|
|
378
|
-
if (byte === void 0) {
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
369
|
if (byte === 0 || byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) {
|
|
382
370
|
isText = false;
|
|
383
371
|
break;
|
|
@@ -31,15 +31,11 @@ function getRequiredEnvVar(key) {
|
|
|
31
31
|
}
|
|
32
32
|
function getBooleanEnvVar(key, defaultValue = false) {
|
|
33
33
|
const value = getEnvVar(key);
|
|
34
|
-
if (!value)
|
|
35
|
-
return defaultValue;
|
|
36
|
-
}
|
|
34
|
+
if (!value) return defaultValue;
|
|
37
35
|
return value.toLowerCase() === "true" || value === "1";
|
|
38
36
|
}
|
|
39
37
|
function loadS3Config() {
|
|
40
|
-
const provider = getRequiredEnvVar(
|
|
41
|
-
ENV_VARS.PELATFORM_S3_PROVIDER
|
|
42
|
-
);
|
|
38
|
+
const provider = getRequiredEnvVar(ENV_VARS.PELATFORM_S3_PROVIDER);
|
|
43
39
|
const validProviders = [
|
|
44
40
|
"aws",
|
|
45
41
|
"cloudflare-r2",
|
|
@@ -79,15 +75,15 @@ function loadStorageConfig() {
|
|
|
79
75
|
if (provider) {
|
|
80
76
|
if (provider === "cloudinary") {
|
|
81
77
|
return loadCloudinaryConfig();
|
|
78
|
+
} else {
|
|
79
|
+
return loadS3Config();
|
|
82
80
|
}
|
|
83
|
-
return loadS3Config();
|
|
84
81
|
}
|
|
85
82
|
const hasCloudinaryVars = getEnvVar(ENV_VARS.PELATFORM_CLOUDINARY_CLOUD_NAME) && getEnvVar(ENV_VARS.PELATFORM_CLOUDINARY_API_KEY) && getEnvVar(ENV_VARS.PELATFORM_CLOUDINARY_API_SECRET);
|
|
86
83
|
const hasS3Vars = getEnvVar(ENV_VARS.PELATFORM_S3_BUCKET) && getEnvVar(ENV_VARS.PELATFORM_S3_ACCESS_KEY_ID) && getEnvVar(ENV_VARS.PELATFORM_S3_SECRET_ACCESS_KEY);
|
|
87
84
|
if (hasCloudinaryVars) {
|
|
88
85
|
return loadCloudinaryConfig();
|
|
89
|
-
}
|
|
90
|
-
if (hasS3Vars) {
|
|
86
|
+
} else if (hasS3Vars) {
|
|
91
87
|
const config = loadS3Config();
|
|
92
88
|
if (!config.provider) {
|
|
93
89
|
config.provider = "aws";
|
|
@@ -111,17 +107,11 @@ function isStorageConfigured() {
|
|
|
111
107
|
}
|
|
112
108
|
function getStorageProvider() {
|
|
113
109
|
const provider = getEnvVar(ENV_VARS.PELATFORM_S3_PROVIDER);
|
|
114
|
-
if (provider)
|
|
115
|
-
return provider;
|
|
116
|
-
}
|
|
110
|
+
if (provider) return provider;
|
|
117
111
|
const hasCloudinaryVars = getEnvVar(ENV_VARS.PELATFORM_CLOUDINARY_CLOUD_NAME);
|
|
118
112
|
const hasS3Vars = getEnvVar(ENV_VARS.PELATFORM_S3_BUCKET);
|
|
119
|
-
if (hasCloudinaryVars)
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
if (hasS3Vars) {
|
|
123
|
-
return "aws";
|
|
124
|
-
}
|
|
113
|
+
if (hasCloudinaryVars) return "cloudinary";
|
|
114
|
+
if (hasS3Vars) return "aws";
|
|
125
115
|
return void 0;
|
|
126
116
|
}
|
|
127
117
|
function validateS3EnvVars() {
|
package/dist/cloudinary.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadCloudinaryConfig
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZTZZCC52.js";
|
|
4
4
|
|
|
5
5
|
// src/providers/cloudinary.ts
|
|
6
6
|
import { v2 as cloudinary } from "cloudinary";
|
|
@@ -30,13 +30,11 @@ 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("/");
|
|
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) {
|
|
40
38
|
publicId = keyParts[keyParts.length - 1];
|
|
41
39
|
}
|
|
42
40
|
const uploadOptions = {
|
|
@@ -296,12 +294,10 @@ var CloudinaryProvider = class {
|
|
|
296
294
|
async listFolders(_options) {
|
|
297
295
|
try {
|
|
298
296
|
const result = await cloudinary.api.root_folders();
|
|
299
|
-
const folders = (result.folders || []).map(
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
})
|
|
304
|
-
);
|
|
297
|
+
const folders = (result.folders || []).map((folder) => ({
|
|
298
|
+
name: folder.name,
|
|
299
|
+
path: folder.path
|
|
300
|
+
}));
|
|
305
301
|
return {
|
|
306
302
|
success: true,
|
|
307
303
|
folders,
|
package/dist/helpers.js
CHANGED
package/dist/index.js
CHANGED
package/dist/s3.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildPublicUrl,
|
|
3
3
|
getMimeType
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-KJMGBTTL.js";
|
|
5
5
|
import {
|
|
6
6
|
loadS3Config
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-ZTZZCC52.js";
|
|
8
8
|
|
|
9
9
|
// src/providers/s3.ts
|
|
10
10
|
import { S3Client } from "@aws-sdk/client-s3";
|
|
@@ -74,9 +74,7 @@ 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)
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
77
|
+
if (done) break;
|
|
80
78
|
chunks.push(value);
|
|
81
79
|
}
|
|
82
80
|
const data = Buffer.concat(chunks);
|
|
@@ -126,9 +124,7 @@ var FileOperations = class {
|
|
|
126
124
|
}
|
|
127
125
|
});
|
|
128
126
|
const result = await this.client.send(command);
|
|
129
|
-
const deleted = result.Deleted?.map((obj) => obj.Key).filter(
|
|
130
|
-
Boolean
|
|
131
|
-
);
|
|
127
|
+
const deleted = result.Deleted?.map((obj) => obj.Key).filter(Boolean);
|
|
132
128
|
const errors = result.Errors?.map((err) => ({
|
|
133
129
|
key: err.Key || "",
|
|
134
130
|
error: err.Message || "Unknown error"
|
|
@@ -353,32 +349,33 @@ var FolderOperations = class {
|
|
|
353
349
|
deletedFiles: []
|
|
354
350
|
};
|
|
355
351
|
}
|
|
356
|
-
const
|
|
352
|
+
const deleteCommand = new DeleteObjectsCommand2({
|
|
357
353
|
Bucket: this.config.bucket,
|
|
358
354
|
Delete: {
|
|
359
355
|
Objects: listResult.Contents.map((obj) => ({ Key: obj.Key })),
|
|
360
356
|
Quiet: false
|
|
361
357
|
}
|
|
362
358
|
});
|
|
363
|
-
const deleteResult = await this.client.send(
|
|
359
|
+
const deleteResult = await this.client.send(deleteCommand);
|
|
364
360
|
const deletedFiles = deleteResult.Deleted?.map((obj) => obj.Key).filter(Boolean) || [];
|
|
365
361
|
return {
|
|
366
362
|
success: true,
|
|
367
363
|
deletedFiles
|
|
368
364
|
};
|
|
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
|
+
};
|
|
369
378
|
}
|
|
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
|
-
};
|
|
382
379
|
} catch (error) {
|
|
383
380
|
return {
|
|
384
381
|
success: false,
|
|
@@ -562,7 +559,7 @@ var S3Provider = class {
|
|
|
562
559
|
secretAccessKey: config.secretAccessKey
|
|
563
560
|
},
|
|
564
561
|
endpoint: config.endpoint,
|
|
565
|
-
forcePathStyle: config.forcePathStyle
|
|
562
|
+
forcePathStyle: config.forcePathStyle || false
|
|
566
563
|
});
|
|
567
564
|
this.fileOps = new FileOperations(this.client, this.config);
|
|
568
565
|
this.folderOps = new FolderOperations(this.client, this.config);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pelatform/storage",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Storage utilities for SaaS applications.",
|
|
5
5
|
"author": "Pelatform",
|
|
6
6
|
"license": "MIT",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@aws-sdk/client-s3": "^3.943.0",
|
|
58
58
|
"@aws-sdk/s3-request-presigner": "^3.943.0",
|
|
59
|
-
"@pelatform/tsconfig": "0.1.
|
|
59
|
+
"@pelatform/tsconfig": "0.1.1",
|
|
60
60
|
"@types/mime-types": "^3.0.1",
|
|
61
61
|
"@types/node": "^24.10.1",
|
|
62
62
|
"cloudinary": "^2.8.0",
|