@fincity/kirun-js 2.15.1 → 2.16.1

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.
@@ -47,6 +47,9 @@ export class KIRuntime extends AbstractFunction {
47
47
  private fd: FunctionDefinition;
48
48
 
49
49
  private debugMode: boolean = false;
50
+
51
+ // Cache for function lookups to avoid repeated async calls
52
+ private functionCache: Map<string, Function> = new Map();
50
53
 
51
54
  public constructor(fd: FunctionDefinition, debugMode: boolean = false) {
52
55
  super();
@@ -67,13 +70,37 @@ export class KIRuntime extends AbstractFunction {
67
70
  return this.fd;
68
71
  }
69
72
 
73
+ private async getCachedFunction(
74
+ fRepo: Repository<Function>,
75
+ namespace: string,
76
+ name: string,
77
+ ): Promise<Function | undefined> {
78
+ const key = `${namespace}.${name}`;
79
+ if (this.functionCache.has(key)) {
80
+ return this.functionCache.get(key);
81
+ }
82
+ const fun = await fRepo.find(namespace, name);
83
+ if (fun) {
84
+ this.functionCache.set(key, fun);
85
+ }
86
+ return fun;
87
+ }
88
+
70
89
  public async getExecutionPlan(
71
90
  fRepo: Repository<Function>,
72
91
  sRepo: Repository<Schema>,
73
92
  ): Promise<ExecutionGraph<string, StatementExecution>> {
74
93
  let g: ExecutionGraph<string, StatementExecution> = new ExecutionGraph();
75
- for (let s of Array.from(this.fd.getSteps().values()))
76
- g.addVertex(await this.prepareStatementExecution(s, fRepo, sRepo));
94
+
95
+ // Parallelize statement preparation
96
+ const statements = Array.from(this.fd.getSteps().values());
97
+ const statementExecutions = await Promise.all(
98
+ statements.map((s) => this.prepareStatementExecution(s, fRepo, sRepo))
99
+ );
100
+
101
+ for (const se of statementExecutions) {
102
+ g.addVertex(se);
103
+ }
77
104
 
78
105
  let unresolved = this.makeEdges(g);
79
106
 
@@ -117,11 +144,6 @@ export class KIRuntime extends AbstractFunction {
117
144
  console.log(`EID: ${inContext.getExecutionId()} ${eGraph?.toString()}`);
118
145
  }
119
146
 
120
- // if (logger.isDebugEnabled()) {
121
- // logger.debug(StringFormatter.format("Executing : $.$", this.fd.getNamespace(), this.fd.getName()));
122
- // logger.debug(eGraph.toString());
123
- // }
124
-
125
147
  let messages: string[] = eGraph
126
148
  .getVerticesData()
127
149
  .filter((e) => e.getMessages().length)
@@ -157,13 +179,19 @@ export class KIRuntime extends AbstractFunction {
157
179
  (!executionQue.isEmpty() || !branchQue.isEmpty()) &&
158
180
  !inContext.getEvents()?.has(Event.OUTPUT)
159
181
  ) {
182
+ const prevExecQueSize = executionQue.length;
183
+ const prevBranchQueSize = branchQue.length;
184
+
160
185
  await this.processBranchQue(inContext, executionQue, branchQue);
161
186
  await this.processExecutionQue(inContext, executionQue, branchQue);
162
187
 
163
- inContext.setCount(inContext.getCount() + 1);
188
+ // Only increment count when actual work was done
189
+ if (prevExecQueSize !== executionQue.length || prevBranchQueSize !== branchQue.length) {
190
+ inContext.setCount(inContext.getCount() + 1);
164
191
 
165
- if (inContext.getCount() == KIRuntime.MAX_EXECUTION_ITERATIONS)
166
- throw new KIRuntimeException('Execution locked in an infinite loop');
192
+ if (inContext.getCount() == KIRuntime.MAX_EXECUTION_ITERATIONS)
193
+ throw new KIRuntimeException('Execution locked in an infinite loop');
194
+ }
167
195
  }
168
196
 
169
197
  if (!eGraph.isSubGraph() && !inContext.getEvents()?.size) {
@@ -192,19 +220,44 @@ export class KIRuntime extends AbstractFunction {
192
220
  >
193
221
  >,
194
222
  ) {
195
- if (!executionQue.isEmpty()) {
196
- let vertex: GraphVertex<string, StatementExecution> = executionQue.pop();
197
-
198
- if (!(await this.allDependenciesResolvedVertex(vertex, inContext.getSteps()!)))
199
- executionQue.add(vertex);
200
- else
201
- await this.executeVertex(
202
- vertex,
203
- inContext,
204
- branchQue,
205
- executionQue,
206
- inContext.getFunctionRepository(),
207
- );
223
+ if (executionQue.isEmpty()) return;
224
+
225
+ // Collect all vertices from the queue
226
+ const allVertices: GraphVertex<string, StatementExecution>[] = [];
227
+ while (!executionQue.isEmpty()) {
228
+ allVertices.push(executionQue.pop());
229
+ }
230
+
231
+ // Separate ready and not-ready vertices
232
+ const readyVertices: GraphVertex<string, StatementExecution>[] = [];
233
+ const notReadyVertices: GraphVertex<string, StatementExecution>[] = [];
234
+
235
+ for (const vertex of allVertices) {
236
+ if (this.allDependenciesResolvedVertex(vertex, inContext.getSteps()!)) {
237
+ readyVertices.push(vertex);
238
+ } else {
239
+ notReadyVertices.push(vertex);
240
+ }
241
+ }
242
+
243
+ // Add not-ready vertices back to the queue
244
+ for (const vertex of notReadyVertices) {
245
+ executionQue.add(vertex);
246
+ }
247
+
248
+ // Execute all ready vertices in parallel
249
+ if (readyVertices.length > 0) {
250
+ await Promise.all(
251
+ readyVertices.map((vertex) =>
252
+ this.executeVertex(
253
+ vertex,
254
+ inContext,
255
+ branchQue,
256
+ executionQue,
257
+ inContext.getFunctionRepository(),
258
+ )
259
+ )
260
+ );
208
261
  }
209
262
  }
210
263
 
@@ -220,17 +273,40 @@ export class KIRuntime extends AbstractFunction {
220
273
  >
221
274
  >,
222
275
  ) {
223
- if (branchQue.length) {
224
- let branch: Tuple4<
225
- ExecutionGraph<string, StatementExecution>,
226
- Tuple2<string, string>[],
227
- FunctionOutput,
228
- GraphVertex<string, StatementExecution>
229
- > = branchQue.pop();
276
+ if (!branchQue.length) return;
230
277
 
231
- if (!(await this.allDependenciesResolvedTuples(branch.getT2(), inContext.getSteps()!)))
232
- branchQue.add(branch);
233
- else await this.executeBranch(inContext, executionQue, branch);
278
+ // Collect all branches from the queue
279
+ const allBranches: Tuple4<
280
+ ExecutionGraph<string, StatementExecution>,
281
+ Tuple2<string, string>[],
282
+ FunctionOutput,
283
+ GraphVertex<string, StatementExecution>
284
+ >[] = [];
285
+
286
+ while (branchQue.length) {
287
+ allBranches.push(branchQue.pop());
288
+ }
289
+
290
+ // Separate ready and not-ready branches
291
+ const readyBranches: typeof allBranches = [];
292
+ const notReadyBranches: typeof allBranches = [];
293
+
294
+ for (const branch of allBranches) {
295
+ if (this.allDependenciesResolvedTuples(branch.getT2(), inContext.getSteps()!)) {
296
+ readyBranches.push(branch);
297
+ } else {
298
+ notReadyBranches.push(branch);
299
+ }
300
+ }
301
+
302
+ // Add not-ready branches back to the queue
303
+ for (const branch of notReadyBranches) {
304
+ branchQue.add(branch);
305
+ }
306
+
307
+ // Execute all ready branches (sequentially since they may have shared state)
308
+ for (const branch of readyBranches) {
309
+ await this.executeBranch(inContext, executionQue, branch);
234
310
  }
235
311
  }
236
312
 
@@ -246,13 +322,21 @@ export class KIRuntime extends AbstractFunction {
246
322
  ) {
247
323
  let vertex: GraphVertex<string, StatementExecution> = branch.getT4();
248
324
  let nextOutput: EventResult | undefined = undefined;
325
+
326
+ // Pre-compute statement names to delete - avoid recalculating each iteration
327
+ const statementsToDelete = branch
328
+ .getT1()
329
+ .getVerticesData()
330
+ .map((e) => e.getStatement().getStatementName());
249
331
 
250
332
  do {
251
- branch
252
- .getT1()
253
- .getVerticesData()
254
- .map((e) => e.getStatement().getStatementName())
255
- .forEach((e) => inContext.getSteps()?.delete(e));
333
+ // Clear previous iteration's step outputs
334
+ const steps = inContext.getSteps();
335
+ if (steps) {
336
+ for (const statementName of statementsToDelete) {
337
+ steps.delete(statementName);
338
+ }
339
+ }
256
340
 
257
341
  await this.executeGraph(branch.getT1(), inContext);
258
342
  nextOutput = branch.getT3().next();
@@ -285,10 +369,12 @@ export class KIRuntime extends AbstractFunction {
285
369
  } while (nextOutput && nextOutput.getName() != Event.OUTPUT);
286
370
 
287
371
  if (nextOutput?.getName() == Event.OUTPUT && vertex.getOutVertices().has(Event.OUTPUT)) {
288
- (vertex?.getOutVertices()?.get(Event.OUTPUT) ?? []).forEach(async (e) => {
289
- if (await this.allDependenciesResolvedVertex(e, inContext.getSteps()!))
372
+ const outVertices = Array.from(vertex?.getOutVertices()?.get(Event.OUTPUT) ?? []);
373
+ for (const e of outVertices) {
374
+ if (this.allDependenciesResolvedVertex(e, inContext.getSteps()!)) {
290
375
  executionQue.add(e);
291
- });
376
+ }
377
+ }
292
378
  }
293
379
  }
294
380
 
@@ -317,10 +403,12 @@ export class KIRuntime extends AbstractFunction {
317
403
  })
318
404
  .every((e) => !isNullValue(e) && e !== false);
319
405
 
320
- if (!allTrue) return;
406
+ if (!allTrue) {
407
+ return;
408
+ }
321
409
  }
322
410
 
323
- let fun: Function | undefined = await fRepo.find(s.getNamespace(), s.getName());
411
+ let fun: Function | undefined = await this.getCachedFunction(fRepo, s.getNamespace(), s.getName());
324
412
 
325
413
  if (!fun) {
326
414
  throw new KIRuntimeException(
@@ -413,17 +501,23 @@ export class KIRuntime extends AbstractFunction {
413
501
 
414
502
  if (!isOutput) {
415
503
  let subGraph = vertex.getSubGraphOfType(er.getName());
416
- let unResolvedDependencies: Tuple2<string, string>[] = this.makeEdges(subGraph).getT1();
504
+ let unResolvedDependencies: Tuple2<string, string>[] = [];
505
+ if (!subGraph.areEdgesBuilt()) {
506
+ unResolvedDependencies = this.makeEdges(subGraph).getT1();
507
+ subGraph.setEdgesBuilt(true);
508
+ }
417
509
  branchQue.push(new Tuple4(subGraph, unResolvedDependencies, result, vertex));
418
510
  } else {
419
511
  let out: Set<GraphVertex<string, StatementExecution>> | undefined = vertex
420
512
  .getOutVertices()
421
513
  .get(Event.OUTPUT);
422
- if (out)
423
- out.forEach(async (e) => {
424
- if (await this.allDependenciesResolvedVertex(e, inContext.getSteps()!))
514
+ if (out) {
515
+ for (const e of Array.from(out)) {
516
+ if (this.allDependenciesResolvedVertex(e, inContext.getSteps()!)) {
425
517
  executionQue.add(e);
426
- });
518
+ }
519
+ }
520
+ }
427
521
  }
428
522
  }
429
523
 
@@ -433,22 +527,20 @@ export class KIRuntime extends AbstractFunction {
433
527
  ): Map<string, any> {
434
528
  if (!result) return result;
435
529
 
436
- return Array.from(result.entries())
437
- .map((e) => new Tuple2(e[0], this.resolveInternalExpression(e[1], inContext)))
438
- .reduce((a, c) => {
439
- a.set(c.getT1(), c.getT2());
440
- return a;
441
- }, new Map());
530
+ const resolved = new Map<string, any>();
531
+ for (const [key, value] of result.entries()) {
532
+ resolved.set(key, this.resolveInternalExpression(value, inContext));
533
+ }
534
+ return resolved;
442
535
  }
443
536
 
444
537
  private resolveInternalExpression(value: any, inContext: FunctionExecutionParameters): any {
445
538
  if (isNullValue(value) || typeof value != 'object') return value;
446
539
 
447
540
  if (value instanceof JsonExpression) {
448
- let exp: ExpressionEvaluator = new ExpressionEvaluator(
541
+ return new ExpressionEvaluator(
449
542
  (value as JsonExpression).getExpression(),
450
- );
451
- return exp.evaluate(inContext.getValuesMap());
543
+ ).evaluate(inContext.getValuesMap());
452
544
  }
453
545
 
454
546
  if (Array.isArray(value)) {
@@ -490,16 +582,18 @@ export class KIRuntime extends AbstractFunction {
490
582
  vertex: GraphVertex<string, StatementExecution>,
491
583
  output: Map<string, Map<string, Map<string, any>>>,
492
584
  ): boolean {
493
- if (!vertex.getInVertices().size) return true;
494
-
495
- return (
496
- Array.from(vertex.getInVertices()).filter((e) => {
497
- let stepName: string = e.getT1().getData().getStatement().getStatementName();
498
- let type: string = e.getT2();
499
-
500
- return !(output.has(stepName) && output.get(stepName)?.has(type));
501
- }).length == 0
502
- );
585
+ const inVertices = vertex.getInVertices();
586
+ if (!inVertices.size) return true;
587
+
588
+ // Use for..of directly instead of Array.from + filter
589
+ for (const e of inVertices) {
590
+ const stepName = e.getT1().getData().getStatement().getStatementName();
591
+ const type = e.getT2();
592
+ if (!(output.has(stepName) && output.get(stepName)?.has(type))) {
593
+ return false;
594
+ }
595
+ }
596
+ return true;
503
597
  }
504
598
 
505
599
  private getArgumentsFromParametersMap(
@@ -507,35 +601,33 @@ export class KIRuntime extends AbstractFunction {
507
601
  s: Statement,
508
602
  paramSet: Map<string, Parameter>,
509
603
  ): Map<string, any> {
510
- return Array.from(s.getParameterMap().entries())
511
- .map((e) => {
512
- let prList: ParameterReference[] = Array.from(e[1]?.values() ?? []);
513
-
514
- let ret: any = undefined;
515
-
516
- if (!prList?.length) return new Tuple2(e[0], ret);
517
-
518
- let pDef: Parameter | undefined = paramSet.get(e[0]);
519
-
520
- if (!pDef) return new Tuple2(e[0], undefined);
521
-
522
- if (pDef.isVariableArgument()) {
523
- ret = prList
524
- .sort((a, b) => (a.getOrder() ?? 0) - (b.getOrder() ?? 0))
525
- .filter((r) => !isNullValue(r))
526
- .map((r) => this.parameterReferenceEvaluation(inContext, r))
527
- .flatMap((r) => (Array.isArray(r) ? r : [r]));
528
- } else {
529
- ret = this.parameterReferenceEvaluation(inContext, prList[0]);
530
- }
604
+ const args = new Map<string, any>();
605
+
606
+ for (const [paramName, paramRefMap] of s.getParameterMap().entries()) {
607
+ const prList: ParameterReference[] = Array.from(paramRefMap?.values() ?? []);
608
+
609
+ if (!prList?.length) continue;
610
+
611
+ const pDef: Parameter | undefined = paramSet.get(paramName);
612
+ if (!pDef) continue;
613
+
614
+ let ret: any;
615
+ if (pDef.isVariableArgument()) {
616
+ ret = prList
617
+ .sort((a, b) => (a.getOrder() ?? 0) - (b.getOrder() ?? 0))
618
+ .filter((r) => !isNullValue(r))
619
+ .map((r) => this.parameterReferenceEvaluation(inContext, r))
620
+ .flatMap((r) => (Array.isArray(r) ? r : [r]));
621
+ } else {
622
+ ret = this.parameterReferenceEvaluation(inContext, prList[0]);
623
+ }
531
624
 
532
- return new Tuple2(e[0], ret);
533
- })
534
- .filter((e) => !isNullValue(e.getT2()))
535
- .reduce((a, c) => {
536
- a.set(c.getT1(), c.getT2());
537
- return a;
538
- }, new Map());
625
+ if (!isNullValue(ret)) {
626
+ args.set(paramName, ret);
627
+ }
628
+ }
629
+
630
+ return args;
539
631
  }
540
632
 
541
633
  private parameterReferenceEvaluation(
@@ -550,8 +642,7 @@ export class KIRuntime extends AbstractFunction {
550
642
  ref.getType() == ParameterReferenceType.EXPRESSION &&
551
643
  !StringUtil.isNullOrBlank(ref.getExpression())
552
644
  ) {
553
- let exp: ExpressionEvaluator = new ExpressionEvaluator(ref.getExpression() ?? '');
554
- ret = exp.evaluate(inContext.getValuesMap());
645
+ ret = new ExpressionEvaluator(ref.getExpression() ?? '').evaluate(inContext.getValuesMap());
555
646
  }
556
647
  return ret;
557
648
  }
@@ -563,7 +654,7 @@ export class KIRuntime extends AbstractFunction {
563
654
  ): Promise<StatementExecution> {
564
655
  let se: StatementExecution = new StatementExecution(s);
565
656
 
566
- let fun: Function | undefined = await fRepo.find(s.getNamespace(), s.getName());
657
+ let fun: Function | undefined = await this.getCachedFunction(fRepo, s.getNamespace(), s.getName());
567
658
 
568
659
  if (!fun) {
569
660
  se.addMessage(
@@ -13,6 +13,10 @@ export class Expression extends ExpressionToken {
13
13
  private tokens: LinkedList<ExpressionToken> = new LinkedList();
14
14
  // Data structure for storing operations
15
15
  private ops: LinkedList<Operation> = new LinkedList();
16
+
17
+ // Cached arrays for fast evaluation (avoids LinkedList traversal)
18
+ private cachedTokensArray?: ExpressionToken[];
19
+ private cachedOpsArray?: Operation[];
16
20
 
17
21
  public constructor(
18
22
  expression?: string,
@@ -45,6 +49,21 @@ export class Expression extends ExpressionToken {
45
49
  public getOperations(): LinkedList<Operation> {
46
50
  return this.ops;
47
51
  }
52
+
53
+ // Fast array access for evaluation (cached)
54
+ public getTokensArray(): ExpressionToken[] {
55
+ if (!this.cachedTokensArray) {
56
+ this.cachedTokensArray = this.tokens.toArray();
57
+ }
58
+ return this.cachedTokensArray;
59
+ }
60
+
61
+ public getOperationsArray(): Operation[] {
62
+ if (!this.cachedOpsArray) {
63
+ this.cachedOpsArray = this.ops.toArray();
64
+ }
65
+ return this.cachedOpsArray;
66
+ }
48
67
 
49
68
  private evaluate(): void {
50
69
  const length: number = this.expression.length;
@@ -345,11 +364,8 @@ export class Expression extends ExpressionToken {
345
364
  'Missing a closed parenthesis',
346
365
  );
347
366
 
348
- while (
349
- subExp.length() > 2 &&
350
- subExp.charAt(0) == '(' &&
351
- subExp.charAt(subExp.length() - 1) == ')'
352
- ) {
367
+ // Only remove outer parentheses if they actually match
368
+ while (subExp.length() > 2 && this.hasMatchingOuterParentheses(subExp.toString())) {
353
369
  subExp.deleteCharAt(0);
354
370
  subExp.setLength(subExp.length() - 1);
355
371
  }
@@ -421,6 +437,22 @@ export class Expression extends ExpressionToken {
421
437
  return pre2 < pre1;
422
438
  }
423
439
 
440
+ private hasMatchingOuterParentheses(str: string): boolean {
441
+ if (str.length < 2 || str.charAt(0) !== '(' || str.charAt(str.length - 1) !== ')') {
442
+ return false;
443
+ }
444
+ // Check if the first '(' matches the last ')'
445
+ // by verifying that the nesting level never drops to 0 before the end
446
+ let level = 0;
447
+ for (let i = 0; i < str.length - 1; i++) {
448
+ const ch = str.charAt(i);
449
+ if (ch === '(') level++;
450
+ else if (ch === ')') level--;
451
+ if (level === 0) return false; // First paren closed before end
452
+ }
453
+ return level === 1; // Should be 1 just before the last ')'
454
+ }
455
+
424
456
  public toString(): string {
425
457
  if (this.ops.isEmpty()) {
426
458
  if (this.tokens.size() == 1) return this.tokens.get(0).toString();