@lde/pipeline 0.43.0 → 0.45.0

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.
@@ -105,25 +105,25 @@ export class ImportResolver {
105
105
  await this.options.server.start();
106
106
  }
107
107
  catch (error) {
108
- callbacks?.onImportFailed?.(importResult.distribution, error instanceof Error ? error.message : String(error));
108
+ callbacks?.onImportFailed?.(importResult.distribution, error instanceof Error ? error : new Error(String(error)));
109
109
  return new NoDistributionAvailable(dataset, 'SPARQL server failed to start after import', probeResults);
110
110
  }
111
111
  const distribution = Distribution.sparql(this.options.server.queryEndpoint, importResult.identifier);
112
112
  distribution.subjectFilter = importResult.distribution.subjectFilter;
113
113
  return new ResolvedDistribution(distribution, probeResults, {
114
114
  from: importResult.distribution,
115
- duration: Date.now() - importStart,
115
+ durationMs: Date.now() - importStart,
116
116
  tripleCount: importResult.tripleCount,
117
117
  engine: importResult.engine,
118
118
  });
119
119
  }
120
120
  if (importResult.type === 'failed') {
121
- callbacks?.onImportFailed?.(importResult.distribution, importResult.error);
121
+ callbacks?.onImportFailed?.(importResult.distribution, new Error(importResult.error));
122
122
  return new NoDistributionAvailable(dataset, 'No SPARQL endpoint or importable data dump available', probeResults, importResult);
123
123
  }
124
124
  if (importResult.type === 'not-supported') {
125
125
  const failedDistribution = importResult.distribution ?? candidates[0];
126
- callbacks?.onImportFailed?.(failedDistribution, 'No supported import format');
126
+ callbacks?.onImportFailed?.(failedDistribution, new Error('No supported import format'));
127
127
  return new NoDistributionAvailable(dataset, 'No supported import format available', probeResults);
128
128
  }
129
129
  // Unreachable by type, but not at runtime: an importer built against an
@@ -15,7 +15,7 @@ export interface ImportDetails {
15
15
  /** The data dump that was imported. */
16
16
  readonly from: Distribution;
17
17
  /** Milliseconds the import took. */
18
- readonly duration: number;
18
+ readonly durationMs: number;
19
19
  /** Triples in the imported data, when the importer can tell. */
20
20
  readonly tripleCount?: number;
21
21
  /** The engine that built the imported data, when the importer can tell. */
@@ -56,7 +56,7 @@ export interface ResolveCallbacks {
56
56
  /** Called when a data-dump import begins (resolve phase). */
57
57
  onImportStart?: () => void;
58
58
  /** Called when importing a distribution fails (resolve phase). */
59
- onImportFailed?: (distribution: Distribution, error: string) => void;
59
+ onImportFailed?: (distribution: Distribution, error: Error) => void;
60
60
  }
61
61
  /**
62
62
  * Resolves a dataset to a usable distribution in two phases so the pipeline can
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/distribution/resolver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/distribution/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AAEjC,qBAAa,oBAAoB;IAE7B,QAAQ,CAAC,YAAY,EAAE,YAAY;IACnC,QAAQ,CAAC,YAAY,EAAE,eAAe,EAAE;IACxC,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa;gBAHxB,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,eAAe,EAAE;IACxC,kEAAkE;IACzD,QAAQ,CAAC,EAAE,aAAa,YAAA;CAEpC;AAED,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,uBAAuB;IAEhC,QAAQ,CAAC,OAAO,EAAE,OAAO;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,QAAQ,CAAC,YAAY,EAAE,eAAe,EAAE;IACxC,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY;gBAH3B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,eAAe,EAAE,EAC/B,YAAY,CAAC,EAAE,YAAY,YAAA;CAEvC;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,YAAY,CAAC;IAC3B,WAAW,EAAE,eAAe,CAAC;CAC9B;AAED;;;;;GAKG;AACH,qBAAa,mBAAmB;IAE5B,QAAQ,CAAC,OAAO,EAAE,OAAO;IACzB,QAAQ,CAAC,YAAY,EAAE,eAAe,EAAE;IACxC,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;gBAF3B,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,eAAe,EAAE,EAC/B,MAAM,EAAE,YAAY,GAAG,IAAI;CAEvC;AAED,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IACxE,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,kEAAkE;IAClE,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACrE;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,CACH,OAAO,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,gBAAgB,GAC3B,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAChC,OAAO,CACL,MAAM,EAAE,mBAAmB,EAC3B,SAAS,CAAC,EAAE,gBAAgB,GAC3B,OAAO,CAAC,oBAAoB,GAAG,uBAAuB,CAAC,CAAC;IAC3D;;;;;;;;;;OAUG;IACH,eAAe,CAAC,CACd,MAAM,EAAE,mBAAmB,EAC3B,SAAS,CAAC,EAAE,gBAAgB,GAC3B,OAAO,CAAC,oBAAoB,GAAG,uBAAuB,CAAC,CAAC;IAC3D,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,MAAM,WAAW,iCAAiC;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,0BAA2B,YAAW,oBAAoB;IACrE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,CAAC,EAAE,iCAAiC;IAIjD,KAAK,CACT,OAAO,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,gBAAgB,GAC3B,OAAO,CAAC,mBAAmB,CAAC;IA4BzB,OAAO,CACX,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,oBAAoB,GAAG,uBAAuB,CAAC;CAc3D"}
package/dist/index.d.ts CHANGED
@@ -3,7 +3,8 @@ export * from './namespaceAlias.js';
3
3
  export * from './asyncQueue.js';
4
4
  export * from './batch.js';
5
5
  export * from './pipeline.js';
6
- export * from './progressReporter.js';
6
+ export * from './pipelineEvent.js';
7
+ export * from './reporter.js';
7
8
  export * from './selector.js';
8
9
  export * from './stage.js';
9
10
  export * from './stageOutputResolver.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oCAAoC,CAAC;AACnD,cAAc,oCAAoC,CAAC"}
package/dist/index.js CHANGED
@@ -3,7 +3,8 @@ export * from './namespaceAlias.js';
3
3
  export * from './asyncQueue.js';
4
4
  export * from './batch.js';
5
5
  export * from './pipeline.js';
6
- export * from './progressReporter.js';
6
+ export * from './pipelineEvent.js';
7
+ export * from './reporter.js';
7
8
  export * from './selector.js';
8
9
  export * from './stage.js';
9
10
  export * from './stageOutputResolver.js';
@@ -7,7 +7,7 @@ import type { Writer } from './writer/writer.js';
7
7
  import { type DistributionResolver } from './distribution/resolver.js';
8
8
  import type { ProvenanceStore } from './provenance/store.js';
9
9
  import type { StageOutputResolver } from './stageOutputResolver.js';
10
- import type { ProgressReporter } from './progressReporter.js';
10
+ import type { Reporter } from './reporter.js';
11
11
  import { type TimeoutPolicy } from './sparql/timeoutPolicy.js';
12
12
  /**
13
13
  * Why a run was abandoned rather than committed: its selector produced no
@@ -90,13 +90,8 @@ export interface PipelineOptions<Out = Quad> {
90
90
  stageOutputResolver: StageOutputResolver;
91
91
  outputDir: string;
92
92
  };
93
- /**
94
- * Observer(s) notified of pipeline lifecycle events. Pass an array to have
95
- * several reporters observe the same run – e.g. a console reporter alongside
96
- * a verdict-collecting one; every reporter receives each event in array
97
- * order. A single reporter may be passed directly.
98
- */
99
- reporter?: ProgressReporter | readonly ProgressReporter[];
93
+ /** Reporter(s) handed every {@link PipelineEvent} of a run, in array order. */
94
+ reporter?: Reporter | readonly Reporter[];
100
95
  /**
101
96
  * Optional per-dataset processing memory. When set, the pipeline skips a
102
97
  * dataset whose source-change fingerprint and {@link pipelineVersion} both
@@ -134,13 +129,15 @@ export declare class Pipeline<Out = Quad> {
134
129
  private readonly beforeDatasetWrite?;
135
130
  private readonly distributionResolver;
136
131
  private readonly chaining?;
137
- private readonly reporter?;
132
+ private readonly reporters;
138
133
  private readonly timeoutFactory;
139
134
  private readonly provenanceStore?;
140
135
  private readonly pipelineVersion?;
141
136
  constructor(options: PipelineOptions<Out>);
142
137
  run(): Promise<void>;
143
138
  private processDataset;
139
+ private report;
140
+ private reportStageFailed;
144
141
  /** Persist the processing record for a dataset, when a store is configured. */
145
142
  private recordOutcome;
146
143
  private reportValidators;
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
package/dist/pipeline.js CHANGED
@@ -9,8 +9,8 @@ import { sourceFingerprint } from './provenance/sourceFingerprint.js';
9
9
  import { shouldReprocess } from './provenance/reprocessDecision.js';
10
10
  import { NetworkError, SparqlProbeResult, } from '@lde/distribution-probe';
11
11
  import { importOutcomeToVerdict, probeResultToVerdict, } from '@lde/distribution-health';
12
- import { NotSupported } from './sparql/reader.js';
13
- import { combineReporters } from './combineReporters.js';
12
+ import { NoItemsSelected, NotSupported } from './sparql/reader.js';
13
+ import { levelOf, } from './pipelineEvent.js';
14
14
  import { UnreadValueTally } from './unreadValueTally.js';
15
15
  import { ConstantTimeoutPolicy, } from './sparql/timeoutPolicy.js';
16
16
  /**
@@ -238,7 +238,7 @@ export class Pipeline {
238
238
  beforeDatasetWrite;
239
239
  distributionResolver;
240
240
  chaining;
241
- reporter;
241
+ reporters;
242
242
  timeoutFactory;
243
243
  provenanceStore;
244
244
  pipelineVersion;
@@ -278,11 +278,7 @@ export class Pipeline {
278
278
  this.distributionResolver =
279
279
  options.distributionResolver ?? new SparqlDistributionResolver();
280
280
  this.chaining = options.chaining;
281
- // `Array.isArray` narrows the array branch but not the readonly-array out of
282
- // the else branch, so cast the single-reporter case explicitly.
283
- this.reporter = Array.isArray(options.reporter)
284
- ? combineReporters(options.reporter)
285
- : options.reporter;
281
+ this.reporters = [options.reporter ?? []].flat();
286
282
  this.timeoutFactory =
287
283
  options.timeout ?? (() => new ConstantTimeoutPolicy(300_000));
288
284
  this.provenanceStore = options.provenanceStore;
@@ -290,7 +286,8 @@ export class Pipeline {
290
286
  }
291
287
  async run() {
292
288
  const start = Date.now();
293
- this.reporter?.pipelineStart?.(this.name);
289
+ const runId = randomUUID();
290
+ this.report(runId, { type: 'pipelineStart', name: this.name });
294
291
  // Fail fast on a store that cannot persist records (e.g. a provenance
295
292
  // file on a mount the runtime user cannot write to): every dataset would
296
293
  // process fine, but the records would be lost and skip-unchanged would
@@ -298,17 +295,21 @@ export class Pipeline {
298
295
  await this.provenanceStore?.check?.();
299
296
  const selectStart = Date.now();
300
297
  const datasets = await this.datasetSelector.select();
301
- this.reporter?.datasetsSelected?.(datasets.total, Date.now() - selectStart);
298
+ this.report(runId, {
299
+ type: 'datasetsSelected',
300
+ count: datasets.total,
301
+ durationMs: Date.now() - selectStart,
302
+ });
302
303
  // The run transaction: one openRun → write* → commit/abort per pipeline
303
304
  // run. `selectedSources` accumulates every selected dataset – including
304
305
  // ones skipped as unchanged – so a writer's commit can sweep by registry
305
306
  // membership.
306
307
  const selectedSources = [];
307
308
  const context = {
308
- runId: randomUUID(),
309
+ runId,
309
310
  startedAt: new Date().toISOString(),
310
311
  selectedSources: () => selectedSources,
311
- itemsRejected: (dataset, rejection) => this.reporter?.itemsRejected?.(dataset, rejection),
312
+ itemsRejected: (dataset, rejection) => this.report(runId, { type: 'itemsRejected', dataset, rejection }),
312
313
  provenance: this.provenanceStore,
313
314
  };
314
315
  const openedWriter = await this.writer.openRun(context);
@@ -350,36 +351,61 @@ export class Pipeline {
350
351
  // meant not to do.
351
352
  if (abandoned) {
352
353
  const reason = new EmptySelection();
353
- this.reporter?.selectionEmpty?.(reason.message);
354
+ this.report(runId, { type: 'selectionEmpty', message: reason.message });
354
355
  await runWriter.abort(reason);
355
356
  }
356
357
  const finalMemory = process.memoryUsage();
357
- this.reporter?.pipelineComplete?.({
358
- duration: Date.now() - start,
358
+ this.report(runId, {
359
+ type: 'pipelineComplete',
360
+ durationMs: Date.now() - start,
359
361
  memoryUsageBytes: finalMemory.rss,
360
362
  heapUsedBytes: finalMemory.heapUsed,
361
363
  });
362
364
  }
363
365
  async processDataset(dataset, runWriter, context) {
364
- this.reporter?.datasetStart?.(dataset);
366
+ const { runId } = context;
367
+ const datasetStart = Date.now();
368
+ this.report(runId, { type: 'datasetStart', dataset });
369
+ const skip = (reason) => this.report(runId, { type: 'datasetSkipped', dataset, reason });
370
+ const skipOnError = (code, prefix, thrown) => {
371
+ const error = thrown instanceof Error
372
+ ? thrown
373
+ : new Error(String(thrown), { cause: thrown });
374
+ this.report(runId, {
375
+ type: 'datasetSkipped',
376
+ dataset,
377
+ reason: { code, message: `${prefix}: ${error.message}` },
378
+ error,
379
+ });
380
+ };
365
381
  // Probe phase: gather probe results and the source-to-be, without importing.
366
382
  let probed;
367
383
  try {
368
384
  probed = await this.distributionResolver.probe(dataset, {
369
385
  onProbe: (distribution, result) => {
370
- this.reporter?.distributionProbed?.(mapProbeResult(distribution, result));
386
+ this.report(runId, {
387
+ type: 'distributionProbed',
388
+ dataset,
389
+ distribution,
390
+ result: mapProbeResult(distribution, result),
391
+ });
371
392
  // Shallow validity: the probe parse-validates small RDF bodies as a
372
393
  // by-product. Surface that verdict per distribution, judged against
373
394
  // the distribution's own observed fingerprint.
374
395
  const verdict = probeResultToVerdict(result, sourceFingerprint(distribution, result));
375
396
  if (verdict) {
376
- this.reporter?.distributionValidated?.(distribution, verdict);
397
+ this.report(runId, {
398
+ type: 'distributionValidated',
399
+ dataset,
400
+ distribution,
401
+ verdict,
402
+ });
377
403
  }
378
404
  },
379
405
  });
380
406
  }
381
407
  catch (error) {
382
- this.reporter?.datasetSkipped?.(dataset, `Distribution probing failed: ${error instanceof Error ? error.message : String(error)}`);
408
+ skipOnError('probeFailed', 'Distribution probing failed', error);
383
409
  return;
384
410
  }
385
411
  // Derive the source-change fingerprint from the probed source: null for a
@@ -406,24 +432,26 @@ export class Pipeline {
406
432
  sourceFingerprint: fingerprint,
407
433
  pipelineVersion: this.pipelineVersion,
408
434
  }, stored)) {
409
- this.reporter?.datasetSkipped?.(dataset, 'Unchanged since last run');
435
+ skip({ code: 'unchanged', message: 'Unchanged since last run' });
410
436
  return;
411
437
  }
412
438
  }
413
439
  // Resolve phase: import a data dump only when the source is one.
440
+ const importCallbacks = {
441
+ onImportStart: () => this.report(runId, { type: 'importStarted', dataset }),
442
+ onImportFailed: (distribution, error) => this.report(runId, {
443
+ type: 'importFailed',
444
+ dataset,
445
+ distribution,
446
+ error,
447
+ }),
448
+ };
414
449
  let resolved;
415
450
  try {
416
- resolved = await this.distributionResolver.resolve(probed, {
417
- onImportStart: () => {
418
- this.reporter?.importStarted?.();
419
- },
420
- onImportFailed: (distribution, error) => {
421
- this.reporter?.importFailed?.(distribution, error);
422
- },
423
- });
451
+ resolved = await this.distributionResolver.resolve(probed, importCallbacks);
424
452
  }
425
453
  catch (error) {
426
- this.reporter?.datasetSkipped?.(dataset, `Distribution resolution failed: ${error instanceof Error ? error.message : String(error)}`);
454
+ skipOnError('resolveFailed', 'Distribution resolution failed', error);
427
455
  return;
428
456
  }
429
457
  if (resolved instanceof NoDistributionAvailable) {
@@ -432,20 +460,29 @@ export class Pipeline {
432
460
  // no summary, so an invalid distribution is recorded rather than silently
433
461
  // dropped.
434
462
  if (resolved.importFailed) {
435
- this.reporter?.distributionValidated?.(resolved.importFailed.distribution, importOutcomeToVerdict(resolved.importFailed, fingerprint));
463
+ this.report(runId, {
464
+ type: 'distributionValidated',
465
+ dataset,
466
+ distribution: resolved.importFailed.distribution,
467
+ verdict: importOutcomeToVerdict(resolved.importFailed, fingerprint),
468
+ });
436
469
  }
437
470
  // Record the failure so a dataset whose source is unchanged is not
438
471
  // re-imported every run; it is retried at the next fingerprint change or
439
472
  // version rotation.
440
- await this.recordOutcome(dataset, fingerprint, 'failed');
441
- this.reporter?.datasetSkipped?.(dataset, resolved.message);
473
+ await this.recordOutcome(runId, dataset, fingerprint, 'failed');
474
+ skip({ code: 'noDistributionAvailable', message: resolved.message });
442
475
  return;
443
476
  }
444
- this.reportSelectedDistribution(dataset, resolved, fingerprint);
477
+ this.reportSelectedDistribution(runId, dataset, resolved, fingerprint);
445
478
  const timeout = this.timeoutFactory();
446
479
  const unsubscribe = timeout.subscribe?.({
447
- onTighten: (event) => this.reporter?.timeoutTightened?.(event),
448
- onRelax: (event) => this.reporter?.timeoutRelaxed?.(event),
480
+ onTighten: (transition) => this.report(runId, {
481
+ ...transition,
482
+ type: 'timeoutTightened',
483
+ dataset,
484
+ }),
485
+ onRelax: (transition) => this.report(runId, { ...transition, type: 'timeoutRelaxed', dataset }),
449
486
  });
450
487
  let stageFailed = false;
451
488
  // What the kept attempt’s stages could not read, reported once the dataset
@@ -467,10 +504,7 @@ export class Pipeline {
467
504
  // path. The dataset stays recorded as failed (stageFailed is already
468
505
  // true) and processing continues with the next dataset.
469
506
  try {
470
- const fallback = await this.distributionResolver.resolveFallback(probed, {
471
- onImportStart: () => this.reporter?.importStarted?.(),
472
- onImportFailed: (distribution, error) => this.reporter?.importFailed?.(distribution, error),
473
- });
507
+ const fallback = await this.distributionResolver.resolveFallback(probed, importCallbacks);
474
508
  if (fallback instanceof ResolvedDistribution) {
475
509
  // The dump is now the dataset's effective source: report it as
476
510
  // selected/validated under its own change fingerprint so the next
@@ -484,7 +518,7 @@ export class Pipeline {
484
518
  fallbackFingerprint = sourceFingerprint(dump, dumpProbeResult);
485
519
  }
486
520
  }
487
- this.reportSelectedDistribution(dataset, fallback, fallbackFingerprint);
521
+ this.reportSelectedDistribution(runId, dataset, fallback, fallbackFingerprint);
488
522
  // Discard the endpoint-sourced partial output before the re-run so
489
523
  // the dump-sourced stats replace it rather than appending to it.
490
524
  await runWriter.reset?.(dataset);
@@ -503,11 +537,16 @@ export class Pipeline {
503
537
  // A failed dump import is a deep validity verdict on that dump –
504
538
  // surface it rather than silently keeping the endpoint's partial
505
539
  // output, matching the primary NoDistributionAvailable path.
506
- this.reporter?.distributionValidated?.(fallback.importFailed.distribution, importOutcomeToVerdict(fallback.importFailed, fingerprint));
540
+ this.report(runId, {
541
+ type: 'distributionValidated',
542
+ dataset,
543
+ distribution: fallback.importFailed.distribution,
544
+ verdict: importOutcomeToVerdict(fallback.importFailed, fingerprint),
545
+ });
507
546
  }
508
547
  }
509
548
  catch (error) {
510
- this.reporter?.stageFailed?.('reactive-dump-fallback', error instanceof Error ? error : new Error(String(error)));
549
+ this.reportStageFailed(runId, dataset, 'reactive-dump-fallback', error);
511
550
  }
512
551
  }
513
552
  }
@@ -522,26 +561,60 @@ export class Pipeline {
522
561
  // A per-dataset writer failure must not abort the whole run: isolate it
523
562
  // like a stage failure so the remaining datasets still process. The
524
563
  // dataset is recorded 'failed' below and retried on the next run.
525
- this.reporter?.stageFailed?.('write', error instanceof Error ? error : new Error(String(error)));
564
+ this.reportStageFailed(runId, dataset, 'write', error);
526
565
  stageFailed = true;
527
566
  }
528
- await this.reportValidators(dataset);
567
+ await this.reportValidators(runId, dataset);
529
568
  // A dataset whose stages threw produced incomplete output; record it as
530
569
  // ‘failed’ rather than freezing a broken result under a ‘success’ record.
531
- await this.recordOutcome(dataset, fingerprint, stageFailed ? 'failed' : 'success');
570
+ await this.recordOutcome(runId, dataset, fingerprint, stageFailed ? 'failed' : 'success');
532
571
  if (!stageFailed && unread !== undefined) {
533
572
  for (const entry of unread.entries()) {
534
- this.reporter?.valuesUnread?.(dataset, entry);
573
+ this.report(runId, { type: 'valuesUnread', dataset, unread: entry });
535
574
  }
536
575
  }
537
576
  const datasetMemory = process.memoryUsage();
538
- this.reporter?.datasetComplete?.(dataset, {
577
+ this.report(runId, {
578
+ type: 'datasetComplete',
579
+ dataset,
580
+ durationMs: Date.now() - datasetStart,
539
581
  memoryUsageBytes: datasetMemory.rss,
540
582
  heapUsedBytes: datasetMemory.heapUsed,
541
583
  });
542
584
  }
585
+ report(runId, payload) {
586
+ if (this.reporters.length === 0)
587
+ return;
588
+ const event = {
589
+ ...payload,
590
+ defaultLevel: levelOf(payload),
591
+ timestamp: new Date(),
592
+ runId,
593
+ };
594
+ for (const reporter of this.reporters) {
595
+ // A reporter only observes the run, so its failure must not change it:
596
+ // the other reporters still get the event, and the run carries on.
597
+ try {
598
+ reporter.onEvent(event);
599
+ }
600
+ catch (error) {
601
+ process.emitWarning(`A reporter threw on a ${event.type} event: ${error instanceof Error ? error.message : String(error)}`, {
602
+ type: 'ReporterWarning',
603
+ detail: error instanceof Error ? error.stack : undefined,
604
+ });
605
+ }
606
+ }
607
+ }
608
+ reportStageFailed(runId, dataset, stage, error) {
609
+ this.report(runId, {
610
+ type: 'stageFailed',
611
+ dataset,
612
+ stage,
613
+ error: error instanceof Error ? error : new Error(String(error)),
614
+ });
615
+ }
543
616
  /** Persist the processing record for a dataset, when a store is configured. */
544
- async recordOutcome(dataset, fingerprint, status) {
617
+ async recordOutcome(runId, dataset, fingerprint, status) {
545
618
  if (!this.provenanceStore)
546
619
  return;
547
620
  try {
@@ -557,10 +630,10 @@ export class Pipeline {
557
630
  // reprocesses next run, its record not yet updated – but it must be
558
631
  // surfaced: a store that fails every write silently disables
559
632
  // skip-unchanged.
560
- this.reporter?.stageFailed?.('provenance', error instanceof Error ? error : new Error(String(error)));
633
+ this.reportStageFailed(runId, dataset, 'provenance', error);
561
634
  }
562
635
  }
563
- async reportValidators(dataset) {
636
+ async reportValidators(runId, dataset) {
564
637
  const validators = new Set();
565
638
  for (const stage of this.collectStages(this.stages)) {
566
639
  if (stage.validator)
@@ -568,7 +641,7 @@ export class Pipeline {
568
641
  }
569
642
  for (const validator of validators) {
570
643
  const report = await validator.report(dataset);
571
- this.reporter?.datasetValidated?.(dataset, report);
644
+ this.report(runId, { type: 'datasetValidated', dataset, report });
572
645
  }
573
646
  }
574
647
  // Walks every stage and sub-stage to collect validators; it reads only
@@ -603,15 +676,25 @@ export class Pipeline {
603
676
  * yielded no triples); native SPARQL endpoints are not imported and carry no
604
677
  * deep verdict.
605
678
  */
606
- reportSelectedDistribution(dataset, resolved, fingerprint) {
607
- this.reporter?.distributionSelected?.(dataset, resolved.distribution, resolved.imported);
679
+ reportSelectedDistribution(runId, dataset, resolved, fingerprint) {
680
+ this.report(runId, {
681
+ type: 'distributionSelected',
682
+ dataset,
683
+ distribution: resolved.distribution,
684
+ imported: resolved.imported,
685
+ });
608
686
  if (resolved.imported) {
609
- this.reporter?.distributionValidated?.(resolved.imported.from, importOutcomeToVerdict({
610
- type: 'successful',
687
+ this.report(runId, {
688
+ type: 'distributionValidated',
689
+ dataset,
611
690
  distribution: resolved.imported.from,
612
- tripleCount: resolved.imported.tripleCount,
613
- warnings: [],
614
- }, fingerprint));
691
+ verdict: importOutcomeToVerdict({
692
+ type: 'successful',
693
+ distribution: resolved.imported.from,
694
+ tripleCount: resolved.imported.tripleCount,
695
+ warnings: [],
696
+ }, fingerprint),
697
+ });
615
698
  }
616
699
  }
617
700
  /**
@@ -630,12 +713,12 @@ export class Pipeline {
630
713
  await this.runChain(dataset, distribution, stage, runWriter, context, timeout);
631
714
  }
632
715
  else {
633
- await this.runStage(dataset, distribution, stage, this.stageWriter(runWriter, stage.name), timeout, unread);
716
+ await this.runStage(context.runId, dataset, distribution, stage, this.stageWriter(runWriter, stage.name), timeout, unread);
634
717
  }
635
718
  }
636
719
  catch (error) {
637
720
  stageFailed = true;
638
- this.reporter?.stageFailed?.(stage.name, error instanceof Error ? error : new Error(String(error)));
721
+ this.reportStageFailed(context.runId, dataset, stage.name, error);
639
722
  }
640
723
  }
641
724
  return { stageFailed, unread };
@@ -644,17 +727,21 @@ export class Pipeline {
644
727
  * Run a stage with reporting and return whether it was supported.
645
728
  * Returns `true` if the stage produced results, `false` if NotSupported.
646
729
  */
647
- async runStage(dataset, distribution, stage, writer, timeout, unread) {
648
- this.reporter?.stageStart?.(stage.name);
730
+ async runStage(runId, dataset, distribution, stage, writer, timeout, unread) {
731
+ this.report(runId, { type: 'stageStart', dataset, stage: stage.name });
649
732
  const stageStart = Date.now();
650
733
  let itemsProcessed = 0;
651
734
  let quadsGenerated = 0;
735
+ let summary;
652
736
  const result = await stage.run(dataset, distribution, writer, {
653
737
  onProgress: (items, quads) => {
654
738
  itemsProcessed = items;
655
739
  quadsGenerated = quads;
656
740
  const stageMemory = process.memoryUsage();
657
- this.reporter?.stageProgress?.({
741
+ this.report(runId, {
742
+ type: 'stageProgress',
743
+ dataset,
744
+ stage: stage.name,
658
745
  itemsProcessed,
659
746
  quadsGenerated,
660
747
  memoryUsageBytes: stageMemory.rss,
@@ -663,21 +750,38 @@ export class Pipeline {
663
750
  },
664
751
  timeout,
665
752
  valueUnread: unread && ((target, field, value) => unread.add(target, field, value)),
753
+ onComplete: (stageSummary) => {
754
+ summary = stageSummary;
755
+ },
666
756
  });
667
757
  if (result instanceof NotSupported) {
668
- this.reporter?.stageSkipped?.(stage.name, result.message);
758
+ this.report(runId, {
759
+ type: 'stageSkipped',
760
+ dataset,
761
+ stage: stage.name,
762
+ reason: {
763
+ code: result instanceof NoItemsSelected
764
+ ? 'noItemsSelected'
765
+ : 'notSupported',
766
+ message: result.message,
767
+ },
768
+ });
669
769
  return false;
670
770
  }
671
- this.reporter?.stageComplete?.(stage.name, {
771
+ this.report(runId, {
772
+ type: 'stageComplete',
773
+ dataset,
774
+ stage: stage.name,
672
775
  itemsProcessed,
673
776
  quadsGenerated,
674
- duration: Date.now() - stageStart,
777
+ durationMs: Date.now() - stageStart,
778
+ ...summary,
675
779
  });
676
780
  return true;
677
781
  }
678
782
  /** Run a stage in chained mode, throwing if the stage is not supported. */
679
- async runChainedStage(dataset, distribution, stage, writer, timeout) {
680
- const supported = await this.runStage(dataset, distribution, stage, writer, timeout);
783
+ async runChainedStage(runId, dataset, distribution, stage, writer, timeout) {
784
+ const supported = await this.runStage(runId, dataset, distribution, stage, writer, timeout);
681
785
  if (!supported) {
682
786
  throw new Error(`Stage '${stage.name}' returned NotSupported in chained mode`);
683
787
  }
@@ -698,7 +802,7 @@ export class Pipeline {
698
802
  });
699
803
  const scratchRun = await scratchWriter.openRun(context);
700
804
  try {
701
- await this.runChainedStage(dataset, stageDistribution, chainedStage, scratchRun, timeout);
805
+ await this.runChainedStage(context.runId, dataset, stageDistribution, chainedStage, scratchRun, timeout);
702
806
  await scratchRun.flush(dataset, 'success');
703
807
  await scratchRun.commit();
704
808
  }
@@ -751,7 +855,6 @@ function mapProbeResult(distribution, result) {
751
855
  const fingerprint = sourceFingerprint(distribution, result);
752
856
  if (result instanceof NetworkError) {
753
857
  return {
754
- distribution,
755
858
  type: 'network-error',
756
859
  available: false,
757
860
  error: result.message,
@@ -760,7 +863,6 @@ function mapProbeResult(distribution, result) {
760
863
  };
761
864
  }
762
865
  return {
763
- distribution,
764
866
  type: result instanceof SparqlProbeResult
765
867
  ? 'sparql'
766
868
  : 'data-dump',