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