@open-mercato/core 0.6.2-develop.3330.1.8ad2b3b84c → 0.6.2-develop.3366.1.4692954e44

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 (102) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/sync_excel_upload/index.js +37 -0
  3. package/dist/generated/entities/sync_excel_upload/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +4 -0
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +19 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/customers/search.js +3 -2
  9. package/dist/modules/customers/search.js.map +2 -2
  10. package/dist/modules/data_sync/api/runs/[id]/cancel.js +16 -0
  11. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  12. package/dist/modules/data_sync/di.js +2 -1
  13. package/dist/modules/data_sync/di.js.map +2 -2
  14. package/dist/modules/data_sync/lib/sync-engine.js +178 -54
  15. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  16. package/dist/modules/data_sync/lib/sync-run-service.js +23 -0
  17. package/dist/modules/data_sync/lib/sync-run-service.js.map +3 -3
  18. package/dist/modules/integrations/backend/integrations/[id]/page.js +241 -55
  19. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +3 -3
  20. package/dist/modules/sync_excel/acl.js +10 -0
  21. package/dist/modules/sync_excel/acl.js.map +7 -0
  22. package/dist/modules/sync_excel/api/import/route.js +164 -0
  23. package/dist/modules/sync_excel/api/import/route.js.map +7 -0
  24. package/dist/modules/sync_excel/api/preview/route.js +84 -0
  25. package/dist/modules/sync_excel/api/preview/route.js.map +7 -0
  26. package/dist/modules/sync_excel/api/upload/route.js +131 -0
  27. package/dist/modules/sync_excel/api/upload/route.js.map +7 -0
  28. package/dist/modules/sync_excel/data/entities.js +79 -0
  29. package/dist/modules/sync_excel/data/entities.js.map +7 -0
  30. package/dist/modules/sync_excel/data/validators.js +58 -0
  31. package/dist/modules/sync_excel/data/validators.js.map +7 -0
  32. package/dist/modules/sync_excel/di.js +11 -0
  33. package/dist/modules/sync_excel/di.js.map +7 -0
  34. package/dist/modules/sync_excel/index.js +11 -0
  35. package/dist/modules/sync_excel/index.js.map +7 -0
  36. package/dist/modules/sync_excel/integration.js +36 -0
  37. package/dist/modules/sync_excel/integration.js.map +7 -0
  38. package/dist/modules/sync_excel/lib/adapters/customers.js +852 -0
  39. package/dist/modules/sync_excel/lib/adapters/customers.js.map +7 -0
  40. package/dist/modules/sync_excel/lib/column-detector.js +77 -0
  41. package/dist/modules/sync_excel/lib/column-detector.js.map +7 -0
  42. package/dist/modules/sync_excel/lib/mapping.js +16 -0
  43. package/dist/modules/sync_excel/lib/mapping.js.map +7 -0
  44. package/dist/modules/sync_excel/lib/parser.js +125 -0
  45. package/dist/modules/sync_excel/lib/parser.js.map +7 -0
  46. package/dist/modules/sync_excel/lib/scope.js +44 -0
  47. package/dist/modules/sync_excel/lib/scope.js.map +7 -0
  48. package/dist/modules/sync_excel/lib/upload-storage.js +63 -0
  49. package/dist/modules/sync_excel/lib/upload-storage.js.map +7 -0
  50. package/dist/modules/sync_excel/migrations/Migration20260329181120_sync_excel.js +11 -0
  51. package/dist/modules/sync_excel/migrations/Migration20260329181120_sync_excel.js.map +7 -0
  52. package/dist/modules/sync_excel/setup.js +19 -0
  53. package/dist/modules/sync_excel/setup.js.map +7 -0
  54. package/dist/modules/sync_excel/widgets/injection/upload-config/target-options.js +293 -0
  55. package/dist/modules/sync_excel/widgets/injection/upload-config/target-options.js.map +7 -0
  56. package/dist/modules/sync_excel/widgets/injection/upload-config/widget.client.js +934 -0
  57. package/dist/modules/sync_excel/widgets/injection/upload-config/widget.client.js.map +7 -0
  58. package/dist/modules/sync_excel/widgets/injection/upload-config/widget.js +15 -0
  59. package/dist/modules/sync_excel/widgets/injection/upload-config/widget.js.map +7 -0
  60. package/dist/modules/sync_excel/widgets/injection-table.js +17 -0
  61. package/dist/modules/sync_excel/widgets/injection-table.js.map +7 -0
  62. package/generated/entities/sync_excel_upload/index.ts +17 -0
  63. package/generated/entities.ids.generated.ts +4 -0
  64. package/generated/entity-fields-registry.ts +19 -0
  65. package/package.json +7 -7
  66. package/src/modules/customers/search.ts +3 -2
  67. package/src/modules/data_sync/api/runs/[id]/cancel.ts +18 -0
  68. package/src/modules/data_sync/di.ts +4 -1
  69. package/src/modules/data_sync/lib/adapter.ts +15 -0
  70. package/src/modules/data_sync/lib/sync-engine.ts +205 -52
  71. package/src/modules/data_sync/lib/sync-run-service.ts +24 -0
  72. package/src/modules/integrations/backend/integrations/[id]/page.tsx +220 -15
  73. package/src/modules/integrations/i18n/de.json +9 -0
  74. package/src/modules/integrations/i18n/en.json +9 -0
  75. package/src/modules/integrations/i18n/es.json +9 -0
  76. package/src/modules/integrations/i18n/pl.json +9 -0
  77. package/src/modules/sync_excel/acl.ts +6 -0
  78. package/src/modules/sync_excel/api/import/route.ts +182 -0
  79. package/src/modules/sync_excel/api/preview/route.ts +89 -0
  80. package/src/modules/sync_excel/api/upload/route.ts +144 -0
  81. package/src/modules/sync_excel/data/entities.ts +61 -0
  82. package/src/modules/sync_excel/data/validators.ts +58 -0
  83. package/src/modules/sync_excel/di.ts +9 -0
  84. package/src/modules/sync_excel/i18n/de.json +107 -0
  85. package/src/modules/sync_excel/i18n/en.json +107 -0
  86. package/src/modules/sync_excel/i18n/es.json +107 -0
  87. package/src/modules/sync_excel/i18n/pl.json +107 -0
  88. package/src/modules/sync_excel/index.ts +7 -0
  89. package/src/modules/sync_excel/integration.ts +31 -0
  90. package/src/modules/sync_excel/lib/adapters/customers.ts +1097 -0
  91. package/src/modules/sync_excel/lib/column-detector.ts +91 -0
  92. package/src/modules/sync_excel/lib/mapping.ts +19 -0
  93. package/src/modules/sync_excel/lib/parser.ts +165 -0
  94. package/src/modules/sync_excel/lib/scope.ts +63 -0
  95. package/src/modules/sync_excel/lib/upload-storage.ts +79 -0
  96. package/src/modules/sync_excel/migrations/.snapshot-open-mercato.json +199 -0
  97. package/src/modules/sync_excel/migrations/Migration20260329181120_sync_excel.ts +10 -0
  98. package/src/modules/sync_excel/setup.ts +20 -0
  99. package/src/modules/sync_excel/widgets/injection/upload-config/target-options.ts +351 -0
  100. package/src/modules/sync_excel/widgets/injection/upload-config/widget.client.tsx +1224 -0
  101. package/src/modules/sync_excel/widgets/injection/upload-config/widget.ts +30 -0
  102. package/src/modules/sync_excel/widgets/injection-table.ts +15 -0
@@ -2,6 +2,7 @@ import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import { getIntegration } from '@open-mercato/shared/modules/integrations/types'
3
3
  import type { CredentialsService } from '../../integrations/lib/credentials-service'
4
4
  import type { IntegrationLogService } from '../../integrations/lib/log-service'
5
+ import type { IntegrationStateService } from '../../integrations/lib/state-service'
5
6
  import type { ProgressService } from '../../progress/lib/progressService'
6
7
  import { refreshCoverageSnapshot } from '../../query_index/lib/coverage'
7
8
  import { emitDataSyncEvent } from '../events'
@@ -20,6 +21,7 @@ type EngineDeps = {
20
21
  syncRunService: SyncRunService
21
22
  integrationCredentialsService: CredentialsService
22
23
  integrationLogService: IntegrationLogService
24
+ integrationStateService?: IntegrationStateService
23
25
  progressService: ProgressService
24
26
  }
25
27
 
@@ -71,7 +73,7 @@ function applyExportCounters(batch: ExportBatch): SyncCounterDelta {
71
73
  }
72
74
 
73
75
  export function createSyncEngine(deps: EngineDeps) {
74
- const { syncRunService, integrationCredentialsService, integrationLogService, progressService } = deps
76
+ const { syncRunService, integrationCredentialsService, integrationLogService, integrationStateService, progressService } = deps
75
77
 
76
78
  async function resolveMapping(adapter: DataSyncAdapter, entityType: string, scope: SyncScope): Promise<DataMapping> {
77
79
  return adapter.getMapping({
@@ -128,7 +130,7 @@ export function createSyncEngine(deps: EngineDeps) {
128
130
  ? item.data.sourceIdentifier.trim()
129
131
  : null
130
132
  const message = [
131
- `Failed to import Akeneo product ${item.externalId}`,
133
+ `Failed to import item ${item.externalId}`,
132
134
  sourceProductUuid ? `(uuid: ${sourceProductUuid})` : null,
133
135
  sourceIdentifier ? `(identifier: ${sourceIdentifier})` : null,
134
136
  `: ${errorMessage}`,
@@ -147,10 +149,65 @@ export function createSyncEngine(deps: EngineDeps) {
147
149
  }
148
150
  }
149
151
 
150
- async function finalizeRun(runId: string, status: 'completed' | 'failed' | 'cancelled', scope: SyncScope, error?: string): Promise<void> {
152
+ async function writeOperationalLog(params: {
153
+ integrationId: string
154
+ runId: string
155
+ level: 'info' | 'warn' | 'error'
156
+ message: string
157
+ scope: SyncScope
158
+ enabled: boolean
159
+ payload?: Record<string, unknown>
160
+ }): Promise<void> {
161
+ if (!params.enabled) return
162
+
163
+ await integrationLogService.write(
164
+ {
165
+ integrationId: params.integrationId,
166
+ runId: params.runId,
167
+ level: params.level,
168
+ message: params.message,
169
+ payload: params.payload,
170
+ },
171
+ params.scope,
172
+ )
173
+ }
174
+
175
+ async function updateOperationalState(params: {
176
+ integrationId: string
177
+ status: 'healthy' | 'degraded' | 'unhealthy'
178
+ scope: SyncScope
179
+ enabled: boolean
180
+ }): Promise<void> {
181
+ if (!params.enabled || !integrationStateService) return
182
+
183
+ await integrationStateService.upsert(
184
+ params.integrationId,
185
+ {
186
+ lastHealthStatus: params.status,
187
+ lastHealthCheckedAt: new Date(),
188
+ },
189
+ params.scope,
190
+ )
191
+ }
192
+
193
+ async function finalizeRun(
194
+ runId: string,
195
+ status: 'completed' | 'failed' | 'cancelled',
196
+ scope: SyncScope,
197
+ error?: string,
198
+ operationalTelemetry = false,
199
+ ): Promise<void> {
200
+ const existingRun = await syncRunService.getRun(runId, scope)
201
+ const alreadyFinalizedWithSameStatus = existingRun?.status === status
202
+ && (status === 'completed' || status === 'failed' || status === 'cancelled')
203
+
151
204
  const run = await syncRunService.markStatus(runId, status, scope, error)
152
205
  if (!run) return
153
206
 
207
+ if (alreadyFinalizedWithSameStatus) {
208
+ return
209
+ }
210
+
154
211
  if (run.progressJobId) {
155
212
  if (status === 'completed') {
156
213
  await progressService.completeJob(
@@ -194,6 +251,70 @@ export function createSyncEngine(deps: EngineDeps) {
194
251
  }
195
252
  }
196
253
 
254
+ if (status === 'completed') {
255
+ await updateOperationalState({
256
+ integrationId: run.integrationId,
257
+ status: 'healthy',
258
+ scope,
259
+ enabled: operationalTelemetry,
260
+ })
261
+ await writeOperationalLog({
262
+ integrationId: run.integrationId,
263
+ runId: run.id,
264
+ level: 'info',
265
+ message: 'Sync run completed',
266
+ scope,
267
+ enabled: operationalTelemetry,
268
+ payload: {
269
+ operationalStatus: 'completed',
270
+ summary: `Sync completed with ${run.createdCount} created, ${run.updatedCount} updated, ${run.failedCount} failed.`,
271
+ createdCount: run.createdCount,
272
+ updatedCount: run.updatedCount,
273
+ skippedCount: run.skippedCount,
274
+ failedCount: run.failedCount,
275
+ batchesCompleted: run.batchesCompleted,
276
+ },
277
+ })
278
+ } else if (status === 'cancelled') {
279
+ await updateOperationalState({
280
+ integrationId: run.integrationId,
281
+ status: 'degraded',
282
+ scope,
283
+ enabled: operationalTelemetry,
284
+ })
285
+ await writeOperationalLog({
286
+ integrationId: run.integrationId,
287
+ runId: run.id,
288
+ level: 'warn',
289
+ message: 'Sync run cancelled',
290
+ scope,
291
+ enabled: operationalTelemetry,
292
+ payload: {
293
+ operationalStatus: 'cancelled',
294
+ summary: 'The sync run was cancelled before completion.',
295
+ },
296
+ })
297
+ } else {
298
+ await updateOperationalState({
299
+ integrationId: run.integrationId,
300
+ status: 'unhealthy',
301
+ scope,
302
+ enabled: operationalTelemetry,
303
+ })
304
+ await writeOperationalLog({
305
+ integrationId: run.integrationId,
306
+ runId: run.id,
307
+ level: 'error',
308
+ message: error ?? 'Sync run failed',
309
+ scope,
310
+ enabled: operationalTelemetry,
311
+ payload: {
312
+ operationalStatus: 'failed',
313
+ summary: error ?? 'The sync run failed.',
314
+ },
315
+ })
316
+ }
317
+
197
318
  if (status === 'completed') {
198
319
  await emitDataSyncEvent('data_sync.run.completed', {
199
320
  runId,
@@ -252,6 +373,7 @@ export function createSyncEngine(deps: EngineDeps) {
252
373
  if (!adapter?.streamImport) {
253
374
  throw new Error(`No import adapter registered for provider ${providerKey}`)
254
375
  }
376
+ const operationalTelemetry = adapter.operationalTelemetry === true
255
377
 
256
378
  const credentials = await integrationCredentialsService.resolve(run.integrationId, scope)
257
379
  if (!credentials) {
@@ -260,13 +382,6 @@ export function createSyncEngine(deps: EngineDeps) {
260
382
 
261
383
  const activeRun = await syncRunService.markStatus(run.id, 'running', scope)
262
384
  if (!activeRun || activeRun.status !== 'running') {
263
- if (run.progressJobId) {
264
- await progressService.markCancelled(run.progressJobId, {
265
- tenantId: scope.tenantId,
266
- organizationId: scope.organizationId,
267
- userId: scope.userId,
268
- })
269
- }
270
385
  return
271
386
  }
272
387
  await emitDataSyncEvent('data_sync.run.started', {
@@ -277,6 +392,26 @@ export function createSyncEngine(deps: EngineDeps) {
277
392
  tenantId: scope.tenantId,
278
393
  organizationId: scope.organizationId,
279
394
  })
395
+ await updateOperationalState({
396
+ integrationId: run.integrationId,
397
+ status: 'degraded',
398
+ scope,
399
+ enabled: operationalTelemetry,
400
+ })
401
+ await writeOperationalLog({
402
+ integrationId: run.integrationId,
403
+ runId: run.id,
404
+ level: 'info',
405
+ message: 'Sync run started',
406
+ scope,
407
+ enabled: operationalTelemetry,
408
+ payload: {
409
+ operationalStatus: 'running',
410
+ summary: `Import run started for ${run.entityType}.`,
411
+ entityType: run.entityType,
412
+ direction: run.direction,
413
+ },
414
+ })
280
415
 
281
416
  if (run.progressJobId) {
282
417
  await progressService.startJob(run.progressJobId, {
@@ -298,9 +433,10 @@ export function createSyncEngine(deps: EngineDeps) {
298
433
  credentials,
299
434
  mapping,
300
435
  scope: { organizationId: scope.organizationId, tenantId: scope.tenantId },
436
+ runId: run.id,
301
437
  })) {
302
438
  if (run.progressJobId && await progressService.isCancellationRequested(run.progressJobId, scope.tenantId)) {
303
- await finalizeRun(run.id, 'cancelled', scope)
439
+ await finalizeRun(run.id, 'cancelled', scope, undefined, operationalTelemetry)
304
440
  return
305
441
  }
306
442
 
@@ -323,23 +459,24 @@ export function createSyncEngine(deps: EngineDeps) {
323
459
  await refreshCoverageSnapshots(batch.refreshCoverageEntityTypes, scope)
324
460
  await logImportItemFailures(run.id, run.integrationId, batch.items, scope)
325
461
 
326
- await integrationLogService.write(
327
- {
328
- integrationId: run.integrationId,
329
- runId: run.id,
330
- level: 'info',
331
- message: batch.message?.trim().length
332
- ? batch.message.trim()
333
- : `Processed import batch ${batch.batchIndex}`,
334
- payload: {
335
- processedCount,
336
- batchSize: batch.items.length,
337
- processedBatchCount,
338
- cursor: batch.cursor,
339
- },
340
- },
462
+ await writeOperationalLog({
463
+ integrationId: run.integrationId,
464
+ runId: run.id,
465
+ level: 'info',
466
+ message: batch.message?.trim().length
467
+ ? batch.message.trim()
468
+ : `Processed import batch ${batch.batchIndex}`,
341
469
  scope,
342
- )
470
+ enabled: operationalTelemetry,
471
+ payload: {
472
+ operationalStatus: 'running',
473
+ summary: `Processed ${processedCount}${totalCount ? ` of ${totalCount}` : ''} rows so far.`,
474
+ processedCount,
475
+ batchSize: batch.items.length,
476
+ processedBatchCount,
477
+ cursor: batch.cursor,
478
+ },
479
+ })
343
480
  }
344
481
  } catch (error) {
345
482
  const message = error instanceof Error ? error.message : 'Sync import failed'
@@ -352,11 +489,11 @@ export function createSyncEngine(deps: EngineDeps) {
352
489
  },
353
490
  scope,
354
491
  )
355
- await finalizeRun(run.id, 'failed', scope, message)
492
+ await finalizeRun(run.id, 'failed', scope, message, operationalTelemetry)
356
493
  return
357
494
  }
358
495
 
359
- await finalizeRun(run.id, 'completed', scope)
496
+ await finalizeRun(run.id, 'completed', scope, undefined, operationalTelemetry)
360
497
  },
361
498
 
362
499
  async runExport(runId: string, batchSize: number, scope: SyncScope): Promise<void> {
@@ -381,6 +518,7 @@ export function createSyncEngine(deps: EngineDeps) {
381
518
  if (!adapter?.streamExport) {
382
519
  throw new Error(`No export adapter registered for provider ${providerKey}`)
383
520
  }
521
+ const operationalTelemetry = adapter.operationalTelemetry === true
384
522
 
385
523
  const credentials = await integrationCredentialsService.resolve(run.integrationId, scope)
386
524
  if (!credentials) {
@@ -389,13 +527,6 @@ export function createSyncEngine(deps: EngineDeps) {
389
527
 
390
528
  const activeRun = await syncRunService.markStatus(run.id, 'running', scope)
391
529
  if (!activeRun || activeRun.status !== 'running') {
392
- if (run.progressJobId) {
393
- await progressService.markCancelled(run.progressJobId, {
394
- tenantId: scope.tenantId,
395
- organizationId: scope.organizationId,
396
- userId: scope.userId,
397
- })
398
- }
399
530
  return
400
531
  }
401
532
  await emitDataSyncEvent('data_sync.run.started', {
@@ -406,6 +537,26 @@ export function createSyncEngine(deps: EngineDeps) {
406
537
  tenantId: scope.tenantId,
407
538
  organizationId: scope.organizationId,
408
539
  })
540
+ await updateOperationalState({
541
+ integrationId: run.integrationId,
542
+ status: 'degraded',
543
+ scope,
544
+ enabled: operationalTelemetry,
545
+ })
546
+ await writeOperationalLog({
547
+ integrationId: run.integrationId,
548
+ runId: run.id,
549
+ level: 'info',
550
+ message: 'Sync run started',
551
+ scope,
552
+ enabled: operationalTelemetry,
553
+ payload: {
554
+ operationalStatus: 'running',
555
+ summary: `Export run started for ${run.entityType}.`,
556
+ entityType: run.entityType,
557
+ direction: run.direction,
558
+ },
559
+ })
409
560
 
410
561
  if (run.progressJobId) {
411
562
  await progressService.startJob(run.progressJobId, {
@@ -426,9 +577,10 @@ export function createSyncEngine(deps: EngineDeps) {
426
577
  credentials,
427
578
  mapping,
428
579
  scope: { organizationId: scope.organizationId, tenantId: scope.tenantId },
580
+ runId: run.id,
429
581
  })) {
430
582
  if (run.progressJobId && await progressService.isCancellationRequested(run.progressJobId, scope.tenantId)) {
431
- await finalizeRun(run.id, 'cancelled', scope)
583
+ await finalizeRun(run.id, 'cancelled', scope, undefined, operationalTelemetry)
432
584
  return
433
585
  }
434
586
 
@@ -450,20 +602,21 @@ export function createSyncEngine(deps: EngineDeps) {
450
602
  await syncRunService.updateCursor(run.id, batch.cursor, scope)
451
603
  await updateProgress(run.progressJobId, processedCount, null, scope)
452
604
 
453
- await integrationLogService.write(
454
- {
455
- integrationId: run.integrationId,
456
- runId: run.id,
457
- level: 'info',
458
- message: `Processed export batch ${batch.batchIndex}`,
459
- payload: {
460
- processedCount,
461
- batchSize: batch.results.length,
462
- cursor: batch.cursor,
463
- },
464
- },
605
+ await writeOperationalLog({
606
+ integrationId: run.integrationId,
607
+ runId: run.id,
608
+ level: 'info',
609
+ message: `Processed export batch ${batch.batchIndex}`,
465
610
  scope,
466
- )
611
+ enabled: operationalTelemetry,
612
+ payload: {
613
+ operationalStatus: 'running',
614
+ summary: `Processed ${processedCount} export items so far.`,
615
+ processedCount,
616
+ batchSize: batch.results.length,
617
+ cursor: batch.cursor,
618
+ },
619
+ })
467
620
  }
468
621
  } catch (error) {
469
622
  const message = error instanceof Error ? error.message : 'Sync export failed'
@@ -476,11 +629,11 @@ export function createSyncEngine(deps: EngineDeps) {
476
629
  },
477
630
  scope,
478
631
  )
479
- await finalizeRun(run.id, 'failed', scope, message)
632
+ await finalizeRun(run.id, 'failed', scope, message, operationalTelemetry)
480
633
  return
481
634
  }
482
635
 
483
- await finalizeRun(run.id, 'completed', scope)
636
+ await finalizeRun(run.id, 'completed', scope, undefined, operationalTelemetry)
484
637
  },
485
638
  }
486
639
  }
@@ -86,6 +86,30 @@ export function createSyncRunService(em: EntityManager) {
86
86
  },
87
87
 
88
88
  async markStatus(runId: string, status: SyncRun['status'], scope: SyncScope, error?: string): Promise<SyncRun | null> {
89
+ if (status === 'running') {
90
+ const updated = await em.nativeUpdate(
91
+ SyncRun,
92
+ {
93
+ id: runId,
94
+ organizationId: scope.organizationId,
95
+ tenantId: scope.tenantId,
96
+ deletedAt: null,
97
+ status: 'pending',
98
+ },
99
+ {
100
+ status,
101
+ ...(error !== undefined ? { lastError: error } : {}),
102
+ updatedAt: new Date(),
103
+ },
104
+ )
105
+ if (updated === 0) return null
106
+ const row = await this.getRun(runId, scope)
107
+ if (row && typeof em.refresh === 'function') {
108
+ await em.refresh(row)
109
+ }
110
+ return row
111
+ }
112
+
89
113
  const row = await this.getRun(runId, scope)
90
114
  if (!row) return null
91
115
  const isTerminal = row.status === 'completed' || row.status === 'failed' || row.status === 'cancelled'