@lde/pipeline 0.34.1 → 0.34.2
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/pipeline.d.ts +18 -9
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +27 -11
- package/dist/stage.d.ts +42 -6
- package/dist/stage.d.ts.map +1 -1
- package/dist/stage.js +49 -22
- package/package.json +2 -2
package/dist/pipeline.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Dataset } from '@lde/dataset';
|
|
2
|
+
import type { Quad } from '@rdfjs/types';
|
|
2
3
|
import type { DatasetSelector } from './selector.js';
|
|
3
4
|
import { Stage } from './stage.js';
|
|
4
5
|
import type { QuadTransform } from './stage.js';
|
|
@@ -23,7 +24,7 @@ export interface BeforeStageWriteContext {
|
|
|
23
24
|
/**
|
|
24
25
|
* Context handed to a {@link PipelinePlugin.beforeDatasetWrite} transform: the
|
|
25
26
|
* `dataset` whose complete, cross-stage output is being written. Unlike
|
|
26
|
-
* {@link BeforeStageWriteContext} there is no `stage`
|
|
27
|
+
* {@link BeforeStageWriteContext} there is no `stage` – the transform sees one
|
|
27
28
|
* dataset's quads from every stage as a single stream.
|
|
28
29
|
*/
|
|
29
30
|
export interface BeforeDatasetWriteContext {
|
|
@@ -36,7 +37,7 @@ export interface PipelinePlugin {
|
|
|
36
37
|
* Transform a single stage's merged output before writing (extension point
|
|
37
38
|
* 2: per stage, post-merge). Runs once per stage per dataset, carrying the
|
|
38
39
|
* stage identity so it can mint stable per-`(dataset, stage)` IRIs. The home
|
|
39
|
-
* of stage-scoped concerns
|
|
40
|
+
* of stage-scoped concerns – provenance stamping, namespace rewriting of a
|
|
40
41
|
* stage's own quads.
|
|
41
42
|
*/
|
|
42
43
|
beforeStageWrite?: QuadTransform<BeforeStageWriteContext>;
|
|
@@ -45,7 +46,7 @@ export interface PipelinePlugin {
|
|
|
45
46
|
* dataset, cross-stage). Runs once over the concatenation of every stage's
|
|
46
47
|
* output for one dataset, after all stages complete and before the write is
|
|
47
48
|
* flushed. The home of concerns that need to reconcile quads *across* stages
|
|
48
|
-
*
|
|
49
|
+
* – deduplication, cross-stage roll-ups, or merging partition nodes that
|
|
49
50
|
* different stages contribute to.
|
|
50
51
|
*
|
|
51
52
|
* The transform is a streaming {@link QuadTransform}: the pipeline pipes each
|
|
@@ -55,11 +56,19 @@ export interface PipelinePlugin {
|
|
|
55
56
|
*/
|
|
56
57
|
beforeDatasetWrite?: QuadTransform<BeforeDatasetWriteContext>;
|
|
57
58
|
}
|
|
58
|
-
export interface PipelineOptions {
|
|
59
|
+
export interface PipelineOptions<Out = Quad> {
|
|
59
60
|
datasetSelector: DatasetSelector;
|
|
60
|
-
stages: Stage[];
|
|
61
|
-
writers: Writer | Writer[];
|
|
62
|
-
|
|
61
|
+
stages: Stage<Out>[];
|
|
62
|
+
writers: Writer<Out> | Writer<Out>[];
|
|
63
|
+
/**
|
|
64
|
+
* Quad-level plugins ({@link PipelinePlugin}). Available only to a plain quad
|
|
65
|
+
* pipeline: a projecting pipeline's write-side data is already the projected
|
|
66
|
+
* item, and both plugin points are `QuadTransform`s with no quads to act on,
|
|
67
|
+
* so the type forbids them (`never`). Reader-attached quad transforms are
|
|
68
|
+
* unaffected – they run before the projection seam. See
|
|
69
|
+
* {@link https://github.com/ldelements/lde/blob/main/docs/decisions/0013-project-inside-the-batch-per-root-type.md | ADR 13}.
|
|
70
|
+
*/
|
|
71
|
+
plugins?: [Out] extends [Quad] ? PipelinePlugin[] : never;
|
|
63
72
|
name?: string;
|
|
64
73
|
distributionResolver?: DistributionResolver;
|
|
65
74
|
chaining?: {
|
|
@@ -101,7 +110,7 @@ export interface PipelineOptions {
|
|
|
101
110
|
*/
|
|
102
111
|
timeout?: () => TimeoutPolicy;
|
|
103
112
|
}
|
|
104
|
-
export declare class Pipeline {
|
|
113
|
+
export declare class Pipeline<Out = Quad> {
|
|
105
114
|
private readonly name;
|
|
106
115
|
private readonly datasetSelector;
|
|
107
116
|
private readonly stages;
|
|
@@ -114,7 +123,7 @@ export declare class Pipeline {
|
|
|
114
123
|
private readonly timeoutFactory;
|
|
115
124
|
private readonly provenanceStore?;
|
|
116
125
|
private readonly pipelineVersion?;
|
|
117
|
-
constructor(options: PipelineOptions);
|
|
126
|
+
constructor(options: PipelineOptions<Out>);
|
|
118
127
|
run(): Promise<void>;
|
|
119
128
|
private processDataset;
|
|
120
129
|
/** Persist the processing record for a dataset, when a store is configured. */
|
package/dist/pipeline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAgB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
|
package/dist/pipeline.js
CHANGED
|
@@ -60,7 +60,7 @@ class FanOutWriter {
|
|
|
60
60
|
// Opening a writer's run can have side effects (a cross-pod lock, a fresh
|
|
61
61
|
// collection). If one writer opens but a sibling then fails, the pipeline
|
|
62
62
|
// never gets a RunWriter to abort, so roll the opened ones back here
|
|
63
|
-
// before rethrowing
|
|
63
|
+
// before rethrowing – otherwise their locks and collections leak.
|
|
64
64
|
const results = await Promise.allSettled(this.writers.map((writer) => writer.openRun(context)));
|
|
65
65
|
const opened = results.flatMap((result) => result.status === 'fulfilled' ? [result.value] : []);
|
|
66
66
|
const failure = results.find((result) => result.status === 'rejected');
|
|
@@ -86,8 +86,8 @@ class FanOutRunWriter {
|
|
|
86
86
|
constructor(runs) {
|
|
87
87
|
this.runs = runs;
|
|
88
88
|
}
|
|
89
|
-
async write(dataset,
|
|
90
|
-
const branches = tee(
|
|
89
|
+
async write(dataset, items) {
|
|
90
|
+
const branches = tee(items, this.runs.length);
|
|
91
91
|
await Promise.all(this.runs.map((run, index) => run.write(dataset, branches[index])));
|
|
92
92
|
}
|
|
93
93
|
async flush(dataset, outcome) {
|
|
@@ -241,13 +241,17 @@ export class Pipeline {
|
|
|
241
241
|
this.writer = Array.isArray(options.writers)
|
|
242
242
|
? new FanOutWriter(options.writers)
|
|
243
243
|
: options.writers;
|
|
244
|
-
|
|
244
|
+
// Plugins are quad-typed and only reachable when `Out` is `Quad` (the
|
|
245
|
+
// `plugins` field is `never` otherwise), but inside the class body `Out` is
|
|
246
|
+
// opaque, so the conditional-typed field is read as a plain plugin array.
|
|
247
|
+
const plugins = options.plugins;
|
|
248
|
+
const transforms = plugins
|
|
245
249
|
?.map((p) => p.beforeStageWrite)
|
|
246
250
|
.filter((t) => t !== undefined);
|
|
247
251
|
this.beforeStageWrite = transforms?.length
|
|
248
252
|
? (quads, context) => transforms.reduce((q, fn) => fn(q, context), quads)
|
|
249
253
|
: undefined;
|
|
250
|
-
const datasetTransforms =
|
|
254
|
+
const datasetTransforms = plugins
|
|
251
255
|
?.map((p) => p.beforeDatasetWrite)
|
|
252
256
|
.filter((t) => t !== undefined);
|
|
253
257
|
this.beforeDatasetWrite = datasetTransforms?.length
|
|
@@ -285,7 +289,10 @@ export class Pipeline {
|
|
|
285
289
|
};
|
|
286
290
|
const openedWriter = await this.writer.openRun(context);
|
|
287
291
|
// Wrap the run writer so a whole-dataset transform sees every stage's
|
|
288
|
-
// output for a dataset as one stream before it is written.
|
|
292
|
+
// output for a dataset as one stream before it is written. The transform is
|
|
293
|
+
// quad-typed and only present when `Out` is `Quad` (a projecting pipeline
|
|
294
|
+
// has no plugins), so narrowing the run writer to `RunWriter<Quad>` here is
|
|
295
|
+
// sound – tsc cannot see that invariant.
|
|
289
296
|
const runWriter = this.beforeDatasetWrite
|
|
290
297
|
? new DatasetTransformWriter(openedWriter, this.beforeDatasetWrite)
|
|
291
298
|
: openedWriter;
|
|
@@ -495,6 +502,8 @@ export class Pipeline {
|
|
|
495
502
|
this.reporter?.datasetValidated?.(dataset, report);
|
|
496
503
|
}
|
|
497
504
|
}
|
|
505
|
+
// Walks every stage and sub-stage to collect validators; it reads only
|
|
506
|
+
// `validator`/`stages`, never output, so it is agnostic to the item type.
|
|
498
507
|
*collectStages(stages) {
|
|
499
508
|
for (const stage of stages) {
|
|
500
509
|
yield stage;
|
|
@@ -509,6 +518,9 @@ export class Pipeline {
|
|
|
509
518
|
* per-`(dataset, stage)` IRIs rather than blank nodes.
|
|
510
519
|
*/
|
|
511
520
|
stageWriter(runWriter, stage) {
|
|
521
|
+
// `beforeStageWrite` is quad-typed and only present when `Out` is `Quad` (a
|
|
522
|
+
// projecting pipeline has no plugins), so the `TransformWriter` wrap is
|
|
523
|
+
// reached only in that case; tsc cannot see the invariant, hence the casts.
|
|
512
524
|
return this.beforeStageWrite
|
|
513
525
|
? new TransformWriter(runWriter, this.beforeStageWrite, stage)
|
|
514
526
|
: runWriter;
|
|
@@ -596,10 +608,12 @@ export class Pipeline {
|
|
|
596
608
|
async runChain(dataset, distribution, stage, runWriter, context, timeout) {
|
|
597
609
|
const { stageOutputResolver, outputDir } = this.chaining;
|
|
598
610
|
const outputFiles = [];
|
|
599
|
-
//
|
|
600
|
-
//
|
|
601
|
-
//
|
|
602
|
-
//
|
|
611
|
+
// Chaining is a quad-only concern: every stage in a chain writes N-Triples
|
|
612
|
+
// to a scratch FileWriter (a `Writer<Quad>`), and a chained parent cannot
|
|
613
|
+
// project (a stage with `stages` forbids `project`), so it emits quads. The
|
|
614
|
+
// requirement "sub-stages imply `Out` is `Quad`" is not expressible – `stages`
|
|
615
|
+
// and `chaining` are independent options – so it is a runtime contract, and
|
|
616
|
+
// the scratch machinery treats the parent stage as `Stage<Quad>`.
|
|
603
617
|
const runScratchStage = async (chainedStage, stageDistribution) => {
|
|
604
618
|
const scratchWriter = new FileWriter({
|
|
605
619
|
outputDir: `${outputDir}/${chainedStage.name}`,
|
|
@@ -618,7 +632,7 @@ export class Pipeline {
|
|
|
618
632
|
return scratchWriter.getOutputPath(dataset);
|
|
619
633
|
};
|
|
620
634
|
try {
|
|
621
|
-
// 1. Run parent stage → scratch FileWriter.
|
|
635
|
+
// 1. Run parent stage → scratch FileWriter (quad-only, per above).
|
|
622
636
|
const parentOutput = await runScratchStage(stage, distribution);
|
|
623
637
|
outputFiles.push(parentOutput);
|
|
624
638
|
// 2. Chain through children.
|
|
@@ -632,6 +646,8 @@ export class Pipeline {
|
|
|
632
646
|
}
|
|
633
647
|
// 3. Concatenate all output files → the open run, applying the plugins'
|
|
634
648
|
// beforeStageWrite transforms once for the chain under the parent stage.
|
|
649
|
+
// The concatenation is quads (per the quad-only contract above), written
|
|
650
|
+
// to the `Out`-typed run writer – sound because chaining implies `Quad`.
|
|
635
651
|
await this.stageWriter(runWriter, stage.name).write(dataset, this.readFiles(outputFiles));
|
|
636
652
|
}
|
|
637
653
|
finally {
|
package/dist/stage.d.ts
CHANGED
|
@@ -26,6 +26,23 @@ export interface ReaderContext {
|
|
|
26
26
|
distribution: Distribution;
|
|
27
27
|
stage: string;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The pipeline's one type-changing seam: turn a root-complete batch of quads
|
|
31
|
+
* into a stage's output items. The only extension point whose output is not
|
|
32
|
+
* `Quad` – a quad enrichment is a {@link QuadTransform} attached to a reader
|
|
33
|
+
* (extension point 1), which runs *before* this. See
|
|
34
|
+
* {@link https://github.com/ldelements/lde/blob/main/docs/decisions/0013-project-inside-the-batch-per-root-type.md | ADR 13}.
|
|
35
|
+
*/
|
|
36
|
+
export type BatchTransform<Out> = (quads: readonly Quad[], context: BatchContext) => Iterable<Out> | AsyncIterable<Out>;
|
|
37
|
+
/**
|
|
38
|
+
* Context handed to a {@link BatchTransform}: {@link ReaderContext} plus the
|
|
39
|
+
* batch's selected roots. `bindings` are the item-selector rows the readers were
|
|
40
|
+
* given as a `VALUES` block, so the batch is root-complete by construction – the
|
|
41
|
+
* projection frames exactly these roots rather than discovering them.
|
|
42
|
+
*/
|
|
43
|
+
export interface BatchContext extends ReaderContext {
|
|
44
|
+
bindings: readonly VariableBindings[];
|
|
45
|
+
}
|
|
29
46
|
/**
|
|
30
47
|
* An {@link Reader} with zero or more {@link QuadTransform}s attached as data.
|
|
31
48
|
*
|
|
@@ -45,10 +62,27 @@ export interface AttachedReader {
|
|
|
45
62
|
}
|
|
46
63
|
/** One or more readers, each optionally carrying attached transforms. */
|
|
47
64
|
export type StageReaders = Reader | AttachedReader | (Reader | AttachedReader)[];
|
|
48
|
-
export interface StageOptions {
|
|
65
|
+
export interface StageOptions<Out = Quad> {
|
|
49
66
|
name: string;
|
|
50
67
|
readers: StageReaders;
|
|
51
68
|
itemSelector?: ItemSelector;
|
|
69
|
+
/**
|
|
70
|
+
* Turn each root-complete batch into this stage's output items – the one seam
|
|
71
|
+
* whose output type differs from `Quad` ({@link BatchTransform}). Requires
|
|
72
|
+
* {@link StageOptions.itemSelector} (a batch only exists under a selector) and
|
|
73
|
+
* forbids {@link StageOptions.stages} (a chained stage serializes to
|
|
74
|
+
* N-Triples, which a projected item cannot). Omit for a plain quad stage.
|
|
75
|
+
*/
|
|
76
|
+
project?: BatchTransform<Out>;
|
|
77
|
+
/**
|
|
78
|
+
* Capacity of the bounded queue funnelling this stage's concurrent batches
|
|
79
|
+
* into the single write. The queue applies backpressure at this many items,
|
|
80
|
+
* so it bounds memory – set it where the cost per item is large (a projected
|
|
81
|
+
* document is far heavier than a quad). Only meaningful with an item selector.
|
|
82
|
+
*
|
|
83
|
+
* @default 128
|
|
84
|
+
*/
|
|
85
|
+
queueCapacity?: number;
|
|
52
86
|
/**
|
|
53
87
|
* Maximum number of bindings per reader call.
|
|
54
88
|
*
|
|
@@ -74,7 +108,7 @@ export interface StageOptions {
|
|
|
74
108
|
* Treat a supported stage that produces no quads as a hard failure (throws),
|
|
75
109
|
* rather than a legitimately empty result.
|
|
76
110
|
*
|
|
77
|
-
* Set this for stages whose query must yield output
|
|
111
|
+
* Set this for stages whose query must yield output – typically a scalar
|
|
78
112
|
* aggregate such as `SELECT (COUNT(*) AS ?n)`, which always returns exactly
|
|
79
113
|
* one row, so zero quads can only mean the endpoint truncated or aborted the
|
|
80
114
|
* response (e.g. a timeout surfaced as an empty `HTTP 200`). The resulting
|
|
@@ -109,7 +143,7 @@ export interface SelectOptions {
|
|
|
109
143
|
/** Per-call timeout policy. */
|
|
110
144
|
timeout?: TimeoutPolicy;
|
|
111
145
|
}
|
|
112
|
-
export declare class Stage {
|
|
146
|
+
export declare class Stage<Out = Quad> {
|
|
113
147
|
readonly name: string;
|
|
114
148
|
readonly stages: readonly Stage[];
|
|
115
149
|
/** Whether an empty result is treated as a hard failure. @see {@link StageOptions.expectsOutput} */
|
|
@@ -119,13 +153,15 @@ export declare class Stage {
|
|
|
119
153
|
private readonly batchSize;
|
|
120
154
|
private readonly maxConcurrency;
|
|
121
155
|
private readonly validation?;
|
|
122
|
-
|
|
156
|
+
private readonly project?;
|
|
157
|
+
private readonly queueCapacity?;
|
|
158
|
+
constructor(options: StageOptions<Out>);
|
|
123
159
|
/** The validator for this stage, if configured. */
|
|
124
160
|
get validator(): Validator | undefined;
|
|
125
|
-
run(dataset: Dataset, distribution: Distribution, writer: DatasetWriter
|
|
161
|
+
run(dataset: Dataset, distribution: Distribution, writer: DatasetWriter<Out>, options?: RunOptions): Promise<NotSupported | void>;
|
|
126
162
|
/**
|
|
127
163
|
* Throw when {@link StageOptions.expectsOutput} is set but the stage produced
|
|
128
|
-
* no quads
|
|
164
|
+
* no quads – a supported-but-empty result that signals a truncated or aborted
|
|
129
165
|
* endpoint response rather than a legitimately empty one.
|
|
130
166
|
*/
|
|
131
167
|
private assertProduced;
|
package/dist/stage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stage.d.ts","sourceRoot":"","sources":["../src/stage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,CAAC,GAAG,IAAI,CAC/B,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,EAC1B,OAAO,EAAE,GAAG,KACT,aAAa,CAAC,IAAI,CAAC,CAAC;AAEzB;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,YAAY,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC;CAC3E;AAED,yEAAyE;AACzE,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,cAAc,GACd,CAAC,MAAM,GAAG,cAAc,CAAC,EAAE,CAAC;AAQhC,MAAM,WAAW,YAAY;
|
|
1
|
+
{"version":3,"file":"stage.d.ts","sourceRoot":"","sources":["../src/stage.ts"],"names":[],"mappings":"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"}
|
package/dist/stage.js
CHANGED
|
@@ -11,7 +11,15 @@ export class Stage {
|
|
|
11
11
|
batchSize;
|
|
12
12
|
maxConcurrency;
|
|
13
13
|
validation;
|
|
14
|
+
project;
|
|
15
|
+
queueCapacity;
|
|
14
16
|
constructor(options) {
|
|
17
|
+
if (options.project && !options.itemSelector) {
|
|
18
|
+
throw new Error(`Stage '${options.name}': 'project' requires an 'itemSelector' – without one there is no batch to project, only the readers' whole output.`);
|
|
19
|
+
}
|
|
20
|
+
if (options.project && (options.stages?.length ?? 0) > 0) {
|
|
21
|
+
throw new Error(`Stage '${options.name}': 'project' cannot combine with chained 'stages' – a chained stage serializes to N-Triples, which a projected item cannot.`);
|
|
22
|
+
}
|
|
15
23
|
this.name = options.name;
|
|
16
24
|
this.stages = options.stages ?? [];
|
|
17
25
|
this.readers = normalizeReaders(options.readers);
|
|
@@ -20,6 +28,8 @@ export class Stage {
|
|
|
20
28
|
this.maxConcurrency = options.maxConcurrency ?? 10;
|
|
21
29
|
this.validation = options.validation;
|
|
22
30
|
this.expectsOutput = options.expectsOutput ?? false;
|
|
31
|
+
this.project = options.project;
|
|
32
|
+
this.queueCapacity = options.queueCapacity;
|
|
23
33
|
}
|
|
24
34
|
/** The validator for this stage, if configured. */
|
|
25
35
|
get validator() {
|
|
@@ -36,6 +46,11 @@ export class Stage {
|
|
|
36
46
|
if (streams instanceof NotSupported) {
|
|
37
47
|
return streams;
|
|
38
48
|
}
|
|
49
|
+
// The non-selector path has no batch, so `project` is forbidden here (the
|
|
50
|
+
// constructor rejects `project` without an `itemSelector`). It therefore
|
|
51
|
+
// only ever writes quads, and `Out` is `Quad` at runtime – tsc cannot see
|
|
52
|
+
// that invariant, so the writer is narrowed once.
|
|
53
|
+
const quadWriter = writer;
|
|
39
54
|
// Quads the readers produced (before any validation filtering); used to
|
|
40
55
|
// enforce `expectsOutput` below.
|
|
41
56
|
let produced = 0;
|
|
@@ -50,7 +65,7 @@ export class Stage {
|
|
|
50
65
|
const onInvalid = this.validation.onInvalid ?? 'write';
|
|
51
66
|
if (onInvalid === 'write') {
|
|
52
67
|
await Promise.all([
|
|
53
|
-
|
|
68
|
+
quadWriter.write(dataset, (async function* () {
|
|
54
69
|
yield* buffer;
|
|
55
70
|
})()),
|
|
56
71
|
this.validation.validator.validate(buffer, dataset),
|
|
@@ -59,7 +74,7 @@ export class Stage {
|
|
|
59
74
|
else {
|
|
60
75
|
const accepted = await this.validateBuffer(buffer, dataset);
|
|
61
76
|
if (accepted.length > 0) {
|
|
62
|
-
await
|
|
77
|
+
await quadWriter.write(dataset, (async function* () {
|
|
63
78
|
yield* accepted;
|
|
64
79
|
})());
|
|
65
80
|
}
|
|
@@ -68,18 +83,18 @@ export class Stage {
|
|
|
68
83
|
else if (this.expectsOutput) {
|
|
69
84
|
// Only thread the per-quad counter through when the count is actually
|
|
70
85
|
// needed; the default path stays a plain streaming write with no overhead.
|
|
71
|
-
await
|
|
86
|
+
await quadWriter.write(dataset, countQuads(mergeStreams(streams), (count) => {
|
|
72
87
|
produced = count;
|
|
73
88
|
}));
|
|
74
89
|
}
|
|
75
90
|
else {
|
|
76
|
-
await
|
|
91
|
+
await quadWriter.write(dataset, mergeStreams(streams));
|
|
77
92
|
}
|
|
78
93
|
this.assertProduced(produced);
|
|
79
94
|
}
|
|
80
95
|
/**
|
|
81
96
|
* Throw when {@link StageOptions.expectsOutput} is set but the stage produced
|
|
82
|
-
* no quads
|
|
97
|
+
* no quads – a supported-but-empty result that signals a truncated or aborted
|
|
83
98
|
* endpoint response rather than a legitimately empty one.
|
|
84
99
|
*/
|
|
85
100
|
assertProduced(produced) {
|
|
@@ -107,8 +122,11 @@ export class Stage {
|
|
|
107
122
|
yield next.value;
|
|
108
123
|
}
|
|
109
124
|
})();
|
|
110
|
-
const queue = new AsyncQueue();
|
|
125
|
+
const queue = new AsyncQueue(this.queueCapacity);
|
|
111
126
|
let itemsProcessed = 0;
|
|
127
|
+
// Output items written this run. Equals quads written for a plain stage;
|
|
128
|
+
// for a projecting stage it counts projected items (documents), one or more
|
|
129
|
+
// – or fewer – per root, which is what `expectsOutput`/`onProgress` report.
|
|
112
130
|
let quadsGenerated = 0;
|
|
113
131
|
let hasResults = false;
|
|
114
132
|
const onInvalid = this.validation?.onInvalid ?? 'write';
|
|
@@ -157,25 +175,34 @@ export class Stage {
|
|
|
157
175
|
return quads;
|
|
158
176
|
}));
|
|
159
177
|
const batchQuads = readerOutputs.flat();
|
|
178
|
+
// Validation runs on the quads, before projection – validators are
|
|
179
|
+
// quad-typed. 'skip'/'halt' must resolve it before writing; 'write'
|
|
180
|
+
// validates concurrently, below, without blocking.
|
|
181
|
+
let acceptedQuads = batchQuads;
|
|
160
182
|
if (this.validation &&
|
|
161
183
|
batchQuads.length > 0 &&
|
|
162
184
|
onInvalid !== 'write') {
|
|
163
|
-
|
|
164
|
-
const accepted = await this.validateBuffer(batchQuads, dataset);
|
|
165
|
-
for (const quad of accepted) {
|
|
166
|
-
await queue.push(quad);
|
|
167
|
-
quadsGenerated++;
|
|
168
|
-
}
|
|
185
|
+
acceptedQuads = await this.validateBuffer(batchQuads, dataset);
|
|
169
186
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
187
|
+
// The one type-changing seam: project the root-complete batch into
|
|
188
|
+
// output items, or pass the quads through for a plain stage (`Out`
|
|
189
|
+
// is `Quad` at runtime when there is no projection).
|
|
190
|
+
const items = this.project
|
|
191
|
+
? this.project(acceptedQuads, {
|
|
192
|
+
dataset,
|
|
193
|
+
distribution,
|
|
194
|
+
stage: this.name,
|
|
195
|
+
bindings,
|
|
196
|
+
})
|
|
197
|
+
: acceptedQuads;
|
|
198
|
+
for await (const item of items) {
|
|
199
|
+
await queue.push(item);
|
|
200
|
+
quadsGenerated++;
|
|
201
|
+
}
|
|
202
|
+
if (this.validation &&
|
|
203
|
+
batchQuads.length > 0 &&
|
|
204
|
+
onInvalid === 'write') {
|
|
205
|
+
pendingValidations.push(this.validation.validator.validate(batchQuads, dataset));
|
|
179
206
|
}
|
|
180
207
|
itemsProcessed += bindings.length;
|
|
181
208
|
options?.onProgress?.(itemsProcessed, quadsGenerated);
|
|
@@ -291,7 +318,7 @@ async function* mergeStreams(streams) {
|
|
|
291
318
|
* via `onCount` once the stream is exhausted. Lets a streaming write enforce
|
|
292
319
|
* {@link StageOptions.expectsOutput} without buffering.
|
|
293
320
|
*
|
|
294
|
-
* `onCount` fires only when the consumer drains the stream
|
|
321
|
+
* `onCount` fires only when the consumer drains the stream – which the pipeline
|
|
295
322
|
* writers do. A writer that stops early would leave the count short; callers
|
|
296
323
|
* relying on it for `expectsOutput` must consume the stream fully.
|
|
297
324
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/pipeline",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"url": "git+https://github.com/ldelements/lde.git",
|
|
6
6
|
"directory": "packages/pipeline"
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@rdfjs/namespace": "^2.0.1",
|
|
34
34
|
"@rdfjs/types": "^2.0.1",
|
|
35
35
|
"@tpluscode/rdf-ns-builders": "^5.0.0",
|
|
36
|
-
"@traqula/generator-sparql-1-1": "^1.1.
|
|
36
|
+
"@traqula/generator-sparql-1-1": "^1.1.8",
|
|
37
37
|
"@traqula/parser-sparql-1-1": "^1.1.5",
|
|
38
38
|
"@traqula/rules-sparql-1-1": "^1.1.0",
|
|
39
39
|
"fetch-sparql-endpoint": "^7.1.1",
|