@lde/pipeline 0.42.1 → 0.44.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.
- package/dist/distribution/importResolver.js +4 -4
- package/dist/distribution/resolver.d.ts +2 -2
- package/dist/distribution/resolver.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/pipeline.d.ts +8 -10
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +183 -74
- package/dist/pipelineEvent.d.ts +231 -0
- package/dist/pipelineEvent.d.ts.map +1 -0
- package/dist/pipelineEvent.js +35 -0
- package/dist/reporter.d.ts +14 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/sparql/reader.d.ts +16 -7
- package/dist/sparql/reader.d.ts.map +1 -1
- package/dist/sparql/reader.js +16 -5
- package/dist/stage.d.ts +11 -0
- package/dist/stage.d.ts.map +1 -1
- package/dist/stage.js +8 -2
- package/dist/unreadValueTally.d.ts +17 -0
- package/dist/unreadValueTally.d.ts.map +1 -0
- package/dist/unreadValueTally.js +32 -0
- package/dist/writer/writer.d.ts +1 -1
- package/dist/writer/writer.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/combineReporters.d.ts +0 -16
- package/dist/combineReporters.d.ts.map +0 -1
- package/dist/combineReporters.js +0 -56
- package/dist/progressReporter.d.ts +0 -124
- package/dist/progressReporter.d.ts.map +0 -1
- /package/dist/{progressReporter.js → reporter.js} +0 -0
package/dist/pipeline.js
CHANGED
|
@@ -9,8 +9,9 @@ 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 {
|
|
12
|
+
import { NoItemsSelected, NotSupported } from './sparql/reader.js';
|
|
13
|
+
import { levelOf, } from './pipelineEvent.js';
|
|
14
|
+
import { UnreadValueTally } from './unreadValueTally.js';
|
|
14
15
|
import { ConstantTimeoutPolicy, } from './sparql/timeoutPolicy.js';
|
|
15
16
|
/**
|
|
16
17
|
* Why a run was abandoned rather than committed: its selector produced no
|
|
@@ -237,7 +238,7 @@ export class Pipeline {
|
|
|
237
238
|
beforeDatasetWrite;
|
|
238
239
|
distributionResolver;
|
|
239
240
|
chaining;
|
|
240
|
-
|
|
241
|
+
reporters;
|
|
241
242
|
timeoutFactory;
|
|
242
243
|
provenanceStore;
|
|
243
244
|
pipelineVersion;
|
|
@@ -277,11 +278,7 @@ export class Pipeline {
|
|
|
277
278
|
this.distributionResolver =
|
|
278
279
|
options.distributionResolver ?? new SparqlDistributionResolver();
|
|
279
280
|
this.chaining = options.chaining;
|
|
280
|
-
|
|
281
|
-
// the else branch, so cast the single-reporter case explicitly.
|
|
282
|
-
this.reporter = Array.isArray(options.reporter)
|
|
283
|
-
? combineReporters(options.reporter)
|
|
284
|
-
: options.reporter;
|
|
281
|
+
this.reporters = [options.reporter ?? []].flat();
|
|
285
282
|
this.timeoutFactory =
|
|
286
283
|
options.timeout ?? (() => new ConstantTimeoutPolicy(300_000));
|
|
287
284
|
this.provenanceStore = options.provenanceStore;
|
|
@@ -289,7 +286,8 @@ export class Pipeline {
|
|
|
289
286
|
}
|
|
290
287
|
async run() {
|
|
291
288
|
const start = Date.now();
|
|
292
|
-
|
|
289
|
+
const runId = randomUUID();
|
|
290
|
+
this.report(runId, { type: 'pipelineStart', name: this.name });
|
|
293
291
|
// Fail fast on a store that cannot persist records (e.g. a provenance
|
|
294
292
|
// file on a mount the runtime user cannot write to): every dataset would
|
|
295
293
|
// process fine, but the records would be lost and skip-unchanged would
|
|
@@ -297,17 +295,21 @@ export class Pipeline {
|
|
|
297
295
|
await this.provenanceStore?.check?.();
|
|
298
296
|
const selectStart = Date.now();
|
|
299
297
|
const datasets = await this.datasetSelector.select();
|
|
300
|
-
this.
|
|
298
|
+
this.report(runId, {
|
|
299
|
+
type: 'datasetsSelected',
|
|
300
|
+
count: datasets.total,
|
|
301
|
+
durationMs: Date.now() - selectStart,
|
|
302
|
+
});
|
|
301
303
|
// The run transaction: one openRun → write* → commit/abort per pipeline
|
|
302
304
|
// run. `selectedSources` accumulates every selected dataset – including
|
|
303
305
|
// ones skipped as unchanged – so a writer's commit can sweep by registry
|
|
304
306
|
// membership.
|
|
305
307
|
const selectedSources = [];
|
|
306
308
|
const context = {
|
|
307
|
-
runId
|
|
309
|
+
runId,
|
|
308
310
|
startedAt: new Date().toISOString(),
|
|
309
311
|
selectedSources: () => selectedSources,
|
|
310
|
-
itemsRejected: (dataset, rejection) => this.
|
|
312
|
+
itemsRejected: (dataset, rejection) => this.report(runId, { type: 'itemsRejected', dataset, rejection }),
|
|
311
313
|
provenance: this.provenanceStore,
|
|
312
314
|
};
|
|
313
315
|
const openedWriter = await this.writer.openRun(context);
|
|
@@ -349,36 +351,61 @@ export class Pipeline {
|
|
|
349
351
|
// meant not to do.
|
|
350
352
|
if (abandoned) {
|
|
351
353
|
const reason = new EmptySelection();
|
|
352
|
-
this.
|
|
354
|
+
this.report(runId, { type: 'selectionEmpty', message: reason.message });
|
|
353
355
|
await runWriter.abort(reason);
|
|
354
356
|
}
|
|
355
357
|
const finalMemory = process.memoryUsage();
|
|
356
|
-
this.
|
|
357
|
-
|
|
358
|
+
this.report(runId, {
|
|
359
|
+
type: 'pipelineComplete',
|
|
360
|
+
durationMs: Date.now() - start,
|
|
358
361
|
memoryUsageBytes: finalMemory.rss,
|
|
359
362
|
heapUsedBytes: finalMemory.heapUsed,
|
|
360
363
|
});
|
|
361
364
|
}
|
|
362
365
|
async processDataset(dataset, runWriter, context) {
|
|
363
|
-
|
|
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
|
+
};
|
|
364
381
|
// Probe phase: gather probe results and the source-to-be, without importing.
|
|
365
382
|
let probed;
|
|
366
383
|
try {
|
|
367
384
|
probed = await this.distributionResolver.probe(dataset, {
|
|
368
385
|
onProbe: (distribution, result) => {
|
|
369
|
-
this.
|
|
386
|
+
this.report(runId, {
|
|
387
|
+
type: 'distributionProbed',
|
|
388
|
+
dataset,
|
|
389
|
+
distribution,
|
|
390
|
+
result: mapProbeResult(distribution, result),
|
|
391
|
+
});
|
|
370
392
|
// Shallow validity: the probe parse-validates small RDF bodies as a
|
|
371
393
|
// by-product. Surface that verdict per distribution, judged against
|
|
372
394
|
// the distribution's own observed fingerprint.
|
|
373
395
|
const verdict = probeResultToVerdict(result, sourceFingerprint(distribution, result));
|
|
374
396
|
if (verdict) {
|
|
375
|
-
this.
|
|
397
|
+
this.report(runId, {
|
|
398
|
+
type: 'distributionValidated',
|
|
399
|
+
dataset,
|
|
400
|
+
distribution,
|
|
401
|
+
verdict,
|
|
402
|
+
});
|
|
376
403
|
}
|
|
377
404
|
},
|
|
378
405
|
});
|
|
379
406
|
}
|
|
380
407
|
catch (error) {
|
|
381
|
-
|
|
408
|
+
skipOnError('probeFailed', 'Distribution probing failed', error);
|
|
382
409
|
return;
|
|
383
410
|
}
|
|
384
411
|
// Derive the source-change fingerprint from the probed source: null for a
|
|
@@ -405,24 +432,26 @@ export class Pipeline {
|
|
|
405
432
|
sourceFingerprint: fingerprint,
|
|
406
433
|
pipelineVersion: this.pipelineVersion,
|
|
407
434
|
}, stored)) {
|
|
408
|
-
|
|
435
|
+
skip({ code: 'unchanged', message: 'Unchanged since last run' });
|
|
409
436
|
return;
|
|
410
437
|
}
|
|
411
438
|
}
|
|
412
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
|
+
};
|
|
413
449
|
let resolved;
|
|
414
450
|
try {
|
|
415
|
-
resolved = await this.distributionResolver.resolve(probed,
|
|
416
|
-
onImportStart: () => {
|
|
417
|
-
this.reporter?.importStarted?.();
|
|
418
|
-
},
|
|
419
|
-
onImportFailed: (distribution, error) => {
|
|
420
|
-
this.reporter?.importFailed?.(distribution, error);
|
|
421
|
-
},
|
|
422
|
-
});
|
|
451
|
+
resolved = await this.distributionResolver.resolve(probed, importCallbacks);
|
|
423
452
|
}
|
|
424
453
|
catch (error) {
|
|
425
|
-
|
|
454
|
+
skipOnError('resolveFailed', 'Distribution resolution failed', error);
|
|
426
455
|
return;
|
|
427
456
|
}
|
|
428
457
|
if (resolved instanceof NoDistributionAvailable) {
|
|
@@ -431,24 +460,36 @@ export class Pipeline {
|
|
|
431
460
|
// no summary, so an invalid distribution is recorded rather than silently
|
|
432
461
|
// dropped.
|
|
433
462
|
if (resolved.importFailed) {
|
|
434
|
-
this.
|
|
463
|
+
this.report(runId, {
|
|
464
|
+
type: 'distributionValidated',
|
|
465
|
+
dataset,
|
|
466
|
+
distribution: resolved.importFailed.distribution,
|
|
467
|
+
verdict: importOutcomeToVerdict(resolved.importFailed, fingerprint),
|
|
468
|
+
});
|
|
435
469
|
}
|
|
436
470
|
// Record the failure so a dataset whose source is unchanged is not
|
|
437
471
|
// re-imported every run; it is retried at the next fingerprint change or
|
|
438
472
|
// version rotation.
|
|
439
|
-
await this.recordOutcome(dataset, fingerprint, 'failed');
|
|
440
|
-
|
|
473
|
+
await this.recordOutcome(runId, dataset, fingerprint, 'failed');
|
|
474
|
+
skip({ code: 'noDistributionAvailable', message: resolved.message });
|
|
441
475
|
return;
|
|
442
476
|
}
|
|
443
|
-
this.reportSelectedDistribution(dataset, resolved, fingerprint);
|
|
477
|
+
this.reportSelectedDistribution(runId, dataset, resolved, fingerprint);
|
|
444
478
|
const timeout = this.timeoutFactory();
|
|
445
479
|
const unsubscribe = timeout.subscribe?.({
|
|
446
|
-
onTighten: (
|
|
447
|
-
|
|
480
|
+
onTighten: (transition) => this.report(runId, {
|
|
481
|
+
...transition,
|
|
482
|
+
type: 'timeoutTightened',
|
|
483
|
+
dataset,
|
|
484
|
+
}),
|
|
485
|
+
onRelax: (transition) => this.report(runId, { ...transition, type: 'timeoutRelaxed', dataset }),
|
|
448
486
|
});
|
|
449
487
|
let stageFailed = false;
|
|
488
|
+
// What the kept attempt’s stages could not read, reported once the dataset
|
|
489
|
+
// completes.
|
|
490
|
+
let unread;
|
|
450
491
|
try {
|
|
451
|
-
stageFailed = await this.runStages(dataset, resolved.distribution, timeout, runWriter, context);
|
|
492
|
+
({ stageFailed, unread } = await this.runStages(dataset, resolved.distribution, timeout, runWriter, context));
|
|
452
493
|
// Reactive fallback: an endpoint that passed probing but could not serve
|
|
453
494
|
// the analysis stages is empirically incapable. Switch to the dataset’s
|
|
454
495
|
// data dump and re-run all stages locally, discarding the
|
|
@@ -463,10 +504,7 @@ export class Pipeline {
|
|
|
463
504
|
// path. The dataset stays recorded as failed (stageFailed is already
|
|
464
505
|
// true) and processing continues with the next dataset.
|
|
465
506
|
try {
|
|
466
|
-
const fallback = await this.distributionResolver.resolveFallback(probed,
|
|
467
|
-
onImportStart: () => this.reporter?.importStarted?.(),
|
|
468
|
-
onImportFailed: (distribution, error) => this.reporter?.importFailed?.(distribution, error),
|
|
469
|
-
});
|
|
507
|
+
const fallback = await this.distributionResolver.resolveFallback(probed, importCallbacks);
|
|
470
508
|
if (fallback instanceof ResolvedDistribution) {
|
|
471
509
|
// The dump is now the dataset's effective source: report it as
|
|
472
510
|
// selected/validated under its own change fingerprint so the next
|
|
@@ -480,11 +518,11 @@ export class Pipeline {
|
|
|
480
518
|
fallbackFingerprint = sourceFingerprint(dump, dumpProbeResult);
|
|
481
519
|
}
|
|
482
520
|
}
|
|
483
|
-
this.reportSelectedDistribution(dataset, fallback, fallbackFingerprint);
|
|
521
|
+
this.reportSelectedDistribution(runId, dataset, fallback, fallbackFingerprint);
|
|
484
522
|
// Discard the endpoint-sourced partial output before the re-run so
|
|
485
523
|
// the dump-sourced stats replace it rather than appending to it.
|
|
486
524
|
await runWriter.reset?.(dataset);
|
|
487
|
-
stageFailed = await this.runStages(dataset, fallback.distribution, timeout, runWriter, context);
|
|
525
|
+
({ stageFailed, unread } = await this.runStages(dataset, fallback.distribution, timeout, runWriter, context));
|
|
488
526
|
// Adopt the dump’s fingerprint only once the re-run has succeeded.
|
|
489
527
|
// If the reset or the re-run throws, or a stage fails against the
|
|
490
528
|
// dump, the dataset is recorded ‘failed’ under the endpoint’s
|
|
@@ -499,11 +537,16 @@ export class Pipeline {
|
|
|
499
537
|
// A failed dump import is a deep validity verdict on that dump –
|
|
500
538
|
// surface it rather than silently keeping the endpoint's partial
|
|
501
539
|
// output, matching the primary NoDistributionAvailable path.
|
|
502
|
-
this.
|
|
540
|
+
this.report(runId, {
|
|
541
|
+
type: 'distributionValidated',
|
|
542
|
+
dataset,
|
|
543
|
+
distribution: fallback.importFailed.distribution,
|
|
544
|
+
verdict: importOutcomeToVerdict(fallback.importFailed, fingerprint),
|
|
545
|
+
});
|
|
503
546
|
}
|
|
504
547
|
}
|
|
505
548
|
catch (error) {
|
|
506
|
-
this.
|
|
549
|
+
this.reportStageFailed(runId, dataset, 'reactive-dump-fallback', error);
|
|
507
550
|
}
|
|
508
551
|
}
|
|
509
552
|
}
|
|
@@ -518,21 +561,60 @@ export class Pipeline {
|
|
|
518
561
|
// A per-dataset writer failure must not abort the whole run: isolate it
|
|
519
562
|
// like a stage failure so the remaining datasets still process. The
|
|
520
563
|
// dataset is recorded 'failed' below and retried on the next run.
|
|
521
|
-
this.
|
|
564
|
+
this.reportStageFailed(runId, dataset, 'write', error);
|
|
522
565
|
stageFailed = true;
|
|
523
566
|
}
|
|
524
|
-
await this.reportValidators(dataset);
|
|
567
|
+
await this.reportValidators(runId, dataset);
|
|
525
568
|
// A dataset whose stages threw produced incomplete output; record it as
|
|
526
569
|
// ‘failed’ rather than freezing a broken result under a ‘success’ record.
|
|
527
|
-
await this.recordOutcome(dataset, fingerprint, stageFailed ? 'failed' : 'success');
|
|
570
|
+
await this.recordOutcome(runId, dataset, fingerprint, stageFailed ? 'failed' : 'success');
|
|
571
|
+
if (!stageFailed && unread !== undefined) {
|
|
572
|
+
for (const entry of unread.entries()) {
|
|
573
|
+
this.report(runId, { type: 'valuesUnread', dataset, unread: entry });
|
|
574
|
+
}
|
|
575
|
+
}
|
|
528
576
|
const datasetMemory = process.memoryUsage();
|
|
529
|
-
this.
|
|
577
|
+
this.report(runId, {
|
|
578
|
+
type: 'datasetComplete',
|
|
579
|
+
dataset,
|
|
580
|
+
durationMs: Date.now() - datasetStart,
|
|
530
581
|
memoryUsageBytes: datasetMemory.rss,
|
|
531
582
|
heapUsedBytes: datasetMemory.heapUsed,
|
|
532
583
|
});
|
|
533
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
|
+
}
|
|
534
616
|
/** Persist the processing record for a dataset, when a store is configured. */
|
|
535
|
-
async recordOutcome(dataset, fingerprint, status) {
|
|
617
|
+
async recordOutcome(runId, dataset, fingerprint, status) {
|
|
536
618
|
if (!this.provenanceStore)
|
|
537
619
|
return;
|
|
538
620
|
try {
|
|
@@ -548,10 +630,10 @@ export class Pipeline {
|
|
|
548
630
|
// reprocesses next run, its record not yet updated – but it must be
|
|
549
631
|
// surfaced: a store that fails every write silently disables
|
|
550
632
|
// skip-unchanged.
|
|
551
|
-
this.
|
|
633
|
+
this.reportStageFailed(runId, dataset, 'provenance', error);
|
|
552
634
|
}
|
|
553
635
|
}
|
|
554
|
-
async reportValidators(dataset) {
|
|
636
|
+
async reportValidators(runId, dataset) {
|
|
555
637
|
const validators = new Set();
|
|
556
638
|
for (const stage of this.collectStages(this.stages)) {
|
|
557
639
|
if (stage.validator)
|
|
@@ -559,7 +641,7 @@ export class Pipeline {
|
|
|
559
641
|
}
|
|
560
642
|
for (const validator of validators) {
|
|
561
643
|
const report = await validator.report(dataset);
|
|
562
|
-
this.
|
|
644
|
+
this.report(runId, { type: 'datasetValidated', dataset, report });
|
|
563
645
|
}
|
|
564
646
|
}
|
|
565
647
|
// Walks every stage and sub-stage to collect validators; it reads only
|
|
@@ -594,24 +676,36 @@ export class Pipeline {
|
|
|
594
676
|
* yielded no triples); native SPARQL endpoints are not imported and carry no
|
|
595
677
|
* deep verdict.
|
|
596
678
|
*/
|
|
597
|
-
reportSelectedDistribution(dataset, resolved, fingerprint) {
|
|
598
|
-
this.
|
|
679
|
+
reportSelectedDistribution(runId, dataset, resolved, fingerprint) {
|
|
680
|
+
this.report(runId, {
|
|
681
|
+
type: 'distributionSelected',
|
|
682
|
+
dataset,
|
|
683
|
+
distribution: resolved.distribution,
|
|
684
|
+
imported: resolved.imported,
|
|
685
|
+
});
|
|
599
686
|
if (resolved.imported) {
|
|
600
|
-
this.
|
|
601
|
-
type: '
|
|
687
|
+
this.report(runId, {
|
|
688
|
+
type: 'distributionValidated',
|
|
689
|
+
dataset,
|
|
602
690
|
distribution: resolved.imported.from,
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
691
|
+
verdict: importOutcomeToVerdict({
|
|
692
|
+
type: 'successful',
|
|
693
|
+
distribution: resolved.imported.from,
|
|
694
|
+
tripleCount: resolved.imported.tripleCount,
|
|
695
|
+
warnings: [],
|
|
696
|
+
}, fingerprint),
|
|
697
|
+
});
|
|
606
698
|
}
|
|
607
699
|
}
|
|
608
700
|
/**
|
|
609
701
|
* Run every top-level stage against one distribution, catching and reporting
|
|
610
702
|
* per-stage failures so one failing stage does not abort the rest. Returns
|
|
611
703
|
* whether any stage hard-failed – the signal the reactive dump fallback
|
|
612
|
-
* reacts to
|
|
704
|
+
* reacts to – and what the stages could not read, which belongs to this
|
|
705
|
+
* attempt alone.
|
|
613
706
|
*/
|
|
614
707
|
async runStages(dataset, distribution, timeout, runWriter, context) {
|
|
708
|
+
const unread = new UnreadValueTally();
|
|
615
709
|
let stageFailed = false;
|
|
616
710
|
for (const stage of this.stages) {
|
|
617
711
|
try {
|
|
@@ -619,22 +713,22 @@ export class Pipeline {
|
|
|
619
713
|
await this.runChain(dataset, distribution, stage, runWriter, context, timeout);
|
|
620
714
|
}
|
|
621
715
|
else {
|
|
622
|
-
await this.runStage(dataset, distribution, stage, this.stageWriter(runWriter, stage.name), timeout);
|
|
716
|
+
await this.runStage(context.runId, dataset, distribution, stage, this.stageWriter(runWriter, stage.name), timeout, unread);
|
|
623
717
|
}
|
|
624
718
|
}
|
|
625
719
|
catch (error) {
|
|
626
720
|
stageFailed = true;
|
|
627
|
-
this.
|
|
721
|
+
this.reportStageFailed(context.runId, dataset, stage.name, error);
|
|
628
722
|
}
|
|
629
723
|
}
|
|
630
|
-
return stageFailed;
|
|
724
|
+
return { stageFailed, unread };
|
|
631
725
|
}
|
|
632
726
|
/**
|
|
633
727
|
* Run a stage with reporting and return whether it was supported.
|
|
634
728
|
* Returns `true` if the stage produced results, `false` if NotSupported.
|
|
635
729
|
*/
|
|
636
|
-
async runStage(dataset, distribution, stage, writer, timeout) {
|
|
637
|
-
this.
|
|
730
|
+
async runStage(runId, dataset, distribution, stage, writer, timeout, unread) {
|
|
731
|
+
this.report(runId, { type: 'stageStart', dataset, stage: stage.name });
|
|
638
732
|
const stageStart = Date.now();
|
|
639
733
|
let itemsProcessed = 0;
|
|
640
734
|
let quadsGenerated = 0;
|
|
@@ -643,7 +737,10 @@ export class Pipeline {
|
|
|
643
737
|
itemsProcessed = items;
|
|
644
738
|
quadsGenerated = quads;
|
|
645
739
|
const stageMemory = process.memoryUsage();
|
|
646
|
-
this.
|
|
740
|
+
this.report(runId, {
|
|
741
|
+
type: 'stageProgress',
|
|
742
|
+
dataset,
|
|
743
|
+
stage: stage.name,
|
|
647
744
|
itemsProcessed,
|
|
648
745
|
quadsGenerated,
|
|
649
746
|
memoryUsageBytes: stageMemory.rss,
|
|
@@ -651,21 +748,35 @@ export class Pipeline {
|
|
|
651
748
|
});
|
|
652
749
|
},
|
|
653
750
|
timeout,
|
|
751
|
+
valueUnread: unread && ((target, field, value) => unread.add(target, field, value)),
|
|
654
752
|
});
|
|
655
753
|
if (result instanceof NotSupported) {
|
|
656
|
-
this.
|
|
754
|
+
this.report(runId, {
|
|
755
|
+
type: 'stageSkipped',
|
|
756
|
+
dataset,
|
|
757
|
+
stage: stage.name,
|
|
758
|
+
reason: {
|
|
759
|
+
code: result instanceof NoItemsSelected
|
|
760
|
+
? 'noItemsSelected'
|
|
761
|
+
: 'notSupported',
|
|
762
|
+
message: result.message,
|
|
763
|
+
},
|
|
764
|
+
});
|
|
657
765
|
return false;
|
|
658
766
|
}
|
|
659
|
-
this.
|
|
767
|
+
this.report(runId, {
|
|
768
|
+
type: 'stageComplete',
|
|
769
|
+
dataset,
|
|
770
|
+
stage: stage.name,
|
|
660
771
|
itemsProcessed,
|
|
661
772
|
quadsGenerated,
|
|
662
|
-
|
|
773
|
+
durationMs: Date.now() - stageStart,
|
|
663
774
|
});
|
|
664
775
|
return true;
|
|
665
776
|
}
|
|
666
777
|
/** Run a stage in chained mode, throwing if the stage is not supported. */
|
|
667
|
-
async runChainedStage(dataset, distribution, stage, writer, timeout) {
|
|
668
|
-
const supported = await this.runStage(dataset, distribution, stage, writer, timeout);
|
|
778
|
+
async runChainedStage(runId, dataset, distribution, stage, writer, timeout) {
|
|
779
|
+
const supported = await this.runStage(runId, dataset, distribution, stage, writer, timeout);
|
|
669
780
|
if (!supported) {
|
|
670
781
|
throw new Error(`Stage '${stage.name}' returned NotSupported in chained mode`);
|
|
671
782
|
}
|
|
@@ -686,7 +797,7 @@ export class Pipeline {
|
|
|
686
797
|
});
|
|
687
798
|
const scratchRun = await scratchWriter.openRun(context);
|
|
688
799
|
try {
|
|
689
|
-
await this.runChainedStage(dataset, stageDistribution, chainedStage, scratchRun, timeout);
|
|
800
|
+
await this.runChainedStage(context.runId, dataset, stageDistribution, chainedStage, scratchRun, timeout);
|
|
690
801
|
await scratchRun.flush(dataset, 'success');
|
|
691
802
|
await scratchRun.commit();
|
|
692
803
|
}
|
|
@@ -739,7 +850,6 @@ function mapProbeResult(distribution, result) {
|
|
|
739
850
|
const fingerprint = sourceFingerprint(distribution, result);
|
|
740
851
|
if (result instanceof NetworkError) {
|
|
741
852
|
return {
|
|
742
|
-
distribution,
|
|
743
853
|
type: 'network-error',
|
|
744
854
|
available: false,
|
|
745
855
|
error: result.message,
|
|
@@ -748,7 +858,6 @@ function mapProbeResult(distribution, result) {
|
|
|
748
858
|
};
|
|
749
859
|
}
|
|
750
860
|
return {
|
|
751
|
-
distribution,
|
|
752
861
|
type: result instanceof SparqlProbeResult
|
|
753
862
|
? 'sparql'
|
|
754
863
|
: 'data-dump',
|