mbkbucket 4.1.0 → 4.3.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/lib/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * MBKBucket
package/lib/src/app.js CHANGED
@@ -7,17 +7,15 @@
7
7
 
8
8
  import express from "express";
9
9
  import path from "path";
10
- import fs from "fs";
11
10
  import { fileURLToPath } from "url";
12
- import { engine } from "express-handlebars";
13
11
  import compression from "compression";
14
12
  import mbkautheRoutes from "mbkauthe";
13
+ import { setupViewEngine } from "mbkutils";
15
14
  import { generalLimiter } from "./middleware/rate-limiter.js";
16
15
  import { notFoundHandler, globalErrorHandler } from "./middleware/error-handler.js";
17
16
  import bucketRoutes from "./routes/index.js";
18
17
  import { packageJson } from "./config/index.js";
19
18
  import { createLogger } from "#logger";
20
- import { commonHandlebarsHelpers } from "./utils/helpers.js";
21
19
 
22
20
  const debugServer = createLogger('server');
23
21
  const __filename = fileURLToPath(import.meta.url);
@@ -50,41 +48,31 @@ export function createApp() {
50
48
  const libDir = path.resolve(__dirname, "..");
51
49
  const cwd = process.cwd();
52
50
 
53
- const viewDirs = [
54
- path.join(rootDir, "views"),
51
+ const additionalViewsDirs = [
55
52
  path.join(libDir, "views"),
56
- path.join(rootDir, "node_modules", "mbkauthe", "views"),
57
53
  path.resolve(cwd, "views"),
58
- path.resolve(cwd, "node_modules", "mbkauthe", "views"),
59
- ].filter(dir => fs.existsSync(dir));
54
+ ];
60
55
 
61
- const partialsDir = [
62
- path.join(rootDir, "views", "templates"),
63
- path.join(rootDir, "views", "templates", "notice"),
64
- path.join(rootDir, "views"),
56
+ const additionalPartialsDirs = [
65
57
  path.join(rootDir, "views", "partial"),
66
58
  path.join(libDir, "views", "templates"),
67
59
  path.join(libDir, "views", "templates", "notice"),
68
60
  path.join(libDir, "views"),
69
61
  path.join(libDir, "views", "partial"),
70
- path.join(rootDir, "node_modules", "mbkauthe", "views"),
71
- path.resolve(cwd, "node_modules", "mbkauthe", "views"),
72
- ].filter(dir => fs.existsSync(dir));
62
+ ];
73
63
 
74
64
  // Handlebars template engine
75
- server.engine("hbs", engine({
76
- extname: ".hbs",
65
+ setupViewEngine(server, {
66
+ rootDir,
77
67
  defaultLayout: false,
78
- partialsDir: [...new Set(partialsDir)],
79
- cache: process.env.NODE_ENV === "production",
68
+ additionalViewsDirs,
69
+ additionalPartialsDirs,
80
70
  helpers: {
81
- ...commonHandlebarsHelpers,
82
- mbkbucket_cachebuster: () => `?v=${packageJson.version}`
71
+ bcachebust: (version) => `?v=${(version && typeof version === "string" ? version : packageJson.version)}`,
72
+ cachebust: (version) => `?v=${(version && typeof version === "string" ? version : packageJson.version)}`,
73
+ mbkbucket_cachebuster: (version) => `?v=${(version && typeof version === "string" ? version : packageJson.version)}`
83
74
  }
84
- }));
85
-
86
- server.set("view engine", "hbs");
87
- server.set("views", [...new Set(viewDirs)]);
75
+ });
88
76
 
89
77
  server.use(mbkautheRoutes);
90
78
  server.use(generalLimiter);
@@ -2,7 +2,8 @@ import { pipeline } from "stream/promises";
2
2
  import { listfiles, uploadFile, deleteFile, deleteFiles, deleteFolder, createFolder as createStorageFolder, downloadFile, getFileMetadata, fileExists, ensureKeyHasAppPrefix, ensurePrefix, createMultipartUpload, uploadPart, completeMultipartUpload, abortMultipartUpload, listIncompleteMultipartUploads, cleanupIncompleteMultipartUploads, getStorageProvider, copyFile, moveFile, checkHealth } from "../services/storage.service.js";
3
3
  import { metricsRegistry } from "../core/metrics.js";
4
4
  import { sendApiError, classifyApiError } from "../utils/errors.js";
5
- import { getBaseName, isSensitiveTextType, buildKey, normalizeKeyParam, getFolderPath, destroyStream } from "../utils/helpers.js";
5
+ import { getBaseName } from "mbkutils";
6
+ import { isSensitiveTextType, buildKey, normalizeKeyParam, getFolderPath, destroyStream } from "../utils/helpers.js";
6
7
  import { createLogger } from "#logger";
7
8
 
8
9
 
@@ -2,8 +2,8 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import { resolveStorageName, getAvailableStorageNames, getStorageProvider, getStorageConfig } from "../services/storage.service.js";
4
4
  import { storageManager } from "../core/storage/storage-manager.js";
5
- import { renderPage, mbkautheVar } from "mbkauthe";
6
- import { commonHandlebarsHelpers } from "../utils/helpers.js";
5
+ import { renderPage, commonHandlebarsHelpers } from "mbkutils";
6
+ import { mbkautheVar } from "mbkauthe";
7
7
  import { mbkbucketVar } from "../config/index.js";
8
8
 
9
9
  /**
@@ -1,4 +1,5 @@
1
- import { renderPage, mbkautheVar } from "mbkauthe";
1
+ import { renderPage } from "mbkutils";
2
+ import { mbkautheVar } from "mbkauthe";
2
3
  import { packageJson, getLatestVersion, appVersion, mbkbucketVar } from "../config/index.js";
3
4
  import { getAvailableBucketNames, getStorageProvider } from "../services/storage.service.js";
4
5
  import { storageManager } from "../core/storage/storage-manager.js";
@@ -1,6 +1,7 @@
1
1
  import { downloadFile, getFileMetadata, ensureKeyHasAppPrefix, resolveStorageName, getStorageProvider } from "../services/storage.service.js";
2
2
  import { mbkbucketVar } from "../config/index.js";
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";
3
+ import { escapeHtml, getBaseName, getFileExt } from "mbkutils";
4
+ import { getMimeType, normalizeKeyParam, destroyStream, VIEWABLE_TYPES, VIEWABLE_TYPES_SORTED, STATIC_ASSET_TYPES, VIDEO_TYPES, AUDIO_TYPES, buildCacheControl, parseRangeHeader, renderPlayerPage } from "../utils/helpers.js";
4
5
  import { createLogger } from "#logger";
5
6
 
6
7
  const debugView = createLogger('view');
@@ -1,134 +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();
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();
@@ -1,58 +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
+ /**
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,57 +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
- 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
- }
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
+ }