b2b-platform-utils 1.1.6 → 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.
Files changed (2) hide show
  1. package/fileSystem.js +287 -29
  2. 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "b2b-platform-utils",
3
- "version": "1.1.6",
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",