@harborclient/sdk 1.0.54 → 1.0.55

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.
@@ -1 +1 @@
1
- {"version":3,"file":"substitute.d.ts","sourceRoot":"","sources":["../../src/http/substitute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAI9C,KAAK,QAAQ,GAAG;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAU7F;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GACnC,UAAU,CAWZ;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,QAAQ,EAAE,EAChB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,QAAQ,EAAE,CAKZ"}
1
+ {"version":3,"file":"substitute.d.ts","sourceRoot":"","sources":["../../src/http/substitute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,KAAK,QAAQ,GAAG;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAE7F;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GACnC,UAAU,CAWZ;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,QAAQ,EAAE,EAChB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,QAAQ,EAAE,CAKZ"}
@@ -1,5 +1,4 @@
1
- import { resolveDynamicVariable } from '../variables/dynamic.js';
2
- import { VARIABLE_TOKEN_PATTERN } from '../variables/tokens.js';
1
+ import { substituteVariablesFromMap } from '../variables/tokens.js';
3
2
  /**
4
3
  * Replaces {{key}} placeholders using a runtime variable map.
5
4
  *
@@ -7,15 +6,7 @@ import { VARIABLE_TOKEN_PATTERN } from '../variables/tokens.js';
7
6
  * @param runtimeVars - Current runtime variable values.
8
7
  */
9
8
  export function substituteVariables(text, runtimeVars) {
10
- const pattern = new RegExp(VARIABLE_TOKEN_PATTERN.source, 'g');
11
- return text.replace(pattern, (match, key) => {
12
- const value = runtimeVars[key];
13
- if (value !== undefined) {
14
- return value;
15
- }
16
- const dynamic = resolveDynamicVariable(key);
17
- return dynamic !== undefined ? dynamic : match;
18
- });
9
+ return substituteVariablesFromMap(text, runtimeVars);
19
10
  }
20
11
  /**
21
12
  * Resolves {{variable}} placeholders in auth credential fields.
@@ -11,12 +11,13 @@
11
11
  * `src/main/scripting/scriptApi.ts` and editor completions in
12
12
  * `src/renderer/src/scripting/hcCompletions.ts`.
13
13
  *
14
- * **Import and export** — snippets whose **Name** ends in `.js` (for example
15
- * `pass-testing.js` or `utils/helpers.js`) may be imported from scripts with
16
- * relative ESM paths: `import { fn } from './pass-testing.js'`. Use standard
17
- * `export function`, `export const`, and `export default` in snippet source.
18
- * Cross-snippet imports are resolved at send time in HarborClient; the SDK does
19
- * not type-check import paths between snippet files.
14
+ * **Import and export** — scripts or snippets whose **Name** ends in `.js` (for example
15
+ * `pass-testing.js`, `before.js`, or `utils/helpers.js`) may be imported from scripts with
16
+ * relative ESM paths: `import { fn } from './pass-testing.js'`. Import targets include
17
+ * the snippet library and inline scripts in the same request and collection. Use standard
18
+ * `export function`, `export const`, and `export default` in module source.
19
+ * Cross-module imports are resolved at send time in HarborClient; the SDK does
20
+ * not type-check import paths between snippet or inline script files.
20
21
  *
21
22
  * **Not supported:** npm package imports, `require`, and Node.js built-ins.
22
23
  * See [Request scripts — Snippets](https://harborclient.com/scripting#snippets).
@@ -0,0 +1,33 @@
1
+ /**
2
+ * A filter invocation inside a `{{ variable | filter }}` expression.
3
+ */
4
+ export interface FilterCall {
5
+ /**
6
+ * Filter name (e.g. `upper`, `urlencode`).
7
+ */
8
+ name: string;
9
+ /**
10
+ * Positional arguments for future filter support; always empty for now.
11
+ */
12
+ args: string[];
13
+ }
14
+ /**
15
+ * Registry of Twig-style variable filters.
16
+ *
17
+ * Add or edit entries here to extend filter support. Each filter receives the
18
+ * current piped string value and an argument list (unused until arg support ships).
19
+ */
20
+ export declare const FILTERS: Record<string, (value: string, args: string[]) => string>;
21
+ /**
22
+ * Sorted list of registered filter names for autocomplete and documentation.
23
+ */
24
+ export declare const FILTER_NAMES: string[];
25
+ /**
26
+ * Pipes a resolved variable value through a chain of filters left-to-right.
27
+ *
28
+ * @param value - Resolved variable value before filtering.
29
+ * @param filters - Ordered filter calls from the expression.
30
+ * @returns Filtered value, or null when an unknown filter name is encountered.
31
+ */
32
+ export declare function applyFilters(value: string, filters: FilterCall[]): string | null;
33
+ //# sourceMappingURL=filters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../../src/variables/filters.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;;;;GAKG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,CAoB7E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,UAA8B,CAAC;AAExD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,GAAG,IAAI,CAYhF"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Registry of Twig-style variable filters.
3
+ *
4
+ * Add or edit entries here to extend filter support. Each filter receives the
5
+ * current piped string value and an argument list (unused until arg support ships).
6
+ */
7
+ export const FILTERS = {
8
+ upper: (value) => value.toUpperCase(),
9
+ lower: (value) => value.toLowerCase(),
10
+ urlencode: (value) => encodeURIComponent(value),
11
+ trim: (value) => value.trim(),
12
+ length: (value) => String(value.length),
13
+ striptags: (value) => value.replace(/<[^>]*>/g, ''),
14
+ capitalize: (value) => {
15
+ if (value.length === 0) {
16
+ return value;
17
+ }
18
+ return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
19
+ },
20
+ round: (value) => {
21
+ const numeric = Number(value);
22
+ if (Number.isNaN(numeric)) {
23
+ return value;
24
+ }
25
+ return String(Math.round(numeric));
26
+ }
27
+ };
28
+ /**
29
+ * Sorted list of registered filter names for autocomplete and documentation.
30
+ */
31
+ export const FILTER_NAMES = Object.keys(FILTERS).sort();
32
+ /**
33
+ * Pipes a resolved variable value through a chain of filters left-to-right.
34
+ *
35
+ * @param value - Resolved variable value before filtering.
36
+ * @param filters - Ordered filter calls from the expression.
37
+ * @returns Filtered value, or null when an unknown filter name is encountered.
38
+ */
39
+ export function applyFilters(value, filters) {
40
+ let current = value;
41
+ for (const filter of filters) {
42
+ const handler = FILTERS[filter.name];
43
+ if (!handler) {
44
+ return null;
45
+ }
46
+ current = handler(current, filter.args);
47
+ }
48
+ return current;
49
+ }
@@ -1,3 +1,4 @@
1
1
  export { DYNAMIC_VARIABLES, DYNAMIC_VARIABLE_CATEGORIES, DYNAMIC_VARIABLE_NAMES, getDynamicVariableDescription, isDynamicVariable, resolveDynamicVariable, type DynamicVariableDefinition } from './dynamic.js';
2
- export { VARIABLE_NAME_CHARS, VARIABLE_TOKEN_PATTERN, getVariableTokenAtOffset, getVariableTooltipContent, resolveVariable, substituteVariables, tokenizeVariables, type VariableToken, type VariableTokenMatch, type VariableTooltipContent } from './tokens.js';
2
+ export { FILTER_NAMES, FILTERS, applyFilters, type FilterCall } from './filters.js';
3
+ export { VARIABLE_NAME_CHARS, VARIABLE_TOKEN_PATTERN, getVariableTokenAtOffset, getVariableTooltipContent, parseVariableTokens, resolveVariable, substituteVariables, substituteVariablesFromMap, substituteVariablesWithResolver, tokenizeVariables, type ParsedVariableToken, type VariableToken, type VariableTokenMatch, type VariableTooltipContent } from './tokens.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/variables/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,2BAA2B,EAC3B,sBAAsB,EACtB,6BAA6B,EAC7B,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,yBAAyB,EAC/B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,EACzB,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC5B,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/variables/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,2BAA2B,EAC3B,sBAAsB,EACtB,6BAA6B,EAC7B,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,yBAAyB,EAC/B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,EACzB,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,+BAA+B,EAC/B,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC5B,MAAM,aAAa,CAAC"}
@@ -1,2 +1,3 @@
1
1
  export { DYNAMIC_VARIABLES, DYNAMIC_VARIABLE_CATEGORIES, DYNAMIC_VARIABLE_NAMES, getDynamicVariableDescription, isDynamicVariable, resolveDynamicVariable } from './dynamic.js';
2
- export { VARIABLE_NAME_CHARS, VARIABLE_TOKEN_PATTERN, getVariableTokenAtOffset, getVariableTooltipContent, resolveVariable, substituteVariables, tokenizeVariables } from './tokens.js';
2
+ export { FILTER_NAMES, FILTERS, applyFilters } from './filters.js';
3
+ export { VARIABLE_NAME_CHARS, VARIABLE_TOKEN_PATTERN, getVariableTokenAtOffset, getVariableTooltipContent, parseVariableTokens, resolveVariable, substituteVariables, substituteVariablesFromMap, substituteVariablesWithResolver, tokenizeVariables } from './tokens.js';
@@ -1,10 +1,22 @@
1
1
  import type { Variable } from '../types.js';
2
+ import { type FilterCall } from './filters.js';
2
3
  /**
3
4
  * A segment of text, optionally marking a {{variable}} token.
4
5
  */
5
6
  export interface VariableToken {
6
7
  text: string;
7
8
  key?: string;
9
+ filters?: FilterCall[];
10
+ }
11
+ /**
12
+ * A parsed `{{ variable | filter }}` placeholder with source offsets.
13
+ */
14
+ export interface ParsedVariableToken {
15
+ raw: string;
16
+ key: string;
17
+ filters: FilterCall[];
18
+ start: number;
19
+ end: number;
8
20
  }
9
21
  /**
10
22
  * Allowed characters inside a `{{variable}}` token name (excluding braces).
@@ -12,9 +24,16 @@ export interface VariableToken {
12
24
  */
13
25
  export declare const VARIABLE_NAME_CHARS = "\\w$.-";
14
26
  /**
15
- * Global regex matching `{{variableName}}` placeholders in request text.
27
+ * Global regex matching `{{variableName}}` and filter chains for editor highlighting.
16
28
  */
17
29
  export declare const VARIABLE_TOKEN_PATTERN: RegExp;
30
+ /**
31
+ * Scans text for `{{ variable | filter }}` placeholders using a hand-written lexer.
32
+ *
33
+ * @param text - Text containing variable placeholders.
34
+ * @returns Parsed tokens with character offsets; malformed `{{` sequences are skipped.
35
+ */
36
+ export declare function parseVariableTokens(text: string): ParsedVariableToken[];
18
37
  /**
19
38
  * Splits text into plain and {{variable}} segments.
20
39
  *
@@ -29,6 +48,7 @@ export interface VariableTokenMatch {
29
48
  key: string;
30
49
  start: number;
31
50
  end: number;
51
+ filters?: FilterCall[];
32
52
  }
33
53
  /**
34
54
  * Returns the variable token containing the given character offset, if any.
@@ -72,4 +92,20 @@ export declare function resolveVariable(key: string, variables: Variable[]): str
72
92
  * @returns Text with known variables substituted; unknown tokens are left unchanged.
73
93
  */
74
94
  export declare function substituteVariables(text: string, variables: Variable[]): string;
95
+ /**
96
+ * Replaces {{key}} placeholders using a custom key resolver.
97
+ *
98
+ * @param text - Text containing variable placeholders.
99
+ * @param resolveKey - Resolver for static/runtime variables; undefined leaves token unchanged.
100
+ * @returns Text with known variables substituted and filters applied.
101
+ */
102
+ export declare function substituteVariablesWithResolver(text: string, resolveKey: (key: string) => string | undefined): string;
103
+ /**
104
+ * Replaces {{key}} placeholders using a runtime variable map.
105
+ *
106
+ * @param text - Text containing variable placeholders.
107
+ * @param runtimeVars - Current runtime variable values.
108
+ * @returns Text with known variables substituted and filters applied.
109
+ */
110
+ export declare function substituteVariablesFromMap(text: string, runtimeVars: Record<string, string>): string;
75
111
  //# sourceMappingURL=tokens.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/variables/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAE5C;;GAEG;AACH,eAAO,MAAM,sBAAsB,QAGlC,CAAC;AAgBF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAmB/D;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAahG;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,QAAQ,EAAE,GACpB,sBAAsB,CAYxB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,GAAG,SAAS,CAEtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,CAY/E"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/variables/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,cAAc,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAE5C;;GAEG;AACH,eAAO,MAAM,sBAAsB,QAGlC,CAAC;AA0CF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAmCvE;AAyED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CA0B/D;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAkBhG;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,QAAQ,EAAE,GACpB,sBAAsB,CAYxB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,GAAG,SAAS,CAEtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,CAG/E;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAC9C,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,MAAM,CAER"}
@@ -1,13 +1,84 @@
1
1
  import { getDynamicVariableDescription, resolveDynamicVariable } from './dynamic.js';
2
+ import { applyFilters } from './filters.js';
2
3
  /**
3
4
  * Allowed characters inside a `{{variable}}` token name (excluding braces).
4
5
  * Includes `$` so Postman-style dynamic names such as `$randomUUID` are recognized.
5
6
  */
6
7
  export const VARIABLE_NAME_CHARS = '\\w$.-';
7
8
  /**
8
- * Global regex matching `{{variableName}}` placeholders in request text.
9
+ * Global regex matching `{{variableName}}` and filter chains for editor highlighting.
9
10
  */
10
- export const VARIABLE_TOKEN_PATTERN = new RegExp(`\\{\\{\\s*([${VARIABLE_NAME_CHARS}]+)\\s*\\}\\}`, 'g');
11
+ export const VARIABLE_TOKEN_PATTERN = new RegExp(`\\{\\{\\s*([${VARIABLE_NAME_CHARS}]+)(\\s*\\|\\s*[${VARIABLE_NAME_CHARS}]+)*\\s*\\}\\}`, 'g');
12
+ const VALID_NAME_PATTERN = new RegExp(`^[${VARIABLE_NAME_CHARS}]+$`);
13
+ /**
14
+ * Returns whether a variable key or filter name uses only allowed characters.
15
+ *
16
+ * @param name - Identifier from inside `{{...}}` braces.
17
+ */
18
+ function isValidName(name) {
19
+ return name.length > 0 && VALID_NAME_PATTERN.test(name);
20
+ }
21
+ /**
22
+ * Parses the inner expression of a `{{...}}` token into a key and filter chain.
23
+ *
24
+ * @param inner - Text between opening and closing braces.
25
+ * @returns Parsed key and filters, or null when malformed.
26
+ */
27
+ function parseVariableExpression(inner) {
28
+ const segments = inner.split('|').map((segment) => segment.trim());
29
+ if (segments.length === 0 || segments[0] === '') {
30
+ return null;
31
+ }
32
+ const key = segments[0];
33
+ if (!isValidName(key)) {
34
+ return null;
35
+ }
36
+ const filters = [];
37
+ for (let i = 1; i < segments.length; i++) {
38
+ const name = segments[i];
39
+ if (!isValidName(name)) {
40
+ return null;
41
+ }
42
+ filters.push({ name, args: [] });
43
+ }
44
+ return { key, filters };
45
+ }
46
+ /**
47
+ * Scans text for `{{ variable | filter }}` placeholders using a hand-written lexer.
48
+ *
49
+ * @param text - Text containing variable placeholders.
50
+ * @returns Parsed tokens with character offsets; malformed `{{` sequences are skipped.
51
+ */
52
+ export function parseVariableTokens(text) {
53
+ const tokens = [];
54
+ let index = 0;
55
+ while (index < text.length) {
56
+ const open = text.indexOf('{{', index);
57
+ if (open === -1) {
58
+ break;
59
+ }
60
+ const close = text.indexOf('}}', open + 2);
61
+ if (close === -1) {
62
+ break;
63
+ }
64
+ const raw = text.slice(open, close + 2);
65
+ const inner = text.slice(open + 2, close);
66
+ const parsed = parseVariableExpression(inner);
67
+ if (parsed) {
68
+ tokens.push({
69
+ raw,
70
+ key: parsed.key,
71
+ filters: parsed.filters,
72
+ start: open,
73
+ end: close + 2
74
+ });
75
+ index = close + 2;
76
+ continue;
77
+ }
78
+ index = open + 2;
79
+ }
80
+ return tokens;
81
+ }
11
82
  /**
12
83
  * Builds a lookup map from collection variables.
13
84
  *
@@ -19,6 +90,51 @@ function variableLookup(variables) {
19
90
  .filter((v) => v.key.trim())
20
91
  .map((v) => [v.key.trim(), v.value !== '' ? v.value : v.defaultValue]));
21
92
  }
93
+ /**
94
+ * Resolves a variable key to a string value using a lookup function and dynamic variables.
95
+ *
96
+ * @param key - Base variable name from a parsed token.
97
+ * @param resolveKey - Resolver for static/runtime variables.
98
+ * @returns Resolved value, or undefined when the key is not defined.
99
+ */
100
+ function resolveKeyValue(key, resolveKey) {
101
+ const value = resolveKey(key);
102
+ if (value !== undefined) {
103
+ return value;
104
+ }
105
+ return resolveDynamicVariable(key);
106
+ }
107
+ /**
108
+ * Substitutes parsed variable tokens in text using a key resolver.
109
+ *
110
+ * Unknown variables and unknown filters leave the original token unchanged.
111
+ *
112
+ * @param text - Text containing variable placeholders.
113
+ * @param resolveKey - Resolver for static/runtime variables.
114
+ * @returns Text with known variables substituted and filters applied.
115
+ */
116
+ function substituteWithResolver(text, resolveKey) {
117
+ const parsedTokens = parseVariableTokens(text);
118
+ if (parsedTokens.length === 0) {
119
+ return text;
120
+ }
121
+ let result = '';
122
+ let lastIndex = 0;
123
+ for (const token of parsedTokens) {
124
+ result += text.slice(lastIndex, token.start);
125
+ const resolved = resolveKeyValue(token.key, resolveKey);
126
+ if (resolved === undefined) {
127
+ result += token.raw;
128
+ }
129
+ else {
130
+ const filtered = applyFilters(resolved, token.filters);
131
+ result += filtered ?? token.raw;
132
+ }
133
+ lastIndex = token.end;
134
+ }
135
+ result += text.slice(lastIndex);
136
+ return result;
137
+ }
22
138
  /**
23
139
  * Splits text into plain and {{variable}} segments.
24
140
  *
@@ -26,16 +142,22 @@ function variableLookup(variables) {
26
142
  * @returns Ordered tokens for rendering or further processing.
27
143
  */
28
144
  export function tokenizeVariables(text) {
145
+ const parsedTokens = parseVariableTokens(text);
146
+ if (parsedTokens.length === 0) {
147
+ return [{ text }];
148
+ }
29
149
  const tokens = [];
30
- const pattern = new RegExp(VARIABLE_TOKEN_PATTERN.source, 'g');
31
150
  let lastIndex = 0;
32
- for (const match of text.matchAll(pattern)) {
33
- const index = match.index ?? 0;
34
- if (index > lastIndex) {
35
- tokens.push({ text: text.slice(lastIndex, index) });
151
+ for (const token of parsedTokens) {
152
+ if (token.start > lastIndex) {
153
+ tokens.push({ text: text.slice(lastIndex, token.start) });
36
154
  }
37
- tokens.push({ text: match[0], key: match[1] });
38
- lastIndex = index + match[0].length;
155
+ tokens.push({
156
+ text: token.raw,
157
+ key: token.key,
158
+ filters: token.filters.length > 0 ? token.filters : undefined
159
+ });
160
+ lastIndex = token.end;
39
161
  }
40
162
  if (lastIndex < text.length) {
41
163
  tokens.push({ text: text.slice(lastIndex) });
@@ -55,7 +177,12 @@ export function getVariableTokenAtOffset(text, offset) {
55
177
  const start = position;
56
178
  const end = position + token.text.length;
57
179
  if (token.key && offset >= start && offset <= end) {
58
- return { key: token.key, start, end };
180
+ return {
181
+ key: token.key,
182
+ start,
183
+ end,
184
+ filters: token.filters
185
+ };
59
186
  }
60
187
  position = end;
61
188
  }
@@ -101,13 +228,25 @@ export function resolveVariable(key, variables) {
101
228
  */
102
229
  export function substituteVariables(text, variables) {
103
230
  const lookup = variableLookup(variables);
104
- const pattern = new RegExp(VARIABLE_TOKEN_PATTERN.source, 'g');
105
- return text.replace(pattern, (match, key) => {
106
- const value = lookup.get(key);
107
- if (value !== undefined) {
108
- return value;
109
- }
110
- const dynamic = resolveDynamicVariable(key);
111
- return dynamic !== undefined ? dynamic : match;
112
- });
231
+ return substituteWithResolver(text, (key) => lookup.get(key));
232
+ }
233
+ /**
234
+ * Replaces {{key}} placeholders using a custom key resolver.
235
+ *
236
+ * @param text - Text containing variable placeholders.
237
+ * @param resolveKey - Resolver for static/runtime variables; undefined leaves token unchanged.
238
+ * @returns Text with known variables substituted and filters applied.
239
+ */
240
+ export function substituteVariablesWithResolver(text, resolveKey) {
241
+ return substituteWithResolver(text, resolveKey);
242
+ }
243
+ /**
244
+ * Replaces {{key}} placeholders using a runtime variable map.
245
+ *
246
+ * @param text - Text containing variable placeholders.
247
+ * @param runtimeVars - Current runtime variable values.
248
+ * @returns Text with known variables substituted and filters applied.
249
+ */
250
+ export function substituteVariablesFromMap(text, runtimeVars) {
251
+ return substituteWithResolver(text, (key) => runtimeVars[key]);
113
252
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harborclient/sdk",
3
- "version": "1.0.54",
3
+ "version": "1.0.55",
4
4
  "description": "TypeScript SDK for HarborClient development.",
5
5
  "keywords": [
6
6
  "harborclient",