@lde/pipeline 0.31.5 → 0.32.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/README.md +45 -29
- package/dist/pipeline.d.ts +2 -2
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +102 -40
- package/dist/provenance/fileLoadedSparqlProvenanceStore.d.ts.map +1 -1
- package/dist/provenance/fileLoadedSparqlProvenanceStore.js +5 -2
- package/dist/sparql/index.d.ts +1 -1
- package/dist/sparql/index.d.ts.map +1 -1
- package/dist/sparql/index.js +1 -1
- package/dist/sparql/{executor.d.ts → reader.d.ts} +28 -27
- package/dist/sparql/reader.d.ts.map +1 -0
- package/dist/sparql/{executor.js → reader.js} +18 -17
- package/dist/sparql/selector.d.ts +1 -1
- package/dist/sparql/selector.d.ts.map +1 -1
- package/dist/sparql/values.d.ts +1 -1
- package/dist/sparql/values.d.ts.map +1 -1
- package/dist/stage.d.ts +31 -31
- package/dist/stage.d.ts.map +1 -1
- package/dist/stage.js +27 -27
- package/dist/writer/fileWriter.d.ts +32 -7
- package/dist/writer/fileWriter.d.ts.map +1 -1
- package/dist/writer/fileWriter.js +71 -39
- package/dist/writer/sparqlUpdateWriter.d.ts +19 -8
- package/dist/writer/sparqlUpdateWriter.d.ts.map +1 -1
- package/dist/writer/sparqlUpdateWriter.js +25 -12
- package/dist/writer/writer.d.ts +93 -15
- package/dist/writer/writer.d.ts.map +1 -1
- package/package.json +7 -7
- package/dist/sparql/executor.d.ts.map +0 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Dataset } from '@lde/dataset';
|
|
2
|
-
import
|
|
3
|
-
import { Writer } from './writer.js';
|
|
2
|
+
import { RunContext, RunWriter, Writer } from './writer.js';
|
|
4
3
|
export interface FileWriterOptions {
|
|
5
4
|
/**
|
|
6
5
|
* Output directory for written files.
|
|
@@ -32,21 +31,47 @@ export interface FileWriterOptions {
|
|
|
32
31
|
*/
|
|
33
32
|
graphIri?: (dataset: Dataset) => URL;
|
|
34
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The run writer a {@link FileWriter} opens: per-dataset `flush` and `reset`
|
|
36
|
+
* are always available, so direct callers need no optional chaining.
|
|
37
|
+
*/
|
|
38
|
+
export interface FileRunWriter extends RunWriter {
|
|
39
|
+
flush(dataset: Dataset): Promise<void>;
|
|
40
|
+
reset(dataset: Dataset): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Streams RDF quads to files on disk using N3 Writer.
|
|
44
|
+
*
|
|
45
|
+
* Files are named based on the dataset IRI using filenamify-url.
|
|
46
|
+
*
|
|
47
|
+
* Within a run ({@link openRun}), a single N3Writer is kept open per dataset
|
|
48
|
+
* across all `write` calls, so Turtle prefix declarations are written once and
|
|
49
|
+
* triples can be grouped by subject. `flush` finalizes a dataset's file;
|
|
50
|
+
* `commit` finalizes any files still open, and `abort` discards their
|
|
51
|
+
* temporary output, leaving no half-written final file behind.
|
|
52
|
+
*/
|
|
35
53
|
export declare class FileWriter implements Writer {
|
|
36
54
|
private readonly outputDir;
|
|
37
55
|
readonly format: 'turtle' | 'n-triples' | 'n-quads';
|
|
38
56
|
private readonly replacementCharacter;
|
|
39
57
|
private readonly prefixes?;
|
|
40
58
|
private readonly graphIri?;
|
|
41
|
-
private readonly activeWriters;
|
|
42
59
|
constructor(options: FileWriterOptions);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
openRun(_context?: RunContext): Promise<FileRunWriter>;
|
|
61
|
+
private writeQuads;
|
|
62
|
+
private flushFile;
|
|
63
|
+
private flushPath;
|
|
64
|
+
private discardFile;
|
|
65
|
+
/**
|
|
66
|
+
* Close an open file's stream and remove its temp file. Await the stream
|
|
67
|
+
* closing before removing: the write stream opens its fd lazily, so a
|
|
68
|
+
* pending open could otherwise recreate the file after rm() ran.
|
|
69
|
+
*/
|
|
70
|
+
private closeAndRemove;
|
|
46
71
|
getOutputPath(dataset: Dataset): string;
|
|
47
72
|
getFilename(dataset: Dataset): string;
|
|
48
73
|
private getFilePath;
|
|
49
|
-
private
|
|
74
|
+
private getOrCreateFile;
|
|
50
75
|
private getExtension;
|
|
51
76
|
}
|
|
52
77
|
//# sourceMappingURL=fileWriter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileWriter.d.ts","sourceRoot":"","sources":["../../src/writer/fileWriter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"fileWriter.d.ts","sourceRoot":"","sources":["../../src/writer/fileWriter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAOvC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;IAC5C;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,GAAG,CAAC;CACtC;AAgBD;;;GAGG;AACH,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAED;;;;;;;;;;GAUG;AACH,qBAAa,UAAW,YAAW,MAAM;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;IACpD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAA4B;gBAE1C,OAAO,EAAE,iBAAiB;IAQhC,OAAO,CAAC,QAAQ,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC;YAwB9C,UAAU;YAqCV,SAAS;YAOT,SAAS;YAgCT,WAAW;IAczB;;;;OAIG;YACW,cAAc;IAQ5B,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM;IAIvC,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM;IAQrC,OAAO,CAAC,WAAW;YAIL,eAAe;IA6B7B,OAAO,CAAC,YAAY;CAUrB"}
|
|
@@ -3,27 +3,28 @@ import { mkdir, rename, rm } from 'node:fs/promises';
|
|
|
3
3
|
import { join, dirname } from 'node:path';
|
|
4
4
|
import filenamifyUrl from 'filenamify-url';
|
|
5
5
|
import { DataFactory, Writer as N3Writer } from 'n3';
|
|
6
|
+
const formatMap = {
|
|
7
|
+
turtle: 'Turtle',
|
|
8
|
+
'n-triples': 'N-Triples',
|
|
9
|
+
'n-quads': 'N-Quads',
|
|
10
|
+
};
|
|
6
11
|
/**
|
|
7
12
|
* Streams RDF quads to files on disk using N3 Writer.
|
|
8
13
|
*
|
|
9
14
|
* Files are named based on the dataset IRI using filenamify-url.
|
|
10
15
|
*
|
|
11
|
-
*
|
|
12
|
-
* so Turtle prefix declarations are written once and
|
|
13
|
-
* by subject.
|
|
16
|
+
* Within a run ({@link openRun}), a single N3Writer is kept open per dataset
|
|
17
|
+
* across all `write` calls, so Turtle prefix declarations are written once and
|
|
18
|
+
* triples can be grouped by subject. `flush` finalizes a dataset's file;
|
|
19
|
+
* `commit` finalizes any files still open, and `abort` discards their
|
|
20
|
+
* temporary output, leaving no half-written final file behind.
|
|
14
21
|
*/
|
|
15
|
-
const formatMap = {
|
|
16
|
-
turtle: 'Turtle',
|
|
17
|
-
'n-triples': 'N-Triples',
|
|
18
|
-
'n-quads': 'N-Quads',
|
|
19
|
-
};
|
|
20
22
|
export class FileWriter {
|
|
21
23
|
outputDir;
|
|
22
24
|
format;
|
|
23
25
|
replacementCharacter;
|
|
24
26
|
prefixes;
|
|
25
27
|
graphIri;
|
|
26
|
-
activeWriters = new Map();
|
|
27
28
|
constructor(options) {
|
|
28
29
|
this.outputDir = options.outputDir;
|
|
29
30
|
this.format = options.format ?? 'n-triples';
|
|
@@ -31,13 +32,36 @@ export class FileWriter {
|
|
|
31
32
|
this.prefixes = options.prefixes;
|
|
32
33
|
this.graphIri = options.graphIri;
|
|
33
34
|
}
|
|
34
|
-
async
|
|
35
|
+
async openRun(_context) {
|
|
36
|
+
const openFiles = new Map();
|
|
37
|
+
return {
|
|
38
|
+
write: (dataset, quads) => this.writeQuads(openFiles, dataset, quads),
|
|
39
|
+
flush: (dataset) => this.flushFile(openFiles, dataset),
|
|
40
|
+
reset: (dataset) => this.discardFile(openFiles, dataset),
|
|
41
|
+
commit: async () => {
|
|
42
|
+
// Finalize files not yet flushed per dataset – a safety net so a
|
|
43
|
+
// committed run never leaves a complete output stuck in a temp file.
|
|
44
|
+
for (const { finalPath } of openFiles.values()) {
|
|
45
|
+
await this.flushPath(openFiles, finalPath);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
abort: async () => {
|
|
49
|
+
// Discard all temp output: a crash or abort leaves at most a stale
|
|
50
|
+
// `*.tmp`, never a truncated final file.
|
|
51
|
+
for (const openFile of openFiles.values()) {
|
|
52
|
+
await this.closeAndRemove(openFile);
|
|
53
|
+
}
|
|
54
|
+
openFiles.clear();
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async writeQuads(openFiles, dataset, quads) {
|
|
35
59
|
// Peek at the first quad to avoid creating empty files.
|
|
36
60
|
const iterator = quads[Symbol.asyncIterator]();
|
|
37
61
|
const first = await iterator.next();
|
|
38
62
|
if (first.done)
|
|
39
63
|
return;
|
|
40
|
-
const { n3Writer } = await this.
|
|
64
|
+
const { n3Writer } = await this.getOrCreateFile(openFiles, dataset);
|
|
41
65
|
// Re-emit each quad into the configured named graph (n-quads only). The
|
|
42
66
|
// pipeline's quads carry no graph context, so the graph is supplied here
|
|
43
67
|
// exactly as SparqlUpdateWriter supplies it via INSERT DATA { GRAPH … }.
|
|
@@ -52,23 +76,25 @@ export class FileWriter {
|
|
|
52
76
|
addQuad(quad);
|
|
53
77
|
}
|
|
54
78
|
}
|
|
55
|
-
async
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
79
|
+
async flushFile(openFiles, dataset) {
|
|
80
|
+
await this.flushPath(openFiles, this.getFilePath(dataset));
|
|
81
|
+
}
|
|
82
|
+
async flushPath(openFiles, finalPath) {
|
|
83
|
+
const openFile = openFiles.get(finalPath);
|
|
84
|
+
if (!openFile)
|
|
59
85
|
return;
|
|
60
|
-
|
|
86
|
+
openFiles.delete(finalPath);
|
|
61
87
|
// Quads are streamed to a sibling temp file; only on a clean flush is it
|
|
62
88
|
// atomically renamed onto the final path. A crash therefore leaves at most
|
|
63
89
|
// a stale `*.tmp` — never a truncated final file — so a downstream index
|
|
64
90
|
// rebuild that globs the final extension never reads a half-written file.
|
|
65
91
|
try {
|
|
66
92
|
await new Promise((resolve, reject) => {
|
|
67
|
-
if (
|
|
68
|
-
reject(
|
|
93
|
+
if (openFile.stream.errored) {
|
|
94
|
+
reject(openFile.stream.errored);
|
|
69
95
|
return;
|
|
70
96
|
}
|
|
71
|
-
|
|
97
|
+
openFile.n3Writer.end((error) => {
|
|
72
98
|
if (error)
|
|
73
99
|
reject(error);
|
|
74
100
|
else
|
|
@@ -77,26 +103,32 @@ export class FileWriter {
|
|
|
77
103
|
});
|
|
78
104
|
}
|
|
79
105
|
catch (error) {
|
|
80
|
-
await rm(
|
|
106
|
+
await rm(openFile.tempPath, { force: true, recursive: true });
|
|
81
107
|
throw error;
|
|
82
108
|
}
|
|
83
|
-
await rename(
|
|
109
|
+
await rename(openFile.tempPath, finalPath);
|
|
84
110
|
}
|
|
85
|
-
async
|
|
111
|
+
async discardFile(openFiles, dataset) {
|
|
86
112
|
const key = this.getFilePath(dataset);
|
|
87
|
-
const
|
|
88
|
-
if (!
|
|
113
|
+
const openFile = openFiles.get(key);
|
|
114
|
+
if (!openFile)
|
|
89
115
|
return;
|
|
90
116
|
// Drop the open writer and remove its temp file so the next write starts a
|
|
91
|
-
// fresh file, discarding everything streamed during the previous pass.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
117
|
+
// fresh file, discarding everything streamed during the previous pass.
|
|
118
|
+
openFiles.delete(key);
|
|
119
|
+
await this.closeAndRemove(openFile);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Close an open file's stream and remove its temp file. Await the stream
|
|
123
|
+
* closing before removing: the write stream opens its fd lazily, so a
|
|
124
|
+
* pending open could otherwise recreate the file after rm() ran.
|
|
125
|
+
*/
|
|
126
|
+
async closeAndRemove(openFile) {
|
|
95
127
|
await new Promise((resolve) => {
|
|
96
|
-
|
|
97
|
-
|
|
128
|
+
openFile.stream.once('close', resolve);
|
|
129
|
+
openFile.stream.destroy();
|
|
98
130
|
});
|
|
99
|
-
await rm(
|
|
131
|
+
await rm(openFile.tempPath, { force: true, recursive: true });
|
|
100
132
|
}
|
|
101
133
|
getOutputPath(dataset) {
|
|
102
134
|
return this.getFilePath(dataset);
|
|
@@ -111,16 +143,16 @@ export class FileWriter {
|
|
|
111
143
|
getFilePath(dataset) {
|
|
112
144
|
return join(this.outputDir, this.getFilename(dataset));
|
|
113
145
|
}
|
|
114
|
-
async
|
|
115
|
-
const
|
|
116
|
-
const existing =
|
|
146
|
+
async getOrCreateFile(openFiles, dataset) {
|
|
147
|
+
const finalPath = this.getFilePath(dataset);
|
|
148
|
+
const existing = openFiles.get(finalPath);
|
|
117
149
|
if (existing)
|
|
118
150
|
return existing;
|
|
119
|
-
await mkdir(dirname(
|
|
151
|
+
await mkdir(dirname(finalPath), { recursive: true });
|
|
120
152
|
// Write to a sibling temp file (same directory, so the flush rename stays on
|
|
121
153
|
// one filesystem and is atomic). The `.tmp` suffix keeps it out of any glob
|
|
122
154
|
// on the final extension.
|
|
123
|
-
const tempPath = `${
|
|
155
|
+
const tempPath = `${finalPath}.tmp`;
|
|
124
156
|
const stream = createWriteStream(tempPath, { flags: 'w' });
|
|
125
157
|
stream.on('error', (error) => {
|
|
126
158
|
// Surface stream errors when flushing; prevents 'unhandled error' crashes.
|
|
@@ -130,9 +162,9 @@ export class FileWriter {
|
|
|
130
162
|
format: formatMap[this.format],
|
|
131
163
|
prefixes: this.prefixes,
|
|
132
164
|
});
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
return
|
|
165
|
+
const openFile = { n3Writer, stream, tempPath, finalPath };
|
|
166
|
+
openFiles.set(finalPath, openFile);
|
|
167
|
+
return openFile;
|
|
136
168
|
}
|
|
137
169
|
getExtension() {
|
|
138
170
|
switch (this.format) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Dataset } from '@lde/dataset';
|
|
2
|
-
import
|
|
3
|
-
import { Writer } from './writer.js';
|
|
2
|
+
import { RunContext, RunWriter, Writer } from './writer.js';
|
|
4
3
|
export interface SparqlWriterOptions {
|
|
5
4
|
/**
|
|
6
5
|
* The SPARQL UPDATE endpoint URL.
|
|
@@ -34,12 +33,25 @@ export interface SparqlWriterOptions {
|
|
|
34
33
|
*/
|
|
35
34
|
graphIri?: (dataset: Dataset) => URL;
|
|
36
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* The run writer a {@link SparqlUpdateWriter} opens: per-dataset `reset` is
|
|
38
|
+
* always available, so direct callers need no optional chaining.
|
|
39
|
+
*/
|
|
40
|
+
export interface SparqlUpdateRunWriter extends RunWriter {
|
|
41
|
+
reset(dataset: Dataset): Promise<void>;
|
|
42
|
+
}
|
|
37
43
|
/**
|
|
38
44
|
* Writes RDF data to a SPARQL endpoint using SPARQL UPDATE INSERT DATA queries.
|
|
39
45
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
46
|
+
* Within a run ({@link openRun}), the named graph is cleared before the first
|
|
47
|
+
* write per dataset, then quads are streamed in batches to avoid accumulating
|
|
48
|
+
* the entire dataset in memory. Subsequent writes for the same dataset within
|
|
49
|
+
* the run append rather than replace. Each new run starts with fresh
|
|
50
|
+
* cleared-graph state, so re-running a pipeline replaces each graph again.
|
|
51
|
+
*
|
|
52
|
+
* The writes are visible as they land (no run-level staging), so `commit` and
|
|
53
|
+
* `abort` are no-ops: an aborted run leaves the graphs written so far, to be
|
|
54
|
+
* replaced by the next run.
|
|
43
55
|
*/
|
|
44
56
|
export declare class SparqlUpdateWriter implements Writer {
|
|
45
57
|
private readonly endpoint;
|
|
@@ -47,10 +59,9 @@ export declare class SparqlUpdateWriter implements Writer {
|
|
|
47
59
|
private readonly fetch;
|
|
48
60
|
private readonly batchSize;
|
|
49
61
|
private readonly graphIri;
|
|
50
|
-
private readonly clearedGraphs;
|
|
51
62
|
constructor(options: SparqlWriterOptions);
|
|
52
|
-
|
|
53
|
-
|
|
63
|
+
openRun(_context?: RunContext): Promise<SparqlUpdateRunWriter>;
|
|
64
|
+
private writeQuads;
|
|
54
65
|
private clearGraph;
|
|
55
66
|
private insertBatch;
|
|
56
67
|
private executeUpdate;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sparqlUpdateWriter.d.ts","sourceRoot":"","sources":["../../src/writer/sparqlUpdateWriter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAiB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"sparqlUpdateWriter.d.ts","sourceRoot":"","sources":["../../src/writer/sparqlUpdateWriter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAiB,MAAM,cAAc,CAAC;AAGtD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG5D,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,QAAQ,EAAE,GAAG,CAAC;IACd;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,GAAG,CAAC;CACtC;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAsB,SAAQ,SAAS;IACtD,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,kBAAmB,YAAW,MAAM;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAM;IAC/B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0B;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;gBAEzC,OAAO,EAAE,mBAAmB;IAQlC,OAAO,CAAC,QAAQ,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,qBAAqB,CAAC;YActD,UAAU;YAkBV,UAAU;YAIV,WAAW;YAOX,aAAa;CAuB5B"}
|
|
@@ -4,9 +4,15 @@ import { serializeQuads } from './serialize.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Writes RDF data to a SPARQL endpoint using SPARQL UPDATE INSERT DATA queries.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Within a run ({@link openRun}), the named graph is cleared before the first
|
|
8
|
+
* write per dataset, then quads are streamed in batches to avoid accumulating
|
|
9
|
+
* the entire dataset in memory. Subsequent writes for the same dataset within
|
|
10
|
+
* the run append rather than replace. Each new run starts with fresh
|
|
11
|
+
* cleared-graph state, so re-running a pipeline replaces each graph again.
|
|
12
|
+
*
|
|
13
|
+
* The writes are visible as they land (no run-level staging), so `commit` and
|
|
14
|
+
* `abort` are no-ops: an aborted run leaves the graphs written so far, to be
|
|
15
|
+
* replaced by the next run.
|
|
10
16
|
*/
|
|
11
17
|
export class SparqlUpdateWriter {
|
|
12
18
|
endpoint;
|
|
@@ -14,7 +20,6 @@ export class SparqlUpdateWriter {
|
|
|
14
20
|
fetch;
|
|
15
21
|
batchSize;
|
|
16
22
|
graphIri;
|
|
17
|
-
clearedGraphs = new Set();
|
|
18
23
|
constructor(options) {
|
|
19
24
|
this.endpoint = options.endpoint;
|
|
20
25
|
this.auth = options.auth;
|
|
@@ -22,22 +27,30 @@ export class SparqlUpdateWriter {
|
|
|
22
27
|
this.batchSize = options.batchSize ?? 10000;
|
|
23
28
|
this.graphIri = options.graphIri ?? ((dataset) => dataset.iri);
|
|
24
29
|
}
|
|
25
|
-
async
|
|
30
|
+
async openRun(_context) {
|
|
31
|
+
const clearedGraphs = new Set();
|
|
32
|
+
return {
|
|
33
|
+
write: (dataset, quads) => this.writeQuads(clearedGraphs, dataset, quads),
|
|
34
|
+
reset: async (dataset) => {
|
|
35
|
+
// Forget the graph’s cleared state so the next write re-issues
|
|
36
|
+
// CLEAR GRAPH, replacing the prior output instead of appending to it.
|
|
37
|
+
clearedGraphs.delete(this.graphIri(dataset).toString());
|
|
38
|
+
},
|
|
39
|
+
commit: () => Promise.resolve(),
|
|
40
|
+
abort: () => Promise.resolve(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async writeQuads(clearedGraphs, dataset, quads) {
|
|
26
44
|
const graphUri = this.graphIri(dataset).toString();
|
|
27
45
|
assertSafeIri(graphUri);
|
|
28
|
-
if (!
|
|
46
|
+
if (!clearedGraphs.has(graphUri)) {
|
|
29
47
|
await this.clearGraph(graphUri);
|
|
30
|
-
|
|
48
|
+
clearedGraphs.add(graphUri);
|
|
31
49
|
}
|
|
32
50
|
for await (const chunk of batch(quads, this.batchSize)) {
|
|
33
51
|
await this.insertBatch(graphUri, chunk);
|
|
34
52
|
}
|
|
35
53
|
}
|
|
36
|
-
async reset(dataset) {
|
|
37
|
-
// Forget the graph’s cleared state so the next write re-issues CLEAR GRAPH,
|
|
38
|
-
// replacing the prior output instead of appending to it.
|
|
39
|
-
this.clearedGraphs.delete(this.graphIri(dataset).toString());
|
|
40
|
-
}
|
|
41
54
|
async clearGraph(graphUri) {
|
|
42
55
|
await this.executeUpdate(`CLEAR GRAPH <${graphUri}>`);
|
|
43
56
|
}
|
package/dist/writer/writer.d.ts
CHANGED
|
@@ -1,35 +1,113 @@
|
|
|
1
1
|
import { Dataset } from '@lde/dataset';
|
|
2
2
|
import type { Quad } from '@rdfjs/types';
|
|
3
|
+
import type { ProvenanceStore } from '../provenance/store.js';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
+
* Context for one pipeline run, handed to {@link Writer.openRun}.
|
|
5
6
|
*/
|
|
6
|
-
export interface
|
|
7
|
+
export interface RunContext {
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
+
* Unique identifier of this run. An In-place writer stamps written documents
|
|
10
|
+
* with it so its commit sweep can delete what the run did not touch; a
|
|
11
|
+
* Blue/green writer may use it to name the fresh collection it builds.
|
|
12
|
+
*/
|
|
13
|
+
readonly runId: string;
|
|
14
|
+
/**
|
|
15
|
+
* ISO 8601 timestamp taken when the run opened. Injected so writers need no
|
|
16
|
+
* clock of their own.
|
|
17
|
+
*/
|
|
18
|
+
readonly startedAt: string;
|
|
19
|
+
/**
|
|
20
|
+
* IRIs of every dataset the selector produced this run – including datasets
|
|
21
|
+
* the pipeline skipped as unchanged. Grows while the pipeline iterates the
|
|
22
|
+
* selection; it is complete by the time {@link RunWriter.commit} runs, which
|
|
23
|
+
* is when a registry-membership sweep should read it.
|
|
24
|
+
*/
|
|
25
|
+
selectedSources(): Iterable<string>;
|
|
26
|
+
/**
|
|
27
|
+
* The pipeline’s per-dataset processing memory, present when skip-unchanged
|
|
28
|
+
* is enabled ({@link PipelineOptions.provenanceStore}).
|
|
29
|
+
*/
|
|
30
|
+
readonly provenance?: ProvenanceStore;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The per-dataset write surface of a {@link RunWriter}: what a stage writes
|
|
34
|
+
* its output through. Separate from the run lifecycle so stages can write but
|
|
35
|
+
* never commit or abort the run.
|
|
36
|
+
*/
|
|
37
|
+
export interface DatasetWriter<Item = Quad> {
|
|
38
|
+
/**
|
|
39
|
+
* Write a dataset’s items to the destination. Called one or more times per
|
|
40
|
+
* dataset (once per stage) within an open run.
|
|
9
41
|
*
|
|
10
42
|
* @param dataset The dataset metadata
|
|
11
|
-
* @param
|
|
43
|
+
* @param items The items to write
|
|
12
44
|
*/
|
|
13
|
-
write(dataset: Dataset,
|
|
45
|
+
write(dataset: Dataset, items: AsyncIterable<Item>): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* One open run transaction on a destination: per-dataset writes bracketed by
|
|
49
|
+
* exactly one {@link commit} or {@link abort}. Obtained from
|
|
50
|
+
* {@link Writer.openRun}; the pipeline drives
|
|
51
|
+
* `openRun → write* → commit/abort` uniformly and never branches on the
|
|
52
|
+
* writer’s update mode – an atomic swap or a deletion sweep is the writer’s
|
|
53
|
+
* own business, inside {@link commit}.
|
|
54
|
+
*/
|
|
55
|
+
export interface RunWriter<Item = Quad> extends DatasetWriter<Item> {
|
|
14
56
|
/**
|
|
15
|
-
* Finalize writing for a dataset. Called after all stages complete
|
|
57
|
+
* Finalize writing for a dataset. Called after all stages complete for that
|
|
58
|
+
* dataset.
|
|
16
59
|
*
|
|
17
60
|
* Writers that buffer output across multiple {@link write} calls (e.g. to
|
|
18
61
|
* share Turtle prefix declarations) should implement this to flush remaining
|
|
19
|
-
* data and release resources.
|
|
62
|
+
* data and release per-dataset resources.
|
|
20
63
|
*/
|
|
21
64
|
flush?(dataset: Dataset): Promise<void>;
|
|
22
65
|
/**
|
|
23
|
-
* Discard a dataset’s already-written output so a subsequent
|
|
24
|
-
* a clean slate. Called by the pipeline before it re-runs all stages
|
|
25
|
-
* a fallback source (an imported data dump), so endpoint-sourced
|
|
26
|
-
* results are not mixed with the dump-sourced re-run.
|
|
66
|
+
* Discard a dataset’s already-written output so a subsequent pass starts
|
|
67
|
+
* from a clean slate. Called by the pipeline before it re-runs all stages
|
|
68
|
+
* against a fallback source (an imported data dump), so endpoint-sourced
|
|
69
|
+
* partial results are not mixed with the dump-sourced re-run.
|
|
27
70
|
*
|
|
28
|
-
* Writers that build a complete replacement per dataset
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* replaceable output may omit it; the re-run then appends.
|
|
71
|
+
* Writers that build a complete replacement per dataset should implement
|
|
72
|
+
* this to reset that per-dataset state. Writers without replaceable output
|
|
73
|
+
* may omit it; the re-run then appends.
|
|
32
74
|
*/
|
|
33
75
|
reset?(dataset: Dataset): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Finalize the run: the one place a destination goes live. A Blue/green
|
|
78
|
+
* writer atomically swaps its alias to the freshly built collection; an
|
|
79
|
+
* In-place writer sweeps documents the run did not touch and releases its
|
|
80
|
+
* lock. Called exactly once, after every dataset has been processed.
|
|
81
|
+
*
|
|
82
|
+
* The driver flushes every written dataset before committing (the Pipeline
|
|
83
|
+
* does), so unflushed writes only reach `commit` under direct use; a writer
|
|
84
|
+
* that can encounter them should finalize them non-destructively.
|
|
85
|
+
*/
|
|
86
|
+
commit(): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Abandon the run after a failure: the live destination must be left as it
|
|
89
|
+
* was before the run. A Blue/green writer drops its half-built collection;
|
|
90
|
+
* an In-place writer releases its lock and lets the next run reconcile.
|
|
91
|
+
*
|
|
92
|
+
* @param error The failure that ended the run
|
|
93
|
+
*/
|
|
94
|
+
abort(error: unknown): Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A destination for pipeline output: a factory of per-run transactions.
|
|
98
|
+
*
|
|
99
|
+
* Each pipeline run opens one {@link RunWriter} via {@link openRun} – the
|
|
100
|
+
* home of run-level lifecycle such as alias swaps, deletion sweeps and
|
|
101
|
+
* cross-pod locks. A destination without run-level state simply returns its
|
|
102
|
+
* per-dataset writes with no-op `commit`/`abort` (see
|
|
103
|
+
* {@link SparqlUpdateWriter} for the pattern).
|
|
104
|
+
*/
|
|
105
|
+
export interface Writer<Item = Quad> {
|
|
106
|
+
/**
|
|
107
|
+
* Open a run transaction against this destination.
|
|
108
|
+
*
|
|
109
|
+
* @param context The run’s identity and selection scope
|
|
110
|
+
*/
|
|
111
|
+
openRun(context: RunContext): Promise<RunWriter<Item>>;
|
|
34
112
|
}
|
|
35
113
|
//# sourceMappingURL=writer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/writer/writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/writer/writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,eAAe,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,eAAe,CAAC;CACvC;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa,CAAC,IAAI,GAAG,IAAI;IACxC;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpE;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS,CAAC,IAAI,GAAG,IAAI,CAAE,SAAQ,aAAa,CAAC,IAAI,CAAC;IACjE;;;;;;;OAOG;IACH,KAAK,CAAC,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC;;;;;;;;;OASG;IACH,KAAK,CAAC,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC;;;;;;;;;OASG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAExB;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,MAAM,CAAC,IAAI,GAAG,IAAI;IACjC;;;;OAIG;IACH,OAAO,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;CACxD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/pipeline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"url": "git+https://github.com/ldelements/lde.git",
|
|
6
6
|
"directory": "packages/pipeline"
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
"!**/*.tsbuildinfo"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@lde/dataset": "^0.7.
|
|
28
|
-
"@lde/dataset-registry-client": "^0.8.
|
|
29
|
-
"@lde/distribution-health": "^0.2.
|
|
30
|
-
"@lde/distribution-probe": "^0.2.
|
|
31
|
-
"@lde/sparql-importer": "^0.6.
|
|
32
|
-
"@lde/sparql-server": "^0.4.
|
|
27
|
+
"@lde/dataset": "^0.7.8",
|
|
28
|
+
"@lde/dataset-registry-client": "^0.8.5",
|
|
29
|
+
"@lde/distribution-health": "^0.2.3",
|
|
30
|
+
"@lde/distribution-probe": "^0.2.3",
|
|
31
|
+
"@lde/sparql-importer": "^0.6.6",
|
|
32
|
+
"@lde/sparql-server": "^0.4.12",
|
|
33
33
|
"@rdfjs/namespace": "^2.0.1",
|
|
34
34
|
"@rdfjs/types": "^2.0.1",
|
|
35
35
|
"@tpluscode/rdf-ns-builders": "^5.0.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/sparql/executor.ts"],"names":[],"mappings":"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"}
|