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 +287 -29
- package/localCache.js +29 -0
- package/package.json +1 -1
package/fileSystem.js
CHANGED
|
@@ -1,40 +1,298 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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)
|
|
39
|
+
return path.isAbsolute(fileName)
|
|
40
|
+
? fileName
|
|
41
|
+
: path.resolve(process.cwd(), fileName);
|
|
11
42
|
}
|
|
12
43
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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.
|
|
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",
|