@decaf-ts/core 0.8.50 → 0.8.52

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 (104) hide show
  1. package/README.md +206 -93
  2. package/dist/core.cjs +1 -1
  3. package/dist/core.cjs.map +1 -1
  4. package/dist/core.js +1 -1
  5. package/dist/core.js.map +1 -1
  6. package/lib/esm/fs/FilesystemAdapter.d.ts +67 -0
  7. package/lib/esm/fs/FilesystemAdapter.js +430 -0
  8. package/lib/esm/fs/FilesystemAdapter.js.map +1 -0
  9. package/lib/esm/fs/FsDispatch.d.ts +7 -0
  10. package/lib/esm/fs/FsDispatch.js +20 -0
  11. package/lib/esm/fs/FsDispatch.js.map +1 -0
  12. package/lib/esm/fs/helpers.d.ts +17 -0
  13. package/lib/esm/fs/helpers.js +64 -0
  14. package/lib/esm/fs/helpers.js.map +1 -0
  15. package/lib/esm/fs/index.d.ts +4 -0
  16. package/lib/esm/fs/index.js +5 -0
  17. package/lib/esm/fs/index.js.map +1 -0
  18. package/lib/esm/fs/indexStore.d.ts +28 -0
  19. package/lib/esm/fs/indexStore.js +173 -0
  20. package/lib/esm/fs/indexStore.js.map +1 -0
  21. package/lib/esm/fs/locks/FilesystemLock.d.ts +13 -0
  22. package/lib/esm/fs/locks/FilesystemLock.js +49 -0
  23. package/lib/esm/fs/locks/FilesystemLock.js.map +1 -0
  24. package/lib/esm/fs/locks/FilesystemMultiLock.d.ts +8 -0
  25. package/lib/esm/fs/locks/FilesystemMultiLock.js +23 -0
  26. package/lib/esm/fs/locks/FilesystemMultiLock.js.map +1 -0
  27. package/lib/esm/fs/types.d.ts +34 -0
  28. package/lib/esm/fs/types.js +2 -0
  29. package/lib/esm/fs/types.js.map +1 -0
  30. package/lib/esm/index.d.ts +1 -1
  31. package/lib/esm/index.js +1 -1
  32. package/lib/esm/query/Paginator.js +2 -2
  33. package/lib/esm/query/Paginator.js.map +1 -1
  34. package/lib/esm/ram/RamAdapter.d.ts +1 -1
  35. package/lib/esm/ram/types.d.ts +2 -1
  36. package/lib/esm/tasks/TaskEngine.d.ts +30 -1
  37. package/lib/esm/tasks/TaskEngine.js +361 -16
  38. package/lib/esm/tasks/TaskEngine.js.map +1 -1
  39. package/lib/esm/tasks/TaskService.js +3 -0
  40. package/lib/esm/tasks/TaskService.js.map +1 -1
  41. package/lib/esm/tasks/builder.js +1 -1
  42. package/lib/esm/tasks/builder.js.map +1 -1
  43. package/lib/esm/tasks/constants.js +1 -0
  44. package/lib/esm/tasks/constants.js.map +1 -1
  45. package/lib/esm/tasks/types.d.ts +12 -0
  46. package/lib/esm/tasks/workers/WorkThreadEnvironment.d.ts +32 -0
  47. package/lib/esm/tasks/workers/WorkThreadEnvironment.js +28 -0
  48. package/lib/esm/tasks/workers/WorkThreadEnvironment.js.map +1 -0
  49. package/lib/esm/tasks/workers/messages.d.ts +69 -0
  50. package/lib/esm/tasks/workers/messages.js +2 -0
  51. package/lib/esm/tasks/workers/messages.js.map +1 -0
  52. package/lib/esm/tasks/workers/workerThread.d.ts +1 -0
  53. package/lib/esm/tasks/workers/workerThread.js +185 -0
  54. package/lib/esm/tasks/workers/workerThread.js.map +1 -0
  55. package/lib/fs/FilesystemAdapter.cjs +437 -0
  56. package/lib/fs/FilesystemAdapter.d.ts +67 -0
  57. package/lib/fs/FilesystemAdapter.js.map +1 -0
  58. package/lib/fs/FsDispatch.cjs +24 -0
  59. package/lib/fs/FsDispatch.d.ts +7 -0
  60. package/lib/fs/FsDispatch.js.map +1 -0
  61. package/lib/fs/helpers.cjs +76 -0
  62. package/lib/fs/helpers.d.ts +17 -0
  63. package/lib/fs/helpers.js.map +1 -0
  64. package/lib/fs/index.cjs +21 -0
  65. package/lib/fs/index.d.ts +4 -0
  66. package/lib/fs/index.js.map +1 -0
  67. package/lib/fs/indexStore.cjs +181 -0
  68. package/lib/fs/indexStore.d.ts +28 -0
  69. package/lib/fs/indexStore.js.map +1 -0
  70. package/lib/fs/locks/FilesystemLock.cjs +56 -0
  71. package/lib/fs/locks/FilesystemLock.d.ts +13 -0
  72. package/lib/fs/locks/FilesystemLock.js.map +1 -0
  73. package/lib/fs/locks/FilesystemMultiLock.cjs +30 -0
  74. package/lib/fs/locks/FilesystemMultiLock.d.ts +8 -0
  75. package/lib/fs/locks/FilesystemMultiLock.js.map +1 -0
  76. package/lib/fs/types.cjs +3 -0
  77. package/lib/fs/types.d.ts +34 -0
  78. package/lib/fs/types.js.map +1 -0
  79. package/lib/index.cjs +1 -1
  80. package/lib/index.d.ts +1 -1
  81. package/lib/query/Paginator.cjs +2 -2
  82. package/lib/query/Paginator.js.map +1 -1
  83. package/lib/ram/RamAdapter.d.ts +1 -1
  84. package/lib/ram/types.d.ts +2 -1
  85. package/lib/tasks/TaskEngine.cjs +360 -15
  86. package/lib/tasks/TaskEngine.d.ts +30 -1
  87. package/lib/tasks/TaskEngine.js.map +1 -1
  88. package/lib/tasks/TaskService.cjs +3 -0
  89. package/lib/tasks/TaskService.js.map +1 -1
  90. package/lib/tasks/builder.cjs +1 -1
  91. package/lib/tasks/builder.js.map +1 -1
  92. package/lib/tasks/constants.cjs +1 -0
  93. package/lib/tasks/constants.js.map +1 -1
  94. package/lib/tasks/types.d.ts +12 -0
  95. package/lib/tasks/workers/WorkThreadEnvironment.cjs +31 -0
  96. package/lib/tasks/workers/WorkThreadEnvironment.d.ts +32 -0
  97. package/lib/tasks/workers/WorkThreadEnvironment.js.map +1 -0
  98. package/lib/tasks/workers/messages.cjs +3 -0
  99. package/lib/tasks/workers/messages.d.ts +69 -0
  100. package/lib/tasks/workers/messages.js.map +1 -0
  101. package/lib/tasks/workers/workerThread.cjs +220 -0
  102. package/lib/tasks/workers/workerThread.d.ts +1 -0
  103. package/lib/tasks/workers/workerThread.js.map +1 -0
  104. package/package.json +19 -8
@@ -11,6 +11,7 @@ const TaskStepResultModel_1 = require("./models/TaskStepResultModel.cjs");
11
11
  const TaskLogEntryModel_1 = require("./models/TaskLogEntryModel.cjs");
12
12
  const TaskBackoffModel_1 = require("./models/TaskBackoffModel.cjs");
13
13
  const TaskStepSpecModel_1 = require("./models/TaskStepSpecModel.cjs");
14
+ const node_worker_threads_1 = require("node:worker_threads");
14
15
  const constants_1 = require("./constants.cjs");
15
16
  const logging_1 = require("@decaf-ts/logging");
16
17
  const ContextualLoggedClass_1 = require("./../utils/ContextualLoggedClass.cjs");
@@ -23,6 +24,7 @@ const TaskErrorModel_1 = require("./models/TaskErrorModel.cjs");
23
24
  const TaskTracker_1 = require("./TaskTracker.cjs");
24
25
  const transactional_decorators_1 = require("@decaf-ts/transactional-decorators");
25
26
  const index_1 = require("./../persistence/index.cjs");
27
+ const WorkThreadEnvironment_1 = require("./workers/WorkThreadEnvironment.cjs");
26
28
  class TaskEngine extends ContextualLoggedClass_1.AbsContextual {
27
29
  get Context() {
28
30
  return TaskContext_1.TaskContext;
@@ -52,15 +54,87 @@ class TaskEngine extends ContextualLoggedClass_1.AbsContextual {
52
54
  this._events = this._events.override(this.config.overrides);
53
55
  return this._events;
54
56
  }
57
+ static createTaskContext(base, overrides) {
58
+ const ctx = new TaskContext_1.TaskContext(base);
59
+ if (overrides && Object.keys(overrides).length) {
60
+ return ctx.accumulate(overrides);
61
+ }
62
+ return ctx;
63
+ }
55
64
  constructor(config) {
56
65
  super();
57
66
  this.config = config;
67
+ this.workerThreads = [];
68
+ this.workerJobQueue = [];
69
+ this.workerJobs = new Map();
70
+ this.workerCounter = 0;
71
+ this.workerThreadCapacity = 1;
58
72
  this.lock = new transactional_decorators_1.Lock();
59
73
  this.running = false;
74
+ if (config.workerPool && !config.workerAdapter) {
75
+ throw new db_decorators_1.InternalError("Worker pool requires workerAdapter descriptor in TaskEngineConfig");
76
+ }
60
77
  this.config = Object.assign({}, constants_1.DefaultTaskEngineConfig, config, {
61
78
  bus: config.bus || new TaskEventBus_1.TaskEventBus(),
62
79
  registry: config.registry || new TaskHandlerRegistry_1.TaskHandlerRegistry(),
63
80
  });
81
+ this.workerThreadCapacity = Math.max(1, this.config.workerConcurrency ?? 1);
82
+ this.workerPoolConfig = this.normalizeWorkerPoolConfig(this.config.workerPool);
83
+ }
84
+ normalizeWorkerPoolConfig(pool) {
85
+ if (!pool)
86
+ return undefined;
87
+ if (!pool.entry) {
88
+ throw new db_decorators_1.InternalError("Worker pool configuration requires an explicit entry file path");
89
+ }
90
+ if (pool.size != null && pool.size !== this.config.concurrency) {
91
+ throw new db_decorators_1.InternalError("TaskEngine concurrency must match workerPool.size when worker pool is enabled");
92
+ }
93
+ return Object.assign({}, pool, {
94
+ size: this.config.concurrency,
95
+ });
96
+ }
97
+ hasWorkerPool() {
98
+ return !!this.workerPoolConfig && (this.workerPoolConfig.size ?? 0) > 0;
99
+ }
100
+ getWorkerCount() {
101
+ if (!this.workerPoolConfig)
102
+ return 0;
103
+ return this.workerPoolConfig.size ?? 0;
104
+ }
105
+ getWorkerExecutionSlots() {
106
+ return this.getWorkerCount() * this.workerThreadCapacity;
107
+ }
108
+ canDispatchToWorkers() {
109
+ return this.hasWorkerPool();
110
+ }
111
+ getExecutionConcurrency() {
112
+ if (this.hasWorkerPool()) {
113
+ return Math.max(1, this.getWorkerExecutionSlots());
114
+ }
115
+ return this.config.concurrency;
116
+ }
117
+ computeWorkerModules() {
118
+ const adapterDescriptor = this.config.workerAdapter ?? WorkThreadEnvironment_1.DefaultWorkThreadEnvironment.persistence;
119
+ if (!adapterDescriptor?.adapterModule) {
120
+ throw new db_decorators_1.InternalError("Worker adapter descriptor must include adapterModule");
121
+ }
122
+ const configuredImports = this.workerPoolConfig?.modules?.imports ??
123
+ WorkThreadEnvironment_1.DefaultWorkThreadEnvironment.modules.imports;
124
+ const imports = [];
125
+ const append = (specifier) => {
126
+ if (!specifier)
127
+ return;
128
+ if (!imports.includes(specifier))
129
+ imports.push(specifier);
130
+ };
131
+ append(adapterDescriptor.adapterModule);
132
+ for (const specifier of configuredImports) {
133
+ if (specifier === adapterDescriptor.adapterModule)
134
+ continue;
135
+ append(specifier);
136
+ }
137
+ return { imports };
64
138
  }
65
139
  async push(task, track = false, ...args) {
66
140
  const { ctx, log } = (await this.logCtx(args, db_decorators_1.OperationKeys.CREATE, true)).for(this.push);
@@ -142,10 +216,13 @@ class TaskEngine extends ContextualLoggedClass_1.AbsContextual {
142
216
  async start(...args) {
143
217
  const { ctx } = (await this.logCtx(args, "run", true)).for(this.start);
144
218
  await this.lock.acquire();
145
- if (this.running)
219
+ if (this.running) {
220
+ this.lock.release();
146
221
  return;
222
+ }
147
223
  this.running = true;
148
224
  this.lock.release();
225
+ await this.spawnWorkers();
149
226
  void this.loop(ctx);
150
227
  }
151
228
  async stop(...args) {
@@ -161,7 +238,7 @@ class TaskEngine extends ContextualLoggedClass_1.AbsContextual {
161
238
  .execute(ctx);
162
239
  const timeout = ctx.getOrUndefined?.("gracefulShutdownMsTimeout") ??
163
240
  this.config.gracefulShutdownMsTimeout;
164
- return new Promise((resolve, reject) => {
241
+ await new Promise((resolve, reject) => {
165
242
  const timer = setTimeout(() => {
166
243
  log.error(`Graceful shutdown interrupted after ${timeout} ms...`);
167
244
  resolve();
@@ -183,6 +260,249 @@ class TaskEngine extends ContextualLoggedClass_1.AbsContextual {
183
260
  reject(err);
184
261
  });
185
262
  });
263
+ await this.shutdownWorkers();
264
+ }
265
+ // -------------------------
266
+ // Worker pool orchestration
267
+ // -------------------------
268
+ async spawnWorkers() {
269
+ if (!this.hasWorkerPool())
270
+ return;
271
+ const target = this.getWorkerCount();
272
+ const creations = [];
273
+ while (this.workerThreads.length < target) {
274
+ const ready = this.createWorker();
275
+ if (ready)
276
+ creations.push(ready);
277
+ }
278
+ if (creations.length) {
279
+ await Promise.all(creations);
280
+ }
281
+ }
282
+ createWorker() {
283
+ if (!this.workerPoolConfig)
284
+ return undefined;
285
+ let resolveReady;
286
+ let rejectReady;
287
+ const readyPromise = new Promise((resolve, reject) => {
288
+ resolveReady = resolve;
289
+ rejectReady = reject;
290
+ });
291
+ const entry = this.workerPoolConfig.entry;
292
+ const workerId = `${this.config.workerId}-${this.workerCounter++}`;
293
+ const env = {
294
+ workerId,
295
+ mode: this.workerPoolConfig.mode ?? "node",
296
+ persistence: Object.assign({}, this.config.workerAdapter ?? WorkThreadEnvironment_1.DefaultWorkThreadEnvironment.persistence, {
297
+ alias: this.adapter.alias,
298
+ flavour: this.adapter.flavour,
299
+ }),
300
+ taskEngine: {
301
+ concurrency: this.workerThreadCapacity,
302
+ leaseMs: this.config.leaseMs,
303
+ pollMsBusy: this.config.pollMsBusy,
304
+ pollMsIdle: this.config.pollMsIdle,
305
+ logTailMax: this.config.logTailMax,
306
+ streamBufferSize: this.config.streamBufferSize,
307
+ maxLoggingBuffer: this.config.maxLoggingBuffer,
308
+ loggingBufferTruncation: this.config.loggingBufferTruncation,
309
+ gracefulShutdownMsTimeout: this.config.gracefulShutdownMsTimeout,
310
+ },
311
+ modules: this.computeWorkerModules(),
312
+ };
313
+ const worker = new node_worker_threads_1.Worker(entry, {
314
+ workerData: { environment: env },
315
+ });
316
+ const state = {
317
+ id: workerId,
318
+ worker,
319
+ ready: false,
320
+ activeJobs: 0,
321
+ capacity: this.workerThreadCapacity,
322
+ readyPromise,
323
+ resolveReady,
324
+ rejectReady,
325
+ };
326
+ this.workerThreads.push(state);
327
+ worker.on("message", (msg) => this.handleWorkerMessage(state, msg));
328
+ worker.on("error", (err) => this.handleWorkerError(state, err));
329
+ worker.on("exit", (code) => this.handleWorkerExit(state, code));
330
+ return readyPromise;
331
+ }
332
+ async shutdownWorkers() {
333
+ for (const state of this.workerThreads.splice(0)) {
334
+ const message = {
335
+ type: "control",
336
+ command: "shutdown",
337
+ };
338
+ try {
339
+ state.worker.postMessage(message);
340
+ }
341
+ catch {
342
+ // ignore
343
+ }
344
+ await state.worker.terminate();
345
+ }
346
+ for (const job of this.workerJobQueue.splice(0)) {
347
+ job.reject(new db_decorators_1.InternalError(`Worker pool shutting down before job ${job.id} could start`));
348
+ }
349
+ for (const job of this.workerJobs.values()) {
350
+ job.reject(new db_decorators_1.InternalError(`Worker terminated before finishing job ${job.id}`));
351
+ }
352
+ this.workerJobs.clear();
353
+ }
354
+ handleWorkerError(state, err) {
355
+ this.log.error(`worker ${state.id} error: ${err.message}`, err);
356
+ if (state.rejectReady) {
357
+ state.rejectReady(err);
358
+ state.resolveReady = undefined;
359
+ state.rejectReady = undefined;
360
+ }
361
+ }
362
+ handleWorkerExit(state, code) {
363
+ this.log.info(`worker ${state.id} exited with code ${code}`);
364
+ if (state.rejectReady) {
365
+ state.rejectReady(new Error(`worker ${state.id} exited before reporting ready`));
366
+ state.resolveReady = undefined;
367
+ state.rejectReady = undefined;
368
+ }
369
+ const idx = this.workerThreads.indexOf(state);
370
+ if (idx >= 0)
371
+ this.workerThreads.splice(idx, 1);
372
+ for (const [jobId, job] of this.workerJobs.entries()) {
373
+ if (job.worker === state) {
374
+ job.worker = undefined;
375
+ this.workerJobs.delete(jobId);
376
+ this.workerJobQueue.unshift(job);
377
+ }
378
+ }
379
+ if (this.running) {
380
+ void this.spawnWorkers().catch((err) => this.log.error(`failed to respawn worker`, err));
381
+ this.processWorkerQueue();
382
+ }
383
+ }
384
+ handleWorkerMessage(state, msg) {
385
+ if (msg.type === "ready") {
386
+ state.ready = true;
387
+ if (state.resolveReady) {
388
+ state.resolveReady();
389
+ state.resolveReady = undefined;
390
+ state.rejectReady = undefined;
391
+ }
392
+ this.log.info(`worker ${state.id} ready`);
393
+ this.processWorkerQueue();
394
+ return;
395
+ }
396
+ if (msg.type === "error") {
397
+ this.log.error(`worker ${state.id} reported error: ${msg.error}`);
398
+ return;
399
+ }
400
+ if (msg.type === "log") {
401
+ const job = this.workerJobs.get(msg.jobId);
402
+ if (!job)
403
+ return;
404
+ void this.appendLog(job.ctx, job.task, msg.entries)
405
+ .then(([updated, entries]) => {
406
+ job.task = updated;
407
+ return this.emitLog(job.ctx, job.task.id, entries);
408
+ })
409
+ .catch((err) => this.log.error(`Failed to append worker log`, err));
410
+ return;
411
+ }
412
+ if (msg.type === "progress") {
413
+ const job = this.workerJobs.get(msg.jobId);
414
+ if (!job)
415
+ return;
416
+ void this.emitProgress(job.ctx, job.task.id, msg.payload);
417
+ return;
418
+ }
419
+ if (msg.type === "heartbeat") {
420
+ const job = this.workerJobs.get(msg.jobId);
421
+ if (!job)
422
+ return;
423
+ job.task.leaseExpiry = new Date(Date.now() + this.config.leaseMs);
424
+ void this.tasks.update(job.task).catch(() => null);
425
+ return;
426
+ }
427
+ if (msg.type === "result") {
428
+ const job = this.workerJobs.get(msg.jobId);
429
+ if (!job)
430
+ return;
431
+ this.workerJobs.delete(job.id);
432
+ state.activeJobs = Math.max(0, state.activeJobs - 1);
433
+ this.applyWorkerCache(job.ctx, msg.cache);
434
+ switch (msg.status) {
435
+ case "success":
436
+ job.resolve(msg.output);
437
+ break;
438
+ case "error": {
439
+ const err = new Error(msg.error.message);
440
+ if (msg.error.name)
441
+ err.name = msg.error.name;
442
+ if (msg.error.stack)
443
+ err.stack = msg.error.stack;
444
+ job.reject(err);
445
+ break;
446
+ }
447
+ case "state-change":
448
+ job.reject(new TaskStateChangeError_1.TaskStateChangeError(msg.request));
449
+ break;
450
+ }
451
+ this.processWorkerQueue();
452
+ return;
453
+ }
454
+ }
455
+ applyWorkerCache(ctx, cache) {
456
+ if (!cache)
457
+ return;
458
+ Object.entries(cache).forEach(([key, value]) => {
459
+ ctx.cacheResult(key, value);
460
+ });
461
+ }
462
+ processWorkerQueue() {
463
+ if (!this.hasWorkerPool())
464
+ return;
465
+ const available = this.workerThreads
466
+ .filter((state) => state.ready && state.activeJobs < state.capacity)
467
+ .sort((a, b) => a.activeJobs - b.activeJobs);
468
+ for (const state of available) {
469
+ while (state.activeJobs < state.capacity &&
470
+ this.workerJobQueue.length > 0) {
471
+ const job = this.workerJobQueue.shift();
472
+ if (!job)
473
+ break;
474
+ this.assignWorker(state, job);
475
+ }
476
+ if (!this.workerJobQueue.length)
477
+ break;
478
+ }
479
+ }
480
+ assignWorker(state, job) {
481
+ if (!this.workerPoolConfig)
482
+ return;
483
+ const payload = {
484
+ jobId: job.id,
485
+ taskId: job.task.id,
486
+ classification: job.classification,
487
+ input: job.input,
488
+ attempt: job.task.attempt ?? 0,
489
+ resultCache: job.ctx.resultCache ?? {},
490
+ streamBufferSize: this.config.streamBufferSize,
491
+ maxLoggingBuffer: this.config.maxLoggingBuffer,
492
+ loggingBufferTruncation: this.config.loggingBufferTruncation,
493
+ };
494
+ job.worker = state;
495
+ this.workerJobs.set(job.id, job);
496
+ state.activeJobs += 1;
497
+ const message = {
498
+ type: "execute",
499
+ job: payload,
500
+ };
501
+ state.worker.postMessage(message);
502
+ }
503
+ enqueueWorkerJob(job) {
504
+ this.workerJobQueue.push(job);
505
+ this.processWorkerQueue();
186
506
  }
187
507
  // -------------------------
188
508
  // Worker loop
@@ -217,10 +537,11 @@ class TaskEngine extends ContextualLoggedClass_1.AbsContextual {
217
537
  .or(condLeaseExpired)
218
538
  .or(condScheduled);
219
539
  // Fetch more than concurrency because some will fail to claim due to conflicts
540
+ const concurrency = this.getExecutionConcurrency();
220
541
  const candidates = await this.tasks
221
542
  .select()
222
543
  .where(runnable)
223
- .limit(Math.max(this.config.concurrency * 4, 20))
544
+ .limit(Math.max(concurrency * 4, 20))
224
545
  .execute();
225
546
  log.verbose(`claimBatch candidates:${candidates.length}`);
226
547
  const out = [];
@@ -228,7 +549,7 @@ class TaskEngine extends ContextualLoggedClass_1.AbsContextual {
228
549
  const claimed = await this.tryClaim(c, ctx);
229
550
  if (claimed)
230
551
  out.push(claimed);
231
- if (out.length >= this.config.concurrency)
552
+ if (out.length >= concurrency)
232
553
  break;
233
554
  }
234
555
  log.verbose(`claimBatch claimed:${out.length}`);
@@ -264,9 +585,39 @@ class TaskEngine extends ContextualLoggedClass_1.AbsContextual {
264
585
  // -------------------------
265
586
  // Execution
266
587
  // -------------------------
588
+ async runHandlerInline(classification, input, ctx) {
589
+ const handler = this.registry.get(classification);
590
+ if (!handler)
591
+ throw new db_decorators_1.InternalError(`No task handler registered for type: ${classification}`);
592
+ return handler.run(input, ctx);
593
+ }
594
+ async dispatchToWorker(classification, input, task, ctx) {
595
+ if (!this.canDispatchToWorkers()) {
596
+ return this.runHandlerInline(classification, input, ctx);
597
+ }
598
+ const uuid = await index_1.UUID.instance.generate();
599
+ return new Promise((resolve, reject) => {
600
+ const job = {
601
+ id: uuid,
602
+ classification,
603
+ input,
604
+ task,
605
+ ctx,
606
+ resolve,
607
+ reject,
608
+ };
609
+ this.enqueueWorkerJob(job);
610
+ });
611
+ }
612
+ async invokeHandler(classification, input, task, ctx) {
613
+ if (!this.hasWorkerPool()) {
614
+ return this.runHandlerInline(classification, input, ctx);
615
+ }
616
+ return this.dispatchToWorker(classification, input, task, ctx);
617
+ }
267
618
  async executeClaimed(task) {
268
619
  const { ctx, log } = (await this.logCtx([], task.classification, true)).for(this.executeClaimed);
269
- const taskCtx = new TaskContext_1.TaskContext(ctx).accumulate({
620
+ const taskCtx = TaskEngine.createTaskContext(ctx, {
270
621
  taskId: task.id,
271
622
  logger: new logging_2.TaskLogger(log, this.config.streamBufferSize, this.config.maxLoggingBuffer),
272
623
  attempt: task.attempt,
@@ -276,7 +627,7 @@ class TaskEngine extends ContextualLoggedClass_1.AbsContextual {
276
627
  await this.emitLog(taskCtx, task.id, logs);
277
628
  },
278
629
  flush: async () => {
279
- return taskCtx.logger.flush(taskCtx.pipe);
630
+ await taskCtx.logger.flush(taskCtx.pipe);
280
631
  },
281
632
  progress: async (data) => {
282
633
  await this.emitProgress(taskCtx, task.id, data);
@@ -311,11 +662,8 @@ class TaskEngine extends ContextualLoggedClass_1.AbsContextual {
311
662
  }
312
663
  }
313
664
  else {
314
- const handler = this.registry.get(task.classification);
315
- log.debug(`handler type for ${task.id} is ${handler?.constructor?.name ?? "none"}`);
316
- if (!handler)
317
- throw new db_decorators_1.InternalError(`No task handler registered for type: ${task.classification}`);
318
- output = await handler.run(task.input, taskCtx);
665
+ log.debug(`dispatching handler for ${task.id}`);
666
+ output = await this.invokeHandler(task.classification, task.input, task, taskCtx);
319
667
  log.verbose(`handler finished for ${task.id}`);
320
668
  }
321
669
  task.status = constants_1.TaskStatus.SUCCEEDED;
@@ -473,15 +821,12 @@ class TaskEngine extends ContextualLoggedClass_1.AbsContextual {
473
821
  }
474
822
  while (idx < steps.length) {
475
823
  const step = steps[idx];
476
- const handler = this.registry.get(step.classification);
477
- if (!handler)
478
- throw new Error(`No task handler registered for composite step: ${step.classification}`);
479
824
  await context.pipe([
480
825
  logging_1.LogLevel.info,
481
826
  `Composite step ${idx + 1}/${steps.length}: ${step.classification}`,
482
827
  ]);
483
828
  try {
484
- const out = await handler.run(step.input, context);
829
+ const out = await this.invokeHandler(step.classification, step.input, task, context);
485
830
  const stepIndex = idx;
486
831
  const now = new Date();
487
832
  results[stepIndex] = new TaskStepResultModel_1.TaskStepResultModel({
@@ -4,17 +4,25 @@ import { TaskEventModel } from "./models/TaskEventModel";
4
4
  import { TaskHandlerRegistry } from "./TaskHandlerRegistry";
5
5
  import { TaskEventBus } from "./TaskEventBus";
6
6
  import { Adapter } from "../persistence/Adapter";
7
+ import { Context } from "../persistence/Context";
7
8
  import { ContextOf, FlagsOf } from "../persistence/types";
8
9
  import { AbsContextual, MaybeContextualArg } from "../utils/ContextualLoggedClass";
9
10
  import { OperationKeys } from "@decaf-ts/db-decorators";
11
+ import { TaskContext } from "./TaskContext";
10
12
  import { DateTarget } from "@decaf-ts/decorator-validation";
11
13
  import { Constructor } from "@decaf-ts/decoration";
12
- import { TaskEngineConfig } from "./types";
14
+ import { TaskEngineConfig, TaskFlags } from "./types";
13
15
  import { TaskTracker } from "./TaskTracker";
14
16
  export declare class TaskEngine<A extends Adapter<any, any, any, any>> extends AbsContextual<ContextOf<A>> {
15
17
  private config;
16
18
  private _tasks?;
17
19
  private _events?;
20
+ private workerPoolConfig?;
21
+ private workerThreads;
22
+ private workerJobQueue;
23
+ private workerJobs;
24
+ private workerCounter;
25
+ private workerThreadCapacity;
18
26
  private lock;
19
27
  protected get Context(): Constructor<ContextOf<A>>;
20
28
  protected get adapter(): A;
@@ -23,7 +31,15 @@ export declare class TaskEngine<A extends Adapter<any, any, any, any>> extends A
23
31
  protected get tasks(): Repo<TaskModel>;
24
32
  protected get events(): Repo<TaskEventModel>;
25
33
  protected running: boolean;
34
+ static createTaskContext(base?: Context<any>, overrides?: Partial<TaskFlags>): TaskContext;
26
35
  constructor(config: TaskEngineConfig<A>);
36
+ private normalizeWorkerPoolConfig;
37
+ private hasWorkerPool;
38
+ private getWorkerCount;
39
+ private getWorkerExecutionSlots;
40
+ private canDispatchToWorkers;
41
+ private getExecutionConcurrency;
42
+ private computeWorkerModules;
27
43
  push<I, O>(task: TaskModel<I, O>, ...args: MaybeContextualArg<any>): Promise<TaskModel<I, O>>;
28
44
  push<I, O>(task: TaskModel<I, O>, track: false, ...args: MaybeContextualArg<any>): Promise<TaskModel<I, O>>;
29
45
  push<I, O>(task: TaskModel<I, O>, track: true, ...args: MaybeContextualArg<any>): Promise<{
@@ -51,9 +67,22 @@ export declare class TaskEngine<A extends Adapter<any, any, any, any>> extends A
51
67
  isRunning(): Promise<boolean>;
52
68
  start(...args: MaybeContextualArg<any>): Promise<void>;
53
69
  stop(...args: MaybeContextualArg<any>): Promise<void>;
70
+ private spawnWorkers;
71
+ private createWorker;
72
+ private shutdownWorkers;
73
+ private handleWorkerError;
74
+ private handleWorkerExit;
75
+ private handleWorkerMessage;
76
+ private applyWorkerCache;
77
+ private processWorkerQueue;
78
+ private assignWorker;
79
+ private enqueueWorkerJob;
54
80
  private loop;
55
81
  private claimBatch;
56
82
  private tryClaim;
83
+ private runHandlerInline;
84
+ private dispatchToWorker;
85
+ private invokeHandler;
57
86
  private executeClaimed;
58
87
  private handleTaskStateChange;
59
88
  private runComposite;