lifecycleion 0.0.5 → 0.0.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Lifecycleion v0.0.5
1
+ # Lifecycleion v0.0.7
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/lifecycleion.svg)](https://badge.fury.io/js/lifecycleion)
4
4
 
@@ -25,16 +25,52 @@ __export(curly_brackets_exports, {
25
25
  module.exports = __toCommonJS(curly_brackets_exports);
26
26
 
27
27
  // src/lib/internal/path-utils.ts
28
- var PATH_SEGMENT_PATTERN = /\w+|\[(\d+)\]/g;
28
+ var PATH_SEGMENT_PATTERN = /(\w+)|\[(\d+)\]|\["((?:[^"\\]|\\.)*)"\]|\['((?:[^'\\]|\\.)*)'\]/y;
29
+ function unescapeQuotedPathPart(value) {
30
+ return value.replace(/\\(["'\\])/g, "$1");
31
+ }
29
32
  function getPathParts(path) {
30
- return Array.from(
31
- path.matchAll(PATH_SEGMENT_PATTERN),
32
- (match) => match[1] ?? match[0]
33
- );
33
+ const parts = [];
34
+ let index = 0;
35
+ while (index < path.length) {
36
+ if (path[index] === ".") {
37
+ index++;
38
+ if (index >= path.length) {
39
+ return null;
40
+ }
41
+ }
42
+ PATH_SEGMENT_PATTERN.lastIndex = index;
43
+ const match = PATH_SEGMENT_PATTERN.exec(path);
44
+ if (!match) {
45
+ return null;
46
+ }
47
+ if (match[1] !== void 0) {
48
+ parts.push(match[1]);
49
+ } else if (match[2] !== void 0) {
50
+ parts.push(match[2]);
51
+ } else if (match[3] !== void 0) {
52
+ parts.push(unescapeQuotedPathPart(match[3]));
53
+ } else if (match[4] !== void 0) {
54
+ parts.push(unescapeQuotedPathPart(match[4]));
55
+ }
56
+ index = PATH_SEGMENT_PATTERN.lastIndex;
57
+ if (index < path.length && path[index] !== "." && path[index] !== "[") {
58
+ return null;
59
+ }
60
+ }
61
+ return parts;
62
+ }
63
+
64
+ // src/lib/internal/stringify-template-value.ts
65
+ function stringifyTemplateValue(value) {
66
+ if (typeof value === "string") {
67
+ return value;
68
+ }
69
+ return String(value);
34
70
  }
35
71
 
36
72
  // src/lib/curly-brackets.ts
37
- var PLACEHOLDER_PATTERN = /(?:\\)?{{(\s*\w+(?:\[\d+\])*(?:\.\w+(?:\[\d+\])*)*\s*)(?:\\)?\s*}}/g;
73
+ var PLACEHOLDER_PATTERN = /(?:\\)?{{(\s*[^{}]+?\s*)(?:\\)?\s*}}/g;
38
74
  var CurlyBrackets = function(str = "", locals = {}, fallback = "(null)") {
39
75
  if (!str.includes("{{")) {
40
76
  return str;
@@ -62,6 +98,9 @@ CurlyBrackets.compileTemplate = function(str, fallback = "(null)") {
62
98
  }
63
99
  const key = p1.trim();
64
100
  const parts = getPathParts(key);
101
+ if (!parts || parts.length === 0) {
102
+ return match;
103
+ }
65
104
  let replacement = locals;
66
105
  for (const part of parts) {
67
106
  if (replacement !== void 0 && replacement !== null && typeof replacement === "object" && part in replacement) {
@@ -74,7 +113,7 @@ CurlyBrackets.compileTemplate = function(str, fallback = "(null)") {
74
113
  if (replacement === void 0 || replacement === null) {
75
114
  return fallback;
76
115
  }
77
- return String(replacement);
116
+ return stringifyTemplateValue(replacement);
78
117
  });
79
118
  };
80
119
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/curly-brackets.ts","../../src/lib/internal/path-utils.ts"],"sourcesContent":["import { getPathParts } from './internal/path-utils';\n\nexport type TemplateFunction = (locals: Record<string, unknown>) => string;\n\ninterface CurlyBracketsFunction {\n (str?: string, locals?: Record<string, unknown>, fallback?: string): string;\n compileTemplate: (str: string, fallback?: string) => TemplateFunction;\n escape: (str: string) => string;\n}\n\nconst PLACEHOLDER_PATTERN =\n /(?:\\\\)?{{(\\s*\\w+(?:\\[\\d+\\])*(?:\\.\\w+(?:\\[\\d+\\])*)*\\s*)(?:\\\\)?\\s*}}/g;\n\n/**\n * Processes a template string, replacing placeholders with corresponding values from a provided object.\n *\n * @param {string} str - The template string to process.\n * @param locals - An object containing key-value pairs for placeholder replacement.\n * @param fallback - A default string to use when a placeholder's corresponding value is not found.\n * @returns - The processed string with placeholders replaced by their corresponding values.\n */\n\nconst CurlyBrackets: CurlyBracketsFunction = function (\n str: string = '',\n locals: Record<string, unknown> = {},\n fallback: string = '(null)',\n): string {\n // Short-circuit if no brackets - no need to process\n if (!str.includes('{{')) {\n return str;\n }\n\n const compiled = CurlyBrackets.compileTemplate(str, fallback);\n\n return compiled(locals);\n} as CurlyBracketsFunction;\n\n/**\n * Compiles a template string into a reusable function, which can be called with different sets of locals.\n * This is more efficient when you have a template that you want to use with different sets of locals,\n * as it avoids the overhead of parsing the template string each time it is used.\n *\n * @param {string} str - The template string to compile.\n * @param {string} fallback - A default string to use when a placeholder's corresponding value is not found in locals.\n * @returns A function that takes an object of locals and returns a processed string.\n */\n\nCurlyBrackets.compileTemplate = function (\n str: string,\n fallback: string = '(null)',\n): TemplateFunction {\n return (locals: Record<string, unknown>): string => {\n return str.replace(PLACEHOLDER_PATTERN, (match, p1: string) => {\n if (typeof p1 !== 'string') {\n return match;\n }\n\n const hasLeadingEscape = match.startsWith('\\\\');\n const hasEndingEscape = match.endsWith('\\\\}}');\n const isFullyEscaped = hasLeadingEscape && hasEndingEscape;\n\n if (isFullyEscaped) {\n return match.slice(1, -3) + '}}';\n }\n\n if (hasLeadingEscape) {\n return match.slice(1);\n }\n\n if (hasEndingEscape) {\n return '{{' + p1.trim() + '}}';\n }\n\n const key = p1.trim();\n const parts = getPathParts(key);\n\n // Use a more specific approach to ensure the type is consistent\n let replacement: unknown = locals;\n\n for (const part of parts) {\n if (\n replacement !== undefined &&\n replacement !== null &&\n typeof replacement === 'object' &&\n part in replacement\n ) {\n replacement = (replacement as Record<string, unknown>)[part];\n } else {\n replacement = undefined;\n break;\n }\n }\n\n if (replacement === undefined || replacement === null) {\n return fallback;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-base-to-string\n return String(replacement);\n });\n };\n};\n\n/**\n * Escapes placeholders in a string by prefixing them with a backslash, preventing them from being replaced when processed.\n *\n * @param {string} str - The string in which to escape placeholders.\n * @returns {string} - The string with placeholders escaped.\n */\n\nCurlyBrackets.escape = function (str: string): string {\n // Use a regex to replace instances of {{ and }} that are not already preceded by a backslash\n return str\n .replace(/(\\\\)?{{/g, (match, backslash) => (backslash ? match : '\\\\{{'))\n .replace(/(\\\\)?}}/g, (match, backslash) => (backslash ? match : '\\\\}}'));\n};\n\nexport { CurlyBrackets };\n","const PATH_SEGMENT_PATTERN = /\\w+|\\[(\\d+)\\]/g;\n\n/**\n * Parses a mixed object/array path such as \"user.roles[0].name\" into lookup parts.\n */\nexport function getPathParts(path: string): string[] {\n return Array.from(\n path.matchAll(PATH_SEGMENT_PATTERN),\n (match) => match[1] ?? match[0],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAM,uBAAuB;AAKtB,SAAS,aAAa,MAAwB;AACnD,SAAO,MAAM;AAAA,IACX,KAAK,SAAS,oBAAoB;AAAA,IAClC,CAAC,UAAU,MAAM,CAAC,KAAK,MAAM,CAAC;AAAA,EAChC;AACF;;;ADAA,IAAM,sBACJ;AAWF,IAAM,gBAAuC,SAC3C,MAAc,IACd,SAAkC,CAAC,GACnC,WAAmB,UACX;AAER,MAAI,CAAC,IAAI,SAAS,IAAI,GAAG;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,cAAc,gBAAgB,KAAK,QAAQ;AAE5D,SAAO,SAAS,MAAM;AACxB;AAYA,cAAc,kBAAkB,SAC9B,KACA,WAAmB,UACD;AAClB,SAAO,CAAC,WAA4C;AAClD,WAAO,IAAI,QAAQ,qBAAqB,CAAC,OAAO,OAAe;AAC7D,UAAI,OAAO,OAAO,UAAU;AAC1B,eAAO;AAAA,MACT;AAEA,YAAM,mBAAmB,MAAM,WAAW,IAAI;AAC9C,YAAM,kBAAkB,MAAM,SAAS,MAAM;AAC7C,YAAM,iBAAiB,oBAAoB;AAE3C,UAAI,gBAAgB;AAClB,eAAO,MAAM,MAAM,GAAG,EAAE,IAAI;AAAA,MAC9B;AAEA,UAAI,kBAAkB;AACpB,eAAO,MAAM,MAAM,CAAC;AAAA,MACtB;AAEA,UAAI,iBAAiB;AACnB,eAAO,OAAO,GAAG,KAAK,IAAI;AAAA,MAC5B;AAEA,YAAM,MAAM,GAAG,KAAK;AACpB,YAAM,QAAQ,aAAa,GAAG;AAG9B,UAAI,cAAuB;AAE3B,iBAAW,QAAQ,OAAO;AACxB,YACE,gBAAgB,UAChB,gBAAgB,QAChB,OAAO,gBAAgB,YACvB,QAAQ,aACR;AACA,wBAAe,YAAwC,IAAI;AAAA,QAC7D,OAAO;AACL,wBAAc;AACd;AAAA,QACF;AAAA,MACF;AAEA,UAAI,gBAAgB,UAAa,gBAAgB,MAAM;AACrD,eAAO;AAAA,MACT;AAGA,aAAO,OAAO,WAAW;AAAA,IAC3B,CAAC;AAAA,EACH;AACF;AASA,cAAc,SAAS,SAAU,KAAqB;AAEpD,SAAO,IACJ,QAAQ,YAAY,CAAC,OAAO,cAAe,YAAY,QAAQ,MAAO,EACtE,QAAQ,YAAY,CAAC,OAAO,cAAe,YAAY,QAAQ,MAAO;AAC3E;","names":[]}
1
+ {"version":3,"sources":["../../src/lib/curly-brackets.ts","../../src/lib/internal/path-utils.ts","../../src/lib/internal/stringify-template-value.ts"],"sourcesContent":["import { getPathParts } from './internal/path-utils';\nimport { stringifyTemplateValue } from './internal/stringify-template-value';\n\nexport type TemplateFunction = (locals: Record<string, unknown>) => string;\n\ninterface CurlyBracketsFunction {\n (str?: string, locals?: Record<string, unknown>, fallback?: string): string;\n compileTemplate: (str: string, fallback?: string) => TemplateFunction;\n escape: (str: string) => string;\n}\n\nconst PLACEHOLDER_PATTERN = /(?:\\\\)?{{(\\s*[^{}]+?\\s*)(?:\\\\)?\\s*}}/g;\n\n/**\n * Processes a template string, replacing placeholders with corresponding values from a provided object.\n *\n * @param {string} str - The template string to process.\n * @param locals - An object containing key-value pairs for placeholder replacement.\n * @param fallback - A default string to use when a placeholder's corresponding value is not found.\n * @returns - The processed string with placeholders replaced by their corresponding values.\n */\n\nconst CurlyBrackets: CurlyBracketsFunction = function (\n str: string = '',\n locals: Record<string, unknown> = {},\n fallback: string = '(null)',\n): string {\n // Short-circuit if no brackets - no need to process\n if (!str.includes('{{')) {\n return str;\n }\n\n const compiled = CurlyBrackets.compileTemplate(str, fallback);\n\n return compiled(locals);\n} as CurlyBracketsFunction;\n\n/**\n * Compiles a template string into a reusable function, which can be called with different sets of locals.\n * This is more efficient when you have a template that you want to use with different sets of locals,\n * as it avoids the overhead of parsing the template string each time it is used.\n *\n * @param {string} str - The template string to compile.\n * @param {string} fallback - A default string to use when a placeholder's corresponding value is not found in locals.\n * @returns A function that takes an object of locals and returns a processed string.\n */\n\nCurlyBrackets.compileTemplate = function (\n str: string,\n fallback: string = '(null)',\n): TemplateFunction {\n return (locals: Record<string, unknown>): string => {\n return str.replace(PLACEHOLDER_PATTERN, (match, p1: string) => {\n if (typeof p1 !== 'string') {\n return match;\n }\n\n const hasLeadingEscape = match.startsWith('\\\\');\n const hasEndingEscape = match.endsWith('\\\\}}');\n const isFullyEscaped = hasLeadingEscape && hasEndingEscape;\n\n if (isFullyEscaped) {\n return match.slice(1, -3) + '}}';\n }\n\n if (hasLeadingEscape) {\n return match.slice(1);\n }\n\n if (hasEndingEscape) {\n return '{{' + p1.trim() + '}}';\n }\n\n const key = p1.trim();\n const parts = getPathParts(key);\n\n if (!parts || parts.length === 0) {\n return match;\n }\n\n // Use a more specific approach to ensure the type is consistent\n let replacement: unknown = locals;\n\n for (const part of parts) {\n if (\n replacement !== undefined &&\n replacement !== null &&\n typeof replacement === 'object' &&\n part in replacement\n ) {\n replacement = (replacement as Record<string, unknown>)[part];\n } else {\n replacement = undefined;\n break;\n }\n }\n\n if (replacement === undefined || replacement === null) {\n return fallback;\n }\n\n return stringifyTemplateValue(replacement);\n });\n };\n};\n\n/**\n * Escapes placeholders in a string by prefixing them with a backslash, preventing them from being replaced when processed.\n *\n * @param {string} str - The string in which to escape placeholders.\n * @returns {string} - The string with placeholders escaped.\n */\n\nCurlyBrackets.escape = function (str: string): string {\n // Use a regex to replace instances of {{ and }} that are not already preceded by a backslash\n return str\n .replace(/(\\\\)?{{/g, (match, backslash) => (backslash ? match : '\\\\{{'))\n .replace(/(\\\\)?}}/g, (match, backslash) => (backslash ? match : '\\\\}}'));\n};\n\nexport { CurlyBrackets };\n","const PATH_SEGMENT_PATTERN =\n /(\\w+)|\\[(\\d+)\\]|\\[\"((?:[^\"\\\\]|\\\\.)*)\"\\]|\\['((?:[^'\\\\]|\\\\.)*)'\\]/y;\n\nfunction unescapeQuotedPathPart(value: string): string {\n return value.replace(/\\\\([\"'\\\\])/g, '$1');\n}\n\n/**\n * Parses a mixed object/array path such as \"user.roles[0].name\" into lookup parts.\n */\nexport function getPathParts(path: string): string[] | null {\n const parts: string[] = [];\n let index = 0;\n\n while (index < path.length) {\n if (path[index] === '.') {\n index++;\n\n if (index >= path.length) {\n return null;\n }\n }\n\n PATH_SEGMENT_PATTERN.lastIndex = index;\n const match = PATH_SEGMENT_PATTERN.exec(path);\n\n if (!match) {\n return null;\n }\n\n if (match[1] !== undefined) {\n parts.push(match[1]);\n } else if (match[2] !== undefined) {\n parts.push(match[2]);\n } else if (match[3] !== undefined) {\n parts.push(unescapeQuotedPathPart(match[3]));\n } else if (match[4] !== undefined) {\n parts.push(unescapeQuotedPathPart(match[4]));\n }\n\n index = PATH_SEGMENT_PATTERN.lastIndex;\n\n if (index < path.length && path[index] !== '.' && path[index] !== '[') {\n return null;\n }\n }\n\n return parts;\n}\n","/**\n * Normalizes values to the same string representation used by template rendering.\n */\nexport function stringifyTemplateValue(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n\n return String(value);\n}\n"],"mappings":"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","names":[]}
@@ -1,14 +1,50 @@
1
1
  // src/lib/internal/path-utils.ts
2
- var PATH_SEGMENT_PATTERN = /\w+|\[(\d+)\]/g;
2
+ var PATH_SEGMENT_PATTERN = /(\w+)|\[(\d+)\]|\["((?:[^"\\]|\\.)*)"\]|\['((?:[^'\\]|\\.)*)'\]/y;
3
+ function unescapeQuotedPathPart(value) {
4
+ return value.replace(/\\(["'\\])/g, "$1");
5
+ }
3
6
  function getPathParts(path) {
4
- return Array.from(
5
- path.matchAll(PATH_SEGMENT_PATTERN),
6
- (match) => match[1] ?? match[0]
7
- );
7
+ const parts = [];
8
+ let index = 0;
9
+ while (index < path.length) {
10
+ if (path[index] === ".") {
11
+ index++;
12
+ if (index >= path.length) {
13
+ return null;
14
+ }
15
+ }
16
+ PATH_SEGMENT_PATTERN.lastIndex = index;
17
+ const match = PATH_SEGMENT_PATTERN.exec(path);
18
+ if (!match) {
19
+ return null;
20
+ }
21
+ if (match[1] !== void 0) {
22
+ parts.push(match[1]);
23
+ } else if (match[2] !== void 0) {
24
+ parts.push(match[2]);
25
+ } else if (match[3] !== void 0) {
26
+ parts.push(unescapeQuotedPathPart(match[3]));
27
+ } else if (match[4] !== void 0) {
28
+ parts.push(unescapeQuotedPathPart(match[4]));
29
+ }
30
+ index = PATH_SEGMENT_PATTERN.lastIndex;
31
+ if (index < path.length && path[index] !== "." && path[index] !== "[") {
32
+ return null;
33
+ }
34
+ }
35
+ return parts;
36
+ }
37
+
38
+ // src/lib/internal/stringify-template-value.ts
39
+ function stringifyTemplateValue(value) {
40
+ if (typeof value === "string") {
41
+ return value;
42
+ }
43
+ return String(value);
8
44
  }
9
45
 
10
46
  // src/lib/curly-brackets.ts
11
- var PLACEHOLDER_PATTERN = /(?:\\)?{{(\s*\w+(?:\[\d+\])*(?:\.\w+(?:\[\d+\])*)*\s*)(?:\\)?\s*}}/g;
47
+ var PLACEHOLDER_PATTERN = /(?:\\)?{{(\s*[^{}]+?\s*)(?:\\)?\s*}}/g;
12
48
  var CurlyBrackets = function(str = "", locals = {}, fallback = "(null)") {
13
49
  if (!str.includes("{{")) {
14
50
  return str;
@@ -36,6 +72,9 @@ CurlyBrackets.compileTemplate = function(str, fallback = "(null)") {
36
72
  }
37
73
  const key = p1.trim();
38
74
  const parts = getPathParts(key);
75
+ if (!parts || parts.length === 0) {
76
+ return match;
77
+ }
39
78
  let replacement = locals;
40
79
  for (const part of parts) {
41
80
  if (replacement !== void 0 && replacement !== null && typeof replacement === "object" && part in replacement) {
@@ -48,7 +87,7 @@ CurlyBrackets.compileTemplate = function(str, fallback = "(null)") {
48
87
  if (replacement === void 0 || replacement === null) {
49
88
  return fallback;
50
89
  }
51
- return String(replacement);
90
+ return stringifyTemplateValue(replacement);
52
91
  });
53
92
  };
54
93
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/internal/path-utils.ts","../../src/lib/curly-brackets.ts"],"sourcesContent":["const PATH_SEGMENT_PATTERN = /\\w+|\\[(\\d+)\\]/g;\n\n/**\n * Parses a mixed object/array path such as \"user.roles[0].name\" into lookup parts.\n */\nexport function getPathParts(path: string): string[] {\n return Array.from(\n path.matchAll(PATH_SEGMENT_PATTERN),\n (match) => match[1] ?? match[0],\n );\n}\n","import { getPathParts } from './internal/path-utils';\n\nexport type TemplateFunction = (locals: Record<string, unknown>) => string;\n\ninterface CurlyBracketsFunction {\n (str?: string, locals?: Record<string, unknown>, fallback?: string): string;\n compileTemplate: (str: string, fallback?: string) => TemplateFunction;\n escape: (str: string) => string;\n}\n\nconst PLACEHOLDER_PATTERN =\n /(?:\\\\)?{{(\\s*\\w+(?:\\[\\d+\\])*(?:\\.\\w+(?:\\[\\d+\\])*)*\\s*)(?:\\\\)?\\s*}}/g;\n\n/**\n * Processes a template string, replacing placeholders with corresponding values from a provided object.\n *\n * @param {string} str - The template string to process.\n * @param locals - An object containing key-value pairs for placeholder replacement.\n * @param fallback - A default string to use when a placeholder's corresponding value is not found.\n * @returns - The processed string with placeholders replaced by their corresponding values.\n */\n\nconst CurlyBrackets: CurlyBracketsFunction = function (\n str: string = '',\n locals: Record<string, unknown> = {},\n fallback: string = '(null)',\n): string {\n // Short-circuit if no brackets - no need to process\n if (!str.includes('{{')) {\n return str;\n }\n\n const compiled = CurlyBrackets.compileTemplate(str, fallback);\n\n return compiled(locals);\n} as CurlyBracketsFunction;\n\n/**\n * Compiles a template string into a reusable function, which can be called with different sets of locals.\n * This is more efficient when you have a template that you want to use with different sets of locals,\n * as it avoids the overhead of parsing the template string each time it is used.\n *\n * @param {string} str - The template string to compile.\n * @param {string} fallback - A default string to use when a placeholder's corresponding value is not found in locals.\n * @returns A function that takes an object of locals and returns a processed string.\n */\n\nCurlyBrackets.compileTemplate = function (\n str: string,\n fallback: string = '(null)',\n): TemplateFunction {\n return (locals: Record<string, unknown>): string => {\n return str.replace(PLACEHOLDER_PATTERN, (match, p1: string) => {\n if (typeof p1 !== 'string') {\n return match;\n }\n\n const hasLeadingEscape = match.startsWith('\\\\');\n const hasEndingEscape = match.endsWith('\\\\}}');\n const isFullyEscaped = hasLeadingEscape && hasEndingEscape;\n\n if (isFullyEscaped) {\n return match.slice(1, -3) + '}}';\n }\n\n if (hasLeadingEscape) {\n return match.slice(1);\n }\n\n if (hasEndingEscape) {\n return '{{' + p1.trim() + '}}';\n }\n\n const key = p1.trim();\n const parts = getPathParts(key);\n\n // Use a more specific approach to ensure the type is consistent\n let replacement: unknown = locals;\n\n for (const part of parts) {\n if (\n replacement !== undefined &&\n replacement !== null &&\n typeof replacement === 'object' &&\n part in replacement\n ) {\n replacement = (replacement as Record<string, unknown>)[part];\n } else {\n replacement = undefined;\n break;\n }\n }\n\n if (replacement === undefined || replacement === null) {\n return fallback;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-base-to-string\n return String(replacement);\n });\n };\n};\n\n/**\n * Escapes placeholders in a string by prefixing them with a backslash, preventing them from being replaced when processed.\n *\n * @param {string} str - The string in which to escape placeholders.\n * @returns {string} - The string with placeholders escaped.\n */\n\nCurlyBrackets.escape = function (str: string): string {\n // Use a regex to replace instances of {{ and }} that are not already preceded by a backslash\n return str\n .replace(/(\\\\)?{{/g, (match, backslash) => (backslash ? match : '\\\\{{'))\n .replace(/(\\\\)?}}/g, (match, backslash) => (backslash ? match : '\\\\}}'));\n};\n\nexport { CurlyBrackets };\n"],"mappings":";AAAA,IAAM,uBAAuB;AAKtB,SAAS,aAAa,MAAwB;AACnD,SAAO,MAAM;AAAA,IACX,KAAK,SAAS,oBAAoB;AAAA,IAClC,CAAC,UAAU,MAAM,CAAC,KAAK,MAAM,CAAC;AAAA,EAChC;AACF;;;ACAA,IAAM,sBACJ;AAWF,IAAM,gBAAuC,SAC3C,MAAc,IACd,SAAkC,CAAC,GACnC,WAAmB,UACX;AAER,MAAI,CAAC,IAAI,SAAS,IAAI,GAAG;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,cAAc,gBAAgB,KAAK,QAAQ;AAE5D,SAAO,SAAS,MAAM;AACxB;AAYA,cAAc,kBAAkB,SAC9B,KACA,WAAmB,UACD;AAClB,SAAO,CAAC,WAA4C;AAClD,WAAO,IAAI,QAAQ,qBAAqB,CAAC,OAAO,OAAe;AAC7D,UAAI,OAAO,OAAO,UAAU;AAC1B,eAAO;AAAA,MACT;AAEA,YAAM,mBAAmB,MAAM,WAAW,IAAI;AAC9C,YAAM,kBAAkB,MAAM,SAAS,MAAM;AAC7C,YAAM,iBAAiB,oBAAoB;AAE3C,UAAI,gBAAgB;AAClB,eAAO,MAAM,MAAM,GAAG,EAAE,IAAI;AAAA,MAC9B;AAEA,UAAI,kBAAkB;AACpB,eAAO,MAAM,MAAM,CAAC;AAAA,MACtB;AAEA,UAAI,iBAAiB;AACnB,eAAO,OAAO,GAAG,KAAK,IAAI;AAAA,MAC5B;AAEA,YAAM,MAAM,GAAG,KAAK;AACpB,YAAM,QAAQ,aAAa,GAAG;AAG9B,UAAI,cAAuB;AAE3B,iBAAW,QAAQ,OAAO;AACxB,YACE,gBAAgB,UAChB,gBAAgB,QAChB,OAAO,gBAAgB,YACvB,QAAQ,aACR;AACA,wBAAe,YAAwC,IAAI;AAAA,QAC7D,OAAO;AACL,wBAAc;AACd;AAAA,QACF;AAAA,MACF;AAEA,UAAI,gBAAgB,UAAa,gBAAgB,MAAM;AACrD,eAAO;AAAA,MACT;AAGA,aAAO,OAAO,WAAW;AAAA,IAC3B,CAAC;AAAA,EACH;AACF;AASA,cAAc,SAAS,SAAU,KAAqB;AAEpD,SAAO,IACJ,QAAQ,YAAY,CAAC,OAAO,cAAe,YAAY,QAAQ,MAAO,EACtE,QAAQ,YAAY,CAAC,OAAO,cAAe,YAAY,QAAQ,MAAO;AAC3E;","names":[]}
1
+ {"version":3,"sources":["../../src/lib/internal/path-utils.ts","../../src/lib/internal/stringify-template-value.ts","../../src/lib/curly-brackets.ts"],"sourcesContent":["const PATH_SEGMENT_PATTERN =\n /(\\w+)|\\[(\\d+)\\]|\\[\"((?:[^\"\\\\]|\\\\.)*)\"\\]|\\['((?:[^'\\\\]|\\\\.)*)'\\]/y;\n\nfunction unescapeQuotedPathPart(value: string): string {\n return value.replace(/\\\\([\"'\\\\])/g, '$1');\n}\n\n/**\n * Parses a mixed object/array path such as \"user.roles[0].name\" into lookup parts.\n */\nexport function getPathParts(path: string): string[] | null {\n const parts: string[] = [];\n let index = 0;\n\n while (index < path.length) {\n if (path[index] === '.') {\n index++;\n\n if (index >= path.length) {\n return null;\n }\n }\n\n PATH_SEGMENT_PATTERN.lastIndex = index;\n const match = PATH_SEGMENT_PATTERN.exec(path);\n\n if (!match) {\n return null;\n }\n\n if (match[1] !== undefined) {\n parts.push(match[1]);\n } else if (match[2] !== undefined) {\n parts.push(match[2]);\n } else if (match[3] !== undefined) {\n parts.push(unescapeQuotedPathPart(match[3]));\n } else if (match[4] !== undefined) {\n parts.push(unescapeQuotedPathPart(match[4]));\n }\n\n index = PATH_SEGMENT_PATTERN.lastIndex;\n\n if (index < path.length && path[index] !== '.' && path[index] !== '[') {\n return null;\n }\n }\n\n return parts;\n}\n","/**\n * Normalizes values to the same string representation used by template rendering.\n */\nexport function stringifyTemplateValue(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n\n return String(value);\n}\n","import { getPathParts } from './internal/path-utils';\nimport { stringifyTemplateValue } from './internal/stringify-template-value';\n\nexport type TemplateFunction = (locals: Record<string, unknown>) => string;\n\ninterface CurlyBracketsFunction {\n (str?: string, locals?: Record<string, unknown>, fallback?: string): string;\n compileTemplate: (str: string, fallback?: string) => TemplateFunction;\n escape: (str: string) => string;\n}\n\nconst PLACEHOLDER_PATTERN = /(?:\\\\)?{{(\\s*[^{}]+?\\s*)(?:\\\\)?\\s*}}/g;\n\n/**\n * Processes a template string, replacing placeholders with corresponding values from a provided object.\n *\n * @param {string} str - The template string to process.\n * @param locals - An object containing key-value pairs for placeholder replacement.\n * @param fallback - A default string to use when a placeholder's corresponding value is not found.\n * @returns - The processed string with placeholders replaced by their corresponding values.\n */\n\nconst CurlyBrackets: CurlyBracketsFunction = function (\n str: string = '',\n locals: Record<string, unknown> = {},\n fallback: string = '(null)',\n): string {\n // Short-circuit if no brackets - no need to process\n if (!str.includes('{{')) {\n return str;\n }\n\n const compiled = CurlyBrackets.compileTemplate(str, fallback);\n\n return compiled(locals);\n} as CurlyBracketsFunction;\n\n/**\n * Compiles a template string into a reusable function, which can be called with different sets of locals.\n * This is more efficient when you have a template that you want to use with different sets of locals,\n * as it avoids the overhead of parsing the template string each time it is used.\n *\n * @param {string} str - The template string to compile.\n * @param {string} fallback - A default string to use when a placeholder's corresponding value is not found in locals.\n * @returns A function that takes an object of locals and returns a processed string.\n */\n\nCurlyBrackets.compileTemplate = function (\n str: string,\n fallback: string = '(null)',\n): TemplateFunction {\n return (locals: Record<string, unknown>): string => {\n return str.replace(PLACEHOLDER_PATTERN, (match, p1: string) => {\n if (typeof p1 !== 'string') {\n return match;\n }\n\n const hasLeadingEscape = match.startsWith('\\\\');\n const hasEndingEscape = match.endsWith('\\\\}}');\n const isFullyEscaped = hasLeadingEscape && hasEndingEscape;\n\n if (isFullyEscaped) {\n return match.slice(1, -3) + '}}';\n }\n\n if (hasLeadingEscape) {\n return match.slice(1);\n }\n\n if (hasEndingEscape) {\n return '{{' + p1.trim() + '}}';\n }\n\n const key = p1.trim();\n const parts = getPathParts(key);\n\n if (!parts || parts.length === 0) {\n return match;\n }\n\n // Use a more specific approach to ensure the type is consistent\n let replacement: unknown = locals;\n\n for (const part of parts) {\n if (\n replacement !== undefined &&\n replacement !== null &&\n typeof replacement === 'object' &&\n part in replacement\n ) {\n replacement = (replacement as Record<string, unknown>)[part];\n } else {\n replacement = undefined;\n break;\n }\n }\n\n if (replacement === undefined || replacement === null) {\n return fallback;\n }\n\n return stringifyTemplateValue(replacement);\n });\n };\n};\n\n/**\n * Escapes placeholders in a string by prefixing them with a backslash, preventing them from being replaced when processed.\n *\n * @param {string} str - The string in which to escape placeholders.\n * @returns {string} - The string with placeholders escaped.\n */\n\nCurlyBrackets.escape = function (str: string): string {\n // Use a regex to replace instances of {{ and }} that are not already preceded by a backslash\n return str\n .replace(/(\\\\)?{{/g, (match, backslash) => (backslash ? match : '\\\\{{'))\n .replace(/(\\\\)?}}/g, (match, backslash) => (backslash ? match : '\\\\}}'));\n};\n\nexport { CurlyBrackets };\n"],"mappings":"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","names":[]}
@@ -935,16 +935,52 @@ function ms() {
935
935
  }
936
936
 
937
937
  // src/lib/internal/path-utils.ts
938
- var PATH_SEGMENT_PATTERN = /\w+|\[(\d+)\]/g;
938
+ var PATH_SEGMENT_PATTERN = /(\w+)|\[(\d+)\]|\["((?:[^"\\]|\\.)*)"\]|\['((?:[^'\\]|\\.)*)'\]/y;
939
+ function unescapeQuotedPathPart(value) {
940
+ return value.replace(/\\(["'\\])/g, "$1");
941
+ }
939
942
  function getPathParts(path) {
940
- return Array.from(
941
- path.matchAll(PATH_SEGMENT_PATTERN),
942
- (match) => match[1] ?? match[0]
943
- );
943
+ const parts = [];
944
+ let index = 0;
945
+ while (index < path.length) {
946
+ if (path[index] === ".") {
947
+ index++;
948
+ if (index >= path.length) {
949
+ return null;
950
+ }
951
+ }
952
+ PATH_SEGMENT_PATTERN.lastIndex = index;
953
+ const match = PATH_SEGMENT_PATTERN.exec(path);
954
+ if (!match) {
955
+ return null;
956
+ }
957
+ if (match[1] !== void 0) {
958
+ parts.push(match[1]);
959
+ } else if (match[2] !== void 0) {
960
+ parts.push(match[2]);
961
+ } else if (match[3] !== void 0) {
962
+ parts.push(unescapeQuotedPathPart(match[3]));
963
+ } else if (match[4] !== void 0) {
964
+ parts.push(unescapeQuotedPathPart(match[4]));
965
+ }
966
+ index = PATH_SEGMENT_PATTERN.lastIndex;
967
+ if (index < path.length && path[index] !== "." && path[index] !== "[") {
968
+ return null;
969
+ }
970
+ }
971
+ return parts;
972
+ }
973
+
974
+ // src/lib/internal/stringify-template-value.ts
975
+ function stringifyTemplateValue(value) {
976
+ if (typeof value === "string") {
977
+ return value;
978
+ }
979
+ return String(value);
944
980
  }
945
981
 
946
982
  // src/lib/curly-brackets.ts
947
- var PLACEHOLDER_PATTERN = /(?:\\)?{{(\s*\w+(?:\[\d+\])*(?:\.\w+(?:\[\d+\])*)*\s*)(?:\\)?\s*}}/g;
983
+ var PLACEHOLDER_PATTERN = /(?:\\)?{{(\s*[^{}]+?\s*)(?:\\)?\s*}}/g;
948
984
  var CurlyBrackets = function(str = "", locals = {}, fallback = "(null)") {
949
985
  if (!str.includes("{{")) {
950
986
  return str;
@@ -972,6 +1008,9 @@ CurlyBrackets.compileTemplate = function(str, fallback = "(null)") {
972
1008
  }
973
1009
  const key = p1.trim();
974
1010
  const parts = getPathParts(key);
1011
+ if (!parts || parts.length === 0) {
1012
+ return match;
1013
+ }
975
1014
  let replacement = locals;
976
1015
  for (const part of parts) {
977
1016
  if (replacement !== void 0 && replacement !== null && typeof replacement === "object" && part in replacement) {
@@ -984,7 +1023,7 @@ CurlyBrackets.compileTemplate = function(str, fallback = "(null)") {
984
1023
  if (replacement === void 0 || replacement === null) {
985
1024
  return fallback;
986
1025
  }
987
- return String(replacement);
1026
+ return stringifyTemplateValue(replacement);
988
1027
  });
989
1028
  };
990
1029
  };
@@ -1318,6 +1357,9 @@ var defaultRedactFunction = (_keyName, value) => {
1318
1357
  };
1319
1358
  function setNestedValue(obj, path, value) {
1320
1359
  const parts = getPathParts(path);
1360
+ if (!parts || parts.length === 0) {
1361
+ return;
1362
+ }
1321
1363
  let current = obj;
1322
1364
  for (let i = 0; i < parts.length - 1; i++) {
1323
1365
  const part = parts[i];
@@ -1337,6 +1379,9 @@ function setNestedValue(obj, path, value) {
1337
1379
  }
1338
1380
  function getNestedValue(obj, path) {
1339
1381
  const parts = getPathParts(path);
1382
+ if (!parts || parts.length === 0) {
1383
+ return void 0;
1384
+ }
1340
1385
  let current = obj;
1341
1386
  for (const part of parts) {
1342
1387
  if (current === void 0 || current === null || typeof current !== "object" || !(part in current)) {
@@ -1354,14 +1399,17 @@ function applyRedaction(params, redactedKeys, redactFunction) {
1354
1399
  const redactedParams = deepClone(params);
1355
1400
  for (const key of redactedKeys) {
1356
1401
  if (key.includes(".") || key.includes("[")) {
1357
- const value = getNestedValue(redactedParams, key);
1402
+ const value = getNestedValue(params, key);
1358
1403
  if (value !== void 0) {
1359
- const redactedValue = redactFn(key, value);
1404
+ const redactedValue = redactFn(key, stringifyTemplateValue(value));
1360
1405
  setNestedValue(redactedParams, key, redactedValue);
1361
1406
  }
1362
1407
  } else {
1363
- if (key in redactedParams) {
1364
- redactedParams[key] = redactFn(key, redactedParams[key]);
1408
+ if (key in params) {
1409
+ redactedParams[key] = redactFn(
1410
+ key,
1411
+ stringifyTemplateValue(params[key])
1412
+ );
1365
1413
  }
1366
1414
  }
1367
1415
  }
@@ -2441,8 +2489,9 @@ var Logger = class _Logger extends EventEmitter {
2441
2489
  const params = options?.params;
2442
2490
  const tags = options?.tags;
2443
2491
  const redactedKeys = options?.redactedKeys;
2444
- const message = params ? CurlyBrackets(template, params) : template;
2445
- const redactedParams = params ? applyRedaction(params, redactedKeys, this.redactFunction) : void 0;
2492
+ const redactedParams = params && redactedKeys && redactedKeys.length > 0 ? applyRedaction(params, redactedKeys, this.redactFunction) : void 0;
2493
+ const messageParams = redactedParams ?? params;
2494
+ const message = messageParams ? CurlyBrackets(template, messageParams) : template;
2446
2495
  const entry = {
2447
2496
  timestamp,
2448
2497
  type,