b2b-platform-utils 1.1.5 → 1.1.7

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/fileSystem.js CHANGED
@@ -1,40 +1,298 @@
1
1
  'use strict';
2
2
 
3
- // Purpose: Simple sync file helpers that resolve relative paths from the service root (process.cwd()).
3
+ /**
4
+ * @module fileSystem
5
+ * @description
6
+ * Synchronous file and media-related helpers.
7
+ *
8
+ * Responsibilities:
9
+ * - Safe read/write/remove operations on local filesystem.
10
+ * - Basic MIME / extension resolution.
11
+ * - Utility to build absolute media URLs based on service context.
12
+ *
13
+ * Notes:
14
+ * - All filesystem paths are resolved against process.cwd() unless already absolute.
15
+ * - Synchronous I/O will block the Node.js event loop. This is acceptable for:
16
+ * - service bootstrap
17
+ * - low-frequency admin/debug tasks
18
+ * - cron/workers with low concurrency
19
+ * It is NOT recommended in hot request/response paths.
20
+ */
4
21
 
5
22
  const fs = require('fs');
6
23
  const path = require('path');
24
+ const { warningNote } = require('./logger');
25
+ const { getValue } = require('./localCache');
7
26
 
8
- /** Resolve absolute path; if relative, resolve from process.cwd(). */
27
+ // -----------------------------------------------------------------------------
28
+ // Internal helpers
29
+ // -----------------------------------------------------------------------------
30
+
31
+ /**
32
+ * Resolve an absolute filesystem path. If the provided path is relative,
33
+ * it will be resolved against process.cwd().
34
+ *
35
+ * @param {string} fileName - Absolute or relative path.
36
+ * @returns {string} Absolute path.
37
+ */
9
38
  function resolvePath(fileName) {
10
- return path.isAbsolute(fileName) ? fileName : path.resolve(process.cwd(), fileName);
39
+ return path.isAbsolute(fileName)
40
+ ? fileName
41
+ : path.resolve(process.cwd(), fileName);
11
42
  }
12
43
 
13
- module.exports = {
14
- /**
15
- * Save data as JSON into a file (overwrites if exists).
16
- * Ensures parent directory exists.
17
- */
18
- saveFile: (fileName, data) => {
19
- const p = resolvePath(fileName);
20
- fs.mkdirSync(path.dirname(p), { recursive: true });
21
- fs.writeFileSync(p, JSON.stringify(data));
22
- },
23
-
24
- /**
25
- * Read text content from a file using UTF-8.
26
- * Relative paths are resolved from process.cwd().
27
- */
28
- readFile: (fileName) => {
29
- const p = resolvePath(fileName);
30
- return fs.readFileSync(p, 'utf-8');
31
- },
32
-
33
- /**
34
- * Remove a file from the filesystem.
35
- */
36
- removeFile: (fileName) => {
37
- const p = resolvePath(fileName);
38
- fs.unlinkSync(p);
44
+ /**
45
+ * Ensure the parent directory for a given absolute file path exists.
46
+ *
47
+ * @param {string} absolutePath
48
+ */
49
+ function ensureDirForFile(absolutePath) {
50
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
51
+ }
52
+
53
+ /**
54
+ * Normalize file extension:
55
+ * - strips query string / hash fragments
56
+ * - lowercases the extension
57
+ *
58
+ * @param {string} filename
59
+ * @returns {string} extension without dot, e.g. "png"
60
+ */
61
+ 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();
67
+ }
68
+
69
+ // -----------------------------------------------------------------------------
70
+ // Public API: Filesystem I/O
71
+ // -----------------------------------------------------------------------------
72
+
73
+ /**
74
+ * Save a plain JS value as JSON into a file (overwrites if it already exists).
75
+ * Ensures parent directory exists.
76
+ *
77
+ * @param {string} fileName - Target file path (absolute or relative).
78
+ * @param {any} data - Serializable data.
79
+ * @param {Object} [options]
80
+ * @param {number} [options.spaces=0] - JSON indentation level, e.g. 2 for pretty debug output.
81
+ */
82
+ function saveFile(fileName, data, options = {}) {
83
+ const absPath = resolvePath(fileName);
84
+ ensureDirForFile(absPath);
85
+
86
+ const spaces = Number.isInteger(options.spaces) ? options.spaces : 0;
87
+ const json = JSON.stringify(data, null, spaces);
88
+
89
+ fs.writeFileSync(absPath, json, { encoding: 'utf-8' });
90
+ }
91
+
92
+ /**
93
+ * Read file content as UTF-8 text.
94
+ * This will throw if the file is missing or unreadable.
95
+ *
96
+ * @param {string} fileName
97
+ * @returns {string} File contents.
98
+ */
99
+ function readFile(fileName) {
100
+ const absPath = resolvePath(fileName);
101
+ return fs.readFileSync(absPath, 'utf-8');
102
+ }
103
+
104
+ /**
105
+ * Read JSON file and parse it.
106
+ * Returns null if the file does not exist.
107
+ * Will throw if file exists but content is not valid JSON.
108
+ *
109
+ * @param {string} fileName
110
+ * @returns {any|null}
111
+ */
112
+ function readJsonFile(fileName) {
113
+ const absPath = resolvePath(fileName);
114
+
115
+ if (!fs.existsSync(absPath)) {
116
+ return null;
117
+ }
118
+
119
+ const raw = fs.readFileSync(absPath, 'utf-8');
120
+ return JSON.parse(raw);
121
+ }
122
+
123
+ /**
124
+ * Remove a file from the filesystem if it exists.
125
+ * Safe no-op if file is already missing.
126
+ *
127
+ * @param {string} fileName
128
+ */
129
+ function removeFile(fileName) {
130
+ const absPath = resolvePath(fileName);
131
+
132
+ if (!fs.existsSync(absPath)) {
133
+ return;
134
+ }
135
+
136
+ fs.unlinkSync(absPath);
137
+ }
138
+
139
+ // -----------------------------------------------------------------------------
140
+ // Public API: MIME / extension helpers
141
+ // -----------------------------------------------------------------------------
142
+
143
+ /**
144
+ * Map known MIME types to file extensions.
145
+ * Unknown types return extension = null and also return an error message.
146
+ * A warning is logged for visibility.
147
+ *
148
+ * @param {string} mimetype - e.g. "image/png", "application/pdf".
149
+ * @returns {{ extension: string|null, error: string }}
150
+ */
151
+ 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
+ }
202
+
203
+ return { extension, error };
204
+ }
205
+
206
+ /**
207
+ * Infer MIME type from file extension.
208
+ * If extension is not recognized, returns an empty string.
209
+ *
210
+ * @param {string} filename - Full filename, may contain query/hash.
211
+ * @returns {string} MIME type string or "" if unknown.
212
+ */
213
+ 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 '';
39
250
  }
251
+ }
252
+
253
+ // -----------------------------------------------------------------------------
254
+ // Public API: URL builders
255
+ // -----------------------------------------------------------------------------
256
+
257
+ /**
258
+ * Build absolute URL for static/media resource exposed by the platform.
259
+ * Uses runtime config from localCache.
260
+ *
261
+ * Example:
262
+ * baseApiURL = "https://cdn.example.com"
263
+ * service = "avatar"
264
+ * instance = "user123"
265
+ * fileName = "profile.png"
266
+ *
267
+ * Result:
268
+ * "https://cdn.example.com/file/avatar/user123/profile.png"
269
+ *
270
+ * @param {string} service - Logical service name, e.g. "avatar", "cms", "game".
271
+ * @param {string} instance - Instance or tenant identifier.
272
+ * @param {string} fileName - File name including extension.
273
+ * @returns {string} Absolute URL.
274
+ */
275
+ function getAbsoluteMediaResourceUrl(service, instance, fileName) {
276
+ const baseUrl = getValue('baseApiURL') || '';
277
+ return `${baseUrl}/file/${service}/${instance}/${fileName}`;
278
+ }
279
+
280
+ // -----------------------------------------------------------------------------
281
+ // Exports
282
+ // -----------------------------------------------------------------------------
283
+
284
+ 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
40
298
  };
package/localCache.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * Process-scoped in-memory key-value store for configuration.
7
7
  * - Backward compatible with existing API surface.
8
8
  * - Adds currency helpers with explicit opt-in ENV fallback.
9
+ * - Provides default GEO and Locale helpers for runtime consumers.
9
10
  *
10
11
  * Design goals:
11
12
  * 1) No side effects on import (ENV is read lazily, only if enabled).
@@ -27,6 +28,9 @@ let bufferChunkSize = 512000;
27
28
  /** @type {string} */
28
29
  const GEO_DEFAULT = 'WW';
29
30
 
31
+ /** @type {string} */
32
+ const LOCALE_DEFAULT = 'en-US';
33
+
30
34
  /** @type {boolean} */
31
35
  let envBaseCurrencyFallbackEnabled = false; // opt-in per process
32
36
 
@@ -46,6 +50,7 @@ function ensureCommonBag() {
46
50
  /**
47
51
  * Normalize to a 3-letter uppercase currency code.
48
52
  * Returns null if invalid.
53
+ *
49
54
  * @param {unknown} value
50
55
  * @returns {string|null}
51
56
  */
@@ -59,6 +64,7 @@ function normalizeCurrencyCode(value) {
59
64
 
60
65
  /**
61
66
  * Set a top-level key in the cache.
67
+ *
62
68
  * @param {string} key
63
69
  * @param {any} value
64
70
  */
@@ -68,6 +74,7 @@ function setValue(key, value) {
68
74
 
69
75
  /**
70
76
  * Get a top-level value from the cache.
77
+ *
71
78
  * @param {string} key
72
79
  * @returns {any|null}
73
80
  */
@@ -79,6 +86,7 @@ function getValue(key) {
79
86
 
80
87
  /**
81
88
  * Shallow-merge a config object into the cache.
89
+ *
82
90
  * @param {Record<string, any>} data
83
91
  */
84
92
  function applyConfig(data) {
@@ -91,6 +99,7 @@ function applyConfig(data) {
91
99
 
92
100
  /**
93
101
  * Return a direct reference to the whole cache (mutable).
102
+ *
94
103
  * @returns {Record<string, any>}
95
104
  */
96
105
  function getData() {
@@ -101,6 +110,7 @@ function getData() {
101
110
 
102
111
  /**
103
112
  * Return current microservice key.
113
+ *
104
114
  * @returns {string}
105
115
  */
106
116
  function getService() {
@@ -109,6 +119,7 @@ function getService() {
109
119
 
110
120
  /**
111
121
  * Override microservice key at runtime.
122
+ *
112
123
  * @param {string} key
113
124
  */
114
125
  function setService(key) {
@@ -119,6 +130,7 @@ function setService(key) {
119
130
 
120
131
  /**
121
132
  * Get configured buffer chunk size.
133
+ *
122
134
  * @returns {number}
123
135
  */
124
136
  function getBufferChunkSize() {
@@ -128,6 +140,7 @@ function getBufferChunkSize() {
128
140
  /**
129
141
  * Initialize environment/customer from process params.
130
142
  * Kept for compatibility with existing bootstrap flows.
143
+ *
131
144
  * @param {string[]} params
132
145
  */
133
146
  function parseInitConfig(params) {
@@ -139,6 +152,7 @@ function parseInitConfig(params) {
139
152
 
140
153
  /**
141
154
  * Return the "common" bag snapshot ({} if missing).
155
+ *
142
156
  * @returns {Record<string, any>}
143
157
  */
144
158
  function getCommonValue() {
@@ -151,16 +165,28 @@ function getCommonValue() {
151
165
 
152
166
  /**
153
167
  * Read GEODefault from "common" or return fallback.
168
+ *
154
169
  * @returns {string}
155
170
  */
156
171
  function getDefaultGEO() {
157
172
  return getValue('common')?.GEODefault || GEO_DEFAULT;
158
173
  }
159
174
 
175
+ /**
176
+ * Read LocaleDefault from "common" or return fallback.
177
+ * Locale codes must follow the pattern: en-US, de-DE, pt-BR etc.
178
+ *
179
+ * @returns {string}
180
+ */
181
+ function getDefaultLocale() {
182
+ return getValue('common')?.LocaleDefault || LOCALE_DEFAULT;
183
+ }
184
+
160
185
  /**
161
186
  * Enable or disable ENV fallback for BaseCurrency.
162
187
  * When enabled and "common.BaseCurrency" is not set,
163
188
  * getBaseCurrency() will use process.env.BASE_CURRENCY.
189
+ *
164
190
  * @param {boolean} enabled
165
191
  */
166
192
  function enableEnvBaseCurrencyFallback(enabled = true) {
@@ -170,6 +196,7 @@ function enableEnvBaseCurrencyFallback(enabled = true) {
170
196
  /**
171
197
  * Get BaseCurrency from "common", otherwise (optionally) from ENV.
172
198
  * Returns null if nothing is set or valid.
199
+ *
173
200
  * @returns {string|null}
174
201
  */
175
202
  function getBaseCurrency() {
@@ -190,6 +217,7 @@ function getBaseCurrency() {
190
217
 
191
218
  /**
192
219
  * Explicitly set BaseCurrency into the "common" bag.
220
+ *
193
221
  * @param {string} code - 3-letter code, e.g., "USD", "EUR", "GBP".
194
222
  * @throws {Error} If code is invalid.
195
223
  */
@@ -222,6 +250,7 @@ module.exports = {
222
250
 
223
251
  // Domain helpers
224
252
  getDefaultGEO,
253
+ getDefaultLocale,
225
254
 
226
255
  // Currency helpers
227
256
  getBaseCurrency,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "b2b-platform-utils",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
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",