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.
@@ -1,146 +1,146 @@
1
- /**
2
- * MBKBucket - Core Storage Provider Interface
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
- import { StorageCapabilities } from "./capabilities.js";
9
-
10
- export class StorageProvider {
11
- constructor(name, config = {}) {
12
- if (new.target === StorageProvider) {
13
- throw new TypeError("Cannot construct StorageProvider instances directly");
14
- }
15
- this.name = String(name);
16
- this.config = Object.freeze({ ...config });
17
- this.rootPrefix = config.rootPrefix || config.basePath || '';
18
- }
19
-
20
- get type() {
21
- return 'generic';
22
- }
23
-
24
- get capabilities() {
25
- return new StorageCapabilities({
26
- multipart: false,
27
- nativeFolders: false,
28
- ranges: true,
29
- copy: false,
30
- move: false,
31
- search: false,
32
- directDownloadUrl: false,
33
- versioning: false,
34
- publicUrls: false,
35
- metadata: true
36
- });
37
- }
38
-
39
- supports(capabilityName) {
40
- return this.capabilities.has(capabilityName);
41
- }
42
-
43
- assertCapability(capabilityName, operationName) {
44
- this.capabilities.assert(capabilityName, operationName, `provider '${this.type}'`);
45
- }
46
-
47
- async listFiles(prefix = '', options = {}) {
48
- throw new StorageUnsupportedOperationError(`listFiles() not implemented by ${this.constructor.name}`);
49
- }
50
-
51
- async uploadFile(key, fileBuffer, contentType, options = {}) {
52
- throw new StorageUnsupportedOperationError(`uploadFile() not implemented by ${this.constructor.name}`);
53
- }
54
-
55
- async downloadFile(key, options = {}) {
56
- throw new StorageUnsupportedOperationError(`downloadFile() not implemented by ${this.constructor.name}`);
57
- }
58
-
59
- async deleteFile(key, options = {}) {
60
- throw new StorageUnsupportedOperationError(`deleteFile() not implemented by ${this.constructor.name}`);
61
- }
62
-
63
- async deleteFiles(keys, options = {}) {
64
- const concurrency = Math.max(1, Math.min(Number(options.concurrency) || 8, 32));
65
- let deletedCount = 0;
66
- const errors = [];
67
- const results = [];
68
-
69
- for (let i = 0; i < keys.length; i += concurrency) {
70
- const batch = keys.slice(i, i + concurrency);
71
- const batchResults = await Promise.allSettled(
72
- batch.map(key => this.deleteFile(key, options))
73
- );
74
- batchResults.forEach((outcome, idx) => {
75
- const key = batch[idx];
76
- if (outcome.status === 'fulfilled') {
77
- results.push(outcome.value);
78
- deletedCount++;
79
- } else {
80
- errors.push({ key, error: outcome.reason?.message || 'Delete failed' });
81
- }
82
- });
83
- }
84
-
85
- return { results, deletedCount, errors, deletedAt: new Date().toISOString() };
86
- }
87
-
88
- async deleteFolder(prefix, options = {}) {
89
- throw new StorageUnsupportedOperationError(`deleteFolder() not implemented by ${this.constructor.name}`);
90
- }
91
-
92
- async createFolder(key, options = {}) {
93
- throw new StorageUnsupportedOperationError(`createFolder() not implemented by ${this.constructor.name}`);
94
- }
95
-
96
- async getFileMetadata(key, options = {}) {
97
- throw new StorageUnsupportedOperationError(`getFileMetadata() not implemented by ${this.constructor.name}`);
98
- }
99
-
100
- async fileExists(key, options = {}) {
101
- try {
102
- const meta = await this.getFileMetadata(key, options);
103
- return Boolean(meta?.exists);
104
- } catch {
105
- return false;
106
- }
107
- }
108
-
109
- async getFileSize(key, options = {}) {
110
- try {
111
- const meta = await this.getFileMetadata(key, options);
112
- return meta?.exists ? (meta.ContentLength ?? null) : null;
113
- } catch {
114
- return null;
115
- }
116
- }
117
-
118
- async copyFile(sourceKey, destKey, options = {}) {
119
- this.assertCapability('copy', 'copyFile');
120
- throw new StorageUnsupportedOperationError(`copyFile() is not supported by provider ${this.type}`);
121
- }
122
-
123
- async moveFile(sourceKey, destKey, options = {}) {
124
- this.assertCapability('move', 'moveFile');
125
- await this.copyFile(sourceKey, destKey, options);
126
- await this.deleteFile(sourceKey, options);
127
- return { sourceKey, destKey, movedAt: new Date().toISOString() };
128
- }
129
-
130
- async initialize() {
131
- return this;
132
- }
133
-
134
- async dispose() {
135
- return this;
136
- }
137
-
138
- async ping() {
139
- return this.checkHealth();
140
- }
141
-
142
- async checkHealth() {
143
- throw new StorageUnsupportedOperationError(`checkHealth() not implemented by ${this.constructor.name}`);
144
- }
145
- }
146
-
1
+ /**
2
+ * MBKBucket - Core Storage Provider Interface
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
+ import { StorageCapabilities } from "./capabilities.js";
9
+
10
+ export class StorageProvider {
11
+ constructor(name, config = {}) {
12
+ if (new.target === StorageProvider) {
13
+ throw new TypeError("Cannot construct StorageProvider instances directly");
14
+ }
15
+ this.name = String(name);
16
+ this.config = Object.freeze({ ...config });
17
+ this.rootPrefix = config.rootPrefix || config.basePath || '';
18
+ }
19
+
20
+ get type() {
21
+ return 'generic';
22
+ }
23
+
24
+ get capabilities() {
25
+ return new StorageCapabilities({
26
+ multipart: false,
27
+ nativeFolders: false,
28
+ ranges: true,
29
+ copy: false,
30
+ move: false,
31
+ search: false,
32
+ directDownloadUrl: false,
33
+ versioning: false,
34
+ publicUrls: false,
35
+ metadata: true
36
+ });
37
+ }
38
+
39
+ supports(capabilityName) {
40
+ return this.capabilities.has(capabilityName);
41
+ }
42
+
43
+ assertCapability(capabilityName, operationName) {
44
+ this.capabilities.assert(capabilityName, operationName, `provider '${this.type}'`);
45
+ }
46
+
47
+ async listFiles(prefix = '', options = {}) {
48
+ throw new StorageUnsupportedOperationError(`listFiles() not implemented by ${this.constructor.name}`);
49
+ }
50
+
51
+ async uploadFile(key, fileBuffer, contentType, options = {}) {
52
+ throw new StorageUnsupportedOperationError(`uploadFile() not implemented by ${this.constructor.name}`);
53
+ }
54
+
55
+ async downloadFile(key, options = {}) {
56
+ throw new StorageUnsupportedOperationError(`downloadFile() not implemented by ${this.constructor.name}`);
57
+ }
58
+
59
+ async deleteFile(key, options = {}) {
60
+ throw new StorageUnsupportedOperationError(`deleteFile() not implemented by ${this.constructor.name}`);
61
+ }
62
+
63
+ async deleteFiles(keys, options = {}) {
64
+ const concurrency = Math.max(1, Math.min(Number(options.concurrency) || 8, 32));
65
+ let deletedCount = 0;
66
+ const errors = [];
67
+ const results = [];
68
+
69
+ for (let i = 0; i < keys.length; i += concurrency) {
70
+ const batch = keys.slice(i, i + concurrency);
71
+ const batchResults = await Promise.allSettled(
72
+ batch.map(key => this.deleteFile(key, options))
73
+ );
74
+ batchResults.forEach((outcome, idx) => {
75
+ const key = batch[idx];
76
+ if (outcome.status === 'fulfilled') {
77
+ results.push(outcome.value);
78
+ deletedCount++;
79
+ } else {
80
+ errors.push({ key, error: outcome.reason?.message || 'Delete failed' });
81
+ }
82
+ });
83
+ }
84
+
85
+ return { results, deletedCount, errors, deletedAt: new Date().toISOString() };
86
+ }
87
+
88
+ async deleteFolder(prefix, options = {}) {
89
+ throw new StorageUnsupportedOperationError(`deleteFolder() not implemented by ${this.constructor.name}`);
90
+ }
91
+
92
+ async createFolder(key, options = {}) {
93
+ throw new StorageUnsupportedOperationError(`createFolder() not implemented by ${this.constructor.name}`);
94
+ }
95
+
96
+ async getFileMetadata(key, options = {}) {
97
+ throw new StorageUnsupportedOperationError(`getFileMetadata() not implemented by ${this.constructor.name}`);
98
+ }
99
+
100
+ async fileExists(key, options = {}) {
101
+ try {
102
+ const meta = await this.getFileMetadata(key, options);
103
+ return Boolean(meta?.exists);
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ async getFileSize(key, options = {}) {
110
+ try {
111
+ const meta = await this.getFileMetadata(key, options);
112
+ return meta?.exists ? (meta.ContentLength ?? null) : null;
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+
118
+ async copyFile(sourceKey, destKey, options = {}) {
119
+ this.assertCapability('copy', 'copyFile');
120
+ throw new StorageUnsupportedOperationError(`copyFile() is not supported by provider ${this.type}`);
121
+ }
122
+
123
+ async moveFile(sourceKey, destKey, options = {}) {
124
+ this.assertCapability('move', 'moveFile');
125
+ await this.copyFile(sourceKey, destKey, options);
126
+ await this.deleteFile(sourceKey, options);
127
+ return { sourceKey, destKey, movedAt: new Date().toISOString() };
128
+ }
129
+
130
+ async initialize() {
131
+ return this;
132
+ }
133
+
134
+ async dispose() {
135
+ return this;
136
+ }
137
+
138
+ async ping() {
139
+ return this.checkHealth();
140
+ }
141
+
142
+ async checkHealth() {
143
+ throw new StorageUnsupportedOperationError(`checkHealth() not implemented by ${this.constructor.name}`);
144
+ }
145
+ }
146
+
@@ -1,52 +1,15 @@
1
- import { createLogger } from "#logger";
2
- import { isJsonRequest, sendError, sanitizeErrorDetails, renderPage } from "mbkauthe";
3
-
4
- const debugServer = createLogger('server');
5
- const BASE_ERROR_DATA = { layout: false, pagename: "Home", page: "/" };
6
-
7
- export function notFoundHandler(req, res, next) {
8
- const url = req.originalUrl || req.url || '';
9
- if (!url.startsWith('/mbkbucket') && typeof next === 'function') return next();
10
-
11
- debugServer("Path not found: %s %s", req.method, req.url);
12
-
13
- if (isJsonRequest(req)) {
14
- return sendError(res, "The requested API route was not found.", {
15
- statusCode: 404,
16
- code: "ROUTE_NOT_FOUND",
17
- req,
18
- });
19
- }
20
-
21
- res.status(404);
22
- return renderPage(req, res, "Error/dError.hbs", false, {
23
- ...BASE_ERROR_DATA,
24
- code: 404,
25
- error: "Not Found",
26
- message: "The requested page was not found.",
27
- });
28
- }
29
-
30
- export function globalErrorHandler(err, req, res, _next) {
31
- console.error(`[mbkbucket] ${err.stack || err}`);
32
- const statusCode = Number(err.status || err.statusCode || 500);
33
-
34
- if (isJsonRequest(req)) {
35
- return sendError(res, err, {
36
- statusCode,
37
- req,
38
- details: process.env.NODE_ENV !== 'production' ? err.stack : undefined,
39
- });
40
- }
41
-
42
- const sanitizedDetails = err.message ? sanitizeErrorDetails(err.message) : undefined;
43
-
44
- res.status(statusCode);
45
- return renderPage(req, res, "Error/dError.hbs", false, {
46
- ...BASE_ERROR_DATA,
47
- code: statusCode,
48
- error: statusCode >= 500 ? "Internal app Error" : (err.name || "Client Error"),
49
- message: err.message || "An unexpected error occurred on the app.",
50
- ...(sanitizedDetails && { details: sanitizedDetails }),
51
- });
52
- }
1
+ import { createNotFoundHandler, createErrorHandler } from "mbkutils";
2
+
3
+ export const notFoundHandler = createNotFoundHandler({
4
+ appName: "mbkbucket",
5
+ defaultPage: "/",
6
+ defaultPageName: "Home",
7
+ });
8
+
9
+ export const globalErrorHandler = createErrorHandler({
10
+ appName: "mbkbucket",
11
+ defaultPage: "/",
12
+ defaultPageName: "Home",
13
+ });
14
+
15
+ export default { notFoundHandler, globalErrorHandler };
@@ -1,5 +1,5 @@
1
1
  import rateLimit from "express-rate-limit";
2
- import { renderPage } from "mbkauthe";
2
+ import { renderPage } from "mbkutils";
3
3
 
4
4
  export const generalLimiter = rateLimit({
5
5
  windowMs: 2 * 60 * 1000,
@@ -10,7 +10,7 @@ import { PathStrategy } from "../core/storage/path-strategy.js";
10
10
  import { StorageValidationError } from "../core/storage/errors.js";
11
11
  import { metricsRegistry } from "../core/metrics.js";
12
12
  import { createLogger } from "#logger";
13
- import { trimSlashes } from "#helpers";
13
+ import { trimSlashes } from "mbkutils";
14
14
 
15
15
  dotenv.config();
16
16
  const debugStorage = createLogger('storage-service');
@@ -1,91 +1,91 @@
1
- /**
2
- * MBKBucket - Incomplete Multipart Upload Cleanup Scheduler
3
- * Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
4
- * Licensed under the MIT License.
5
- */
6
-
7
- import { cleanupIncompleteMultipartUploads, getAvailableStorageNames } from "../services/storage.service.js";
8
- import { createLogger } from "#logger";
9
-
10
- const debugCleanup = createLogger('cleanup-scheduler');
11
-
12
- export class CleanupScheduler {
13
- constructor() {
14
- this._timer = null;
15
- this._running = false;
16
- this._lastRun = null;
17
- this._lastStats = null;
18
- }
19
-
20
- async runOnce({ olderThanDays = 7, connectionNames = null } = {}) {
21
- const targets = connectionNames && Array.isArray(connectionNames) && connectionNames.length > 0
22
- ? connectionNames
23
- : getAvailableStorageNames();
24
-
25
- const summary = {
26
- startedAt: new Date().toISOString(),
27
- connectionsProcessed: 0,
28
- totalAborted: 0,
29
- errors: []
30
- };
31
-
32
- for (const name of targets) {
33
- try {
34
- const result = await cleanupIncompleteMultipartUploads(olderThanDays, '', name);
35
- summary.connectionsProcessed++;
36
- summary.totalAborted += (result.abortedCount || 0);
37
- } catch (err) {
38
- debugCleanup('Cleanup error for connection %s: %s', name, err.message);
39
- summary.errors.push({ connection: name, error: err.message });
40
- }
41
- }
42
-
43
- summary.finishedAt = new Date().toISOString();
44
- this._lastRun = summary.finishedAt;
45
- this._lastStats = summary;
46
- debugCleanup('Cleanup cycle completed: %d uploads aborted across %d connections', summary.totalAborted, summary.connectionsProcessed);
47
- return summary;
48
- }
49
-
50
- start({ intervalHours = 24, olderThanDays = 7, connectionNames = null } = {}) {
51
- if (this._running) return;
52
- this._running = true;
53
-
54
- const intervalMs = Math.max(60000, Number(intervalHours) * 3600 * 1000);
55
- debugCleanup('Starting cleanup scheduler every %s hour(s)', intervalHours);
56
-
57
- // Run first pass asynchronously
58
- this.runOnce({ olderThanDays, connectionNames }).catch(err => {
59
- debugCleanup('Initial cleanup run error: %s', err.message);
60
- });
61
-
62
- this._timer = setInterval(() => {
63
- this.runOnce({ olderThanDays, connectionNames }).catch(err => {
64
- debugCleanup('Scheduled cleanup run error: %s', err.message);
65
- });
66
- }, intervalMs);
67
-
68
- if (this._timer.unref) {
69
- this._timer.unref();
70
- }
71
- }
72
-
73
- stop() {
74
- if (this._timer) {
75
- clearInterval(this._timer);
76
- this._timer = null;
77
- }
78
- this._running = false;
79
- debugCleanup('Cleanup scheduler stopped');
80
- }
81
-
82
- getStatus() {
83
- return {
84
- running: this._running,
85
- lastRun: this._lastRun,
86
- lastStats: this._lastStats
87
- };
88
- }
89
- }
90
-
91
- export const cleanupScheduler = new CleanupScheduler();
1
+ /**
2
+ * MBKBucket - Incomplete Multipart Upload Cleanup Scheduler
3
+ * Copyright (c) 2026 Muhammad Bin Khalid, MBKTech.org and contributors
4
+ * Licensed under the MIT License.
5
+ */
6
+
7
+ import { cleanupIncompleteMultipartUploads, getAvailableStorageNames } from "../services/storage.service.js";
8
+ import { createLogger } from "#logger";
9
+
10
+ const debugCleanup = createLogger('cleanup-scheduler');
11
+
12
+ export class CleanupScheduler {
13
+ constructor() {
14
+ this._timer = null;
15
+ this._running = false;
16
+ this._lastRun = null;
17
+ this._lastStats = null;
18
+ }
19
+
20
+ async runOnce({ olderThanDays = 7, connectionNames = null } = {}) {
21
+ const targets = connectionNames && Array.isArray(connectionNames) && connectionNames.length > 0
22
+ ? connectionNames
23
+ : getAvailableStorageNames();
24
+
25
+ const summary = {
26
+ startedAt: new Date().toISOString(),
27
+ connectionsProcessed: 0,
28
+ totalAborted: 0,
29
+ errors: []
30
+ };
31
+
32
+ for (const name of targets) {
33
+ try {
34
+ const result = await cleanupIncompleteMultipartUploads(olderThanDays, '', name);
35
+ summary.connectionsProcessed++;
36
+ summary.totalAborted += (result.abortedCount || 0);
37
+ } catch (err) {
38
+ debugCleanup('Cleanup error for connection %s: %s', name, err.message);
39
+ summary.errors.push({ connection: name, error: err.message });
40
+ }
41
+ }
42
+
43
+ summary.finishedAt = new Date().toISOString();
44
+ this._lastRun = summary.finishedAt;
45
+ this._lastStats = summary;
46
+ debugCleanup('Cleanup cycle completed: %d uploads aborted across %d connections', summary.totalAborted, summary.connectionsProcessed);
47
+ return summary;
48
+ }
49
+
50
+ start({ intervalHours = 24, olderThanDays = 7, connectionNames = null } = {}) {
51
+ if (this._running) return;
52
+ this._running = true;
53
+
54
+ const intervalMs = Math.max(60000, Number(intervalHours) * 3600 * 1000);
55
+ debugCleanup('Starting cleanup scheduler every %s hour(s)', intervalHours);
56
+
57
+ // Run first pass asynchronously
58
+ this.runOnce({ olderThanDays, connectionNames }).catch(err => {
59
+ debugCleanup('Initial cleanup run error: %s', err.message);
60
+ });
61
+
62
+ this._timer = setInterval(() => {
63
+ this.runOnce({ olderThanDays, connectionNames }).catch(err => {
64
+ debugCleanup('Scheduled cleanup run error: %s', err.message);
65
+ });
66
+ }, intervalMs);
67
+
68
+ if (this._timer.unref) {
69
+ this._timer.unref();
70
+ }
71
+ }
72
+
73
+ stop() {
74
+ if (this._timer) {
75
+ clearInterval(this._timer);
76
+ this._timer = null;
77
+ }
78
+ this._running = false;
79
+ debugCleanup('Cleanup scheduler stopped');
80
+ }
81
+
82
+ getStatus() {
83
+ return {
84
+ running: this._running,
85
+ lastRun: this._lastRun,
86
+ lastStats: this._lastStats
87
+ };
88
+ }
89
+ }
90
+
91
+ export const cleanupScheduler = new CleanupScheduler();
@@ -2,24 +2,9 @@
2
2
  * Shared helper utilities used across controllers, services, and CLI.
3
3
  */
4
4
 
5
- const HTML_ESCAPE_MAP = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
5
+ import { trimSlashes, trimLeadingSlashes, getFileExt, nowIso, formatDate } from "mbkutils";
6
6
 
7
- export const escapeHtml = (value = '') => String(value ?? '').replace(/[&<>"']/g, c => HTML_ESCAPE_MAP[c]);
8
-
9
- export function getBaseName(path = '') {
10
- const value = String(path ?? '');
11
- const lastSlash = value.lastIndexOf('/');
12
- return lastSlash === -1 ? value : value.slice(lastSlash + 1);
13
- }
14
-
15
- export function getFileExt(fileName = '') {
16
- const name = getBaseName(fileName);
17
- const dotIndex = name.lastIndexOf('.');
18
- return dotIndex === -1 ? '' : name.slice(dotIndex + 1).toLowerCase();
19
- }
20
-
21
- export const trimSlashes = (str = '') => String(str ?? '').replace(/^\/+|\/+$/g, '');
22
- export const trimLeadingSlashes = (str = '') => String(str ?? '').replace(/^\/+/, '');
7
+ export { nowIso, formatDate };
23
8
 
24
9
  export function getFolderPath(key = '') {
25
10
  const s = String(key ?? '');
@@ -35,8 +20,6 @@ export function buildKey(prefix, fileName) {
35
20
  return cleanPrefix ? `${cleanPrefix}/${cleanName}` : cleanName;
36
21
  }
37
22
 
38
- export const nowIso = () => new Date().toISOString();
39
-
40
23
  export function formatBytes(bytes) {
41
24
  if (!bytes) return '0 B';
42
25
  const units = ['B', 'KB', 'MB', 'GB', 'TB'];
@@ -44,14 +27,6 @@ export function formatBytes(bytes) {
44
27
  return `${(bytes / 1024 ** i).toFixed(i > 0 ? 1 : 0)} ${units[i]}`;
45
28
  }
46
29
 
47
- export function formatDate(iso) {
48
- try {
49
- return new Date(iso).toLocaleString();
50
- } catch {
51
- return iso || '—';
52
- }
53
- }
54
-
55
30
  export async function streamToBuffer(stream) {
56
31
  if (Buffer.isBuffer(stream)) return stream;
57
32
  const chunks = [];
@@ -192,22 +167,4 @@ export const renderPlayerPage = (safeFileName, encodedKey, bucketQuery) => `<!do
192
167
  </body>
193
168
  </html>`;
194
169
 
195
- // ---------------------------------------------------------------------------
196
- // Common Handlebars helpers
197
- // ---------------------------------------------------------------------------
198
170
 
199
- export const commonHandlebarsHelpers = {
200
- eq: (a, b) => a === b,
201
- neq: (a, b) => a !== b,
202
- or: (...args) => args.slice(0, -1).some(Boolean),
203
- and: (...args) => args.slice(0, -1).every(Boolean),
204
- not: val => !val,
205
- gt: (a, b) => a > b,
206
- gte: (a, b) => a >= b,
207
- lt: (a, b) => a < b,
208
- lte: (a, b) => a <= b,
209
- includes: (arr, val) => Array.isArray(arr) && arr.includes(val),
210
- json: obj => JSON.stringify(obj, null, 2),
211
- jsonStringify: obj => JSON.stringify(obj),
212
- getInitials: username => username ? username.split(/[._]/).map(p => p.charAt(0)).join('').toUpperCase().slice(0, 2) : '?',
213
- };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mbkbucket",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "description": "Flexible S3/R2/Google Drive/Local bucket management — library, Express router, and standalone server CLI for mbktech.org applications. Provides upload, download, listing, multipart utilities, and a standalone server runner with built-in configuration validation and health checks",
5
5
  "author": "Muhammad Bin Khalid",
6
6
  "homepage": "https://github.com/MIbnEKhalid/mbkbucket#readme",
@@ -29,7 +29,8 @@
29
29
  "express": "^5.2.1",
30
30
  "express-handlebars": "^9.0.1",
31
31
  "express-rate-limit": "^7.5.1",
32
- "mbkauthe": "^6.2.3",
32
+ "mbkauthe": "latest",
33
+ "mbkutils": "latest",
33
34
  "multer": "^2.4.0"
34
35
  },
35
36
  "devDependencies": {