@harborclient/sdk 1.0.54 → 1.0.57

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,9 +1,17 @@
1
1
  import type { Extension } from '@codemirror/state';
2
+ import { EditorView } from '@codemirror/view';
2
3
  import { type RenderHighlightedPlaceholderOptions } from './renderHighlightedPlaceholder.js';
3
4
  /**
4
5
  * Options for building a muted syntax-highlighted placeholder layer.
5
6
  */
6
7
  export type SyntaxHighlightedPlaceholderOptions = RenderHighlightedPlaceholderOptions;
8
+ /**
9
+ * Returns whether the syntax-highlighted placeholder should be visible.
10
+ *
11
+ * @param view - Parent CodeMirror editor view.
12
+ * @param engaged - True after the user pressed inside the editor chrome.
13
+ */
14
+ export declare function shouldShowSyntaxPlaceholder(view: EditorView, engaged: boolean): boolean;
7
15
  /**
8
16
  * Returns CodeMirror extensions that show muted syntax-highlighted placeholder content
9
17
  * when the editor document is empty and unfocused.
@@ -1 +1 @@
1
- {"version":3,"file":"syntaxHighlightedPlaceholder.d.ts","sourceRoot":"","sources":["../../../src/components/CodeEditor/syntaxHighlightedPlaceholder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AASnD,OAAO,EACL,KAAK,mCAAmC,EAEzC,MAAM,mCAAmC,CAAC;AAE3C;;GAEG;AACH,MAAM,MAAM,mCAAmC,GAAG,mCAAmC,CAAC;AA2DtF;;;;;;GAMG;AACH,wBAAgB,kCAAkC,CAChD,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,mCAAmC,GAC3C,SAAS,EAAE,CA+Cb"}
1
+ {"version":3,"file":"syntaxHighlightedPlaceholder.d.ts","sourceRoot":"","sources":["../../../src/components/CodeEditor/syntaxHighlightedPlaceholder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAGL,UAAU,EAIX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,KAAK,mCAAmC,EAEzC,MAAM,mCAAmC,CAAC;AAE3C;;GAEG;AACH,MAAM,MAAM,mCAAmC,GAAG,mCAAmC,CAAC;AA6CtF;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAQvF;AAiGD;;;;;;GAMG;AACH,wBAAgB,kCAAkC,CAChD,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,mCAAmC,GAC3C,SAAS,EAAE,CA+Eb"}
@@ -28,13 +28,32 @@ class SyntaxHighlightedPlaceholderWidget extends WidgetType {
28
28
  return true;
29
29
  }
30
30
  }
31
+ /**
32
+ * Returns whether focus currently lives inside the editor's DOM subtree.
33
+ *
34
+ * @param view - Parent CodeMirror editor view.
35
+ */
36
+ function isEditorDomFocused(view) {
37
+ const active = view.root.activeElement;
38
+ if (active == null) {
39
+ return false;
40
+ }
41
+ return view.dom.contains(active);
42
+ }
31
43
  /**
32
44
  * Returns whether the syntax-highlighted placeholder should be visible.
33
45
  *
34
46
  * @param view - Parent CodeMirror editor view.
47
+ * @param engaged - True after the user pressed inside the editor chrome.
35
48
  */
36
- function shouldShowSyntaxPlaceholder(view) {
37
- return view.state.doc.length === 0 && !view.hasFocus;
49
+ export function shouldShowSyntaxPlaceholder(view, engaged) {
50
+ if (view.state.doc.length > 0) {
51
+ return false;
52
+ }
53
+ if (engaged || isEditorDomFocused(view)) {
54
+ return false;
55
+ }
56
+ return true;
38
57
  }
39
58
  /**
40
59
  * Builds widget decorations for the muted placeholder layer.
@@ -50,6 +69,75 @@ function buildSyntaxPlaceholderDecorations(text, options) {
50
69
  }).range(0)
51
70
  ]);
52
71
  }
72
+ /**
73
+ * ViewPlugin that tracks editor engagement and renders placeholder decorations.
74
+ */
75
+ class SyntaxHighlightedPlaceholderPlugin {
76
+ text;
77
+ options;
78
+ placeholder;
79
+ /**
80
+ * True after a pointer press inside the editor; cleared when focus leaves the editor
81
+ * while the document is still empty.
82
+ */
83
+ engaged = false;
84
+ /**
85
+ * @param view - Parent CodeMirror editor view.
86
+ * @param text - Placeholder source shown until the user engages or types.
87
+ * @param options - Highlighting and theme options.
88
+ */
89
+ constructor(view, text, options) {
90
+ this.text = text;
91
+ this.options = options;
92
+ this.placeholder = this.compute(view);
93
+ }
94
+ /**
95
+ * Recomputes placeholder decorations from document length and engagement state.
96
+ *
97
+ * @param view - Parent CodeMirror editor view.
98
+ */
99
+ compute(view) {
100
+ return shouldShowSyntaxPlaceholder(view, this.engaged)
101
+ ? buildSyntaxPlaceholderDecorations(this.text, this.options)
102
+ : Decoration.none;
103
+ }
104
+ /**
105
+ * Applies a new engagement flag and refreshes decorations when it changes.
106
+ *
107
+ * @param view - Parent CodeMirror editor view.
108
+ * @param nextEngaged - Whether the user has pressed inside the editor chrome.
109
+ */
110
+ setEngaged(view, nextEngaged) {
111
+ if (this.engaged === nextEngaged) {
112
+ return;
113
+ }
114
+ this.engaged = nextEngaged;
115
+ this.placeholder = this.compute(view);
116
+ view.dispatch({});
117
+ }
118
+ /**
119
+ * Shows or hides the placeholder when the document, focus, or engagement changes.
120
+ *
121
+ * @param update - Parent view update.
122
+ */
123
+ update(update) {
124
+ if (update.docChanged || update.focusChanged || update.view.state.doc.length === 0) {
125
+ this.placeholder = this.compute(update.view);
126
+ }
127
+ }
128
+ get decorations() {
129
+ return this.placeholder;
130
+ }
131
+ }
132
+ /**
133
+ * Returns whether a DOM event target lies inside the editor root element.
134
+ *
135
+ * @param view - Parent CodeMirror editor view.
136
+ * @param target - Event target node.
137
+ */
138
+ function isEventTargetInEditor(view, target) {
139
+ return target instanceof Node && view.dom.contains(target);
140
+ }
53
141
  /**
54
142
  * Returns CodeMirror extensions that show muted syntax-highlighted placeholder content
55
143
  * when the editor document is empty and unfocused.
@@ -58,37 +146,66 @@ function buildSyntaxPlaceholderDecorations(text, options) {
58
146
  * @param options - Highlighting and theme options shared with the parent editor.
59
147
  */
60
148
  export function createSyntaxHighlightedPlaceholder(text, options) {
61
- const plugin = ViewPlugin.fromClass(class {
62
- placeholder;
149
+ const placeholderPlugin = ViewPlugin.fromClass(class extends SyntaxHighlightedPlaceholderPlugin {
63
150
  /**
64
151
  * Initializes placeholder decorations for an empty, unfocused document.
65
152
  *
66
153
  * @param view - Parent CodeMirror editor view.
67
154
  */
68
155
  constructor(view) {
69
- this.placeholder = shouldShowSyntaxPlaceholder(view)
70
- ? buildSyntaxPlaceholderDecorations(text, options)
71
- : Decoration.none;
156
+ super(view, text, options);
72
157
  }
73
- /**
74
- * Shows or hides the placeholder when the document, focus, or content changes.
75
- *
76
- * @param update - Parent view update.
77
- */
78
- update(update) {
79
- if (!update.docChanged && !update.focusChanged) {
158
+ }, { decorations: (v) => v.decorations });
159
+ /**
160
+ * Marks the editor engaged on pointer presses inside the editor chrome only.
161
+ *
162
+ * @param view - Parent CodeMirror editor view.
163
+ * @param event - Pointer or mouse event from the editor root.
164
+ */
165
+ const handleEditorPointerDown = (view, event) => {
166
+ if (view.state.doc.length > 0 || !isEventTargetInEditor(view, event.target)) {
167
+ return;
168
+ }
169
+ const plugin = view.plugin(placeholderPlugin);
170
+ if (plugin == null) {
171
+ return;
172
+ }
173
+ plugin.setEngaged(view, true);
174
+ };
175
+ /**
176
+ * Clears engagement after focus leaves the editor while the document is still empty.
177
+ *
178
+ * @param view - Parent CodeMirror editor view.
179
+ */
180
+ const handleEditorFocusOut = (view) => {
181
+ requestAnimationFrame(() => {
182
+ if (view.state.doc.length > 0 || isEditorDomFocused(view)) {
80
183
  return;
81
184
  }
82
- this.placeholder = shouldShowSyntaxPlaceholder(update.view)
83
- ? buildSyntaxPlaceholderDecorations(text, options)
84
- : Decoration.none;
85
- }
86
- get decorations() {
87
- return this.placeholder;
185
+ const plugin = view.plugin(placeholderPlugin);
186
+ if (plugin == null) {
187
+ return;
188
+ }
189
+ plugin.setEngaged(view, false);
190
+ });
191
+ };
192
+ const engagementHandlers = EditorView.domEventHandlers({
193
+ mousedown(event, view) {
194
+ handleEditorPointerDown(view, event);
195
+ return false;
196
+ },
197
+ pointerdown(event, view) {
198
+ handleEditorPointerDown(view, event);
199
+ return false;
200
+ },
201
+ focusout(_event, view) {
202
+ handleEditorFocusOut(view);
203
+ return false;
88
204
  }
89
- }, { decorations: (v) => v.decorations });
205
+ });
90
206
  return [
91
- plugin,
207
+ placeholderPlugin,
208
+ engagementHandlers,
92
209
  EditorView.contentAttributes.of({ 'aria-placeholder': text }),
93
210
  EditorView.theme({
94
211
  '.cm-content:has(.cm-syntax-placeholder) .cm-activeLine': {
@@ -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.57",
4
4
  "description": "TypeScript SDK for HarborClient development.",
5
5
  "keywords": [
6
6
  "harborclient",