@twin.org/dataspace-data-plane-service 0.0.3-next.25 → 0.0.3-next.26

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.
@@ -4,7 +4,7 @@ import { ArrayHelper, BaseError, ComponentFactory, ConflictError, Converter, Gen
4
4
  import { Blake2b } from "@twin.org/crypto";
5
5
  import { DataTypeHelper, JsonSchemaHelper } from "@twin.org/data-core";
6
6
  import { JsonLdDataTypes, JsonLdHelper, JsonLdProcessor } from "@twin.org/data-json-ld";
7
- import { ActivityProcessingStatus, DataRequestType, DataspaceAppFactory, DataspaceContexts, DataspaceDataTypes, DataspaceTypes } from "@twin.org/dataspace-models";
7
+ import { ActivityProcessingStatus, ActivityTaskStatus, DataRequestType, DataspaceAppFactory, DataspaceContexts, DataspaceDataTypes, DataspaceTypes } from "@twin.org/dataspace-models";
8
8
  import { EngineCoreFactory } from "@twin.org/engine-models";
9
9
  import { ComparisonOperator, LogicalOperator } from "@twin.org/entity";
10
10
  import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
@@ -70,6 +70,11 @@ export class DataspaceDataPlaneService {
70
70
  * @internal
71
71
  */
72
72
  _activityLogStatusCallbacks;
73
+ /**
74
+ * Track task handler registrations to avoid resetting worker pools on every task.
75
+ * @internal
76
+ */
77
+ _registeredTaskTypes;
73
78
  /**
74
79
  * Task retention. -1 retain forever.
75
80
  * @internal
@@ -80,6 +85,11 @@ export class DataspaceDataPlaneService {
80
85
  * @internal
81
86
  */
82
87
  _retainActivityLogsFor;
88
+ /**
89
+ * Retry count for failed tasks.
90
+ * @internal
91
+ */
92
+ _retryCount;
83
93
  /**
84
94
  * Clean up interval for activity logs.
85
95
  * @internal
@@ -144,11 +154,13 @@ export class DataspaceDataPlaneService {
144
154
  DataspaceProtocolDataTypes.registerRedirects();
145
155
  DataspaceProtocolDataTypes.registerTypes();
146
156
  this._activityLogStatusCallbacks = {};
157
+ this._registeredTaskTypes = [];
147
158
  this._partitionContextIds = options?.partitionContextIds;
148
159
  this._retainTasksFor =
149
160
  DataspaceDataPlaneService._DEFAULT_RETAIN_INTERVAL * DataspaceDataPlaneService._MS_PER_MINUTE;
150
161
  this._retainActivityLogsFor =
151
162
  DataspaceDataPlaneService._DEFAULT_RETAIN_INTERVAL * DataspaceDataPlaneService._MS_PER_MINUTE;
163
+ this._retryCount = options?.config?.retryCount;
152
164
  this._activityLogCleanUpInterval = DataspaceDataPlaneService._DEFAULT_CLEANUP_INTERVAL;
153
165
  this._cleanUpProcessOngoing = false;
154
166
  const validationErrors = [];
@@ -225,7 +237,7 @@ export class DataspaceDataPlaneService {
225
237
  /**
226
238
  * Notify an Activity.
227
239
  * @param activity The Activity notified.
228
- * @returns The Activity's Log Entry identifier.
240
+ * @returns The activity's id or entry.
229
241
  */
230
242
  async notifyActivity(activity) {
231
243
  Guards.object(DataspaceDataPlaneService.CLASS_NAME, "activity", activity);
@@ -255,10 +267,11 @@ export class DataspaceDataPlaneService {
255
267
  const activityLogId = Converter.bytesToHex(Blake2b.sum256(canonicalBytes));
256
268
  const activityLogEntryId = `urn:x-activity-log:${activityLogId}`;
257
269
  // Check if entry already exists
258
- const existingLogEntry = await this._entityStorageActivityLogs.get(activityLogEntryId);
270
+ let logEntry = await this._entityStorageActivityLogs.get(activityLogEntryId);
259
271
  let existingSuccessfulApps = [];
260
272
  let isRetry = false;
261
- if (!Is.undefined(existingLogEntry)) {
273
+ const now = Date.now();
274
+ if (!Is.undefined(logEntry)) {
262
275
  // Check if there are failed tasks that can be retried
263
276
  const existingEntry = await this.getActivityLogEntry(activityLogEntryId);
264
277
  // If all tasks completed successfully, this is a duplicate
@@ -276,69 +289,46 @@ export class DataspaceDataPlaneService {
276
289
  isRetry = true;
277
290
  }
278
291
  else {
279
- const logEntry = {
292
+ logEntry = {
280
293
  id: activityLogEntryId,
281
- activityId: Is.string(activity.id) ? activity.id : undefined,
294
+ activityId: activity.id,
282
295
  generator: this.calculateActivityGeneratorIdentity(activity),
283
- dateCreated: new Date().toISOString(),
284
- dateModified: new Date().toISOString()
296
+ dateCreated: new Date(now).toISOString(),
297
+ dateModified: new Date(now).toISOString()
285
298
  };
286
299
  await this._entityStorageActivityLogs.set(logEntry);
287
300
  }
288
301
  const activityQuerySet = await this.calculateActivityQuerySet(activity);
289
- const tasksScheduled = [];
290
- const dataspaceAppIds = [];
302
+ const taskEntries = [];
303
+ const handlerApps = {};
291
304
  for (const query of activityQuerySet) {
292
- const appIds = this.getAppForActivityQuery(query);
293
- for (const appId of appIds) {
294
- if (!dataspaceAppIds.includes(appId)) {
295
- dataspaceAppIds.push(appId);
305
+ const apps = this.getAppForActivityQuery(query);
306
+ for (const appId in apps) {
307
+ // Only process apps that haven't already completed successfully
308
+ if (!handlerApps[appId] && !existingSuccessfulApps.includes(appId)) {
309
+ handlerApps[appId] = apps[appId];
296
310
  }
297
311
  }
298
312
  }
299
- for (const dataspaceAppId of dataspaceAppIds) {
300
- // Only process apps that haven't already completed successfully
301
- if (!existingSuccessfulApps.includes(dataspaceAppId)) {
302
- const payload = {
303
- activityLogEntryId,
304
- activity: activity,
305
- executorApp: dataspaceAppId
306
- };
307
- const taskType = Converter.bytesToHex(RandomHelper.generate(16));
308
- const taskId = await this._backgroundTaskComponent.create(taskType, payload, {
309
- retainFor: this._retainTasksFor
310
- });
311
- await this._backgroundTaskComponent.registerHandler(taskType, "@twin.org/dataspace-app-runner", "appRunner", async (task) => {
312
- await this.finaliseTask(task);
313
- }, {
314
- initialiseMethod: "appRunnerStart",
315
- shutdownMethod: "appRunnerEnd"
316
- });
317
- tasksScheduled.push({
318
- taskId,
319
- dataspaceAppId
320
- });
321
- await this._logging?.log({
322
- level: "info",
323
- source: DataspaceDataPlaneService.CLASS_NAME,
324
- message: "scheduledTask",
325
- data: {
326
- taskId,
327
- dataspaceAppId,
328
- isRetry
329
- }
330
- });
313
+ let inlineCount = 0;
314
+ for (const handlerAppId in handlerApps) {
315
+ if (await this.processTask(activityLogEntryId, activity, handlerApps, handlerAppId, taskEntries, isRetry)) {
316
+ inlineCount++;
331
317
  }
332
318
  }
333
319
  const existingActivityTasks = isRetry
334
320
  ? await this._entityStorageActivityTasks.get(activityLogEntryId)
335
321
  : undefined;
336
322
  const existingTasksToKeep = existingActivityTasks?.associatedTasks.filter(t => existingSuccessfulApps.includes(t.dataspaceAppId)) ?? [];
337
- await this._entityStorageActivityTasks.set({
323
+ const activityTask = {
338
324
  activityLogEntryId,
339
- associatedTasks: [...existingTasksToKeep, ...tasksScheduled]
340
- });
341
- return activityLogEntryId;
325
+ associatedTasks: [...existingTasksToKeep, ...taskEntries]
326
+ };
327
+ await this._entityStorageActivityTasks.set(activityTask);
328
+ if (inlineCount === taskEntries.length) {
329
+ return this.finaliseActivityLogEntry(activityLogEntryId);
330
+ }
331
+ return activityTask.activityLogEntryId;
342
332
  }
343
333
  /**
344
334
  * Subscribes to the activity log.
@@ -370,68 +360,12 @@ export class DataspaceDataPlaneService {
370
360
  */
371
361
  async getActivityLogEntry(logEntryId) {
372
362
  Guards.stringValue(DataspaceDataPlaneService.CLASS_NAME, "logEntryId", logEntryId);
373
- const result = await this._entityStorageActivityLogs.get(logEntryId);
374
- if (Is.undefined(result)) {
363
+ const activityLog = await this._entityStorageActivityLogs.get(logEntryId);
364
+ if (Is.undefined(activityLog)) {
375
365
  throw new NotFoundError(DataspaceDataPlaneService.CLASS_NAME, "activityLogEntryNotFound", logEntryId);
376
366
  }
377
- let pendingTasks;
378
- let runningTasks;
379
- let finalizedTasks;
380
- let inErrorTasks;
381
- // For calculating the processing status. `Registering` if we cannot determine the activity tasks yet
382
- let status = ActivityProcessingStatus.Registering;
383
- // Now query the associated tasks
384
367
  const activityTasks = await this._entityStorageActivityTasks.get(logEntryId);
385
- // If activity tasks is undefined it is because the corresponding store has not been persisted yet
386
- if (!Is.undefined(activityTasks)) {
387
- pendingTasks = [];
388
- runningTasks = [];
389
- finalizedTasks = [];
390
- inErrorTasks = [];
391
- for (const entity of activityTasks.associatedTasks) {
392
- const taskDetails = await this._backgroundTaskComponent.get(entity.taskId);
393
- if (Is.object(taskDetails)) {
394
- switch (taskDetails.status) {
395
- case TaskStatus.Success:
396
- finalizedTasks.push({
397
- ...entity,
398
- result: JSON.stringify(taskDetails.result),
399
- startDate: taskDetails?.dateCreated,
400
- endDate: taskDetails?.dateCompleted
401
- });
402
- break;
403
- case TaskStatus.Pending:
404
- pendingTasks.push(entity);
405
- break;
406
- case TaskStatus.Processing:
407
- runningTasks.push({ ...entity, startDate: taskDetails.dateCreated });
408
- break;
409
- case TaskStatus.Failed:
410
- inErrorTasks.push({
411
- ...entity,
412
- error: taskDetails.error
413
- });
414
- break;
415
- case TaskStatus.Cancelled:
416
- // Nothing to do for cancelled tasks
417
- break;
418
- }
419
- }
420
- }
421
- if (Is.arrayValue(inErrorTasks)) {
422
- status = ActivityProcessingStatus.Error;
423
- }
424
- else if (Is.arrayValue(runningTasks)) {
425
- status = ActivityProcessingStatus.Running;
426
- }
427
- else if (Is.arrayValue(pendingTasks)) {
428
- status = ActivityProcessingStatus.Pending;
429
- }
430
- else {
431
- status = ActivityProcessingStatus.Completed;
432
- }
433
- }
434
- return { ...result, status, pendingTasks, runningTasks, finalizedTasks, inErrorTasks };
368
+ return this.constructLogEntry(activityLog, activityTasks);
435
369
  }
436
370
  /**
437
371
  * Get Data Asset entities. Allows to retrieve entities by their type or id.
@@ -471,9 +405,7 @@ export class DataspaceDataPlaneService {
471
405
  }
472
406
  const datasetId = serviceDataset["@id"];
473
407
  Guards.stringValue(DataspaceDataPlaneService.CLASS_NAME, "datasetId", datasetId);
474
- const appId = await this.getAppForDataAssetQuery({ datasetId });
475
- // getAppForDataAssetQuery already validates app exists
476
- const app = DataspaceAppFactory.get(appId);
408
+ const app = await this.getAppForDataAssetQuery({ datasetId });
477
409
  const handleDataRequest = app.handleDataRequest?.bind(app);
478
410
  Guards.function(DataspaceDataPlaneService.CLASS_NAME, "handleDataRequest", handleDataRequest);
479
411
  const dataRequest = {
@@ -537,8 +469,7 @@ export class DataspaceDataPlaneService {
537
469
  const serviceDataset = await this.getDatasetFromApps(resolvedDatasetId);
538
470
  const datasetId = serviceDataset["@id"];
539
471
  Guards.stringValue(DataspaceDataPlaneService.CLASS_NAME, "datasetId", datasetId);
540
- const appId = await this.getAppForDataAssetQuery({ datasetId });
541
- const app = DataspaceAppFactory.get(appId);
472
+ const app = await this.getAppForDataAssetQuery({ datasetId });
542
473
  if (!app.supportedQueryTypes().includes(query.type)) {
543
474
  throw new UnprocessableError(DataspaceDataPlaneService.CLASS_NAME, "queryTypeNotSupported", {
544
475
  queryType: query.type
@@ -643,55 +574,65 @@ export class DataspaceDataPlaneService {
643
574
  }
644
575
  /**
645
576
  * Process activity task finalization.
646
- * @param proofEntity The proof entity to process.
577
+ * @param taskId The Id of the Activity Log Entry.
578
+ * @param status The final status of the task.
579
+ * @param payload The execution payload of the task, required to correlate to the Activity Log Entry and update the processing status.
647
580
  * @internal
648
581
  */
649
- async finaliseTask(task) {
650
- const payload = task.payload;
582
+ async finaliseBackgroundTask(taskId, status, payload) {
651
583
  if (Is.empty(payload)) {
652
584
  return;
653
585
  }
654
- const activityLogEntry = await this._entityStorageActivityLogs.get(payload.activityLogEntryId);
655
- if (Is.undefined(activityLogEntry)) {
656
- await this._logging?.log({
657
- level: "error",
658
- source: DataspaceDataPlaneService.CLASS_NAME,
659
- message: "unknownActivityLogEntryId",
660
- data: {
661
- activityLogEntryId: payload.activityLogEntryId
586
+ if (status === TaskStatus.Success || status === TaskStatus.Failed) {
587
+ await this.notifyTaskStatusChanged(payload.activityLogEntryId, payload.activity.id, payload.dataspaceAppId, taskId, status);
588
+ await this.finaliseActivityLogEntry(payload.activityLogEntryId);
589
+ }
590
+ }
591
+ /**
592
+ * Notify registered callbacks about a task status change.
593
+ * @param activityLogEntryId The Id of the Activity Log Entry.
594
+ * @param activityId The Id of the Activity.
595
+ * @param dataspaceAppId The Id of the Dataspace App associated with the task.
596
+ * @param taskId The Id of the task.
597
+ * @param taskStatus The new status of the task.
598
+ * @internal
599
+ */
600
+ async notifyTaskStatusChanged(activityLogEntryId, activityId, dataspaceAppId, taskId, taskStatus) {
601
+ for (const callback of Object.values(this._activityLogStatusCallbacks)) {
602
+ await callback({
603
+ activityLogEntryId,
604
+ activityId,
605
+ taskProcessingStatus: {
606
+ dataspaceAppId,
607
+ taskId,
608
+ taskStatus
662
609
  }
663
610
  });
664
611
  }
665
- if (task.status === TaskStatus.Success || task.status === TaskStatus.Failed) {
666
- for (const callback of Object.values(this._activityLogStatusCallbacks)) {
667
- await callback({
668
- activityLogEntryId: payload.activityLogEntryId,
669
- activityId: Is.string(payload.activity.id) ? payload.activity.id : undefined,
670
- taskProcessingStatus: {
671
- dataspaceAppId: payload.executorApp,
672
- taskId: task.id,
673
- taskStatus: task.status
674
- }
675
- });
676
- }
677
- // Now let's see if the full activity processing has completed, if so the entry must be marked for retention
678
- if (this._retainActivityLogsFor !== -1) {
679
- const entry = await this.getActivityLogEntry(payload.activityLogEntryId);
680
- if (entry.status === ActivityProcessingStatus.Completed ||
681
- entry.status === ActivityProcessingStatus.Error) {
682
- const retainUntil = Date.now() + this._retainActivityLogsFor;
683
- await this._entityStorageActivityLogs.set({
684
- id: entry.id,
685
- activityId: entry.activityId,
686
- generator: entry.generator,
687
- dateCreated: entry.dateCreated,
688
- dateModified: entry.dateModified,
689
- retainUntil,
690
- retryCount: entry.retryCount
691
- });
692
- }
693
- }
612
+ }
613
+ /**
614
+ * Finalizes the Activity Log Entry by checking if all associated tasks have completed and, if so, updating the entry to be retained for the configured retention period.
615
+ * @param activityLogEntryId The Id of the Activity Log Entry to finalize.
616
+ * @returns The Activity Log Entry with updated retention details if applicable.
617
+ * @internal
618
+ */
619
+ async finaliseActivityLogEntry(activityLogEntryId) {
620
+ const entry = await this.getActivityLogEntry(activityLogEntryId);
621
+ if (this._retainActivityLogsFor !== -1 &&
622
+ (entry.status === ActivityProcessingStatus.Completed ||
623
+ entry.status === ActivityProcessingStatus.Error)) {
624
+ const retainUntil = Date.now() + this._retainActivityLogsFor;
625
+ const updatedEntry = {
626
+ id: entry.id,
627
+ activityId: entry.activityId,
628
+ generator: entry.generator,
629
+ dateCreated: entry.dateCreated,
630
+ dateModified: entry.dateModified,
631
+ retainUntil
632
+ };
633
+ await this._entityStorageActivityLogs.set(updatedEntry);
694
634
  }
635
+ return entry;
695
636
  }
696
637
  /**
697
638
  * Cleans up the activity log by deleting those entries that no longer shall be retained.
@@ -850,7 +791,7 @@ export class DataspaceDataPlaneService {
850
791
  * @internal
851
792
  */
852
793
  getAppForActivityQuery(activityQuery) {
853
- const matchingElements = [];
794
+ const matchingElements = {};
854
795
  const appNames = DataspaceAppFactory.names();
855
796
  for (const appId of appNames) {
856
797
  const app = DataspaceAppFactory.get(appId);
@@ -860,10 +801,10 @@ export class DataspaceDataPlaneService {
860
801
  (Is.undefined(appQuery.activityType) ||
861
802
  appQuery.activityType === activityQuery.activityType) &&
862
803
  (Is.undefined(appQuery.targetType) || appQuery.targetType === activityQuery.targetType)) {
863
- // Avoid duplicates. Only one DS App can be executed per activity
864
- if (!matchingElements.includes(appId)) {
865
- matchingElements.push(appId);
866
- }
804
+ matchingElements[appId] = {
805
+ app,
806
+ processingGroupId: appQuery.processingGroupId
807
+ };
867
808
  }
868
809
  }
869
810
  }
@@ -899,18 +840,20 @@ export class DataspaceDataPlaneService {
899
840
  */
900
841
  async getAppForDataAssetQuery(dataAssetQuery) {
901
842
  const matchingElements = [];
843
+ const matchingIds = [];
902
844
  const appNames = DataspaceAppFactory.names();
903
845
  for (const appId of appNames) {
904
846
  const app = DataspaceAppFactory.get(appId);
905
847
  const datasets = await app.datasetsHandled();
906
848
  for (const dataset of datasets) {
907
849
  if (dataset["@id"] === dataAssetQuery.datasetId) {
908
- matchingElements.push(appId);
850
+ matchingElements.push(app);
851
+ matchingIds.push(appId);
909
852
  }
910
853
  }
911
854
  }
912
855
  if (matchingElements.length > 1) {
913
- const error = new ConflictError(DataspaceDataPlaneService.CLASS_NAME, "tooManyAppsRegistered", dataAssetQuery.datasetId, matchingElements, {
856
+ const error = new ConflictError(DataspaceDataPlaneService.CLASS_NAME, "tooManyAppsRegistered", dataAssetQuery.datasetId, matchingIds, {
914
857
  datasetId: dataAssetQuery.datasetId
915
858
  });
916
859
  await this._logging?.log({
@@ -949,11 +892,15 @@ export class DataspaceDataPlaneService {
949
892
  * @internal
950
893
  */
951
894
  async prepareForRetry(activityLogEntryId, existingEntry) {
952
- const appsToRetry = existingEntry.inErrorTasks?.map(t => t.dataspaceAppId) ?? [];
895
+ const appsToRetry = existingEntry.tasks
896
+ ?.filter(t => t.status === ActivityTaskStatus.Failed)
897
+ .map(t => t.dataspaceAppId) ?? [];
953
898
  if (!Is.arrayValue(appsToRetry)) {
954
899
  throw new NotFoundError(DataspaceDataPlaneService.CLASS_NAME, "noFailedTasksToRetry", activityLogEntryId);
955
900
  }
956
- const successfulApps = existingEntry.finalizedTasks?.map(t => t.dataspaceAppId) ?? [];
901
+ const successfulApps = existingEntry.tasks
902
+ ?.filter(t => t.status === ActivityTaskStatus.Success)
903
+ .map(t => t.dataspaceAppId) ?? [];
957
904
  await this._logging?.log({
958
905
  level: "debug",
959
906
  source: DataspaceDataPlaneService.CLASS_NAME,
@@ -971,8 +918,6 @@ export class DataspaceDataPlaneService {
971
918
  if (this._retainActivityLogsFor !== -1) {
972
919
  logEntry.retainUntil = Date.now() + this._retainActivityLogsFor;
973
920
  }
974
- // Monitoring purposes
975
- logEntry.retryCount = (logEntry.retryCount ?? 0) + 1;
976
921
  await this._entityStorageActivityLogs.set(logEntry);
977
922
  }
978
923
  return successfulApps;
@@ -1068,5 +1013,183 @@ export class DataspaceDataPlaneService {
1068
1013
  });
1069
1014
  }
1070
1015
  }
1016
+ /**
1017
+ * Processes a task for an activity, by creating a background task and registering the handler.
1018
+ * @param activityLogEntryId The ID of the activity log entry.
1019
+ * @param activity The activity to be processed.
1020
+ * @param handlerApps The handler applications for the activity.
1021
+ * @param dataspaceAppId The ID of the handler application.
1022
+ * @param taskEntries The list of activity log entries.
1023
+ * @param isRetry Indicates if this is a retry of a previous task.
1024
+ * @returns True if the task was processed inline.
1025
+ * @internal
1026
+ */
1027
+ async processTask(activityLogEntryId, activity, handlerApps, dataspaceAppId, taskEntries, isRetry) {
1028
+ const handlerApp = handlerApps[dataspaceAppId].app;
1029
+ const processingGroupId = handlerApps[dataspaceAppId].processingGroupId;
1030
+ const payload = {
1031
+ activityLogEntryId,
1032
+ activity: activity,
1033
+ dataspaceAppId
1034
+ };
1035
+ // If there is no processing group we execute the task inline without creating a background task
1036
+ const isInlineTask = !Is.stringValue(processingGroupId);
1037
+ if (isInlineTask) {
1038
+ const handleActivity = handlerApp?.handleActivity?.bind(handlerApp);
1039
+ if (!Is.function(handleActivity)) {
1040
+ throw new GeneralError(DataspaceDataPlaneService.CLASS_NAME, "missingHandleActivity", {
1041
+ dataspaceAppId
1042
+ });
1043
+ }
1044
+ let taskError;
1045
+ let taskResult;
1046
+ try {
1047
+ taskResult = await handleActivity(activity);
1048
+ }
1049
+ catch (error) {
1050
+ taskError = BaseError.fromError(error);
1051
+ }
1052
+ const now = Date.now();
1053
+ const taskEntry = {
1054
+ taskId: RandomHelper.generateUuidV7("compact"),
1055
+ dataspaceAppId,
1056
+ processingGroupId,
1057
+ result: taskResult,
1058
+ startDate: new Date(now).toISOString(),
1059
+ endDate: new Date(now).toISOString(),
1060
+ status: Is.empty(taskError) ? ActivityTaskStatus.Success : ActivityTaskStatus.Failed,
1061
+ error: taskError
1062
+ };
1063
+ taskEntries.push(taskEntry);
1064
+ await this.notifyTaskStatusChanged(payload.activityLogEntryId, payload.activity.id, payload.dataspaceAppId, taskEntry.taskId, taskEntry.status);
1065
+ }
1066
+ else {
1067
+ const processingGroups = handlerApp.processingGroups?.() ?? {};
1068
+ if (Is.empty(processingGroups[processingGroupId])) {
1069
+ throw new GeneralError(DataspaceDataPlaneService.CLASS_NAME, "invalidProcessingGroupId", {
1070
+ processingGroupId
1071
+ });
1072
+ }
1073
+ const processingGroupOptions = processingGroups[processingGroupId];
1074
+ const taskType = `${dataspaceAppId}${processingGroupId ? `-${processingGroupId}` : ""}`;
1075
+ const taskId = await this._backgroundTaskComponent.create(taskType, payload, {
1076
+ retainFor: this._retainTasksFor,
1077
+ retryCount: processingGroupOptions?.retryCount ?? this._retryCount
1078
+ });
1079
+ if (!this._registeredTaskTypes.includes(taskType)) {
1080
+ this._registeredTaskTypes.push(taskType);
1081
+ await this._backgroundTaskComponent.registerHandler(taskType, "@twin.org/dataspace-app-runner", "appRunner", async (task) => {
1082
+ await this.finaliseBackgroundTask(task.id, task.status, task.payload);
1083
+ }, {
1084
+ maxWorkerCount: processingGroupOptions?.concurrentTasks,
1085
+ idleShutdownTimeout: processingGroupOptions?.idleShutdownTimeout,
1086
+ initialiseMethod: "appRunnerStart",
1087
+ shutdownMethod: "appRunnerEnd"
1088
+ });
1089
+ }
1090
+ taskEntries.push({
1091
+ taskId,
1092
+ dataspaceAppId,
1093
+ processingGroupId,
1094
+ status: ActivityTaskStatus.Pending
1095
+ });
1096
+ await this._logging?.log({
1097
+ level: "info",
1098
+ source: DataspaceDataPlaneService.CLASS_NAME,
1099
+ message: "scheduledTask",
1100
+ data: {
1101
+ taskId,
1102
+ dataspaceAppId,
1103
+ isRetry
1104
+ }
1105
+ });
1106
+ }
1107
+ return isInlineTask;
1108
+ }
1109
+ /**
1110
+ * Constructs the activity log entry with processing status and associated tasks.
1111
+ * @param activityLog The activity log details retrieved from storage.
1112
+ * @param activityTasks The activity tasks associated with the log entry, if any.
1113
+ * @returns The complete activity log entry with status and tasks.
1114
+ * @internal
1115
+ */
1116
+ async constructLogEntry(activityLog, activityTasks) {
1117
+ let tasks;
1118
+ // For calculating the processing status. `Registering` if we cannot determine the activity tasks yet
1119
+ let status = ActivityProcessingStatus.Registering;
1120
+ // Now query the associated tasks
1121
+ // If activity tasks is undefined it is because the corresponding store has not been persisted yet
1122
+ if (!Is.undefined(activityTasks)) {
1123
+ tasks = [];
1124
+ const typeCount = {
1125
+ [TaskStatus.Pending]: 0,
1126
+ [TaskStatus.Processing]: 0,
1127
+ [TaskStatus.Success]: 0,
1128
+ [TaskStatus.Failed]: 0,
1129
+ [TaskStatus.Cancelled]: 0
1130
+ };
1131
+ for (const entity of activityTasks.associatedTasks) {
1132
+ let entry;
1133
+ if (!Is.stringValue(entity.processingGroupId)) {
1134
+ // If there is no process group, the task was processed inline so the task status is already available in the entity
1135
+ typeCount[entity.status]++;
1136
+ entry = entity;
1137
+ }
1138
+ else {
1139
+ const taskDetails = await this._backgroundTaskComponent.get(entity.taskId);
1140
+ if (!Is.empty(taskDetails)) {
1141
+ typeCount[taskDetails.status]++;
1142
+ switch (taskDetails.status) {
1143
+ case TaskStatus.Success:
1144
+ entry = {
1145
+ ...entity,
1146
+ status: ActivityTaskStatus.Success,
1147
+ result: taskDetails.result,
1148
+ startDate: taskDetails?.dateCreated,
1149
+ endDate: taskDetails?.dateCompleted
1150
+ };
1151
+ break;
1152
+ case TaskStatus.Pending:
1153
+ entry = { ...entity, status: ActivityTaskStatus.Pending };
1154
+ break;
1155
+ case TaskStatus.Processing:
1156
+ entry = {
1157
+ ...entity,
1158
+ status: ActivityTaskStatus.Processing,
1159
+ startDate: taskDetails.dateCreated
1160
+ };
1161
+ break;
1162
+ case TaskStatus.Failed:
1163
+ entry = {
1164
+ ...entity,
1165
+ status: ActivityTaskStatus.Failed,
1166
+ error: taskDetails.error
1167
+ };
1168
+ break;
1169
+ case TaskStatus.Cancelled:
1170
+ // Nothing to do for cancelled tasks
1171
+ break;
1172
+ }
1173
+ }
1174
+ }
1175
+ if (!Is.empty(entry)) {
1176
+ tasks.push(entry);
1177
+ }
1178
+ }
1179
+ if (typeCount[TaskStatus.Failed] > 0) {
1180
+ status = ActivityProcessingStatus.Error;
1181
+ }
1182
+ else if (typeCount[TaskStatus.Processing] > 0) {
1183
+ status = ActivityProcessingStatus.Running;
1184
+ }
1185
+ else if (typeCount[TaskStatus.Pending] > 0) {
1186
+ status = ActivityProcessingStatus.Pending;
1187
+ }
1188
+ else {
1189
+ status = ActivityProcessingStatus.Completed;
1190
+ }
1191
+ }
1192
+ return { ...activityLog, status, tasks };
1193
+ }
1071
1194
  }
1072
1195
  //# sourceMappingURL=dataspaceDataPlaneService.js.map