@principal-ai/principal-view-core 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ConfigurationLoader.js +2 -1
- package/dist/ConfigurationLoader.js.map +1 -1
- package/dist/ConfigurationValidator.js.map +1 -1
- package/dist/EventProcessor.js.map +1 -1
- package/dist/EventRecorderService.js.map +1 -1
- package/dist/LibraryLoader.js.map +1 -1
- package/dist/PathBasedEventProcessor.js.map +1 -1
- package/dist/SessionManager.js +1 -1
- package/dist/SessionManager.js.map +1 -1
- package/dist/ValidationEngine.js.map +1 -1
- package/dist/cli/codegen.js.map +1 -1
- package/dist/codegen/type-generator.js.map +1 -1
- package/dist/codegen/usage-example.js.map +1 -1
- package/dist/helpers/GraphInstrumentationHelper.js +2 -2
- package/dist/helpers/GraphInstrumentationHelper.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/narrative/example.d.ts +11 -0
- package/dist/narrative/example.d.ts.map +1 -0
- package/dist/narrative/example.js +331 -0
- package/dist/narrative/example.js.map +1 -0
- package/dist/narrative/index.d.ts +12 -0
- package/dist/narrative/index.d.ts.map +1 -0
- package/dist/narrative/index.js +14 -0
- package/dist/narrative/index.js.map +1 -0
- package/dist/narrative/scenario-matcher.d.ts +87 -0
- package/dist/narrative/scenario-matcher.d.ts.map +1 -0
- package/dist/narrative/scenario-matcher.js +269 -0
- package/dist/narrative/scenario-matcher.js.map +1 -0
- package/dist/narrative/template-parser.d.ts +33 -0
- package/dist/narrative/template-parser.d.ts.map +1 -0
- package/dist/narrative/template-parser.js +288 -0
- package/dist/narrative/template-parser.js.map +1 -0
- package/dist/narrative/template-renderer.d.ts +18 -0
- package/dist/narrative/template-renderer.d.ts.map +1 -0
- package/dist/narrative/template-renderer.js +367 -0
- package/dist/narrative/template-renderer.js.map +1 -0
- package/dist/narrative/types.d.ts +268 -0
- package/dist/narrative/types.d.ts.map +1 -0
- package/dist/narrative/types.js +10 -0
- package/dist/narrative/types.js.map +1 -0
- package/dist/rules/config.js.map +1 -1
- package/dist/rules/engine.js.map +1 -1
- package/dist/rules/implementations/connection-type-references.js.map +1 -1
- package/dist/rules/implementations/dead-end-states.js.map +1 -1
- package/dist/rules/implementations/library-node-type-match.js.map +1 -1
- package/dist/rules/implementations/minimum-node-sources.js.map +1 -1
- package/dist/rules/implementations/no-unknown-fields.js.map +1 -1
- package/dist/rules/implementations/orphaned-edge-types.js.map +1 -1
- package/dist/rules/implementations/orphaned-node-types.js.map +1 -1
- package/dist/rules/implementations/required-metadata.js.map +1 -1
- package/dist/rules/implementations/state-transition-references.js.map +1 -1
- package/dist/rules/implementations/unreachable-states.js.map +1 -1
- package/dist/rules/implementations/valid-action-patterns.js.map +1 -1
- package/dist/rules/implementations/valid-color-format.js.map +1 -1
- package/dist/rules/implementations/valid-edge-types.js.map +1 -1
- package/dist/rules/implementations/valid-node-types.js.map +1 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/telemetry/coverage.js.map +1 -1
- package/dist/telemetry/event-validator.js.map +1 -1
- package/dist/types/audit.js.map +1 -1
- package/dist/types/canvas.js +5 -5
- package/dist/types/canvas.js.map +1 -1
- package/dist/types/otel.js.map +1 -1
- package/dist/types/resource-match.js.map +1 -1
- package/dist/utils/CanvasConverter.js.map +1 -1
- package/dist/utils/GraphConverter.js.map +1 -1
- package/dist/utils/LibraryConverter.js.map +1 -1
- package/dist/utils/PathMatcher.js.map +1 -1
- package/dist/utils/TraceToCanvas.js +7 -7
- package/dist/utils/TraceToCanvas.js.map +1 -1
- package/dist/utils/YamlParser.js.map +1 -1
- package/package.json +15 -15
- package/src/index.ts +31 -13
- package/src/narrative/README.md +381 -0
- package/src/narrative/__tests__/scenario-matcher.test.ts +368 -0
- package/src/narrative/__tests__/template-parser.test.ts +235 -0
- package/src/narrative/__tests__/template-renderer.test.ts +377 -0
- package/src/narrative/example.ts +349 -0
- package/src/narrative/index.ts +35 -0
- package/src/narrative/scenario-matcher.ts +331 -0
- package/src/narrative/template-parser.ts +298 -0
- package/src/narrative/template-renderer.ts +423 -0
- package/src/narrative/types.ts +368 -0
- package/src/utils/GraphConverter.test.ts +0 -79
- package/dist/utils/ExecutionFileDiscovery.d.ts +0 -206
- package/dist/utils/ExecutionFileDiscovery.d.ts.map +0 -1
- package/dist/utils/ExecutionFileDiscovery.js +0 -340
- package/dist/utils/ExecutionFileDiscovery.js.map +0 -1
- package/src/utils/ExecutionFileDiscovery.ts +0 -522
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Expression Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses and evaluates template expressions like:
|
|
5
|
+
* - Simple: "{config.nodeTypes}"
|
|
6
|
+
* - Conditional: "{result.violations.total > 0 ? '❌ FAILED' : '✅ PASSED'}"
|
|
7
|
+
* - Function calls: "{'━'.repeat(50)}"
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { getNestedValue } from './scenario-matcher';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parse and evaluate a template string with embedded expressions
|
|
14
|
+
*
|
|
15
|
+
* Expressions are enclosed in curly braces: {expression}
|
|
16
|
+
*
|
|
17
|
+
* Supported syntax:
|
|
18
|
+
* - Property access: {config.nodeTypes}
|
|
19
|
+
* - Ternary: {count > 0 ? 'yes' : 'no'}
|
|
20
|
+
* - String methods: {'━'.repeat(50)}
|
|
21
|
+
* - Arithmetic: {duration.ms / 1000}
|
|
22
|
+
*
|
|
23
|
+
* @param template - Template string with {expressions}
|
|
24
|
+
* @param context - Data context for variable lookup
|
|
25
|
+
* @returns Evaluated string
|
|
26
|
+
*/
|
|
27
|
+
export function parseTemplate(template: string, context: Record<string, unknown>): string {
|
|
28
|
+
// Find all {expression} patterns, handling nested braces
|
|
29
|
+
let result = '';
|
|
30
|
+
let i = 0;
|
|
31
|
+
|
|
32
|
+
while (i < template.length) {
|
|
33
|
+
const start = template.indexOf('{', i);
|
|
34
|
+
if (start === -1) {
|
|
35
|
+
// No more expressions
|
|
36
|
+
result += template.substring(i);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Add text before the expression
|
|
41
|
+
result += template.substring(i, start);
|
|
42
|
+
|
|
43
|
+
// Find matching closing brace
|
|
44
|
+
let depth = 1;
|
|
45
|
+
let end = start + 1;
|
|
46
|
+
while (end < template.length && depth > 0) {
|
|
47
|
+
if (template[end] === '{') depth++;
|
|
48
|
+
if (template[end] === '}') depth--;
|
|
49
|
+
end++;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (depth === 0) {
|
|
53
|
+
// Found matching brace
|
|
54
|
+
const expression = template.substring(start + 1, end - 1);
|
|
55
|
+
try {
|
|
56
|
+
const value = evaluateExpression(expression.trim(), context);
|
|
57
|
+
// Check if evaluation failed (returned undefined when it shouldn't)
|
|
58
|
+
if (value === undefined && !expression.trim().includes('undefined')) {
|
|
59
|
+
// If the expression doesn't contain 'undefined' but returned undefined,
|
|
60
|
+
// it likely failed to parse/evaluate
|
|
61
|
+
const isValidExpression =
|
|
62
|
+
expression.trim() in context || // Simple variable
|
|
63
|
+
/^[\w.]+$/.test(expression.trim()) || // Property path
|
|
64
|
+
expression.includes('?') || // Ternary
|
|
65
|
+
/[+\-*/<>=!]/.test(expression); // Operations
|
|
66
|
+
|
|
67
|
+
if (!isValidExpression && expression.includes('(')) {
|
|
68
|
+
result += `{ERROR: Unable to evaluate '${expression}'}`;
|
|
69
|
+
} else {
|
|
70
|
+
result += formatValue(value);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
result += formatValue(value);
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
// Return error placeholder instead of throwing
|
|
77
|
+
result += `{ERROR: ${error instanceof Error ? error.message : 'unknown'}}`;
|
|
78
|
+
}
|
|
79
|
+
i = end;
|
|
80
|
+
} else {
|
|
81
|
+
// Unmatched brace
|
|
82
|
+
result += '{';
|
|
83
|
+
i = start + 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Evaluate a single expression in the given context
|
|
92
|
+
*
|
|
93
|
+
* @param expression - Expression to evaluate
|
|
94
|
+
* @param context - Data context
|
|
95
|
+
* @returns Evaluated value
|
|
96
|
+
*/
|
|
97
|
+
export function evaluateExpression(expression: string, context: Record<string, unknown>): unknown {
|
|
98
|
+
// Handle literals FIRST (before operators to avoid false matches)
|
|
99
|
+
|
|
100
|
+
// Handle string literals
|
|
101
|
+
if ((expression.startsWith("'") && expression.endsWith("'")) || (expression.startsWith('"') && expression.endsWith('"'))) {
|
|
102
|
+
return expression.slice(1, -1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Handle number literals (including negative numbers)
|
|
106
|
+
if (/^-?\d+(\.\d+)?$/.test(expression)) {
|
|
107
|
+
return Number(expression);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Handle boolean literals
|
|
111
|
+
if (expression === 'true') return true;
|
|
112
|
+
if (expression === 'false') return false;
|
|
113
|
+
if (expression === 'null') return null;
|
|
114
|
+
if (expression === 'undefined') return undefined;
|
|
115
|
+
|
|
116
|
+
// Handle ternary operator: condition ? true : false
|
|
117
|
+
const ternaryMatch = expression.match(/^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/);
|
|
118
|
+
if (ternaryMatch) {
|
|
119
|
+
const [, condition, trueValue, falseValue] = ternaryMatch;
|
|
120
|
+
const conditionResult = evaluateExpression(condition, context);
|
|
121
|
+
const result = isTruthy(conditionResult)
|
|
122
|
+
? evaluateExpression(trueValue, context)
|
|
123
|
+
: evaluateExpression(falseValue, context);
|
|
124
|
+
|
|
125
|
+
// If the result is a string containing {expressions}, recursively parse it
|
|
126
|
+
if (typeof result === 'string' && result.includes('{') && result.includes('}')) {
|
|
127
|
+
return parseTemplate(result, context);
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle comparison operators
|
|
133
|
+
if (expression.includes('>=')) {
|
|
134
|
+
const [left, right] = expression.split('>=').map((s) => s.trim());
|
|
135
|
+
return Number(evaluateExpression(left, context)) >= Number(evaluateExpression(right, context));
|
|
136
|
+
}
|
|
137
|
+
if (expression.includes('<=')) {
|
|
138
|
+
const [left, right] = expression.split('<=').map((s) => s.trim());
|
|
139
|
+
return Number(evaluateExpression(left, context)) <= Number(evaluateExpression(right, context));
|
|
140
|
+
}
|
|
141
|
+
if (expression.includes('>')) {
|
|
142
|
+
const [left, right] = expression.split('>').map((s) => s.trim());
|
|
143
|
+
return Number(evaluateExpression(left, context)) > Number(evaluateExpression(right, context));
|
|
144
|
+
}
|
|
145
|
+
if (expression.includes('<')) {
|
|
146
|
+
const [left, right] = expression.split('<').map((s) => s.trim());
|
|
147
|
+
return Number(evaluateExpression(left, context)) < Number(evaluateExpression(right, context));
|
|
148
|
+
}
|
|
149
|
+
if (expression.includes('===')) {
|
|
150
|
+
const [left, right] = expression.split('===').map((s) => s.trim());
|
|
151
|
+
return evaluateExpression(left, context) === evaluateExpression(right, context);
|
|
152
|
+
}
|
|
153
|
+
if (expression.includes('!==')) {
|
|
154
|
+
const [left, right] = expression.split('!==').map((s) => s.trim());
|
|
155
|
+
return evaluateExpression(left, context) !== evaluateExpression(right, context);
|
|
156
|
+
}
|
|
157
|
+
if (expression.includes('==')) {
|
|
158
|
+
const [left, right] = expression.split('==').map((s) => s.trim());
|
|
159
|
+
// eslint-disable-next-line eqeqeq
|
|
160
|
+
return evaluateExpression(left, context) == evaluateExpression(right, context);
|
|
161
|
+
}
|
|
162
|
+
if (expression.includes('!=')) {
|
|
163
|
+
const [left, right] = expression.split('!=').map((s) => s.trim());
|
|
164
|
+
// eslint-disable-next-line eqeqeq
|
|
165
|
+
return evaluateExpression(left, context) != evaluateExpression(right, context);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Handle arithmetic operators (check for operators not at start to avoid matching negative numbers)
|
|
169
|
+
if (expression.includes('+') && !expression.includes("'") && !expression.includes('"')) {
|
|
170
|
+
const parts = expression.split('+');
|
|
171
|
+
if (parts.length > 1) {
|
|
172
|
+
const [left, right] = parts.map((s) => s.trim());
|
|
173
|
+
return Number(evaluateExpression(left, context)) + Number(evaluateExpression(right, context));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (expression.includes('-') && !expression.includes("'") && !expression.includes('"')) {
|
|
177
|
+
// Only treat as subtraction if there's content before the minus (to avoid matching negative numbers)
|
|
178
|
+
const minusIndex = expression.indexOf('-');
|
|
179
|
+
if (minusIndex > 0) {
|
|
180
|
+
const beforeMinus = expression.substring(0, minusIndex).trim();
|
|
181
|
+
if (beforeMinus.length > 0) {
|
|
182
|
+
const [left, right] = expression.split('-').map((s) => s.trim());
|
|
183
|
+
return Number(evaluateExpression(left, context)) - Number(evaluateExpression(right, context));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (expression.includes('*') && !expression.includes("'") && !expression.includes('"')) {
|
|
188
|
+
const [left, right] = expression.split('*').map((s) => s.trim());
|
|
189
|
+
return Number(evaluateExpression(left, context)) * Number(evaluateExpression(right, context));
|
|
190
|
+
}
|
|
191
|
+
if (expression.includes('/') && !expression.includes("'") && !expression.includes('"')) {
|
|
192
|
+
const [left, right] = expression.split('/').map((s) => s.trim());
|
|
193
|
+
return Number(evaluateExpression(left, context)) / Number(evaluateExpression(right, context));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Handle method calls (limited to safe string methods)
|
|
197
|
+
if (expression.includes('.')) {
|
|
198
|
+
// Support both single and double quotes
|
|
199
|
+
const methodMatch = expression.match(/^(['"])(.+)\1\.(\w+)\(([^)]*)\)$/);
|
|
200
|
+
if (methodMatch) {
|
|
201
|
+
const [, , str, method, argsStr] = methodMatch;
|
|
202
|
+
return callStringMethod(str, method, argsStr);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Property access with method call
|
|
206
|
+
const propMethodMatch = expression.match(/^([\w.[\]']+)\.(\w+)\(([^)]*)\)$/);
|
|
207
|
+
if (propMethodMatch) {
|
|
208
|
+
const [, propPath, method, argsStr] = propMethodMatch;
|
|
209
|
+
const value = evaluateExpression(propPath, context);
|
|
210
|
+
if (typeof value === 'string') {
|
|
211
|
+
return callStringMethod(value, method, argsStr);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Simple property access
|
|
216
|
+
return getNestedValue(context, expression);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Simple variable lookup
|
|
220
|
+
return context[expression];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Call a safe string method
|
|
225
|
+
*
|
|
226
|
+
* Only allows specific string methods to prevent code injection.
|
|
227
|
+
*
|
|
228
|
+
* @param str - String to operate on
|
|
229
|
+
* @param method - Method name
|
|
230
|
+
* @param argsStr - Arguments string
|
|
231
|
+
* @returns Result of method call
|
|
232
|
+
*/
|
|
233
|
+
function callStringMethod(str: string, method: string, argsStr: string): unknown {
|
|
234
|
+
const args = argsStr.split(',').map((s) => s.trim());
|
|
235
|
+
|
|
236
|
+
switch (method) {
|
|
237
|
+
case 'repeat': {
|
|
238
|
+
const count = Number(args[0]);
|
|
239
|
+
return str.repeat(count);
|
|
240
|
+
}
|
|
241
|
+
case 'substring': {
|
|
242
|
+
const start = Number(args[0]);
|
|
243
|
+
const end = args[1] !== undefined ? Number(args[1]) : undefined;
|
|
244
|
+
return str.substring(start, end);
|
|
245
|
+
}
|
|
246
|
+
case 'slice': {
|
|
247
|
+
const start = Number(args[0]);
|
|
248
|
+
const end = args[1] !== undefined ? Number(args[1]) : undefined;
|
|
249
|
+
return str.slice(start, end);
|
|
250
|
+
}
|
|
251
|
+
case 'toUpperCase':
|
|
252
|
+
return str.toUpperCase();
|
|
253
|
+
case 'toLowerCase':
|
|
254
|
+
return str.toLowerCase();
|
|
255
|
+
case 'trim':
|
|
256
|
+
return str.trim();
|
|
257
|
+
case 'replace': {
|
|
258
|
+
const search = args[0].replace(/^['"]|['"]$/g, '');
|
|
259
|
+
const replace = args[1].replace(/^['"]|['"]$/g, '');
|
|
260
|
+
return str.replace(search, replace);
|
|
261
|
+
}
|
|
262
|
+
default:
|
|
263
|
+
throw new Error(`Method ${method} is not allowed in templates`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Check if a value is truthy
|
|
269
|
+
*
|
|
270
|
+
* @param value - Value to check
|
|
271
|
+
* @returns True if value is truthy
|
|
272
|
+
*/
|
|
273
|
+
function isTruthy(value: unknown): boolean {
|
|
274
|
+
if (value === null || value === undefined) return false;
|
|
275
|
+
if (typeof value === 'boolean') return value;
|
|
276
|
+
if (typeof value === 'number') return value !== 0;
|
|
277
|
+
if (typeof value === 'string') return value.length > 0;
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Format a value for output
|
|
283
|
+
*
|
|
284
|
+
* @param value - Value to format
|
|
285
|
+
* @returns Formatted string
|
|
286
|
+
*/
|
|
287
|
+
function formatValue(value: unknown): string {
|
|
288
|
+
if (value === null || value === undefined) {
|
|
289
|
+
return '';
|
|
290
|
+
}
|
|
291
|
+
if (typeof value === 'boolean') {
|
|
292
|
+
return value ? 'true' : 'false';
|
|
293
|
+
}
|
|
294
|
+
if (typeof value === 'object') {
|
|
295
|
+
return JSON.stringify(value);
|
|
296
|
+
}
|
|
297
|
+
return String(value);
|
|
298
|
+
}
|