@solidxai/core 0.1.13-beta.2 → 0.1.13-beta.4

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.
Files changed (134) hide show
  1. package/dist/commands/seed.command.d.ts +2 -0
  2. package/dist/commands/seed.command.d.ts.map +1 -1
  3. package/dist/commands/seed.command.js +21 -0
  4. package/dist/commands/seed.command.js.map +1 -1
  5. package/dist/entities/saved-filters.entity.js +3 -3
  6. package/dist/entities/saved-filters.entity.js.map +1 -1
  7. package/dist/entities/workflow-trigger-execution.entity.js +1 -1
  8. package/dist/entities/workflow-trigger-execution.entity.js.map +1 -1
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +2 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/interfaces.d.ts +1 -0
  14. package/dist/interfaces.d.ts.map +1 -1
  15. package/dist/interfaces.js.map +1 -1
  16. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  17. package/dist/seeders/module-metadata-seeder.service.js +44 -27
  18. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  19. package/dist/seeders/seed-data/solid-core-metadata.json +80 -67
  20. package/dist/services/workflow/nodes/execution-fail.node.d.ts +6 -0
  21. package/dist/services/workflow/nodes/execution-fail.node.d.ts.map +1 -0
  22. package/dist/services/workflow/nodes/execution-fail.node.js +81 -0
  23. package/dist/services/workflow/nodes/execution-fail.node.js.map +1 -0
  24. package/dist/services/workflow/nodes/for-each.node.d.ts +2 -0
  25. package/dist/services/workflow/nodes/for-each.node.d.ts.map +1 -1
  26. package/dist/services/workflow/nodes/for-each.node.js +114 -5
  27. package/dist/services/workflow/nodes/for-each.node.js.map +1 -1
  28. package/dist/services/workflow/nodes/if.node.d.ts.map +1 -1
  29. package/dist/services/workflow/nodes/if.node.js +7 -0
  30. package/dist/services/workflow/nodes/if.node.js.map +1 -1
  31. package/dist/services/workflow/nodes/log-write.node.d.ts.map +1 -1
  32. package/dist/services/workflow/nodes/log-write.node.js +2 -0
  33. package/dist/services/workflow/nodes/log-write.node.js.map +1 -1
  34. package/dist/services/workflow/nodes/loop-until.node.d.ts +12 -0
  35. package/dist/services/workflow/nodes/loop-until.node.d.ts.map +1 -0
  36. package/dist/services/workflow/nodes/loop-until.node.js +281 -0
  37. package/dist/services/workflow/nodes/loop-until.node.js.map +1 -0
  38. package/dist/services/workflow/nodes/parallel.node.d.ts.map +1 -1
  39. package/dist/services/workflow/nodes/parallel.node.js +7 -0
  40. package/dist/services/workflow/nodes/parallel.node.js.map +1 -1
  41. package/dist/services/workflow/nodes/sequential.node.d.ts.map +1 -1
  42. package/dist/services/workflow/nodes/sequential.node.js +7 -0
  43. package/dist/services/workflow/nodes/sequential.node.js.map +1 -1
  44. package/dist/services/workflow/nodes/switch.node.d.ts.map +1 -1
  45. package/dist/services/workflow/nodes/switch.node.js +7 -0
  46. package/dist/services/workflow/nodes/switch.node.js.map +1 -1
  47. package/dist/services/workflow/workflow-definition-validator.service.d.ts.map +1 -1
  48. package/dist/services/workflow/workflow-definition-validator.service.js +5 -2
  49. package/dist/services/workflow/workflow-definition-validator.service.js.map +1 -1
  50. package/dist/services/workflow/workflow-expression.service.d.ts.map +1 -1
  51. package/dist/services/workflow/workflow-expression.service.js +5 -0
  52. package/dist/services/workflow/workflow-expression.service.js.map +1 -1
  53. package/dist/services/workflow/workflow-node-registry.service.d.ts +2 -1
  54. package/dist/services/workflow/workflow-node-registry.service.d.ts.map +1 -1
  55. package/dist/services/workflow/workflow-node-registry.service.js +7 -0
  56. package/dist/services/workflow/workflow-node-registry.service.js.map +1 -1
  57. package/dist/services/workflow/workflow-runtime.service.d.ts +8 -0
  58. package/dist/services/workflow/workflow-runtime.service.d.ts.map +1 -1
  59. package/dist/services/workflow/workflow-runtime.service.js +197 -32
  60. package/dist/services/workflow/workflow-runtime.service.js.map +1 -1
  61. package/dist/solid-core.module.d.ts.map +1 -1
  62. package/dist/solid-core.module.js +4 -0
  63. package/dist/solid-core.module.js.map +1 -1
  64. package/dist/types/workflow-dsl.types.d.ts +14 -0
  65. package/dist/types/workflow-dsl.types.d.ts.map +1 -1
  66. package/dist/types/workflow-dsl.types.js.map +1 -1
  67. package/dist-tests/api/authenticate.spec.js +119 -0
  68. package/dist-tests/api/authenticate.spec.js.map +1 -0
  69. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +97 -0
  70. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +1 -0
  71. package/dist-tests/api/ping.spec.js +21 -0
  72. package/dist-tests/api/ping.spec.js.map +1 -0
  73. package/dist-tests/helpers/auth.js +41 -0
  74. package/dist-tests/helpers/auth.js.map +1 -0
  75. package/dist-tests/helpers/env.js +11 -0
  76. package/dist-tests/helpers/env.js.map +1 -0
  77. package/docs/agent-builder/notebook-lm-prompt-for-agent-builder.md +136 -0
  78. package/docs/agent-builder/team-ready-note-agent-builder.md +305 -0
  79. package/docs/agent-hub-grooming.md +301 -0
  80. package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +438 -0
  81. package/docs/dashboards/dashboard-curl-smoke-tests.txt +146 -0
  82. package/docs/dashboards/delete-legacy-dashboard-metadata.sql +172 -0
  83. package/docs/datasource-introspection-ddl-analysis.md +326 -0
  84. package/docs/datasource-introspection-implementation-plan.md +306 -0
  85. package/docs/grouping-enhancements.md +89 -0
  86. package/docs/java-spring/README.md +3 -0
  87. package/docs/java-spring/solid-core-module-deep-dive-report.md +1317 -0
  88. package/docs/module-package-import-handoff.md +691 -0
  89. package/docs/seed-changes.md +65 -0
  90. package/docs/test-data-workflow.md +200 -0
  91. package/docs/type-declaration-import-issue.md +24 -0
  92. package/docs/workflow/business-automation-example-notes.md +309 -0
  93. package/docs/workflow/control-flow-node-addition-sop.md +324 -0
  94. package/docs/workflow/data-engineering-pipeline-example-notes.md +330 -0
  95. package/docs/workflow/foreach-example-notes.md +187 -0
  96. package/docs/workflow/hello-world-example-notes.md +271 -0
  97. package/docs/workflow/kestra-concepts-plugins-blueprints.md +315 -0
  98. package/docs/workflow/loop-until-example-notes.md +198 -0
  99. package/docs/workflow/microservices-and-apis-example-notes.md +264 -0
  100. package/docs/workflow/samples.md +25 -0
  101. package/docs/workflow/what-is-kestra.md +79 -0
  102. package/docs/workflow/workflow-core-module-handoff-summary.md +191 -0
  103. package/docs/workflow/workflow-module-brd.md +185 -0
  104. package/docs/workflow/workflow-module-domain-model.md +252 -0
  105. package/docs/workflow/workflow-module-metadata-dsl.md +302 -0
  106. package/docs/workflow/workflow-module-technical-spec.md +293 -0
  107. package/docs/workflow/workflow-node-type-contracts.md +635 -0
  108. package/docs/workflow/workflow-node-ui-metadata-contract.md +1262 -0
  109. package/docs/workflow/workflow-solidx-model-field-spec.md +284 -0
  110. package/package.json +1 -1
  111. package/src/commands/seed.command.ts +19 -0
  112. package/src/entities/saved-filters.entity.ts +3 -3
  113. package/src/entities/workflow-trigger-execution.entity.ts +1 -1
  114. package/src/index.ts +2 -0
  115. package/src/interfaces.ts +1 -0
  116. package/src/seeders/module-metadata-seeder.service.ts +50 -36
  117. package/src/seeders/seed-data/solid-core-metadata.json +80 -67
  118. package/src/services/workflow/nodes/execution-fail.node.ts +82 -0
  119. package/src/services/workflow/nodes/for-each.node.ts +140 -6
  120. package/src/services/workflow/nodes/if.node.ts +7 -0
  121. package/src/services/workflow/nodes/log-write.node.ts +2 -0
  122. package/src/services/workflow/nodes/loop-until.node.ts +343 -0
  123. package/src/services/workflow/nodes/parallel.node.ts +7 -0
  124. package/src/services/workflow/nodes/sequential.node.ts +7 -0
  125. package/src/services/workflow/nodes/switch.node.ts +7 -0
  126. package/src/services/workflow/workflow-definition-validator.service.ts +5 -2
  127. package/src/services/workflow/workflow-expression.service.ts +5 -0
  128. package/src/services/workflow/workflow-node-registry.service.ts +11 -0
  129. package/src/services/workflow/workflow-runtime.service.ts +274 -39
  130. package/src/solid-core.module.ts +4 -0
  131. package/src/types/workflow-dsl.types.ts +43 -0
  132. package/.claude/settings.local.json +0 -16
  133. package/CLAUDE.md +0 -26
  134. package/src/services/1.js +0 -6
@@ -0,0 +1,343 @@
1
+ import { BadRequestException } from '@nestjs/common';
2
+ import { WorkflowNodeProvider } from '../../../decorators/workflow-node-provider.decorator';
3
+ import { WorkflowNodeHandler } from '../../../interfaces/workflow-node-handler.interface';
4
+ import {
5
+ WorkflowNodeExecutionContext,
6
+ WorkflowNodeHandlerResult,
7
+ } from '../../../types/workflow-dsl.types';
8
+
9
+ type LoopUntilCheckFrequency = {
10
+ interval?: string | number;
11
+ maxIterations?: string | number;
12
+ maxDuration?: string | number;
13
+ failOnMaxReached?: boolean | string;
14
+ };
15
+
16
+ @WorkflowNodeProvider({
17
+ type: 'loopUntil',
18
+ kind: 'control',
19
+ category: 'control-flow',
20
+ label: 'Loop Until',
21
+ description: 'Runs child nodes repeatedly until a condition evaluates true.',
22
+ tags: ['loop', 'iteration', 'condition', 'polling'],
23
+ authoring: {
24
+ defaultConfiguration: {
25
+ condition: '{{ outputs.checkStatus.ready == true }}',
26
+ checkFrequency: {
27
+ interval: 'PT1M',
28
+ failOnMaxReached: false,
29
+ },
30
+ },
31
+ configurationLayout: {
32
+ type: 'tabs',
33
+ tabs: [
34
+ {
35
+ key: 'condition',
36
+ label: 'Condition',
37
+ groups: ['Condition'],
38
+ },
39
+ {
40
+ key: 'guardrails',
41
+ label: 'Guardrails',
42
+ groups: ['Guardrails'],
43
+ },
44
+ ],
45
+ },
46
+ configurationFields: [
47
+ {
48
+ key: 'condition',
49
+ label: 'Condition',
50
+ description:
51
+ 'Boolean expression evaluated after each loop iteration. It can read outputs from the latest child-node run.',
52
+ valueType: 'expression',
53
+ required: true,
54
+ expressionAllowed: true,
55
+ widgetHint: 'textarea',
56
+ group: 'Condition',
57
+ uiSchema: {
58
+ layout: {
59
+ width: 'full',
60
+ },
61
+ },
62
+ },
63
+ {
64
+ key: 'checkFrequency',
65
+ label: 'Check Frequency',
66
+ description:
67
+ 'Optional loop guardrails: interval, maxIterations, maxDuration, and failOnMaxReached.',
68
+ valueType: 'object',
69
+ expressionAllowed: true,
70
+ widgetHint: 'yaml-editor',
71
+ group: 'Guardrails',
72
+ uiSchema: {
73
+ layout: {
74
+ width: 'full',
75
+ },
76
+ editor: {
77
+ height: 'min(320px, calc(90vh - 360px))',
78
+ },
79
+ },
80
+ },
81
+ ],
82
+ childSlots: [
83
+ {
84
+ key: 'tasks',
85
+ label: 'Loop Body',
86
+ description: 'Nodes that run before the condition is evaluated.',
87
+ kind: 'sequence',
88
+ layout: 'sequential',
89
+ required: true,
90
+ },
91
+ {
92
+ key: 'errors',
93
+ label: 'Errors',
94
+ description: 'Nodes that run sequentially if this loop fails.',
95
+ kind: 'sequence',
96
+ layout: 'sequential',
97
+ },
98
+ ],
99
+ outputs: [
100
+ {
101
+ key: 'iterationCount',
102
+ label: 'Iteration Count',
103
+ description: 'Number of loop body iterations that ran.',
104
+ valueType: 'number',
105
+ path: 'iterationCount',
106
+ },
107
+ {
108
+ key: 'conditionMatched',
109
+ label: 'Condition Matched',
110
+ description: 'Whether the loop stopped because the condition evaluated true.',
111
+ valueType: 'boolean',
112
+ path: 'conditionMatched',
113
+ },
114
+ ],
115
+ supportsDescription: true,
116
+ supportsName: true,
117
+ },
118
+ documentation: {
119
+ summary:
120
+ 'Runs the loop body, evaluates the condition, and repeats after the configured interval until the condition is true or a guardrail is reached.',
121
+ },
122
+ ui: {
123
+ icon: 'si-repeat',
124
+ iconColor: '#0f766e',
125
+ iconBackgroundColor: '#ccfbf1',
126
+ iconBorderColor: '#99f6e4',
127
+ modalSize: 'lg',
128
+ layoutHints: {
129
+ groupOrder: ['Condition', 'Guardrails'],
130
+ },
131
+ },
132
+ })
133
+ export class LoopUntilNode implements WorkflowNodeHandler {
134
+ async execute(
135
+ context: WorkflowNodeExecutionContext,
136
+ ): Promise<WorkflowNodeHandlerResult> {
137
+ const configuration = context.node.configuration ?? {};
138
+ const condition = configuration.condition ?? configuration.expression;
139
+ if (!condition) {
140
+ throw new BadRequestException('loopUntil requires configuration.condition.');
141
+ }
142
+
143
+ const checkFrequency = this.resolveCheckFrequency(
144
+ context.expression.interpolate(configuration.checkFrequency ?? {}, context),
145
+ );
146
+ const childNodes = context.node.tasks ?? [];
147
+ const startedAt = Date.now();
148
+ let iterationCount = 0;
149
+ let conditionMatched = false;
150
+ let maxReached = false;
151
+ let latestChildOutputs: Record<string, any> = {};
152
+
153
+ while (true) {
154
+ iterationCount += 1;
155
+ context.outputs[context.node.id] = {
156
+ iterationCount,
157
+ conditionMatched: false,
158
+ maxReached: false,
159
+ };
160
+
161
+ const iterationOutputs = {
162
+ ...(context.outputs ?? {}),
163
+ };
164
+
165
+ latestChildOutputs = await context.runNodes(childNodes, {
166
+ outputs: iterationOutputs,
167
+ });
168
+
169
+ Object.assign(context.outputs, iterationOutputs);
170
+
171
+ conditionMatched = context.expression.evaluateCondition(condition, {
172
+ ...context,
173
+ outputs: context.outputs,
174
+ });
175
+
176
+ context.outputs[context.node.id] = {
177
+ iterationCount,
178
+ conditionMatched,
179
+ maxReached: false,
180
+ latestOutputs: latestChildOutputs,
181
+ };
182
+
183
+ await context.emitLog({
184
+ level: 'info',
185
+ eventType: 'node.loopUntil.evaluated',
186
+ source: 'loopUntil',
187
+ message: `LoopUntil node "${context.node.id}" iteration ${iterationCount} evaluated to ${conditionMatched}.`,
188
+ context: {
189
+ condition,
190
+ iterationCount,
191
+ conditionMatched,
192
+ },
193
+ });
194
+
195
+ if (conditionMatched) {
196
+ break;
197
+ }
198
+
199
+ maxReached = this.hasReachedGuardrail(
200
+ iterationCount,
201
+ startedAt,
202
+ checkFrequency,
203
+ );
204
+ if (maxReached) {
205
+ if (checkFrequency.failOnMaxReached) {
206
+ throw new BadRequestException(
207
+ `loopUntil "${context.node.id}" reached its maximum check frequency guardrail before the condition matched.`,
208
+ );
209
+ }
210
+ break;
211
+ }
212
+
213
+ if (checkFrequency.intervalMs > 0) {
214
+ await this.delay(checkFrequency.intervalMs);
215
+ }
216
+ }
217
+
218
+ return {
219
+ output: {
220
+ iterationCount,
221
+ conditionMatched,
222
+ maxReached,
223
+ latestOutputs: latestChildOutputs,
224
+ },
225
+ };
226
+ }
227
+
228
+ private resolveCheckFrequency(value: any) {
229
+ const checkFrequency: LoopUntilCheckFrequency =
230
+ value && typeof value === 'object' && !Array.isArray(value) ? value : {};
231
+ const maxIterations = this.optionalPositiveInteger(
232
+ checkFrequency.maxIterations,
233
+ 'checkFrequency.maxIterations',
234
+ );
235
+ const maxDurationMs =
236
+ checkFrequency.maxDuration === undefined ||
237
+ checkFrequency.maxDuration === null ||
238
+ checkFrequency.maxDuration === ''
239
+ ? undefined
240
+ : this.parseDurationMs(checkFrequency.maxDuration, 'checkFrequency.maxDuration');
241
+
242
+ return {
243
+ intervalMs: this.parseDurationMs(
244
+ checkFrequency.interval ?? 'PT1M',
245
+ 'checkFrequency.interval',
246
+ ),
247
+ maxIterations,
248
+ maxDurationMs,
249
+ failOnMaxReached: this.resolveBoolean(checkFrequency.failOnMaxReached),
250
+ };
251
+ }
252
+
253
+ private hasReachedGuardrail(
254
+ iterationCount: number,
255
+ startedAt: number,
256
+ checkFrequency: {
257
+ maxIterations?: number;
258
+ maxDurationMs?: number;
259
+ },
260
+ ): boolean {
261
+ return (
262
+ (checkFrequency.maxIterations !== undefined &&
263
+ iterationCount >= checkFrequency.maxIterations) ||
264
+ (checkFrequency.maxDurationMs !== undefined &&
265
+ Date.now() - startedAt >= checkFrequency.maxDurationMs)
266
+ );
267
+ }
268
+
269
+ private optionalPositiveInteger(value: any, label: string): number | undefined {
270
+ if (value === undefined || value === null || value === '') {
271
+ return undefined;
272
+ }
273
+
274
+ const parsed = Number(value);
275
+ if (!Number.isFinite(parsed) || parsed < 1) {
276
+ throw new BadRequestException(`${label} must be a positive integer.`);
277
+ }
278
+
279
+ return Math.floor(parsed);
280
+ }
281
+
282
+ private parseDurationMs(value: any, label: string): number {
283
+ if (typeof value === 'number') {
284
+ if (!Number.isFinite(value) || value < 0) {
285
+ throw new BadRequestException(`${label} must be a non-negative duration.`);
286
+ }
287
+ return value;
288
+ }
289
+
290
+ const raw = String(value ?? '').trim();
291
+ if (!raw) {
292
+ return 0;
293
+ }
294
+
295
+ if (/^\d+$/.test(raw)) {
296
+ return Number(raw);
297
+ }
298
+
299
+ const isoMatch = raw.match(
300
+ /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/i,
301
+ );
302
+ if (isoMatch) {
303
+ const [, days, hours, minutes, seconds] = isoMatch;
304
+ return (
305
+ Number(days ?? 0) * 24 * 60 * 60 * 1000 +
306
+ Number(hours ?? 0) * 60 * 60 * 1000 +
307
+ Number(minutes ?? 0) * 60 * 1000 +
308
+ Number(seconds ?? 0) * 1000
309
+ );
310
+ }
311
+
312
+ const unitMatch = raw.match(/^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/i);
313
+ if (unitMatch) {
314
+ const amount = Number(unitMatch[1]);
315
+ const unit = unitMatch[2].toLowerCase();
316
+ const multiplier =
317
+ unit === 'ms'
318
+ ? 1
319
+ : unit === 's'
320
+ ? 1000
321
+ : unit === 'm'
322
+ ? 60 * 1000
323
+ : unit === 'h'
324
+ ? 60 * 60 * 1000
325
+ : 24 * 60 * 60 * 1000;
326
+ return amount * multiplier;
327
+ }
328
+
329
+ throw new BadRequestException(`${label} must be milliseconds, 1s/1m style, or ISO-8601 duration.`);
330
+ }
331
+
332
+ private resolveBoolean(value: any): boolean {
333
+ if (value === true || value === 'true') {
334
+ return true;
335
+ }
336
+
337
+ return false;
338
+ }
339
+
340
+ private delay(ms: number): Promise<void> {
341
+ return new Promise((resolve) => setTimeout(resolve, ms));
342
+ }
343
+ }
@@ -23,6 +23,13 @@ import {
23
23
  layout: 'parallel',
24
24
  required: true,
25
25
  },
26
+ {
27
+ key: 'errors',
28
+ label: 'Errors',
29
+ description: 'Nodes that run sequentially if this parallel branch fails.',
30
+ kind: 'sequence',
31
+ layout: 'sequential',
32
+ },
26
33
  ],
27
34
  supportsDescription: true,
28
35
  supportsName: true,
@@ -23,6 +23,13 @@ import {
23
23
  layout: 'sequential',
24
24
  required: true,
25
25
  },
26
+ {
27
+ key: 'errors',
28
+ label: 'Errors',
29
+ description: 'Nodes that run sequentially if this sequence fails.',
30
+ kind: 'sequence',
31
+ layout: 'sequential',
32
+ },
26
33
  ],
27
34
  supportsDescription: true,
28
35
  supportsName: true,
@@ -47,6 +47,13 @@ import {
47
47
  'Fallback sequence that runs when no case key matches the resolved value.',
48
48
  kind: 'sequence',
49
49
  },
50
+ {
51
+ key: 'errors',
52
+ label: 'Errors',
53
+ description: 'Nodes that run sequentially if the selected case fails.',
54
+ kind: 'sequence',
55
+ layout: 'sequential',
56
+ },
50
57
  ],
51
58
  supportsDescription: true,
52
59
  supportsName: true,
@@ -30,6 +30,8 @@ export class WorkflowDefinitionValidatorService {
30
30
 
31
31
  const nodeIds = new Set<string>();
32
32
  this.validateNodes(definition.nodes, nodeIds);
33
+ this.validateNodes(definition.errors ?? [], nodeIds);
34
+ this.validateNodes(definition.finally ?? [], nodeIds);
33
35
  this.validateTriggers(definition);
34
36
  }
35
37
 
@@ -144,12 +146,12 @@ export class WorkflowDefinitionValidatorService {
144
146
  for (const unsupportedKey of ['children', 'branches', 'nodes']) {
145
147
  if (Object.prototype.hasOwnProperty.call(node, unsupportedKey)) {
146
148
  throw new BadRequestException(
147
- `Workflow node "${node.id ?? 'unknown'}" uses unsupported child key "${unsupportedKey}". Use "tasks", "then", "else", "defaults", or "cases" instead.`,
149
+ `Workflow node "${node.id ?? 'unknown'}" uses unsupported child key "${unsupportedKey}". Use "tasks", "then", "else", "defaults", "errors", or "cases" instead.`,
148
150
  );
149
151
  }
150
152
  }
151
153
 
152
- for (const childKey of ['tasks', 'then', 'else', 'defaults'] as const) {
154
+ for (const childKey of ['tasks', 'then', 'else', 'defaults', 'errors'] as const) {
153
155
  const childValue = node[childKey];
154
156
  if (
155
157
  childValue !== undefined &&
@@ -216,6 +218,7 @@ export class WorkflowDefinitionValidatorService {
216
218
  this.validateNodes(node.then ?? [], nodeIds);
217
219
  this.validateNodes(node.else ?? [], nodeIds);
218
220
  this.validateNodes(node.defaults ?? [], nodeIds);
221
+ this.validateNodes(node.errors ?? [], nodeIds);
219
222
  Object.values(node.cases ?? {}).forEach((caseNodes) => {
220
223
  this.validateNodes(caseNodes, nodeIds);
221
224
  });
@@ -139,10 +139,15 @@ export class WorkflowExpressionService implements WorkflowExpressionResolver {
139
139
  node: context.node,
140
140
  item: context.item,
141
141
  index: context.index,
142
+ parent: context.parent,
143
+ parents: context.parents,
144
+ loops: context.loops,
145
+ error: context.error,
142
146
  };
143
147
 
144
148
  const parts = path
145
149
  .replace(/\[(?:'|")([^'"]+)(?:'|")\]/g, '.$1')
150
+ .replace(/\[(\d+)\]/g, '.$1')
146
151
  .split('.')
147
152
  .filter(Boolean);
148
153
 
@@ -53,6 +53,17 @@ export class WorkflowNodeRegistryService implements OnModuleInit {
53
53
  return provider.handler;
54
54
  }
55
55
 
56
+ getMetadata(type: string): WorkflowNodeProviderMetadata {
57
+ const provider = this.providers.get(type);
58
+ if (!provider) {
59
+ throw new BadRequestException(
60
+ `No workflow node provider registered for type "${type}".`,
61
+ );
62
+ }
63
+
64
+ return provider.metadata;
65
+ }
66
+
56
67
  list(): WorkflowNodeMetadataResponse[] {
57
68
  return Array.from(this.providers.values())
58
69
  .map(({ metadata }) => this.toMetadataResponse(metadata))