@controlium/utils 1.0.1 → 1.0.2-alpha.2
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/dist/cjs/detokeniser/detokeniser.js +50 -170
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/logger/logger.js +5 -3
- package/dist/cjs/mock/mock.js +509 -0
- package/dist/esm/detokeniser/detokeniser.js +50 -170
- package/dist/esm/index.js +1 -0
- package/dist/esm/logger/logger.js +5 -3
- package/dist/esm/mock/mock.js +505 -0
- package/dist/types/detokeniser/detokeniser.d.ts +46 -128
- package/dist/types/index.d.ts +1 -0
- package/dist/types/logger/types.d.ts +3 -1
- package/dist/types/mock/mock.d.ts +391 -0
- package/package.json +17 -12
- package/dist/cjs/logger/logger.spec.js +0 -875
- package/dist/esm/logger/logger.spec.js +0 -873
- package/dist/types/logger/logger.spec.d.ts +0 -1
|
@@ -31,9 +31,6 @@ const MockUtils = {
|
|
|
31
31
|
* - **expression** — what to compute (type-specific)
|
|
32
32
|
* - **format** — how to format the result (type-specific, often optional)
|
|
33
33
|
*
|
|
34
|
-
* The delimiter `|` and the `[[` / `]]` endstops are configurable via {@link Detokeniser.delimiter} and
|
|
35
|
-
* {@link Detokeniser.tokenStartEndChars}, but the defaults cover the vast majority of use cases.
|
|
36
|
-
*
|
|
37
34
|
* ---
|
|
38
35
|
* ## Built-in token types
|
|
39
36
|
*
|
|
@@ -138,77 +135,21 @@ const MockUtils = {
|
|
|
138
135
|
*
|
|
139
136
|
* ---
|
|
140
137
|
* ## Extending with callbacks
|
|
141
|
-
* Custom token types are registered via {@link Detokeniser.
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
138
|
+
* Custom token types are registered via {@link Detokeniser.addCallback}. Both sync and async handlers
|
|
139
|
+
* share the same registration method and callback list. Callbacks are tried in registration order;
|
|
140
|
+
* return `undefined` to pass to the next callback. If all callbacks return `undefined` and no built-in
|
|
141
|
+
* handler matched, an error is thrown.
|
|
142
|
+
*
|
|
143
|
+
* Async callbacks are silently skipped when {@link Detokeniser.do} (sync) is used — use
|
|
144
|
+
* {@link Detokeniser.doAsync} if your callback returns a Promise.
|
|
145
145
|
*
|
|
146
|
-
* @see {@link Detokeniser.
|
|
147
|
-
* @see {@link Detokeniser.addCallbackAsync}
|
|
146
|
+
* @see {@link Detokeniser.addCallback}
|
|
148
147
|
* @see {@link Detokeniser.do}
|
|
149
148
|
* @see {@link Detokeniser.doAsync}
|
|
150
149
|
*/
|
|
151
150
|
export class Detokeniser {
|
|
152
|
-
/**
|
|
153
|
-
* Gets the current single-character delimiter used to separate token parts.
|
|
154
|
-
* Default: `|`
|
|
155
|
-
*/
|
|
156
|
-
static get delimiter() {
|
|
157
|
-
return this._delimiter;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Sets the single-character delimiter used to separate token parts.
|
|
161
|
-
* The new value applies to all subsequent {@link Detokeniser.do} / {@link Detokeniser.doAsync} calls.
|
|
162
|
-
* @param newDelimiter - Exactly one character
|
|
163
|
-
* @throws If `newDelimiter` is not exactly one character
|
|
164
|
-
* @example
|
|
165
|
-
* Detokeniser.delimiter = ':';
|
|
166
|
-
* Detokeniser.do('[[random:digits:6]]'); // → e.g. '482910'
|
|
167
|
-
* Detokeniser.reset(); // restore default '|'
|
|
168
|
-
*/
|
|
169
|
-
static set delimiter(newDelimiter) {
|
|
170
|
-
if (newDelimiter.length !== 1) {
|
|
171
|
-
const errTxt = `Invalid delimiter [${newDelimiter}]. Must be exactly 1 character!`;
|
|
172
|
-
Log.writeLine(LogLevels.Error, errTxt);
|
|
173
|
-
throw new Error(errTxt);
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
this._delimiter = newDelimiter;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Sets the start and end token sequences.
|
|
181
|
-
* @param startEndChars - An even-length string whose first half is the start sequence and second half the end sequence.
|
|
182
|
-
* @example Detokeniser.tokenStartEndChars = "[[]]"; // start = "[[", end = "]]"
|
|
183
|
-
* @remarks Start and end sequences must differ. Minimum total length is 2 (one char each).
|
|
184
|
-
*/
|
|
185
|
-
static set tokenStartEndChars(startEndChars) {
|
|
186
|
-
if (startEndChars.length < 2 || startEndChars.length % 2 !== 0) {
|
|
187
|
-
const errTxt = `Invalid start/end chars [${startEndChars}]. Must be an even number of characters (minimum 2) — first half as start sequence, second half as end sequence!`;
|
|
188
|
-
Log.writeLine(LogLevels.Error, errTxt);
|
|
189
|
-
throw new Error(errTxt);
|
|
190
|
-
}
|
|
191
|
-
const half = startEndChars.length / 2;
|
|
192
|
-
const start = startEndChars.substring(0, half);
|
|
193
|
-
const end = startEndChars.substring(half);
|
|
194
|
-
if (start === end) {
|
|
195
|
-
const errTxt = `Invalid start/end chars — start sequence [${start}] must differ from end sequence [${end}]!`;
|
|
196
|
-
Log.writeLine(LogLevels.Error, errTxt);
|
|
197
|
-
throw new Error(errTxt);
|
|
198
|
-
}
|
|
199
|
-
this._startTokenChar = start;
|
|
200
|
-
this._endTokenChar = end;
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Gets the current token start/end sequences concatenated (e.g. `"[[]]"`).
|
|
204
|
-
*/
|
|
205
|
-
static get tokenStartEndChars() {
|
|
206
|
-
return this._startTokenChar + this._endTokenChar;
|
|
207
|
-
}
|
|
208
151
|
/**
|
|
209
152
|
* Resets the Detokeniser to factory defaults:
|
|
210
|
-
* - Token endstops restored to `[[` / `]]`
|
|
211
|
-
* - Delimiter restored to `|`
|
|
212
153
|
* - Escape char restored to `/`
|
|
213
154
|
* - All registered sync and async callbacks cleared
|
|
214
155
|
*
|
|
@@ -217,121 +158,58 @@ export class Detokeniser {
|
|
|
217
158
|
* afterEach(() => Detokeniser.reset());
|
|
218
159
|
*/
|
|
219
160
|
static reset() {
|
|
220
|
-
this._endTokenChar = "]]";
|
|
221
|
-
this._startTokenChar = "[[";
|
|
222
161
|
this.EscapeChar = "/";
|
|
223
|
-
this.
|
|
224
|
-
if (this._syncCallbacks) {
|
|
225
|
-
this._syncCallbacks = [];
|
|
226
|
-
}
|
|
227
|
-
if (this._asyncCallbacks) {
|
|
228
|
-
this._asyncCallbacks = [];
|
|
229
|
-
}
|
|
230
|
-
this._asyncCallbacks = undefined;
|
|
231
|
-
this._syncCallbacks = undefined;
|
|
162
|
+
this._callbacks = undefined;
|
|
232
163
|
}
|
|
233
164
|
/**
|
|
234
|
-
* Registers a
|
|
165
|
+
* Registers a custom token handler. Both sync and async handlers are registered via this method
|
|
166
|
+
* and share the same callback list.
|
|
235
167
|
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
168
|
+
* Callbacks are tried in registration order. The first to return a non-`undefined` value wins.
|
|
169
|
+
* Return `undefined` to pass to the next callback. If all callbacks return `undefined` and no
|
|
170
|
+
* built-in handler matched, an error is thrown.
|
|
239
171
|
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
172
|
+
* Async callbacks (those returning a `Promise`) are silently skipped when {@link Detokeniser.do}
|
|
173
|
+
* is used — register an async callback only if you intend to call {@link Detokeniser.doAsync}.
|
|
174
|
+
*
|
|
175
|
+
* @param callback - `(token: string) => string | undefined | Promise<string | undefined>`
|
|
176
|
+
* - `token` — full token body without `[[` / `]]`, e.g. `"mytype|arg1|arg2"` (delimiter is always `|`)
|
|
243
177
|
*
|
|
244
178
|
* @example
|
|
245
|
-
* //
|
|
246
|
-
* Detokeniser.
|
|
247
|
-
* const [type, name] = token.split(
|
|
248
|
-
* if (type
|
|
179
|
+
* // Sync handler for [[env|VAR_NAME]] tokens
|
|
180
|
+
* Detokeniser.addCallback((token) => {
|
|
181
|
+
* const [type, name] = token.split('|');
|
|
182
|
+
* if (type !== 'env') return undefined;
|
|
249
183
|
* return process.env[name] ?? '';
|
|
250
184
|
* });
|
|
251
185
|
* Detokeniser.do('Path: [[env|HOME]]'); // → 'Path: /home/user'
|
|
252
186
|
*
|
|
253
187
|
* @example
|
|
254
|
-
* //
|
|
255
|
-
* Detokeniser.
|
|
256
|
-
* const [type,
|
|
257
|
-
* if (type
|
|
258
|
-
* return undefined;
|
|
259
|
-
* });
|
|
260
|
-
* Detokeniser.addCallbackSync((delimiter, token) => {
|
|
261
|
-
* const [type, value] = token.split(delimiter);
|
|
262
|
-
* if (type === 'lower') return value.toLowerCase();
|
|
263
|
-
* return undefined;
|
|
264
|
-
* });
|
|
265
|
-
* Detokeniser.do('[[upper|hello]] [[lower|WORLD]]'); // → 'HELLO world'
|
|
266
|
-
*
|
|
267
|
-
* @see {@link Detokeniser.resetSyncCallbacks} to remove all sync callbacks
|
|
268
|
-
* @see {@link Detokeniser.addCallbackAsync} for async token handlers
|
|
269
|
-
*/
|
|
270
|
-
static addCallbackSync(callback) {
|
|
271
|
-
if (!this._syncCallbacks) {
|
|
272
|
-
this._syncCallbacks = new Array();
|
|
273
|
-
}
|
|
274
|
-
this._syncCallbacks.push(callback);
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Registers an asynchronous custom token handler.
|
|
278
|
-
*
|
|
279
|
-
* Works identically to {@link Detokeniser.addCallbackSync} but is invoked by {@link Detokeniser.doAsync}.
|
|
280
|
-
* Async callbacks are tried in registration order; the first to return a non-`undefined` value wins.
|
|
281
|
-
* Return `undefined` to pass to the next callback.
|
|
282
|
-
*
|
|
283
|
-
* @param asyncCallback - `(delimiter: string, token: string) => Promise<string | undefined>`
|
|
284
|
-
* - `delimiter` — current delimiter character (default `|`)
|
|
285
|
-
* - `token` — full token body without `[[` / `]]`, e.g. `"mytype|arg1|arg2"`
|
|
286
|
-
*
|
|
287
|
-
* @example
|
|
288
|
-
* // Handle [[db|table|column|whereClause]] tokens
|
|
289
|
-
* Detokeniser.addCallbackAsync(async (delimiter, token) => {
|
|
290
|
-
* const [type, table, column, where] = token.split(delimiter);
|
|
291
|
-
* if (type.toLowerCase() !== 'db') return undefined;
|
|
188
|
+
* // Async handler for [[db|table|column|where]] tokens
|
|
189
|
+
* Detokeniser.addCallback(async (token) => {
|
|
190
|
+
* const [type, table, column, where] = token.split('|');
|
|
191
|
+
* if (type !== 'db') return undefined;
|
|
292
192
|
* const row = await db.query(`SELECT ${column} FROM ${table} WHERE ${where} LIMIT 1`);
|
|
293
193
|
* return String(row[column]);
|
|
294
194
|
* });
|
|
295
195
|
* const result = await Detokeniser.doAsync('ID: [[db|users|id|active=1]]');
|
|
296
196
|
*
|
|
297
|
-
* @
|
|
298
|
-
*
|
|
299
|
-
* Detokeniser.addCallbackAsync(async (delimiter, token) => {
|
|
300
|
-
* const [type, key] = token.split(delimiter);
|
|
301
|
-
* if (type !== 'cache') return undefined;
|
|
302
|
-
* return await redis.get(key);
|
|
303
|
-
* });
|
|
304
|
-
* // [[random|digits|8]] resolves first, then [[cache|...]] receives the result
|
|
305
|
-
* await Detokeniser.doAsync('Val: [[cache|prefix-[[random|digits|8]]]]');
|
|
306
|
-
*
|
|
307
|
-
* @see {@link Detokeniser.resetAsyncCallbacks} to remove all async callbacks
|
|
308
|
-
* @see {@link Detokeniser.addCallbackSync} for synchronous token handlers
|
|
197
|
+
* @see {@link Detokeniser.resetCallbacks} to remove all registered callbacks
|
|
198
|
+
* @see {@link Detokeniser.doAsync} for async token resolution
|
|
309
199
|
*/
|
|
310
|
-
static
|
|
311
|
-
if (!this.
|
|
312
|
-
this.
|
|
200
|
+
static addCallback(callback) {
|
|
201
|
+
if (!this._callbacks) {
|
|
202
|
+
this._callbacks = new Array();
|
|
313
203
|
}
|
|
314
|
-
this.
|
|
204
|
+
this._callbacks.push(callback);
|
|
315
205
|
}
|
|
316
206
|
/**
|
|
317
|
-
* Removes all registered
|
|
207
|
+
* Removes all registered callbacks. Built-in token handlers are unaffected.
|
|
318
208
|
* Use between tests or scenarios to ensure callback isolation.
|
|
319
|
-
* @see {@link Detokeniser.reset} to also
|
|
209
|
+
* @see {@link Detokeniser.reset} to also restore all defaults
|
|
320
210
|
*/
|
|
321
|
-
static
|
|
322
|
-
|
|
323
|
-
this._syncCallbacks = [];
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Removes all registered async callbacks. Built-in token handlers are unaffected.
|
|
328
|
-
* Use between tests or scenarios to ensure callback isolation.
|
|
329
|
-
* @see {@link Detokeniser.reset} to also clear sync callbacks and restore all defaults
|
|
330
|
-
*/
|
|
331
|
-
static resetAsyncCallbacks() {
|
|
332
|
-
if (this._asyncCallbacks) {
|
|
333
|
-
this._asyncCallbacks = [];
|
|
334
|
-
}
|
|
211
|
+
static resetCallbacks() {
|
|
212
|
+
this._callbacks = undefined;
|
|
335
213
|
}
|
|
336
214
|
/**
|
|
337
215
|
* Synchronously resolves all tokens in the given string and returns the result.
|
|
@@ -418,8 +296,8 @@ export class Detokeniser {
|
|
|
418
296
|
*
|
|
419
297
|
* @example
|
|
420
298
|
* // Async callback for database-driven tokens
|
|
421
|
-
* Detokeniser.addCallbackAsync(async (
|
|
422
|
-
* const [type, key] = token.split(
|
|
299
|
+
* Detokeniser.addCallbackAsync(async (token) => {
|
|
300
|
+
* const [type, key] = token.split('|');
|
|
423
301
|
* if (type !== 'db') return undefined;
|
|
424
302
|
* return await fetchFromDatabase(key);
|
|
425
303
|
* });
|
|
@@ -553,9 +431,12 @@ export class Detokeniser {
|
|
|
553
431
|
//
|
|
554
432
|
try {
|
|
555
433
|
if (Utils.isNullOrUndefined(processedToken)) {
|
|
556
|
-
if (typeof this.
|
|
557
|
-
this.
|
|
558
|
-
|
|
434
|
+
if (typeof this._callbacks != "undefined") {
|
|
435
|
+
this._callbacks.every((callback) => {
|
|
436
|
+
const result = callback(token);
|
|
437
|
+
if (result instanceof Promise)
|
|
438
|
+
return true; // skip async callbacks in sync context
|
|
439
|
+
processedToken = result;
|
|
559
440
|
return Utils.isNullOrUndefined(processedToken);
|
|
560
441
|
});
|
|
561
442
|
}
|
|
@@ -586,9 +467,9 @@ export class Detokeniser {
|
|
|
586
467
|
//
|
|
587
468
|
try {
|
|
588
469
|
if (Utils.isNullOrUndefined(processedToken)) {
|
|
589
|
-
if (typeof this.
|
|
590
|
-
for (
|
|
591
|
-
processedToken = await
|
|
470
|
+
if (typeof this._callbacks != "undefined") {
|
|
471
|
+
for (const callback of this._callbacks) {
|
|
472
|
+
processedToken = await Promise.resolve(callback(token));
|
|
592
473
|
if (!Utils.isNullOrUndefined(processedToken))
|
|
593
474
|
break;
|
|
594
475
|
}
|
|
@@ -834,7 +715,7 @@ export class Detokeniser {
|
|
|
834
715
|
case "addhours":
|
|
835
716
|
returnDate = addHours(date, this.getParsedDateOffset(dateTokenPrep.params, dateTokenPrep.errParseDateOffset)).getTime();
|
|
836
717
|
break;
|
|
837
|
-
case "
|
|
718
|
+
case "addminutes":
|
|
838
719
|
returnDate = addMinutes(date, this.getParsedDateOffset(dateTokenPrep.params, dateTokenPrep.errParseDateOffset)).getTime();
|
|
839
720
|
break;
|
|
840
721
|
case "followingday": {
|
|
@@ -1060,8 +941,7 @@ Detokeniser._endTokenChar = "]]";
|
|
|
1060
941
|
Detokeniser._startTokenChar = "[[";
|
|
1061
942
|
Detokeniser.EscapeChar = "/";
|
|
1062
943
|
Detokeniser._delimiter = "|";
|
|
1063
|
-
Detokeniser.
|
|
1064
|
-
Detokeniser._syncCallbacks = undefined;
|
|
944
|
+
Detokeniser._callbacks = undefined;
|
|
1065
945
|
class InnermostToken {
|
|
1066
946
|
constructor(inputString, StartTokenChar, EndTokenChar, EscapeChar) {
|
|
1067
947
|
let startIndex = -1;
|
package/dist/esm/index.js
CHANGED
|
@@ -485,7 +485,7 @@ export class Logger {
|
|
|
485
485
|
const stackObj = {};
|
|
486
486
|
Error.captureStackTrace(stackObj, this.writeLine);
|
|
487
487
|
const stack = stackObj?.stack ?? "[Unknown]";
|
|
488
|
-
const callingMethodDetails = this.callingMethodDetails(stack);
|
|
488
|
+
const callingMethodDetails = this.callingMethodDetails(stack, options?.stackOffset ?? 0);
|
|
489
489
|
const maxLines = options?.maxLines ?? this.options.writeLine.maxLines;
|
|
490
490
|
const suppressAllPreamble = options?.suppressAllPreamble ??
|
|
491
491
|
this.options.writeLine.suppressAllPreamble;
|
|
@@ -705,7 +705,7 @@ export class Logger {
|
|
|
705
705
|
return this.pad(levelOfWrite, WRITE_TYPE_PAD_WIDTH);
|
|
706
706
|
}
|
|
707
707
|
}
|
|
708
|
-
static callingMethodDetails(methodBase) {
|
|
708
|
+
static callingMethodDetails(methodBase, stackOffset = 0) {
|
|
709
709
|
let methodName = "<Unknown>";
|
|
710
710
|
let typeName = "";
|
|
711
711
|
if (methodBase) {
|
|
@@ -717,7 +717,9 @@ export class Logger {
|
|
|
717
717
|
.findIndex((item) => !item.includes(LOGGER_STACK_FRAME_MARKER));
|
|
718
718
|
indexOfFirstNonLogLine =
|
|
719
719
|
indexOfFirstNonLogLine === -1 ? 1 : indexOfFirstNonLogLine + 1;
|
|
720
|
-
|
|
720
|
+
const safeOffset = Math.max(0, stackOffset);
|
|
721
|
+
const targetIndex = Math.min(indexOfFirstNonLogLine + safeOffset, methodBaseLines.length - 1);
|
|
722
|
+
methodName = methodBaseLines[targetIndex]
|
|
721
723
|
.replace(/\s\s+/g, " ")
|
|
722
724
|
.trim();
|
|
723
725
|
if (methodName.startsWith("at ")) {
|