b2b-platform-utils 1.1.24 → 1.1.26

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.
Files changed (3) hide show
  1. package/errorsMap.js +380 -116
  2. package/fileSystem.js +158 -135
  3. package/package.json +1 -1
package/errorsMap.js CHANGED
@@ -1,70 +1,333 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  // Purpose: Static error map with backward compatibility and dynamic shortcuts.
4
4
  // - Old style calls like errorsMap.mailTemplateNotFound?.() continue to work.
5
- // - Any property access matching an errorKey returns a function that yields the error OBJECT.
5
+ // - Any property access matching an errorKey returns a function that yields the errorKey STRING.
6
6
  // - New API: get(), getString(), getCode(), list(), and legacy getSingleError().
7
7
  // - Cache-first: if localCache.errorsMap exists, it overrides static entries.
8
8
 
9
- const { getValue } = require('./localCache');
9
+ const { getValue } = require("./localCache");
10
10
 
11
11
  // ---- Static fallback (source of truth when cache is empty) ----
12
12
  const STATIC_ERRORS = [
13
- { errorCode: 1000, errorKey: 'userNotFound', httpCode: 404, description: 'User was not found' },
14
- { errorCode: 1001, errorKey: 'unauthorized', httpCode: 401, description: 'The server requires user authentication' },
15
- { errorCode: 1002, errorKey: 'gameCategoryNotFound', httpCode: 404, description: 'Game`s category was not found' },
16
- { errorCode: 1003, errorKey: 'mediaResourceNotFound', httpCode: 404, description: 'Media resource was not found' },
17
- { errorCode: 1004, errorKey: 'roleNotFound', httpCode: 404, description: 'Role or permission were not found' },
18
- { errorCode: 1005, errorKey: 'duplicatedUniqueField', httpCode: 422, description: 'Some of unique fields were duplicated.' },
19
- { errorCode: 1006, errorKey: 'gameCollectionNotFound', httpCode: 404, description: 'Game`s collection was not found' },
20
- { errorCode: 1007, errorKey: 'gameTagNotFound', httpCode: 404, description: 'Game`s tag was not found' },
21
- { errorCode: 1008, errorKey: 'invalidConstraintField', httpCode: 422, description: 'Some of foreign keys fields were invalid.' },
22
- { errorCode: 1009, errorKey: 'gameProviderNotFound', httpCode: 404, description: 'Game`s provider was not found' },
23
- { errorCode: 1010, errorKey: 'gameNotFound', httpCode: 404, description: 'Game was not found' },
24
- { errorCode: 1011, errorKey: 'undefinedRequest', httpCode: 422, description: 'Request data or format is undefined for handler.' },
25
- { errorCode: 1012, errorKey: 'widgetNotFound', httpCode: 404, description: 'Widget[CMS] was not found' },
26
- { errorCode: 1013, errorKey: 'segmentNotFound', httpCode: 404, description: 'User`s Segment was not found' },
27
- { errorCode: 1014, errorKey: 'bonusNotFound', httpCode: 404, description: 'Bonus was not found' },
28
- { errorCode: 1015, errorKey: 'userBonusNotFound', httpCode: 404, description: 'User`s bonus was not found' },
29
- { errorCode: 1016, errorKey: 'freeSpinsNotFound', httpCode: 404, description: 'Free spins was not found' },
30
- { errorCode: 1017, errorKey: 'gameWagerListNotFound', httpCode: 404, description: 'Game`s wager list was not found' },
31
- { errorCode: 1018, errorKey: 'bonusAlreadyInUse', httpCode: 422, description: 'Bonus is already use(d)' },
32
- { errorCode: 1019, errorKey: 'bonusRestrictForClaim', httpCode: 422, description: 'Bonus does not fit for user`s claiming' },
33
- { errorCode: 1020, errorKey: 'bonusRewardAttachError', httpCode: 422, description: 'Bonus reward can not be attached.' },
34
- { errorCode: 1021, errorKey: 'loyaltyLevelNotFound', httpCode: 404, description: 'Loyalty level was not found' },
35
- { errorCode: 1022, errorKey: 'loyaltyPointNotFound', httpCode: 404, description: 'Loyalty point was not found' },
36
- { errorCode: 1023, errorKey: 'notificationNotFound', httpCode: 404, description: 'Notification was not found' },
37
- { errorCode: 1024, errorKey: 'bannerNotFound', httpCode: 404, description: 'Banner was not found' },
38
- { errorCode: 1025, errorKey: 'paymentProviderNotFound', httpCode: 404, description: 'Payment provider was not found' },
39
- { errorCode: 1026, errorKey: 'paymentMethodNotFound', httpCode: 404, description: 'Payment method was not found' },
40
- { errorCode: 1027, errorKey: 'depositNotFound', httpCode: 404, description: 'Deposit was not found' },
41
- { errorCode: 1028, errorKey: 'withdrawalNotFound', httpCode: 404, description: 'Withdrawal was not found' },
42
- { errorCode: 1029, errorKey: 'noteNotFound', httpCode: 404, description: 'User`s Note was not found' },
43
- { errorCode: 1030, errorKey: 'promoCodeNotFound', httpCode: 404, description: 'Promo Code was not found' },
44
- { errorCode: 1031, errorKey: 'cashBackNotFound', httpCode: 404, description: 'Cash Back was not found' },
45
- { errorCode: 1032, errorKey: 'tournamentNotFound', httpCode: 404, description: 'Tournament was not found' },
46
- { errorCode: 1033, errorKey: 'pageNotFound', httpCode: 404, description: 'Page was not found' },
47
- { errorCode: 1034, errorKey: 'gameCashBackListNotFound', httpCode: 404, description: 'Game`s cash back list was not found' },
48
- { errorCode: 1035, errorKey: 'bonusDepositRequired', httpCode: 404, description: 'For bonus activation desposit requited.' },
49
- { errorCode: 1036, errorKey: 'taskNotFound', httpCode: 404, description: 'Task for schedule was not found.' },
50
- { errorCode: 1037, errorKey: 'bonusConfirmationRequired', httpCode: 404, description: 'For bonus activation confirmation of personal user data requited.' },
51
- { errorCode: 1038, errorKey: 'promoNotFound', httpCode: 404, description: 'Promo was not found.' },
52
- { errorCode: 1039, errorKey: 'badSignature', httpCode: 401, description: 'Untrusted request, bad signature.' },
53
- { errorCode: 1040, errorKey: 'mailTemplateNotFound', httpCode: 404, description: 'Email template was not found.' },
54
- { errorCode: 1041, errorKey: 'domainNotFound', httpCode: 404, description: 'Domain was not found.' },
55
- { errorCode: 1042, errorKey: 'domainAlreadyExists', httpCode: 422, description: 'Domain already in use.' },
56
- { errorCode: 1043, errorKey: 'domainIsNotActivated', httpCode: 422, description: 'Current Domain is not activated yet.' },
57
- { errorCode: 1044, errorKey: 'domainCountriesNotFound', httpCode: 404, description: 'Domain`s Countries were not found for primary zone.' },
58
- { errorCode: 1045, errorKey: 'footerPartnersNotFound', httpCode: 404, description: 'Footer Partners block were not found.' },
59
- { errorCode: 1046, errorKey: 'footerBrandCoreNotFound', httpCode: 404, description: 'Footer Brand Core block were not found.' },
60
- { errorCode: 1047, errorKey: 'footerPaymentsNotFound', httpCode: 404, description: 'Footer Payments block were not found.' },
61
- { errorCode: 1048, errorKey: 'footerMainTextNotFound', httpCode: 404, description: 'Footer Main Text block were not found.' },
62
- { errorCode: 1049, errorKey: 'footerApplicationsNotFound', httpCode: 404, description: 'Footer Applications block were not found.' },
63
- { errorCode: 1050, errorKey: 'footerBlockAlreadyExists', httpCode: 422, description: 'Footer block item already exists.' },
64
- { errorCode: 1051, errorKey: 'invalidTwoFaCode', httpCode: 422, description: 'Two Factor Auth code is invalid or expired.' }
13
+ {
14
+ errorCode: 1000,
15
+ errorKey: "userNotFound",
16
+ httpCode: 404,
17
+ description: "User was not found",
18
+ },
19
+ {
20
+ errorCode: 1001,
21
+ errorKey: "unauthorized",
22
+ httpCode: 401,
23
+ description: "The server requires user authentication",
24
+ },
25
+ {
26
+ errorCode: 1002,
27
+ errorKey: "gameCategoryNotFound",
28
+ httpCode: 404,
29
+ description: "Game`s category was not found",
30
+ },
31
+ {
32
+ errorCode: 1003,
33
+ errorKey: "mediaResourceNotFound",
34
+ httpCode: 404,
35
+ description: "Media resource was not found",
36
+ },
37
+ {
38
+ errorCode: 1004,
39
+ errorKey: "roleNotFound",
40
+ httpCode: 404,
41
+ description: "Role or permission were not found",
42
+ },
43
+ {
44
+ errorCode: 1005,
45
+ errorKey: "duplicatedUniqueField",
46
+ httpCode: 422,
47
+ description: "Some of unique fields were duplicated.",
48
+ },
49
+ {
50
+ errorCode: 1006,
51
+ errorKey: "gameCollectionNotFound",
52
+ httpCode: 404,
53
+ description: "Game`s collection was not found",
54
+ },
55
+ {
56
+ errorCode: 1007,
57
+ errorKey: "gameTagNotFound",
58
+ httpCode: 404,
59
+ description: "Game`s tag was not found",
60
+ },
61
+ {
62
+ errorCode: 1008,
63
+ errorKey: "invalidConstraintField",
64
+ httpCode: 422,
65
+ description: "Some of foreign keys fields were invalid.",
66
+ },
67
+ {
68
+ errorCode: 1009,
69
+ errorKey: "gameProviderNotFound",
70
+ httpCode: 404,
71
+ description: "Game`s provider was not found",
72
+ },
73
+ {
74
+ errorCode: 1010,
75
+ errorKey: "gameNotFound",
76
+ httpCode: 404,
77
+ description: "Game was not found",
78
+ },
79
+ {
80
+ errorCode: 1011,
81
+ errorKey: "undefinedRequest",
82
+ httpCode: 422,
83
+ description: "Request data or format is undefined for handler.",
84
+ },
85
+ {
86
+ errorCode: 1012,
87
+ errorKey: "widgetNotFound",
88
+ httpCode: 404,
89
+ description: "Widget[CMS] was not found",
90
+ },
91
+ {
92
+ errorCode: 1013,
93
+ errorKey: "segmentNotFound",
94
+ httpCode: 404,
95
+ description: "User`s Segment was not found",
96
+ },
97
+ {
98
+ errorCode: 1014,
99
+ errorKey: "bonusNotFound",
100
+ httpCode: 404,
101
+ description: "Bonus was not found",
102
+ },
103
+ {
104
+ errorCode: 1015,
105
+ errorKey: "userBonusNotFound",
106
+ httpCode: 404,
107
+ description: "User`s bonus was not found",
108
+ },
109
+ {
110
+ errorCode: 1016,
111
+ errorKey: "freeSpinsNotFound",
112
+ httpCode: 404,
113
+ description: "Free spins was not found",
114
+ },
115
+ {
116
+ errorCode: 1017,
117
+ errorKey: "gameWagerListNotFound",
118
+ httpCode: 404,
119
+ description: "Game`s wager list was not found",
120
+ },
121
+ {
122
+ errorCode: 1018,
123
+ errorKey: "bonusAlreadyInUse",
124
+ httpCode: 422,
125
+ description: "Bonus is already use(d)",
126
+ },
127
+ {
128
+ errorCode: 1019,
129
+ errorKey: "bonusRestrictForClaim",
130
+ httpCode: 422,
131
+ description: "Bonus does not fit for user`s claiming",
132
+ },
133
+ {
134
+ errorCode: 1020,
135
+ errorKey: "bonusRewardAttachError",
136
+ httpCode: 422,
137
+ description: "Bonus reward can not be attached.",
138
+ },
139
+ {
140
+ errorCode: 1021,
141
+ errorKey: "loyaltyLevelNotFound",
142
+ httpCode: 404,
143
+ description: "Loyalty level was not found",
144
+ },
145
+ {
146
+ errorCode: 1022,
147
+ errorKey: "loyaltyPointNotFound",
148
+ httpCode: 404,
149
+ description: "Loyalty point was not found",
150
+ },
151
+ {
152
+ errorCode: 1023,
153
+ errorKey: "notificationNotFound",
154
+ httpCode: 404,
155
+ description: "Notification was not found",
156
+ },
157
+ {
158
+ errorCode: 1024,
159
+ errorKey: "bannerNotFound",
160
+ httpCode: 404,
161
+ description: "Banner was not found",
162
+ },
163
+ {
164
+ errorCode: 1025,
165
+ errorKey: "paymentProviderNotFound",
166
+ httpCode: 404,
167
+ description: "Payment provider was not found",
168
+ },
169
+ {
170
+ errorCode: 1026,
171
+ errorKey: "paymentMethodNotFound",
172
+ httpCode: 404,
173
+ description: "Payment method was not found",
174
+ },
175
+ {
176
+ errorCode: 1027,
177
+ errorKey: "depositNotFound",
178
+ httpCode: 404,
179
+ description: "Deposit was not found",
180
+ },
181
+ {
182
+ errorCode: 1028,
183
+ errorKey: "withdrawalNotFound",
184
+ httpCode: 404,
185
+ description: "Withdrawal was not found",
186
+ },
187
+ {
188
+ errorCode: 1029,
189
+ errorKey: "noteNotFound",
190
+ httpCode: 404,
191
+ description: "User`s Note was not found",
192
+ },
193
+ {
194
+ errorCode: 1030,
195
+ errorKey: "promoCodeNotFound",
196
+ httpCode: 404,
197
+ description: "Promo Code was not found",
198
+ },
199
+ {
200
+ errorCode: 1031,
201
+ errorKey: "cashBackNotFound",
202
+ httpCode: 404,
203
+ description: "Cash Back was not found",
204
+ },
205
+ {
206
+ errorCode: 1032,
207
+ errorKey: "tournamentNotFound",
208
+ httpCode: 404,
209
+ description: "Tournament was not found",
210
+ },
211
+ {
212
+ errorCode: 1033,
213
+ errorKey: "pageNotFound",
214
+ httpCode: 404,
215
+ description: "Page was not found",
216
+ },
217
+ {
218
+ errorCode: 1034,
219
+ errorKey: "gameCashBackListNotFound",
220
+ httpCode: 404,
221
+ description: "Game`s cash back list was not found",
222
+ },
223
+ {
224
+ errorCode: 1035,
225
+ errorKey: "bonusDepositRequired",
226
+ httpCode: 404,
227
+ description: "For bonus activation desposit requited.",
228
+ },
229
+ {
230
+ errorCode: 1036,
231
+ errorKey: "taskNotFound",
232
+ httpCode: 404,
233
+ description: "Task for schedule was not found.",
234
+ },
235
+ {
236
+ errorCode: 1037,
237
+ errorKey: "bonusConfirmationRequired",
238
+ httpCode: 404,
239
+ description:
240
+ "For bonus activation confirmation of personal user data requited.",
241
+ },
242
+ {
243
+ errorCode: 1038,
244
+ errorKey: "promoNotFound",
245
+ httpCode: 404,
246
+ description: "Promo was not found.",
247
+ },
248
+ {
249
+ errorCode: 1039,
250
+ errorKey: "badSignature",
251
+ httpCode: 401,
252
+ description: "Untrusted request, bad signature.",
253
+ },
254
+ {
255
+ errorCode: 1040,
256
+ errorKey: "mailTemplateNotFound",
257
+ httpCode: 404,
258
+ description: "Email template was not found.",
259
+ },
260
+ {
261
+ errorCode: 1041,
262
+ errorKey: "domainNotFound",
263
+ httpCode: 404,
264
+ description: "Domain was not found.",
265
+ },
266
+ {
267
+ errorCode: 1042,
268
+ errorKey: "domainAlreadyExists",
269
+ httpCode: 422,
270
+ description: "Domain already in use.",
271
+ },
272
+ {
273
+ errorCode: 1043,
274
+ errorKey: "domainIsNotActivated",
275
+ httpCode: 422,
276
+ description: "Current Domain is not activated yet.",
277
+ },
278
+ {
279
+ errorCode: 1044,
280
+ errorKey: "domainCountriesNotFound",
281
+ httpCode: 404,
282
+ description: "Domain`s Countries were not found for primary zone.",
283
+ },
284
+ {
285
+ errorCode: 1045,
286
+ errorKey: "footerPartnersNotFound",
287
+ httpCode: 404,
288
+ description: "Footer Partners block were not found.",
289
+ },
290
+ {
291
+ errorCode: 1046,
292
+ errorKey: "footerBrandCoreNotFound",
293
+ httpCode: 404,
294
+ description: "Footer Brand Core block were not found.",
295
+ },
296
+ {
297
+ errorCode: 1047,
298
+ errorKey: "footerPaymentsNotFound",
299
+ httpCode: 404,
300
+ description: "Footer Payments block were not found.",
301
+ },
302
+ {
303
+ errorCode: 1048,
304
+ errorKey: "footerMainTextNotFound",
305
+ httpCode: 404,
306
+ description: "Footer Main Text block were not found.",
307
+ },
308
+ {
309
+ errorCode: 1049,
310
+ errorKey: "footerApplicationsNotFound",
311
+ httpCode: 404,
312
+ description: "Footer Applications block were not found.",
313
+ },
314
+ {
315
+ errorCode: 1050,
316
+ errorKey: "footerBlockAlreadyExists",
317
+ httpCode: 422,
318
+ description: "Footer block item already exists.",
319
+ },
320
+ {
321
+ errorCode: 1051,
322
+ errorKey: "invalidTwoFaCode",
323
+ httpCode: 422,
324
+ description: "Two Factor Auth code is invalid or expired.",
325
+ },
65
326
  ];
66
327
 
67
- const STATIC_BY_KEY = Object.fromEntries(STATIC_ERRORS.map((e) => [e.errorKey, e]));
328
+ const STATIC_BY_KEY = Object.fromEntries(
329
+ STATIC_ERRORS.map((e) => [e.errorKey, e])
330
+ );
68
331
 
69
332
  /**
70
333
  * Resolve error by key from cache (if any) or static table.
@@ -72,77 +335,78 @@ const STATIC_BY_KEY = Object.fromEntries(STATIC_ERRORS.map((e) => [e.errorKey, e
72
335
  * @returns {object|undefined}
73
336
  */
74
337
  function resolveErrorByKey(errorKey) {
75
- const cached = getValue('errorsMap');
76
- if (Array.isArray(cached) && cached.length) {
77
- const hit = cached.find((item) => item?.errorKey === errorKey);
78
- if (hit) return hit;
79
- }
80
- return STATIC_BY_KEY[errorKey];
338
+ const cached = getValue("errorsMap");
339
+ if (Array.isArray(cached) && cached.length) {
340
+ const hit = cached.find((item) => item?.errorKey === errorKey);
341
+ if (hit) return hit;
342
+ }
343
+ return STATIC_BY_KEY[errorKey];
81
344
  }
82
345
 
83
346
  // --- Base API object (do not export directly; we wrap it in a Proxy) ---
84
347
  const api = {
85
- /**
86
- * Legacy accessor kept for compatibility (returns JSON string).
87
- * @param {string} errorKey
88
- * @returns {string|undefined}
89
- */
90
- getSingleError: (errorKey) => {
91
- const obj = resolveErrorByKey(errorKey);
92
- return obj ? JSON.stringify(obj) : undefined;
93
- },
348
+ /**
349
+ * Legacy accessor kept for compatibility (returns JSON string).
350
+ * @param {string} errorKey
351
+ * @returns {string|undefined}
352
+ */
353
+ getSingleError: (errorKey) => {
354
+ const obj = resolveErrorByKey(errorKey);
355
+ return obj ? JSON.stringify(obj) : undefined;
356
+ },
94
357
 
95
- /**
96
- * New API: get full error object.
97
- * @param {string} errorKey
98
- * @returns {object|undefined}
99
- */
100
- get: (errorKey) => resolveErrorByKey(errorKey),
358
+ /**
359
+ * New API: get full error object.
360
+ * @param {string} errorKey
361
+ * @returns {object|undefined}
362
+ */
363
+ get: (errorKey) => resolveErrorByKey(errorKey),
101
364
 
102
- /**
103
- * New API: get JSON string version (same shape as legacy).
104
- * @param {string} errorKey
105
- * @returns {string|undefined}
106
- */
107
- getString: (errorKey) => {
108
- const obj = resolveErrorByKey(errorKey);
109
- return obj ? JSON.stringify(obj) : undefined;
110
- },
365
+ /**
366
+ * New API: get JSON string version (same shape as legacy).
367
+ * @param {string} errorKey
368
+ * @returns {string|undefined}
369
+ */
370
+ getString: (errorKey) => {
371
+ const obj = resolveErrorByKey(errorKey);
372
+ return obj ? JSON.stringify(obj) : undefined;
373
+ },
111
374
 
112
- /**
113
- * New API: get numeric error code.
114
- * @param {string} errorKey
115
- * @returns {number|undefined}
116
- */
117
- getCode: (errorKey) => resolveErrorByKey(errorKey)?.errorCode,
375
+ /**
376
+ * New API: get numeric error code.
377
+ * @param {string} errorKey
378
+ * @returns {number|undefined}
379
+ */
380
+ getCode: (errorKey) => resolveErrorByKey(errorKey)?.errorCode,
118
381
 
119
- /**
120
- * New API: list all static errors (useful for diagnostics).
121
- * @returns {object[]}
122
- */
123
- list: () => [...STATIC_ERRORS]
382
+ /**
383
+ * New API: list all static errors (useful for diagnostics).
384
+ * @returns {object[]}
385
+ */
386
+ list: () => [...STATIC_ERRORS],
124
387
  };
125
388
 
126
- // --- Dynamic shortcuts: errorsMap.<errorKey>() returns OBJECT (same as get()) ---
389
+ // --- Dynamic shortcuts: errorsMap.<errorKey>() returns errorKey STRING ---
127
390
  module.exports = new Proxy(api, {
128
- get(target, prop, receiver) {
129
- // If method exists (get, getString, etc.) → return it
130
- if (prop in target) {
131
- return Reflect.get(target, prop, receiver);
132
- }
391
+ get(target, prop, receiver) {
392
+ // If method exists (get, getString, etc.) → return it
393
+ if (prop in target) {
394
+ return Reflect.get(target, prop, receiver);
395
+ }
133
396
 
134
- // If property is a known errorKey → return a function () => get(errorKey) (OBJECT)
135
- if (typeof prop === 'string' && STATIC_BY_KEY[prop]) {
136
- return function () {
137
- return target.get(prop);
138
- };
139
- }
397
+ // If property is a known errorKey (static or cached) → return a function () => errorKey (STRING)
398
+ if (typeof prop === "string" && resolveErrorByKey(prop)) {
399
+ return function () {
400
+ return prop;
401
+ };
402
+ }
140
403
 
141
- // Otherwise undefined
142
- return undefined;
143
- },
404
+ return undefined;
405
+ },
144
406
 
145
- has(target, prop) {
146
- return prop in target || (typeof prop === 'string' && !!STATIC_BY_KEY[prop]);
147
- }
148
- });
407
+ has(target, prop) {
408
+ return (
409
+ prop in target || (typeof prop === "string" && !!resolveErrorByKey(prop))
410
+ );
411
+ },
412
+ });
package/fileSystem.js CHANGED
@@ -1,4 +1,4 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  /**
4
4
  * @module fileSystem
@@ -19,10 +19,11 @@
19
19
  * It is NOT recommended in hot request/response paths.
20
20
  */
21
21
 
22
- const fs = require('fs');
23
- const path = require('path');
24
- const { warningNote } = require('./logger');
25
- const { getValue } = require('./localCache');
22
+ const fs = require("fs");
23
+ const fsPromises = require("fs/promises");
24
+ const path = require("path");
25
+ const { warningNote } = require("./logger");
26
+ const { getValue } = require("./localCache");
26
27
 
27
28
  // -----------------------------------------------------------------------------
28
29
  // Internal helpers
@@ -36,9 +37,9 @@ const { getValue } = require('./localCache');
36
37
  * @returns {string} Absolute path.
37
38
  */
38
39
  function resolvePath(fileName) {
39
- return path.isAbsolute(fileName)
40
- ? fileName
41
- : path.resolve(process.cwd(), fileName);
40
+ return path.isAbsolute(fileName)
41
+ ? fileName
42
+ : path.resolve(process.cwd(), fileName);
42
43
  }
43
44
 
44
45
  /**
@@ -47,7 +48,7 @@ function resolvePath(fileName) {
47
48
  * @param {string} absolutePath
48
49
  */
49
50
  function ensureDirForFile(absolutePath) {
50
- fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
51
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
51
52
  }
52
53
 
53
54
  /**
@@ -59,11 +60,11 @@ function ensureDirForFile(absolutePath) {
59
60
  * @returns {string} extension without dot, e.g. "png"
60
61
  */
61
62
  function extractCleanExtension(filename) {
62
- // remove query/hash if passed like "avatar.png?ts=123" or "file.pdf#v2"
63
- const cleanName = filename.split('?')[0].split('#')[0];
64
- const parts = cleanName.split('.');
65
- if (parts.length < 2) return '';
66
- return parts.pop().toLowerCase();
63
+ // remove query/hash if passed like "avatar.png?ts=123" or "file.pdf#v2"
64
+ const cleanName = filename.split("?")[0].split("#")[0];
65
+ const parts = cleanName.split(".");
66
+ if (parts.length < 2) return "";
67
+ return parts.pop().toLowerCase();
67
68
  }
68
69
 
69
70
  // -----------------------------------------------------------------------------
@@ -80,13 +81,13 @@ function extractCleanExtension(filename) {
80
81
  * @param {number} [options.spaces=0] - JSON indentation level, e.g. 2 for pretty debug output.
81
82
  */
82
83
  function saveFile(fileName, data, options = {}) {
83
- const absPath = resolvePath(fileName);
84
- ensureDirForFile(absPath);
84
+ const absPath = resolvePath(fileName);
85
+ ensureDirForFile(absPath);
85
86
 
86
- const spaces = Number.isInteger(options.spaces) ? options.spaces : 0;
87
- const json = JSON.stringify(data, null, spaces);
87
+ const spaces = Number.isInteger(options.spaces) ? options.spaces : 0;
88
+ const json = JSON.stringify(data, null, spaces);
88
89
 
89
- fs.writeFileSync(absPath, json, { encoding: 'utf-8' });
90
+ fs.writeFileSync(absPath, json, { encoding: "utf-8" });
90
91
  }
91
92
 
92
93
  /**
@@ -97,8 +98,8 @@ function saveFile(fileName, data, options = {}) {
97
98
  * @returns {string} File contents.
98
99
  */
99
100
  function readFile(fileName) {
100
- const absPath = resolvePath(fileName);
101
- return fs.readFileSync(absPath, 'utf-8');
101
+ const absPath = resolvePath(fileName);
102
+ return fs.readFileSync(absPath, "utf-8");
102
103
  }
103
104
 
104
105
  /**
@@ -110,14 +111,14 @@ function readFile(fileName) {
110
111
  * @returns {any|null}
111
112
  */
112
113
  function readJsonFile(fileName) {
113
- const absPath = resolvePath(fileName);
114
+ const absPath = resolvePath(fileName);
114
115
 
115
- if (!fs.existsSync(absPath)) {
116
- return null;
117
- }
116
+ if (!fs.existsSync(absPath)) {
117
+ return null;
118
+ }
118
119
 
119
- const raw = fs.readFileSync(absPath, 'utf-8');
120
- return JSON.parse(raw);
120
+ const raw = fs.readFileSync(absPath, "utf-8");
121
+ return JSON.parse(raw);
121
122
  }
122
123
 
123
124
  /**
@@ -127,13 +128,34 @@ function readJsonFile(fileName) {
127
128
  * @param {string} fileName
128
129
  */
129
130
  function removeFile(fileName) {
130
- const absPath = resolvePath(fileName);
131
+ const absPath = resolvePath(fileName);
131
132
 
132
- if (!fs.existsSync(absPath)) {
133
- return;
134
- }
133
+ if (!fs.existsSync(absPath)) {
134
+ return;
135
+ }
135
136
 
136
- fs.unlinkSync(absPath);
137
+ fs.unlinkSync(absPath);
138
+ }
139
+
140
+ /**
141
+ * Remove a file from the filesystem if it exists (async).
142
+ * Safe no-op if the file is already missing.
143
+ *
144
+ * @param {string} fileName - Target file path (absolute or relative).
145
+ * @returns {Promise<boolean>} True if removed, false if file did not exist.
146
+ */
147
+ async function unlinkIfExists(fileName) {
148
+ const absPath = resolvePath(fileName);
149
+
150
+ try {
151
+ await fsPromises.unlink(absPath);
152
+ return true;
153
+ } catch (err) {
154
+ if (err?.code === "ENOENT") {
155
+ return false;
156
+ }
157
+ throw err;
158
+ }
137
159
  }
138
160
 
139
161
  // -----------------------------------------------------------------------------
@@ -149,58 +171,58 @@ function removeFile(fileName) {
149
171
  * @returns {{ extension: string|null, error: string }}
150
172
  */
151
173
  function getImageExtension(mimetype) {
152
- let extension = null;
153
- let error = '';
154
-
155
- switch (mimetype) {
156
- case 'image/png':
157
- extension = 'png';
158
- break;
159
- case 'image/jpeg':
160
- extension = 'jpeg';
161
- break;
162
- case 'image/jpg':
163
- extension = 'jpg';
164
- break;
165
- case 'image/svg+xml':
166
- extension = 'svg';
167
- break;
168
- case 'image/webp':
169
- extension = 'webp';
170
- break;
171
- case 'image/gif':
172
- extension = 'gif';
173
- break;
174
- case 'image/vnd.microsoft.icon':
175
- extension = 'ico';
176
- break;
177
- case 'font/ttf':
178
- extension = 'ttf';
179
- break;
180
- case 'text/plain':
181
- extension = 'txt';
182
- break;
183
- case 'text/csv':
184
- extension = 'csv';
185
- break;
186
- case 'application/pdf':
187
- extension = 'pdf';
188
- break;
189
- case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
190
- extension = 'xlsx';
191
- break;
192
- default:
193
- error = `Undefined file mimetype: ${mimetype}`;
194
- extension = null;
195
-
196
- // Emit a non-fatal warning. Service can still decide what to do.
197
- if (typeof warningNote === 'function') {
198
- warningNote(error);
199
- }
200
- break;
201
- }
174
+ let extension = null;
175
+ let error = "";
176
+
177
+ switch (mimetype) {
178
+ case "image/png":
179
+ extension = "png";
180
+ break;
181
+ case "image/jpeg":
182
+ extension = "jpeg";
183
+ break;
184
+ case "image/jpg":
185
+ extension = "jpg";
186
+ break;
187
+ case "image/svg+xml":
188
+ extension = "svg";
189
+ break;
190
+ case "image/webp":
191
+ extension = "webp";
192
+ break;
193
+ case "image/gif":
194
+ extension = "gif";
195
+ break;
196
+ case "image/vnd.microsoft.icon":
197
+ extension = "ico";
198
+ break;
199
+ case "font/ttf":
200
+ extension = "ttf";
201
+ break;
202
+ case "text/plain":
203
+ extension = "txt";
204
+ break;
205
+ case "text/csv":
206
+ extension = "csv";
207
+ break;
208
+ case "application/pdf":
209
+ extension = "pdf";
210
+ break;
211
+ case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
212
+ extension = "xlsx";
213
+ break;
214
+ default:
215
+ error = `Undefined file mimetype: ${mimetype}`;
216
+ extension = null;
217
+
218
+ // Emit a non-fatal warning. Service can still decide what to do.
219
+ if (typeof warningNote === "function") {
220
+ warningNote(error);
221
+ }
222
+ break;
223
+ }
202
224
 
203
- return { extension, error };
225
+ return { extension, error };
204
226
  }
205
227
 
206
228
  /**
@@ -211,43 +233,43 @@ function getImageExtension(mimetype) {
211
233
  * @returns {string} MIME type string or "" if unknown.
212
234
  */
213
235
  function getMimeTypeByFileExtension(filename) {
214
- const ext = extractCleanExtension(filename);
215
-
216
- switch (ext) {
217
- case 'png':
218
- return 'image/png';
219
- case 'gif':
220
- return 'image/gif';
221
- case 'jpeg':
222
- case 'jpg':
223
- return 'image/jpeg';
224
- case 'webp':
225
- return 'image/webp';
226
- case 'ico':
227
- return 'image/vnd.microsoft.icon';
228
- case 'svg':
229
- return 'image/svg+xml';
230
- case 'csv':
231
- return 'text/csv';
232
- case 'txt':
233
- return 'text/plain';
234
- case 'xml':
235
- return 'application/xml';
236
- case 'pdf':
237
- return 'application/pdf';
238
- case 'xls':
239
- return 'application/vnd.ms-excel';
240
- case 'xlsx':
241
- return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
242
- case 'doc':
243
- return 'application/msword';
244
- case 'docx':
245
- return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
246
- case 'mp4':
247
- return 'video/mp4';
248
- default:
249
- return '';
250
- }
236
+ const ext = extractCleanExtension(filename);
237
+
238
+ switch (ext) {
239
+ case "png":
240
+ return "image/png";
241
+ case "gif":
242
+ return "image/gif";
243
+ case "jpeg":
244
+ case "jpg":
245
+ return "image/jpeg";
246
+ case "webp":
247
+ return "image/webp";
248
+ case "ico":
249
+ return "image/vnd.microsoft.icon";
250
+ case "svg":
251
+ return "image/svg+xml";
252
+ case "csv":
253
+ return "text/csv";
254
+ case "txt":
255
+ return "text/plain";
256
+ case "xml":
257
+ return "application/xml";
258
+ case "pdf":
259
+ return "application/pdf";
260
+ case "xls":
261
+ return "application/vnd.ms-excel";
262
+ case "xlsx":
263
+ return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
264
+ case "doc":
265
+ return "application/msword";
266
+ case "docx":
267
+ return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
268
+ case "mp4":
269
+ return "video/mp4";
270
+ default:
271
+ return "";
272
+ }
251
273
  }
252
274
 
253
275
  // -----------------------------------------------------------------------------
@@ -273,8 +295,8 @@ function getMimeTypeByFileExtension(filename) {
273
295
  * @returns {string} Absolute URL.
274
296
  */
275
297
  function getAbsoluteMediaResourceUrl(service, instance, fileName) {
276
- const baseUrl = getValue('baseApiURL') || '';
277
- return `${baseUrl}/file/${service}/${instance}/${fileName}`;
298
+ const baseUrl = getValue("baseApiURL") || "";
299
+ return `${baseUrl}/file/${service}/${instance}/${fileName}`;
278
300
  }
279
301
 
280
302
  // -----------------------------------------------------------------------------
@@ -282,17 +304,18 @@ function getAbsoluteMediaResourceUrl(service, instance, fileName) {
282
304
  // -----------------------------------------------------------------------------
283
305
 
284
306
  module.exports = {
285
- // Filesystem I/O
286
- resolvePath,
287
- saveFile,
288
- readFile,
289
- readJsonFile,
290
- removeFile,
291
-
292
- // MIME helpers
293
- getImageExtension,
294
- getMimeTypeByFileExtension,
295
-
296
- // URL helpers
297
- getAbsoluteMediaResourceUrl
307
+ // Filesystem I/O
308
+ resolvePath,
309
+ saveFile,
310
+ readFile,
311
+ readJsonFile,
312
+ removeFile,
313
+ unlinkIfExists,
314
+
315
+ // MIME helpers
316
+ getImageExtension,
317
+ getMimeTypeByFileExtension,
318
+
319
+ // URL helpers
320
+ getAbsoluteMediaResourceUrl,
298
321
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "b2b-platform-utils",
3
- "version": "1.1.24",
3
+ "version": "1.1.26",
4
4
  "description": "Shared utilities for Node.js microservices: errors map, local cache, logger, numbers, dates, filesystem, media optimization, paginator, slugger, crypto wrapper, sanitize HTML, sorting.",
5
5
  "type": "commonjs",
6
6
  "license": "KingSizer",