b2b-platform-utils 1.1.6 → 1.1.8

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/fileSystem.js +287 -29
  2. package/localCache.js +92 -27
  3. package/package.json +1 -1
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
@@ -4,17 +4,24 @@
4
4
  * @module inMemoryConfig
5
5
  * @description
6
6
  * Process-scoped in-memory key-value store for configuration.
7
- * - Backward compatible with existing API surface.
8
- * - Adds currency helpers with explicit opt-in ENV fallback.
9
- * - Provides default GEO and Locale helpers for runtime consumers.
7
+ *
8
+ * Highlights:
9
+ * - Safe in-memory KV for runtime config/state.
10
+ * - "common" bag is the canonical place for cross-service shared config
11
+ * (locale, currency, GEO, etc.).
12
+ * - Optional ENV fallback for BaseCurrency.
13
+ * - Includes service-level tuning constants like bufferChunkSize,
14
+ * defaultLimit, and RPC wait thresholds.
10
15
  *
11
16
  * Design goals:
12
- * 1) No side effects on import (ENV is read lazily, only if enabled).
13
- * 2) "Common" bag is the single source of truth for cross-cutting config.
14
- * 3) Services that do NOT use base currency remain unaffected by ENV.
17
+ * 1) No side effects on import. ENV is only read lazily and only if enabled.
18
+ * 2) Service can identify itself at runtime via setService().
19
+ * 3) Callers can safely read sane defaults (e.g. en-US, USD, WW).
15
20
  */
16
21
 
17
- // ---- Internal state ---------------------------------------------------------
22
+ // -----------------------------------------------------------------------------
23
+ // Internal state
24
+ // -----------------------------------------------------------------------------
18
25
 
19
26
  /** @type {Record<string, any>} */
20
27
  let cacheData = {};
@@ -23,7 +30,13 @@ let cacheData = {};
23
30
  let serviceKey = 'not_set_service';
24
31
 
25
32
  /** @type {number} */
26
- let bufferChunkSize = 512000;
33
+ const bufferChunkSize = 512000;
34
+
35
+ /** @type {number} */
36
+ const defaultLimit = 30;
37
+
38
+ /** @type {number} */
39
+ const RPC_WAIT_FOR_READY = 2000;
27
40
 
28
41
  /** @type {string} */
29
42
  const GEO_DEFAULT = 'WW';
@@ -34,10 +47,17 @@ const LOCALE_DEFAULT = 'en-US';
34
47
  /** @type {boolean} */
35
48
  let envBaseCurrencyFallbackEnabled = false; // opt-in per process
36
49
 
37
- // ---- Utilities --------------------------------------------------------------
50
+ // -----------------------------------------------------------------------------
51
+ // Internal utilities
52
+ // -----------------------------------------------------------------------------
38
53
 
39
54
  /**
40
55
  * Ensure the "common" bag exists and return it by reference.
56
+ * This is the shared namespace for cross-cutting runtime config such as:
57
+ * - GEODefault
58
+ * - LocaleDefault
59
+ * - BaseCurrency
60
+ *
41
61
  * @returns {Record<string, any>}
42
62
  */
43
63
  function ensureCommonBag() {
@@ -49,7 +69,7 @@ function ensureCommonBag() {
49
69
 
50
70
  /**
51
71
  * Normalize to a 3-letter uppercase currency code.
52
- * Returns null if invalid.
72
+ * Returns null if the code is missing or invalid.
53
73
  *
54
74
  * @param {unknown} value
55
75
  * @returns {string|null}
@@ -60,7 +80,9 @@ function normalizeCurrencyCode(value) {
60
80
  return /^[A-Z]{3}$/.test(code) ? code : null;
61
81
  }
62
82
 
63
- // ---- Public API: KV ---------------------------------------------------------
83
+ // -----------------------------------------------------------------------------
84
+ // Public API: KV
85
+ // -----------------------------------------------------------------------------
64
86
 
65
87
  /**
66
88
  * Set a top-level key in the cache.
@@ -74,6 +96,7 @@ function setValue(key, value) {
74
96
 
75
97
  /**
76
98
  * Get a top-level value from the cache.
99
+ * Returns null if the key is not present.
77
100
  *
78
101
  * @param {string} key
79
102
  * @returns {any|null}
@@ -86,6 +109,7 @@ function getValue(key) {
86
109
 
87
110
  /**
88
111
  * Shallow-merge a config object into the cache.
112
+ * Keys in `data` override current values.
89
113
  *
90
114
  * @param {Record<string, any>} data
91
115
  */
@@ -99,6 +123,7 @@ function applyConfig(data) {
99
123
 
100
124
  /**
101
125
  * Return a direct reference to the whole cache (mutable).
126
+ * This is useful for diagnostics, admin endpoints, etc.
102
127
  *
103
128
  * @returns {Record<string, any>}
104
129
  */
@@ -106,10 +131,13 @@ function getData() {
106
131
  return cacheData;
107
132
  }
108
133
 
109
- // ---- Public API: service/meta ----------------------------------------------
134
+ // -----------------------------------------------------------------------------
135
+ // Public API: service/meta
136
+ // -----------------------------------------------------------------------------
110
137
 
111
138
  /**
112
- * Return current microservice key.
139
+ * Return the current microservice key.
140
+ * Example: "gateway", "cms", "analytics".
113
141
  *
114
142
  * @returns {string}
115
143
  */
@@ -118,7 +146,8 @@ function getService() {
118
146
  }
119
147
 
120
148
  /**
121
- * Override microservice key at runtime.
149
+ * Set the current microservice key at runtime.
150
+ * Should typically be called once during bootstrap of each service.
122
151
  *
123
152
  * @param {string} key
124
153
  */
@@ -130,6 +159,7 @@ function setService(key) {
130
159
 
131
160
  /**
132
161
  * Get configured buffer chunk size.
162
+ * Used in streaming / chunked payload logic.
133
163
  *
134
164
  * @returns {number}
135
165
  */
@@ -138,8 +168,28 @@ function getBufferChunkSize() {
138
168
  }
139
169
 
140
170
  /**
141
- * Initialize environment/customer from process params.
142
- * Kept for compatibility with existing bootstrap flows.
171
+ * Get default "limit" value for list/pagination endpoints.
172
+ *
173
+ * @returns {number}
174
+ */
175
+ function getDefaultLimit() {
176
+ return defaultLimit;
177
+ }
178
+
179
+ /**
180
+ * Get default gRPC "wait for ready" timeout in milliseconds.
181
+ * Used by clients before marking remote service as unavailable.
182
+ *
183
+ * @returns {number}
184
+ */
185
+ function getRPCWaitForReady() {
186
+ return RPC_WAIT_FOR_READY;
187
+ }
188
+
189
+ /**
190
+ * Initialize environment/customer from process startup params.
191
+ * Kept for compatibility with legacy bootstrap flows where
192
+ * process.argv-like arrays are passed in.
143
193
  *
144
194
  * @param {string[]} params
145
195
  */
@@ -151,7 +201,8 @@ function parseInitConfig(params) {
151
201
  }
152
202
 
153
203
  /**
154
- * Return the "common" bag snapshot ({} if missing).
204
+ * Return a plain snapshot of the "common" bag,
205
+ * or {} if it is not yet defined.
155
206
  *
156
207
  * @returns {Record<string, any>}
157
208
  */
@@ -161,10 +212,13 @@ function getCommonValue() {
161
212
  : {};
162
213
  }
163
214
 
164
- // ---- Public API: domain helpers --------------------------------------------
215
+ // -----------------------------------------------------------------------------
216
+ // Public API: domain helpers (locale / geo / currency)
217
+ // -----------------------------------------------------------------------------
165
218
 
166
219
  /**
167
220
  * Read GEODefault from "common" or return fallback.
221
+ * GEO codes usually look like "WW", "EU", "US", etc.
168
222
  *
169
223
  * @returns {string}
170
224
  */
@@ -174,7 +228,7 @@ function getDefaultGEO() {
174
228
 
175
229
  /**
176
230
  * Read LocaleDefault from "common" or return fallback.
177
- * Locale codes must follow the pattern: en-US, de-DE, pt-BR etc.
231
+ * Locale codes must follow the pattern en-US, de-DE, pt-BR, etc.
178
232
  *
179
233
  * @returns {string}
180
234
  */
@@ -184,8 +238,12 @@ function getDefaultLocale() {
184
238
 
185
239
  /**
186
240
  * Enable or disable ENV fallback for BaseCurrency.
187
- * When enabled and "common.BaseCurrency" is not set,
188
- * getBaseCurrency() will use process.env.BASE_CURRENCY.
241
+ * When enabled AND "common.BaseCurrency" is not set,
242
+ * getBaseCurrency() will try process.env.BASE_CURRENCY.
243
+ *
244
+ * This allows a service to say:
245
+ * - "I understand base currency matters to me"
246
+ * - "If config is missing, read from ENV"
189
247
  *
190
248
  * @param {boolean} enabled
191
249
  */
@@ -194,8 +252,10 @@ function enableEnvBaseCurrencyFallback(enabled = true) {
194
252
  }
195
253
 
196
254
  /**
197
- * Get BaseCurrency from "common", otherwise (optionally) from ENV.
198
- * Returns null if nothing is set or valid.
255
+ * Get BaseCurrency from "common", or (optionally) from ENV.
256
+ * Returns null if nothing valid is provided.
257
+ *
258
+ * Expected values: "USD", "EUR", "GBP", "JPY", etc.
199
259
  *
200
260
  * @returns {string|null}
201
261
  */
@@ -216,9 +276,10 @@ function getBaseCurrency() {
216
276
  }
217
277
 
218
278
  /**
219
- * Explicitly set BaseCurrency into the "common" bag.
279
+ * Set BaseCurrency into the "common" bag explicitly.
280
+ * Will throw if code is not a valid 3-letter currency code.
220
281
  *
221
- * @param {string} code - 3-letter code, e.g., "USD", "EUR", "GBP".
282
+ * @param {string} code - 3-letter code, e.g. "USD", "EUR", "GBP".
222
283
  * @throws {Error} If code is invalid.
223
284
  */
224
285
  function setBaseCurrency(code) {
@@ -232,10 +293,12 @@ function setBaseCurrency(code) {
232
293
  common.BaseCurrency = normalized;
233
294
  }
234
295
 
235
- // ---- Exports ---------------------------------------------------------------
296
+ // -----------------------------------------------------------------------------
297
+ // Exports
298
+ // -----------------------------------------------------------------------------
236
299
 
237
300
  module.exports = {
238
- // KV
301
+ // KV helpers
239
302
  setValue,
240
303
  getValue,
241
304
  applyConfig,
@@ -245,6 +308,8 @@ module.exports = {
245
308
  getService,
246
309
  setService,
247
310
  getBufferChunkSize,
311
+ getDefaultLimit,
312
+ getRPCWaitForReady,
248
313
  parseInitConfig,
249
314
  getCommonValue,
250
315
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "b2b-platform-utils",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
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",