mbkbucket 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -2
- package/README.md +96 -44
- package/index.js +4 -1
- package/lib/public/bucketadmin.js +255 -33
- package/lib/public/mbkbucket-helper-demo.html +0 -58
- package/lib/public/mbkbucket-helper.js +1 -9
- package/lib/src/config/validation.js +6 -1
- package/lib/src/controllers/api.controller.js +32 -5
- package/lib/src/controllers/info.controller.js +13 -13
- package/lib/src/controllers/view.controller.js +22 -21
- package/lib/src/core/metrics.js +134 -0
- package/lib/src/core/storage/capabilities.js +58 -0
- package/lib/src/core/storage/errors.js +57 -62
- package/lib/src/core/storage/gdrive-auth.js +157 -193
- package/lib/src/core/storage/gdrive-provider.js +714 -656
- package/lib/src/core/storage/index.js +17 -14
- package/lib/src/core/storage/local-provider.js +507 -0
- package/lib/src/core/storage/models.js +120 -80
- package/lib/src/core/storage/path-strategy.js +78 -65
- package/lib/src/core/storage/s3-provider.js +471 -491
- package/lib/src/core/storage/storage-manager.js +223 -203
- package/lib/src/core/storage/storage-provider.js +146 -217
- package/lib/src/routes/api.routes.js +4 -1
- package/lib/src/services/s3.service.js +2 -2
- package/lib/src/services/storage.service.js +117 -19
- package/lib/src/utils/cleanup-scheduler.js +91 -0
- package/lib/src/utils/helpers.js +6 -6
- package/package.json +2 -3
- package/views/bucket.hbs +0 -5
- package/views/bucketadmin_preview_modal.hbs +3 -3
|
@@ -3,7 +3,7 @@ import { packageJson, getLatestVersion, appVersion, mbkbucketVar } from "../conf
|
|
|
3
3
|
import { getAvailableBucketNames, getStorageProvider } from "../services/storage.service.js";
|
|
4
4
|
import { storageManager } from "../core/storage/storage-manager.js";
|
|
5
5
|
|
|
6
|
-
async function getInfoData() {
|
|
6
|
+
async function getInfoData(requestedBucket) {
|
|
7
7
|
let latestVersion = 'unknown';
|
|
8
8
|
try {
|
|
9
9
|
latestVersion = (await getLatestVersion()) || 'unknown';
|
|
@@ -12,16 +12,21 @@ async function getInfoData() {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const buckets = getAvailableBucketNames();
|
|
15
|
-
|
|
15
|
+
let defaultBucket = requestedBucket;
|
|
16
|
+
if (!defaultBucket || !buckets.includes(defaultBucket)) {
|
|
17
|
+
defaultBucket = storageManager.getDefaultConnectionName() || (buckets[0] ?? '');
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
let providerType = 'S3';
|
|
21
|
+
let capabilities = {};
|
|
17
22
|
if (defaultBucket) {
|
|
18
23
|
try {
|
|
19
24
|
const provider = getStorageProvider(defaultBucket);
|
|
20
25
|
providerType = (provider?.type || 's3').toUpperCase();
|
|
26
|
+
capabilities = provider?.capabilities?.toJSON ? provider.capabilities.toJSON() : (provider?.capabilities || {});
|
|
21
27
|
} catch {}
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
// Safe subset only - strictly prevent leaking passwords, DB connection strings, OAuth secrets, or storage credentials
|
|
25
30
|
const safeMbkautheVar = {
|
|
26
31
|
APP_NAME: mbkautheVar?.APP_NAME || mbkautheVar?.app_name || "mbkbucket",
|
|
27
32
|
};
|
|
@@ -41,24 +46,19 @@ async function getInfoData() {
|
|
|
41
46
|
mbkbucketVar: safeMbkbucketVar,
|
|
42
47
|
defaultBucket,
|
|
43
48
|
configuredBucketsCount: buckets.length,
|
|
44
|
-
providerType
|
|
49
|
+
providerType,
|
|
50
|
+
capabilities
|
|
45
51
|
};
|
|
46
52
|
}
|
|
47
53
|
|
|
48
|
-
/**
|
|
49
|
-
* Render the mbkbucket info page (Handlebars).
|
|
50
|
-
*/
|
|
51
54
|
export async function infoPage(req, res) {
|
|
52
55
|
renderPage(req, res, "mbkbucket_info.hbs", false, {
|
|
53
56
|
pageTitle: "MBKBucket Information",
|
|
54
57
|
ogUrl: "/mbkbucket/info",
|
|
55
|
-
...(await getInfoData())
|
|
58
|
+
...(await getInfoData(req.query?.bucket))
|
|
56
59
|
});
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
*/
|
|
62
|
-
export async function infoJson(_req, res) {
|
|
63
|
-
res.json(await getInfoData());
|
|
62
|
+
export async function infoJson(req, res) {
|
|
63
|
+
res.json(await getInfoData(req.query?.bucket));
|
|
64
64
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { downloadFile, getFileMetadata, ensureKeyHasAppPrefix,
|
|
1
|
+
import { downloadFile, getFileMetadata, ensureKeyHasAppPrefix, resolveStorageName, getStorageProvider } from "../services/storage.service.js";
|
|
2
2
|
import { mbkbucketVar } from "../config/index.js";
|
|
3
3
|
import { escapeHtml, getBaseName, getFileExt, getMimeType, normalizeKeyParam, destroyStream, VIEWABLE_TYPES, VIEWABLE_TYPES_SORTED, STATIC_ASSET_TYPES, VIDEO_TYPES, AUDIO_TYPES, buildCacheControl, parseRangeHeader, renderPlayerPage } from "../utils/helpers.js";
|
|
4
4
|
import { createLogger } from "#logger";
|
|
@@ -6,6 +6,7 @@ import { createLogger } from "#logger";
|
|
|
6
6
|
const debugView = createLogger('view');
|
|
7
7
|
|
|
8
8
|
function attachStreamCleanup(req, res, stream, timeoutMs = 120000) {
|
|
9
|
+
if (!stream || typeof stream.on !== 'function') return;
|
|
9
10
|
const cleanup = () => destroyStream(stream);
|
|
10
11
|
|
|
11
12
|
stream.on('error', (err) => {
|
|
@@ -25,6 +26,7 @@ function attachStreamCleanup(req, res, stream, timeoutMs = 120000) {
|
|
|
25
26
|
res.on('close', cleanup);
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
|
|
28
30
|
async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName, publicCache = false } = {}) {
|
|
29
31
|
const fileName = getBaseName(keyToUse);
|
|
30
32
|
const fileExtension = getFileExt(fileName);
|
|
@@ -48,6 +50,8 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
|
|
|
48
50
|
const isPdf = fileExtension === 'pdf';
|
|
49
51
|
const supportsRanges = isVideo || isAudio || isPdf;
|
|
50
52
|
const contentType = getMimeType(fileName);
|
|
53
|
+
const safeAsciiName = fileName.replace(/["\\]/g, '').replace(/[^\x20-\x7E]/g, '_');
|
|
54
|
+
const encodedName = encodeURIComponent(fileName);
|
|
51
55
|
|
|
52
56
|
// Fast path for range requests (video / audio / PDF streaming)
|
|
53
57
|
if (supportsRanges && rangeHeader) {
|
|
@@ -86,21 +90,27 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
|
|
|
86
90
|
res.setHeader('Content-Range', `bytes ${start}-${end}/${total}`);
|
|
87
91
|
res.setHeader('Content-Length', chunksize);
|
|
88
92
|
res.setHeader('Content-Type', contentType);
|
|
93
|
+
res.setHeader('Content-Disposition', `inline; filename="${safeAsciiName}"; filename*=UTF-8''${encodedName}`);
|
|
89
94
|
res.setHeader('Cache-Control', buildCacheControl(contentType, { supportsRanges: true, publicCache }));
|
|
90
95
|
res.setHeader('ETag', etag);
|
|
91
96
|
res.setHeader('Accept-Ranges', 'bytes');
|
|
97
|
+
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
|
92
98
|
if (noindex) res.setHeader('X-Robots-Tag', 'noindex, nofollow');
|
|
93
99
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return rangeResult.Body.pipe(res);
|
|
100
|
+
const stream = rangeResult.Body || rangeResult.stream;
|
|
101
|
+
attachStreamCleanup(req, res, stream);
|
|
102
|
+
return stream.pipe(res);
|
|
98
103
|
} catch (rangeError) {
|
|
99
|
-
console.error('Range request failed:', rangeError);
|
|
104
|
+
console.error('[mbkbucket] Range request failed for %s:', keyToUse, rangeError);
|
|
105
|
+
if (!res.headersSent) {
|
|
106
|
+
return res.status(500).json({ message: 'Range stream failed', error: rangeError.message });
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
100
109
|
}
|
|
101
110
|
}
|
|
102
111
|
}
|
|
103
112
|
|
|
113
|
+
|
|
104
114
|
// Standard path: full-file download
|
|
105
115
|
const downloadOptions = {
|
|
106
116
|
...(ifNoneMatch && { ifNoneMatch }),
|
|
@@ -130,13 +140,14 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
|
|
|
130
140
|
if (ifNoneMatch === etag) return res.status(304).end();
|
|
131
141
|
|
|
132
142
|
res.setHeader('Content-Type', contentType);
|
|
133
|
-
res.setHeader('Content-Disposition', `inline; filename="${
|
|
143
|
+
res.setHeader('Content-Disposition', `inline; filename="${safeAsciiName}"; filename*=UTF-8''${encodedName}`);
|
|
134
144
|
res.setHeader('ETag', etag);
|
|
135
145
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
136
146
|
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
|
137
147
|
if (noindex) res.setHeader('X-Robots-Tag', 'noindex, nofollow');
|
|
138
148
|
if (result.ContentLength) res.setHeader('Content-Length', result.ContentLength);
|
|
139
149
|
if (result.LastModified) res.setHeader('Last-Modified', new Date(result.LastModified).toUTCString());
|
|
150
|
+
|
|
140
151
|
if (fileExtension === 'html' || fileExtension === 'htm') {
|
|
141
152
|
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'none'; object-src 'none';");
|
|
142
153
|
}
|
|
@@ -144,11 +155,13 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
|
|
|
144
155
|
res.setHeader('Cache-Control', buildCacheControl(contentType, { isStaticAsset, supportsRanges, publicCache }));
|
|
145
156
|
res.status(200);
|
|
146
157
|
|
|
158
|
+
const stream = result.Body || result.stream;
|
|
147
159
|
const timeoutDuration = result.ContentLength > 50 * 1024 * 1024 ? 300000 : 120000;
|
|
148
|
-
attachStreamCleanup(req, res,
|
|
149
|
-
|
|
160
|
+
attachStreamCleanup(req, res, stream, timeoutDuration);
|
|
161
|
+
stream.pipe(res);
|
|
150
162
|
}
|
|
151
163
|
|
|
164
|
+
|
|
152
165
|
export async function viewFile(req, res) {
|
|
153
166
|
try {
|
|
154
167
|
const key = normalizeKeyParam(req.params.key);
|
|
@@ -213,18 +226,6 @@ export async function publicView(req, res) {
|
|
|
213
226
|
return res.status(400).json({ message: e.message });
|
|
214
227
|
}
|
|
215
228
|
|
|
216
|
-
if (!mbkbucketVar?.p_view_inline) {
|
|
217
|
-
try {
|
|
218
|
-
const provider = getStorageProvider(bucketName);
|
|
219
|
-
if (provider.capabilities.presignedUrls) {
|
|
220
|
-
const signed = await generateSignedUrl(keyToUse, 'getObject', 3600, bucketName);
|
|
221
|
-
return res.redirect(302, signed.url);
|
|
222
|
-
}
|
|
223
|
-
} catch (err) {
|
|
224
|
-
debugView('Signed URL redirection failed, falling back to inline serve: %s', err.message);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
229
|
await serveFileInline(req, res, keyToUse, { noindex: true, bucketName, publicCache: true });
|
|
229
230
|
} catch (error) {
|
|
230
231
|
console.error('[mbkbucket] p_view error:', error);
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MBKBucket - Metrics & Observability Registry
|
|
3
|
+
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class MetricsRegistry {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.reset();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
reset() {
|
|
13
|
+
this.operations = new Map(); // key: "op:provider:status" -> count
|
|
14
|
+
this.durations = new Map(); // key: "op:provider" -> { totalMs, count, minMs, maxMs }
|
|
15
|
+
this.bytesTransferred = new Map(); // key: "direction:provider" -> bytes
|
|
16
|
+
this.errors = new Map(); // key: "op:provider:errorType" -> count
|
|
17
|
+
this.activeMultipart = 0;
|
|
18
|
+
this.startedAt = new Date().toISOString();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
recordOperation(operation, { provider = 'unknown', status = 'success', durationMs = 0, bytes = 0 } = {}) {
|
|
22
|
+
const opKey = `${operation}:${provider}:${status}`;
|
|
23
|
+
this.operations.set(opKey, (this.operations.get(opKey) || 0) + 1);
|
|
24
|
+
|
|
25
|
+
if (durationMs >= 0) {
|
|
26
|
+
const durKey = `${operation}:${provider}`;
|
|
27
|
+
const existing = this.durations.get(durKey) || { totalMs: 0, count: 0, minMs: Infinity, maxMs: 0 };
|
|
28
|
+
existing.totalMs += durationMs;
|
|
29
|
+
existing.count += 1;
|
|
30
|
+
existing.minMs = Math.min(existing.minMs, durationMs);
|
|
31
|
+
existing.maxMs = Math.max(existing.maxMs, durationMs);
|
|
32
|
+
this.durations.set(durKey, existing);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (bytes > 0) {
|
|
36
|
+
const direction = operation.startsWith('download') || operation.startsWith('view') ? 'outbound' : 'inbound';
|
|
37
|
+
const byteKey = `${direction}:${provider}`;
|
|
38
|
+
this.bytesTransferred.set(byteKey, (this.bytesTransferred.get(byteKey) || 0) + bytes);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
recordError(operation, provider = 'unknown', errorType = 'Error') {
|
|
43
|
+
const errKey = `${operation}:${provider}:${errorType}`;
|
|
44
|
+
this.errors.set(errKey, (this.errors.get(errKey) || 0) + 1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
incrementActiveMultipart(delta = 1) {
|
|
48
|
+
this.activeMultipart = Math.max(0, this.activeMultipart + delta);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getStats() {
|
|
52
|
+
const ops = {};
|
|
53
|
+
for (const [key, count] of this.operations.entries()) {
|
|
54
|
+
ops[key] = count;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const durations = {};
|
|
58
|
+
for (const [key, d] of this.durations.entries()) {
|
|
59
|
+
durations[key] = {
|
|
60
|
+
count: d.count,
|
|
61
|
+
avgMs: d.count ? Math.round((d.totalMs / d.count) * 100) / 100 : 0,
|
|
62
|
+
minMs: d.minMs === Infinity ? 0 : d.minMs,
|
|
63
|
+
maxMs: d.maxMs
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const bytes = {};
|
|
68
|
+
for (const [key, val] of this.bytesTransferred.entries()) {
|
|
69
|
+
bytes[key] = val;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const errs = {};
|
|
73
|
+
for (const [key, count] of this.errors.entries()) {
|
|
74
|
+
errs[key] = count;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
startedAt: this.startedAt,
|
|
79
|
+
activeMultipartUploads: this.activeMultipart,
|
|
80
|
+
operations: ops,
|
|
81
|
+
durations,
|
|
82
|
+
bytesTransferred: bytes,
|
|
83
|
+
errors: errs,
|
|
84
|
+
timestamp: new Date().toISOString()
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
toPrometheusText() {
|
|
89
|
+
const lines = [
|
|
90
|
+
'# HELP mbkbucket_operations_total Total number of storage operations executed',
|
|
91
|
+
'# TYPE mbkbucket_operations_total counter'
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
for (const [key, count] of this.operations.entries()) {
|
|
95
|
+
const [operation, provider, status] = key.split(':');
|
|
96
|
+
lines.push(`mbkbucket_operations_total{operation="${operation}",provider="${provider}",status="${status}"} ${count}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
lines.push('');
|
|
100
|
+
lines.push('# HELP mbkbucket_operation_duration_seconds Total duration of storage operations');
|
|
101
|
+
lines.push('# TYPE mbkbucket_operation_duration_seconds summary');
|
|
102
|
+
for (const [key, d] of this.durations.entries()) {
|
|
103
|
+
const [operation, provider] = key.split(':');
|
|
104
|
+
lines.push(`mbkbucket_operation_duration_seconds_count{operation="${operation}",provider="${provider}"} ${d.count}`);
|
|
105
|
+
lines.push(`mbkbucket_operation_duration_seconds_sum{operation="${operation}",provider="${provider}"} ${(d.totalMs / 1000).toFixed(4)}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
lines.push('');
|
|
109
|
+
lines.push('# HELP mbkbucket_transferred_bytes_total Total bytes transferred into or out of storage');
|
|
110
|
+
lines.push('# TYPE mbkbucket_transferred_bytes_total counter');
|
|
111
|
+
for (const [key, bytes] of this.bytesTransferred.entries()) {
|
|
112
|
+
const [direction, provider] = key.split(':');
|
|
113
|
+
lines.push(`mbkbucket_transferred_bytes_total{direction="${direction}",provider="${provider}"} ${bytes}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
lines.push('');
|
|
117
|
+
lines.push('# HELP mbkbucket_errors_total Total storage errors encountered');
|
|
118
|
+
lines.push('# TYPE mbkbucket_errors_total counter');
|
|
119
|
+
for (const [key, count] of this.errors.entries()) {
|
|
120
|
+
const [operation, provider, errorType] = key.split(':');
|
|
121
|
+
lines.push(`mbkbucket_errors_total{operation="${operation}",provider="${provider}",error_type="${errorType}"} ${count}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
lines.push('');
|
|
125
|
+
lines.push('# HELP mbkbucket_active_multipart_uploads Current number of active multipart uploads');
|
|
126
|
+
lines.push('# TYPE mbkbucket_active_multipart_uploads gauge');
|
|
127
|
+
lines.push(`mbkbucket_active_multipart_uploads ${this.activeMultipart}`);
|
|
128
|
+
lines.push('');
|
|
129
|
+
|
|
130
|
+
return lines.join('\n');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const metricsRegistry = new MetricsRegistry();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MBKBucket - Storage Provider Capabilities
|
|
3
|
+
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { StorageUnsupportedOperationError } from "./errors.js";
|
|
8
|
+
|
|
9
|
+
export class StorageCapabilities {
|
|
10
|
+
constructor(flags = {}) {
|
|
11
|
+
this.multipart = Boolean(flags.multipart);
|
|
12
|
+
this.resumable = Boolean(flags.resumable ?? flags.multipart);
|
|
13
|
+
this.nativeFolders = Boolean(flags.nativeFolders);
|
|
14
|
+
this.ranges = Boolean(flags.ranges ?? true);
|
|
15
|
+
this.copy = Boolean(flags.copy);
|
|
16
|
+
this.move = Boolean(flags.move);
|
|
17
|
+
this.search = Boolean(flags.search);
|
|
18
|
+
this.directDownloadUrl = Boolean(flags.directDownloadUrl);
|
|
19
|
+
this.versioning = Boolean(flags.versioning);
|
|
20
|
+
this.publicUrls = Boolean(flags.publicUrls);
|
|
21
|
+
this.storageQuota = Boolean(flags.storageQuota);
|
|
22
|
+
this.deleteBatch = Boolean(flags.deleteBatch ?? true);
|
|
23
|
+
this.metadata = Boolean(flags.metadata ?? true);
|
|
24
|
+
|
|
25
|
+
Object.freeze(this);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
has(capability) {
|
|
29
|
+
return Boolean(this[capability]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
assert(capability, operationName, providerType = 'storage provider') {
|
|
33
|
+
if (!this.has(capability)) {
|
|
34
|
+
const op = operationName || capability;
|
|
35
|
+
throw new StorageUnsupportedOperationError(
|
|
36
|
+
`Operation '${op}' is not supported by ${providerType} (missing capability: ${capability})`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
toJSON() {
|
|
42
|
+
return {
|
|
43
|
+
multipart: this.multipart,
|
|
44
|
+
resumable: this.resumable,
|
|
45
|
+
nativeFolders: this.nativeFolders,
|
|
46
|
+
ranges: this.ranges,
|
|
47
|
+
copy: this.copy,
|
|
48
|
+
move: this.move,
|
|
49
|
+
search: this.search,
|
|
50
|
+
directDownloadUrl: this.directDownloadUrl,
|
|
51
|
+
versioning: this.versioning,
|
|
52
|
+
publicUrls: this.publicUrls,
|
|
53
|
+
storageQuota: this.storageQuota,
|
|
54
|
+
deleteBatch: this.deleteBatch,
|
|
55
|
+
metadata: this.metadata
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -1,62 +1,57 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MBKBucket - Typed Storage Error Hierarchy
|
|
3
|
-
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export class StorageError extends Error {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export class StorageUnsupportedOperationError extends StorageError {
|
|
59
|
-
constructor(message = 'Operation not supported by this storage provider') {
|
|
60
|
-
super(message, 501, 'NOT_IMPLEMENTED');
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* MBKBucket - Typed Storage Error Hierarchy
|
|
3
|
+
* Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class StorageError extends Error {
|
|
8
|
+
constructor(message, status = 500, code = 'INTERNAL_STORAGE_ERROR') {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = this.constructor.name;
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.code = code;
|
|
13
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class StorageNotFoundError extends StorageError {
|
|
18
|
+
constructor(message = 'File or folder not found') {
|
|
19
|
+
super(message, 404, 'NOT_FOUND');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class StorageAccessDeniedError extends StorageError {
|
|
24
|
+
constructor(message = 'Access denied') {
|
|
25
|
+
super(message, 403, 'ACCESS_DENIED');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class StorageConflictError extends StorageError {
|
|
30
|
+
constructor(message = 'Resource already exists') {
|
|
31
|
+
super(message, 409, 'CONFLICT');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class StorageValidationError extends StorageError {
|
|
36
|
+
constructor(message = 'Validation failed') {
|
|
37
|
+
super(message, 400, 'VALIDATION_ERROR');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class StorageConfigError extends StorageError {
|
|
42
|
+
constructor(message = 'Storage configuration error') {
|
|
43
|
+
super(message, 503, 'STORAGE_CONFIG_ERROR');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class StorageQuotaExceededError extends StorageError {
|
|
48
|
+
constructor(message = 'Storage quota exceeded') {
|
|
49
|
+
super(message, 507, 'QUOTA_EXCEEDED');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class StorageUnsupportedOperationError extends StorageError {
|
|
54
|
+
constructor(message = 'Operation not supported by this storage provider') {
|
|
55
|
+
super(message, 501, 'NOT_IMPLEMENTED');
|
|
56
|
+
}
|
|
57
|
+
}
|