n3.js-messages 0.1.4 → 0.2.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 +67 -6
- package/browser/THIRD-PARTY-LICENSES.txt +172 -0
- package/browser/index.global.js +27 -0
- package/browser/index.global.js.LEGAL.txt +15 -0
- package/browser/index.global.js.map +7 -0
- package/browser/index.js +27 -0
- package/browser/index.js.LEGAL.txt +15 -0
- package/browser/index.js.map +7 -0
- package/browser.d.ts +26 -0
- package/common.d.ts +89 -0
- package/index.d.ts +2 -86
- package/lib/MessageParser.js +38 -4
- package/lib/MessageParser.js.map +1 -0
- package/lib/MessageParserCore.js +42 -6
- package/lib/MessageParserCore.js.map +1 -0
- package/lib/MessageStreamParser.js +12 -1
- package/lib/MessageStreamParser.js.map +1 -0
- package/lib/MessageStreamWriter.js +10 -1
- package/lib/MessageStreamWriter.js.map +1 -0
- package/lib/MessageWriter.js +82 -15
- package/lib/MessageWriter.js.map +1 -0
- package/lib/formats.js +7 -2
- package/lib/formats.js.map +1 -0
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -0
- package/package.json +42 -14
- package/src/MessageParser.js +35 -3
- package/src/MessageParserCore.js +34 -5
- package/src/MessageStreamParser.js +8 -0
- package/src/MessageStreamWriter.js +6 -0
- package/src/MessageWriter.js +78 -16
- package/src/browser.js +61 -0
- package/src/formats.js +5 -1
- package/src/internal.d.ts +15 -0
- package/lib/MessageScanner.js +0 -395
package/src/MessageParserCore.js
CHANGED
|
@@ -5,29 +5,42 @@ import { EventEmitter } from 'events';
|
|
|
5
5
|
import { Parser, DataFactory } from 'n3';
|
|
6
6
|
import { resolveFormat, isMessagesVersion, baseVersion, isSupportedVersion } from './formats.js';
|
|
7
7
|
|
|
8
|
+
/** @typedef {import('@rdfjs/types').Term} BaseTerm */
|
|
9
|
+
/** @template {import('@rdfjs/types').BaseQuad} [Q=import('@rdfjs/types').Quad] */
|
|
8
10
|
export default class MessageParserCore {
|
|
9
11
|
// `sink` receives `message(quads, index)`, `end(prefixes)`, `error(error)`
|
|
10
12
|
// and optionally `quad(quad, messageIndex)`, `prefix(prefix, term)`, `version(label)`
|
|
13
|
+
/**
|
|
14
|
+
* @param {import('../common.js').MessageParserOptions<Q> | undefined} options
|
|
15
|
+
* @param {import('./internal.js').ParserSink<Q>} sink
|
|
16
|
+
*/
|
|
11
17
|
constructor(options, sink) {
|
|
12
18
|
options = options || {};
|
|
13
19
|
const format = resolveFormat(options.format);
|
|
14
20
|
this._sink = sink;
|
|
15
|
-
|
|
21
|
+
|
|
22
|
+
/** @type {import('../common.js').MessageQuadFactory<Q>} */
|
|
23
|
+
this._factory = options.factory || /** @type {import('../common.js').MessageQuadFactory<Q>} */ (DataFactory);
|
|
16
24
|
this._blankNodePrefix = typeof options.blankNodePrefix === 'string' ?
|
|
17
25
|
options.blankNodePrefix.replace(/^_:/, '') : undefined;
|
|
18
26
|
this._blankNodeCounter = 0;
|
|
19
27
|
this._supportedVersions = Parser.SUPPORTED_VERSIONS;
|
|
20
28
|
this._messages = options.messages === true || isMessagesVersion(options.version);
|
|
21
29
|
this._index = 0;
|
|
30
|
+
|
|
31
|
+
/** @type {Q[] | null} */
|
|
22
32
|
this._current = null;
|
|
33
|
+
|
|
34
|
+
/** @type {Map<string, import('@rdfjs/types').BlankNode> | null} */
|
|
23
35
|
this._scope = null;
|
|
24
36
|
this._seenQuad = false;
|
|
25
37
|
this._failed = false;
|
|
26
38
|
this._finished = false;
|
|
27
39
|
|
|
40
|
+
/** @type {import('n3').ParserOptions} */
|
|
28
41
|
const n3Options = {
|
|
29
42
|
format: format.name,
|
|
30
|
-
factory: this._factory,
|
|
43
|
+
factory: /** @type {import('@rdfjs/types').DataFactory} */ (/** @type {unknown} */ (this._factory)),
|
|
31
44
|
// Version labels are validated here, because N3.js does not know `-messages` labels
|
|
32
45
|
parseUnsupportedVersions: true,
|
|
33
46
|
// N3.js reports `MESSAGE` and `@message .` between statements through `onDirective`,
|
|
@@ -47,7 +60,7 @@ export default class MessageParserCore {
|
|
|
47
60
|
this._line = 0;
|
|
48
61
|
this._delimiterLine = 0;
|
|
49
62
|
new Parser(n3Options).parse(this._input, {
|
|
50
|
-
onQuad: (error, quad, prefixes) => this._onQuad(error, quad, prefixes),
|
|
63
|
+
onQuad: (error, quad, prefixes) => this._onQuad(error, /** @type {Q | null} */ (quad), prefixes),
|
|
51
64
|
onPrefix: (prefix, term) => this._failed || sink.prefix && sink.prefix(prefix, term),
|
|
52
65
|
onVersion: label => this._failed || this._announce(label),
|
|
53
66
|
onDirective: () => this._failed || this._delimiter(),
|
|
@@ -56,6 +69,7 @@ export default class MessageParserCore {
|
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
// ### `write` accepts the next chunk of text
|
|
72
|
+
/** @param {string} text */
|
|
59
73
|
write(text) {
|
|
60
74
|
if (!this._failed && !this._finished && text.length > 0)
|
|
61
75
|
this._input.emit('data', text);
|
|
@@ -68,6 +82,7 @@ export default class MessageParserCore {
|
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
// Keeps track of lines; in N-Triples and N-Quads, a delimiter must be on its own line
|
|
85
|
+
/** @param {{ line: number; type: string }} token */
|
|
71
86
|
_onToken(token) {
|
|
72
87
|
const line = token.line, delimiter = token.type === 'MESSAGE';
|
|
73
88
|
if (this._lineMode && !this._failed &&
|
|
@@ -78,6 +93,9 @@ export default class MessageParserCore {
|
|
|
78
93
|
this._line = line;
|
|
79
94
|
}
|
|
80
95
|
|
|
96
|
+
/** @param {Error | null} error
|
|
97
|
+
* @param {Q | null} quad
|
|
98
|
+
* @param {Record<string, import('@rdfjs/types').NamedNode>} [prefixes] */
|
|
81
99
|
_onQuad(error, quad, prefixes) {
|
|
82
100
|
if (this._failed || this._finished)
|
|
83
101
|
return;
|
|
@@ -93,6 +111,7 @@ export default class MessageParserCore {
|
|
|
93
111
|
}
|
|
94
112
|
}
|
|
95
113
|
|
|
114
|
+
/** @param {Q} quad */
|
|
96
115
|
_addQuad(quad) {
|
|
97
116
|
this._seenQuad = true;
|
|
98
117
|
// Blank node labels are scoped to their message
|
|
@@ -105,6 +124,7 @@ export default class MessageParserCore {
|
|
|
105
124
|
this._sink.quad(quad, this._index);
|
|
106
125
|
}
|
|
107
126
|
|
|
127
|
+
/** @param {string} label */
|
|
108
128
|
_announce(label) {
|
|
109
129
|
const error = this._checkVersion(label);
|
|
110
130
|
if (error)
|
|
@@ -119,6 +139,8 @@ export default class MessageParserCore {
|
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
// Validates a version label, and returns an error for unsupported ones
|
|
142
|
+
/** @param {string} label
|
|
143
|
+
* @param {boolean} [throwing] */
|
|
122
144
|
_checkVersion(label, throwing) {
|
|
123
145
|
if (isSupportedVersion(baseVersion(label), this._supportedVersions))
|
|
124
146
|
return null;
|
|
@@ -144,6 +166,7 @@ export default class MessageParserCore {
|
|
|
144
166
|
this._sink.message(quads, this._index++);
|
|
145
167
|
}
|
|
146
168
|
|
|
169
|
+
/** @param {Error} error */
|
|
147
170
|
_fail(error) {
|
|
148
171
|
this._failed = true;
|
|
149
172
|
this._current = null;
|
|
@@ -152,6 +175,8 @@ export default class MessageParserCore {
|
|
|
152
175
|
|
|
153
176
|
// ## Blank node scoping
|
|
154
177
|
|
|
178
|
+
/** @param {Q} quad
|
|
179
|
+
* @returns {Q} */
|
|
155
180
|
_scopeQuad(quad) {
|
|
156
181
|
const subject = this._scopeTerm(quad.subject);
|
|
157
182
|
const predicate = this._scopeTerm(quad.predicate);
|
|
@@ -162,6 +187,9 @@ export default class MessageParserCore {
|
|
|
162
187
|
quad : this._factory.quad(subject, predicate, object, graph);
|
|
163
188
|
}
|
|
164
189
|
|
|
190
|
+
/** @template {BaseTerm} T
|
|
191
|
+
* @param {T} term
|
|
192
|
+
* @returns {T} */
|
|
165
193
|
_scopeTerm(term) {
|
|
166
194
|
if (term.termType === 'BlankNode') {
|
|
167
195
|
const scope = this._scope || (this._scope = new Map());
|
|
@@ -171,8 +199,9 @@ export default class MessageParserCore {
|
|
|
171
199
|
this._factory.blankNode(`${this._blankNodePrefix}${this._blankNodeCounter++}`);
|
|
172
200
|
scope.set(term.value, scoped);
|
|
173
201
|
}
|
|
174
|
-
return scoped;
|
|
202
|
+
return /** @type {T} */ (/** @type {unknown} */ (scoped));
|
|
175
203
|
}
|
|
176
|
-
return term.termType === 'Quad' ?
|
|
204
|
+
return term.termType === 'Quad' ? /** @type {T} */ (/** @type {unknown} */ (
|
|
205
|
+
this._scopeQuad(/** @type {Q} */ (/** @type {unknown} */ (term))))) : term;
|
|
177
206
|
}
|
|
178
207
|
}
|
|
@@ -6,10 +6,14 @@ import { Transform } from 'stream';
|
|
|
6
6
|
import { StringDecoder } from 'string_decoder';
|
|
7
7
|
import MessageParserCore from './MessageParserCore.js';
|
|
8
8
|
|
|
9
|
+
/** @template {import('@rdfjs/types').BaseQuad} [Q=import('@rdfjs/types').Quad] */
|
|
9
10
|
export default class MessageStreamParser extends Transform {
|
|
11
|
+
/** @param {import('../common.js').MessageParserOptions<Q>} [options] */
|
|
10
12
|
constructor(options) {
|
|
11
13
|
super({ decodeStrings: false, readableObjectMode: true });
|
|
12
14
|
this._decoder = new StringDecoder('utf8');
|
|
15
|
+
|
|
16
|
+
/** @type {Error | null} */
|
|
13
17
|
this._error = null;
|
|
14
18
|
this._core = new MessageParserCore(options, {
|
|
15
19
|
message: quads => this.push(quads),
|
|
@@ -20,11 +24,15 @@ export default class MessageStreamParser extends Transform {
|
|
|
20
24
|
});
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
/** @param {string | Buffer} chunk
|
|
28
|
+
* @param {BufferEncoding} encoding
|
|
29
|
+
* @param {import('node:stream').TransformCallback} done */
|
|
23
30
|
_transform(chunk, encoding, done) {
|
|
24
31
|
this._core.write(typeof chunk === 'string' ? chunk : this._decoder.write(chunk));
|
|
25
32
|
done(this._error);
|
|
26
33
|
}
|
|
27
34
|
|
|
35
|
+
/** @param {import('node:stream').TransformCallback} done */
|
|
28
36
|
_flush(done) {
|
|
29
37
|
this._core.write(this._decoder.end());
|
|
30
38
|
this._core.end();
|
|
@@ -5,19 +5,25 @@ import { Transform } from 'stream';
|
|
|
5
5
|
import MessageWriter from './MessageWriter.js';
|
|
6
6
|
|
|
7
7
|
export default class MessageStreamWriter extends Transform {
|
|
8
|
+
/** @param {import('../common.js').MessageWriterOptions} [options] */
|
|
8
9
|
constructor(options) {
|
|
9
10
|
super({ writableObjectMode: true, encoding: 'utf8' });
|
|
10
11
|
// Writing into this stream never blocks, and output is pushed synchronously
|
|
12
|
+
/** @type {Pick<import('./internal.js').Output, 'write'>} */
|
|
11
13
|
const output = { write: (chunk, encoding, done) => (this.push(chunk), done && done()) };
|
|
12
14
|
this._writer = new MessageWriter(output, Object.assign({}, options, { end: false }));
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
/** @param {import('../browser.js').MessageWriterInput} message
|
|
18
|
+
* @param {BufferEncoding} encoding
|
|
19
|
+
* @param {import('node:stream').TransformCallback} done */
|
|
15
20
|
_transform(message, encoding, done) {
|
|
16
21
|
// A message (array of quads), or a quad of a message: `{ quad, messageIndex }`
|
|
17
22
|
if (Array.isArray(message)) this._writer.addMessage(message, done);
|
|
18
23
|
else this._writer.addQuad(message.quad, message.messageIndex, done);
|
|
19
24
|
}
|
|
20
25
|
|
|
26
|
+
/** @param {import('node:stream').TransformCallback} done */
|
|
21
27
|
_flush(done) {
|
|
22
28
|
this._writer.end(done);
|
|
23
29
|
}
|
package/src/MessageWriter.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { Writer } from 'n3';
|
|
10
10
|
import { resolveFormat, isMessagesVersion, baseVersion, isSupportedVersion } from './formats.js';
|
|
11
11
|
|
|
12
|
+
/** @extends {Writer<import('@rdfjs/types').BaseQuad>} */
|
|
12
13
|
class LogWriter extends Writer {
|
|
13
14
|
// ### `addDelimiter` ends the current message
|
|
14
15
|
addDelimiter() {
|
|
@@ -17,11 +18,22 @@ class LogWriter extends Writer {
|
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
/** @typedef {import('./internal.js').Done} Done */
|
|
22
|
+
/** @typedef {import('./internal.js').End} End */
|
|
23
|
+
/** @typedef {import('../common.js').MessageWriterOptions} Options */
|
|
24
|
+
/** @typedef {Pick<import('./internal.js').Output, 'write'> & Partial<Pick<import('./internal.js').Output, 'end'>>} Output */
|
|
20
25
|
export default class MessageWriter {
|
|
21
26
|
// `outputStream` is optional: without it, `end` returns the output as a string
|
|
27
|
+
/** @overload
|
|
28
|
+
* @param {Options} [options] */
|
|
29
|
+
/** @overload
|
|
30
|
+
* @param {Output | null} outputStream
|
|
31
|
+
* @param {Options} [options] */
|
|
32
|
+
/** @param {Output | Options | null} [outputStream]
|
|
33
|
+
* @param {Options} [options] */
|
|
22
34
|
constructor(outputStream, options) {
|
|
23
|
-
if (outputStream && typeof outputStream.write !== 'function')
|
|
24
|
-
options = outputStream, outputStream = null;
|
|
35
|
+
if (outputStream && typeof /** @type {Output} */ (outputStream).write !== 'function')
|
|
36
|
+
options = /** @type {Options} */ (outputStream), outputStream = null;
|
|
25
37
|
options = options || {};
|
|
26
38
|
this._format = resolveFormat(options.format);
|
|
27
39
|
this._options = options;
|
|
@@ -30,24 +42,35 @@ export default class MessageWriter {
|
|
|
30
42
|
throw new TypeError(`Unsupported RDF Messages version: "${this._version}"`);
|
|
31
43
|
|
|
32
44
|
this._output = '';
|
|
33
|
-
this._stream = outputStream;
|
|
45
|
+
this._stream = /** @type {Output | null | undefined} */ (outputStream);
|
|
34
46
|
this._endStream = !outputStream || options.end !== false;
|
|
35
47
|
this._pending = 0;
|
|
48
|
+
|
|
49
|
+
/** @type {Done[]} */
|
|
36
50
|
this._waiting = [];
|
|
51
|
+
|
|
52
|
+
/** @type {Error | null} */
|
|
37
53
|
this._error = null;
|
|
38
54
|
this._started = false;
|
|
39
55
|
this._ended = false;
|
|
40
56
|
this._open = false;
|
|
41
57
|
this._count = 0; // messages begun so far: the index of the next message
|
|
42
58
|
this._declared = Object.assign({}, options.prefixes);
|
|
59
|
+
|
|
60
|
+
/** @type {Record<string, string>} */
|
|
43
61
|
this._newPrefixes = {};
|
|
44
62
|
// The N3.js writer writes to this sink, which it never ends
|
|
63
|
+
/** @type {Pick<Output, 'write'>} */
|
|
45
64
|
this._sink = { write: (chunk, encoding, done) => this._write(chunk, done) };
|
|
65
|
+
|
|
66
|
+
/** @type {LogWriter | null} */
|
|
46
67
|
this._writer = null;
|
|
47
68
|
}
|
|
48
69
|
|
|
49
70
|
// ### `addMessage` writes the quads of one message, followed by a delimiter.
|
|
50
71
|
// The callback (or returned promise) completes once the output is flushed.
|
|
72
|
+
/** @param {import('@rdfjs/types').BaseQuad[]} quads
|
|
73
|
+
* @param {Done} [done] */
|
|
51
74
|
addMessage(quads, done) {
|
|
52
75
|
return this._complete(done, () => {
|
|
53
76
|
if (this._open) throw new Error('A message is already open');
|
|
@@ -58,6 +81,7 @@ export default class MessageWriter {
|
|
|
58
81
|
}
|
|
59
82
|
|
|
60
83
|
// ### `beginMessage` opens a message. It is optional: the first `addQuad` opens one implicitly.
|
|
84
|
+
/** @param {Done} [done] */
|
|
61
85
|
beginMessage(done) {
|
|
62
86
|
return this._complete(done, () => {
|
|
63
87
|
if (this._open) throw new Error('A message is already open');
|
|
@@ -68,6 +92,9 @@ export default class MessageWriter {
|
|
|
68
92
|
// ### `addQuad` writes a quad into the open message right away (a message is opened if there is none).
|
|
69
93
|
// With a `messageIndex` (0-based), a change of index ends the current message and writes the empty
|
|
70
94
|
// messages in between, as rdf-writer-ts does; a lower index than the current one is an error.
|
|
95
|
+
/** @param {import('@rdfjs/types').BaseQuad} quad
|
|
96
|
+
* @param {number | Done} [messageIndex]
|
|
97
|
+
* @param {Done} [done] */
|
|
71
98
|
addQuad(quad, messageIndex, done) {
|
|
72
99
|
if (typeof messageIndex === 'function') done = messageIndex, messageIndex = undefined;
|
|
73
100
|
return this._complete(done, () => {
|
|
@@ -78,6 +105,7 @@ export default class MessageWriter {
|
|
|
78
105
|
}
|
|
79
106
|
|
|
80
107
|
// ### `endMessage` ends the open message with a delimiter. Without an open message, it writes an empty one.
|
|
108
|
+
/** @param {Done} [done] */
|
|
81
109
|
endMessage(done) {
|
|
82
110
|
return this._complete(done, () => {
|
|
83
111
|
if (!this._open) this._begin();
|
|
@@ -87,15 +115,25 @@ export default class MessageWriter {
|
|
|
87
115
|
|
|
88
116
|
// ### `addPrefix` declares a prefix, which is written before the next message
|
|
89
117
|
// and stays in effect for all messages after it
|
|
118
|
+
/** @param {string} prefix
|
|
119
|
+
* @param {string | import('@rdfjs/types').NamedNode} iri
|
|
120
|
+
* @param {Done} [done] */
|
|
90
121
|
addPrefix(prefix, iri, done) {
|
|
91
122
|
return this._complete(done, () => {
|
|
92
|
-
if (!this._format.lineMode && this._declared[prefix] !== (iri
|
|
93
|
-
this._newPrefixes[prefix] = iri
|
|
123
|
+
if (!this._format.lineMode && this._declared[prefix] !== (typeof iri === 'string' ? iri : iri.value))
|
|
124
|
+
this._newPrefixes[prefix] = typeof iri === 'string' ? iri : iri.value;
|
|
94
125
|
});
|
|
95
126
|
}
|
|
96
127
|
|
|
97
128
|
// ### `end` ends the log and, unless disabled, the output stream.
|
|
98
129
|
// The callback receives the output as a string if no output stream was given.
|
|
130
|
+
/** @overload
|
|
131
|
+
* @returns {Promise<string | void>} */
|
|
132
|
+
/** @overload
|
|
133
|
+
* @param {End} done
|
|
134
|
+
* @returns {void} */
|
|
135
|
+
/** @param {End} [done]
|
|
136
|
+
* @returns {Promise<string | void> | void} */
|
|
99
137
|
end(done) {
|
|
100
138
|
// Ending twice is harmless
|
|
101
139
|
if (this._ended)
|
|
@@ -111,6 +149,9 @@ export default class MessageWriter {
|
|
|
111
149
|
// ## Private methods
|
|
112
150
|
|
|
113
151
|
// Runs an action and reports completion with a callback or a promise
|
|
152
|
+
/** @param {Done | undefined} done
|
|
153
|
+
* @param {() => void} action
|
|
154
|
+
* @returns {Promise<void> | void} */
|
|
114
155
|
_complete(done, action) {
|
|
115
156
|
if (this._ended)
|
|
116
157
|
this._error = this._error || new Error('Cannot write after end');
|
|
@@ -119,7 +160,7 @@ export default class MessageWriter {
|
|
|
119
160
|
action();
|
|
120
161
|
}
|
|
121
162
|
catch (error) {
|
|
122
|
-
this._error = this._error || error;
|
|
163
|
+
this._error = this._error || /** @type {Error} */ (error);
|
|
123
164
|
}
|
|
124
165
|
if (typeof done === 'function')
|
|
125
166
|
return this._whenFlushed(done);
|
|
@@ -127,25 +168,29 @@ export default class MessageWriter {
|
|
|
127
168
|
}
|
|
128
169
|
|
|
129
170
|
// Ends the output once all queued writes were accepted
|
|
171
|
+
/** @param {End} callback */
|
|
130
172
|
_finish(callback) {
|
|
131
173
|
this._ended = true;
|
|
132
174
|
this._whenFlushed(error => this._endOutput(error, callback));
|
|
133
175
|
}
|
|
134
176
|
|
|
177
|
+
/** @param {Error | null | undefined} error
|
|
178
|
+
* @param {End} callback */
|
|
135
179
|
_endOutput(error, callback) {
|
|
136
180
|
if (error || !this._endStream)
|
|
137
181
|
return callback(error);
|
|
138
182
|
if (!this._stream)
|
|
139
183
|
return callback(null, this._output);
|
|
140
184
|
try {
|
|
141
|
-
this._stream.end(endError => callback(endError));
|
|
185
|
+
(/** @type {Required<Output>} */ (this._stream)).end(endError => callback(endError));
|
|
142
186
|
}
|
|
143
187
|
catch (endError) {
|
|
144
|
-
callback(endError);
|
|
188
|
+
callback(/** @type {Error} */ (endError));
|
|
145
189
|
}
|
|
146
190
|
}
|
|
147
191
|
|
|
148
192
|
// Calls back once all queued writes were accepted by the output
|
|
193
|
+
/** @param {Done} done */
|
|
149
194
|
_whenFlushed(done) {
|
|
150
195
|
if (this._pending === 0)
|
|
151
196
|
this._settle(done);
|
|
@@ -153,12 +198,15 @@ export default class MessageWriter {
|
|
|
153
198
|
this._waiting.push(done);
|
|
154
199
|
}
|
|
155
200
|
|
|
201
|
+
/** @param {Done} done */
|
|
156
202
|
_settle(done) {
|
|
157
203
|
const error = this._error;
|
|
158
204
|
this._error = null;
|
|
159
205
|
done(error || null);
|
|
160
206
|
}
|
|
161
207
|
|
|
208
|
+
/** @param {string} chunk
|
|
209
|
+
* @param {Done} [done] */
|
|
162
210
|
_write(chunk, done) {
|
|
163
211
|
if (!this._stream) {
|
|
164
212
|
this._output += chunk;
|
|
@@ -169,11 +217,13 @@ export default class MessageWriter {
|
|
|
169
217
|
this._stream.write(chunk, 'utf8', error => this._written(error, done));
|
|
170
218
|
}
|
|
171
219
|
catch (error) {
|
|
172
|
-
this._written(error, done);
|
|
220
|
+
this._written(/** @type {Error} */ (error), done);
|
|
173
221
|
}
|
|
174
222
|
}
|
|
175
223
|
|
|
176
224
|
// Registers that the output accepted a write, and wakes up those waiting for the output
|
|
225
|
+
/** @param {Error | null | undefined} error
|
|
226
|
+
* @param {Done} [done] */
|
|
177
227
|
_written(error, done) {
|
|
178
228
|
this._pending--;
|
|
179
229
|
this._error = this._error || error || null;
|
|
@@ -202,25 +252,30 @@ export default class MessageWriter {
|
|
|
202
252
|
// Opens a message: declares the prefixes that were added since the previous one
|
|
203
253
|
_begin() {
|
|
204
254
|
this._start();
|
|
205
|
-
const writer = this._writer;
|
|
255
|
+
const writer = /** @type {LogWriter} */ (this._writer);
|
|
206
256
|
for (const prefix of Object.keys(this._newPrefixes)) {
|
|
207
|
-
|
|
208
|
-
|
|
257
|
+
const iri = /** @type {string} */ (this._newPrefixes[prefix]);
|
|
258
|
+
writer.addPrefix(prefix, iri);
|
|
259
|
+
this._declared[prefix] = iri;
|
|
209
260
|
}
|
|
210
261
|
this._newPrefixes = {};
|
|
211
262
|
this._open = true;
|
|
212
263
|
this._count++;
|
|
213
264
|
}
|
|
265
|
+
|
|
266
|
+
/** @param {import('@rdfjs/types').BaseQuad} quad */
|
|
214
267
|
_add(quad) {
|
|
268
|
+
/** @type {Done} */
|
|
215
269
|
const record = error => error && (this._error = this._error || error);
|
|
216
|
-
this._writer.addQuad(quad.subject, quad.predicate, quad.object, quad.graph, record);
|
|
270
|
+
(/** @type {LogWriter} */ (this._writer)).addQuad(quad.subject, quad.predicate, quad.object, quad.graph, record);
|
|
217
271
|
}
|
|
218
272
|
_close() {
|
|
219
273
|
this._open = false;
|
|
220
|
-
this._writer.addDelimiter();
|
|
274
|
+
(/** @type {LogWriter} */ (this._writer)).addDelimiter();
|
|
221
275
|
}
|
|
222
276
|
|
|
223
277
|
// Makes message `index` the open message, writing empty messages for the gap
|
|
278
|
+
/** @param {number} index */
|
|
224
279
|
_moveTo(index) {
|
|
225
280
|
const current = this._count - 1;
|
|
226
281
|
if (!Number.isInteger(index) || index < 0) throw new RangeError(`Invalid message index: ${index}`);
|
|
@@ -235,18 +290,25 @@ export default class MessageWriter {
|
|
|
235
290
|
}
|
|
236
291
|
}
|
|
237
292
|
|
|
293
|
+
/** @param {Iterable<import('@rdfjs/types').BaseQuad[]>} messages
|
|
294
|
+
* @param {Options} [options]
|
|
295
|
+
* @returns {string} */
|
|
238
296
|
// ### `writeMessages` synchronously serializes an array of messages to a string
|
|
239
297
|
export function writeMessages(messages, options) {
|
|
240
298
|
const writer = new MessageWriter(options);
|
|
299
|
+
|
|
300
|
+
/** @type {Error[]} */
|
|
241
301
|
const errors = [];
|
|
242
|
-
let output;
|
|
302
|
+
let output = '';
|
|
303
|
+
|
|
304
|
+
/** @param {Error | null | undefined} error */
|
|
243
305
|
function collect(error) {
|
|
244
306
|
if (error)
|
|
245
307
|
errors.push(error);
|
|
246
308
|
}
|
|
247
309
|
for (const message of messages)
|
|
248
310
|
writer.addMessage(message, collect);
|
|
249
|
-
writer.end((error, result) => (collect(error), output = result));
|
|
311
|
+
writer.end((error, result) => (collect(error), output = /** @type {string} */ (result)));
|
|
250
312
|
if (errors.length > 0)
|
|
251
313
|
throw errors[0];
|
|
252
314
|
return output;
|
package/src/browser.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import MessageParserCore from './MessageParserCore.js';
|
|
2
|
+
import MessageWriter from './MessageWriter.js';
|
|
3
|
+
|
|
4
|
+
export { default as MessageParser, parseMessages } from './MessageParser.js';
|
|
5
|
+
export { default as MessageWriter, writeMessages } from './MessageWriter.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A Web Streams parser; strings and UTF-8 bytes can be piped directly from fetch.
|
|
9
|
+
* @template {import('@rdfjs/types').BaseQuad} [Q=import('@rdfjs/types').Quad]
|
|
10
|
+
* @extends {TransformStream<import('../browser.js').MessageChunk, Q[]>}
|
|
11
|
+
*/
|
|
12
|
+
export class MessageStreamParser extends TransformStream {
|
|
13
|
+
/** @param {import('../browser.js').BrowserParserOptions<Q>} [options] */
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
const decoder = new TextDecoder();
|
|
16
|
+
|
|
17
|
+
/** @type {MessageParserCore<Q>} */
|
|
18
|
+
let core;
|
|
19
|
+
super({
|
|
20
|
+
start(controller) {
|
|
21
|
+
core = new MessageParserCore(options, {
|
|
22
|
+
message: quads => controller.enqueue(quads),
|
|
23
|
+
end: () => {},
|
|
24
|
+
error: error => { throw error; },
|
|
25
|
+
prefix: options.onPrefix,
|
|
26
|
+
version: options.onVersion,
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
transform(chunk) {
|
|
30
|
+
core.write(typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true }));
|
|
31
|
+
},
|
|
32
|
+
flush() {
|
|
33
|
+
core.write(decoder.decode());
|
|
34
|
+
core.end();
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A Web Streams writer; accepts messages or individual indexed quads.
|
|
41
|
+
* @extends {TransformStream<import('../browser.js').MessageWriterInput, string>} */
|
|
42
|
+
export class MessageStreamWriter extends TransformStream {
|
|
43
|
+
/** @param {import('../common.js').MessageWriterOptions} [options] */
|
|
44
|
+
constructor(options) {
|
|
45
|
+
/** @type {MessageWriter} */
|
|
46
|
+
let writer;
|
|
47
|
+
super({
|
|
48
|
+
start(controller) {
|
|
49
|
+
/** @type {Pick<import('./internal.js').Output, 'write'>} */
|
|
50
|
+
const output = { write: (chunk, encoding, done) => { controller.enqueue(chunk); done(); } };
|
|
51
|
+
writer = new MessageWriter(output, { ...options, end: false });
|
|
52
|
+
},
|
|
53
|
+
transform(message) {
|
|
54
|
+
return Array.isArray(message) ? writer.addMessage(message) : writer.addQuad(message.quad, message.messageIndex);
|
|
55
|
+
},
|
|
56
|
+
flush() {
|
|
57
|
+
return writer.end().then(() => {});
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/formats.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// **formats** describes the RDF syntaxes that support RDF Messages.
|
|
2
2
|
|
|
3
|
+
/** @type {Record<string, { name: string; lineMode: boolean }>} */
|
|
3
4
|
const FORMATS = {
|
|
4
5
|
turtle: { name: 'Turtle', lineMode: false },
|
|
5
6
|
trig: { name: 'TriG', lineMode: false },
|
|
@@ -12,7 +13,7 @@ const BASE_VERSIONS = ['1.2', '1.2-basic', '1.1'];
|
|
|
12
13
|
|
|
13
14
|
// ### `resolveFormat` maps a format name or media type to a supported syntax
|
|
14
15
|
export function resolveFormat(format = 'TriG') {
|
|
15
|
-
const key = String(format).toLowerCase().
|
|
16
|
+
const key = String(format).toLowerCase().replace(/;[\s\S]*$/, '').trim()
|
|
16
17
|
.replace(/^(?:text|application)\//, '')
|
|
17
18
|
.replace(/^x-/, '')
|
|
18
19
|
.replace(/[-_ ]/g, '');
|
|
@@ -23,16 +24,19 @@ export function resolveFormat(format = 'TriG') {
|
|
|
23
24
|
return resolved;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
/** @param {unknown} version */
|
|
26
28
|
// ### `isMessagesVersion` checks whether a version label announces RDF Messages
|
|
27
29
|
export function isMessagesVersion(version) {
|
|
28
30
|
return typeof version === 'string' && version.endsWith('-messages');
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
/** @param {string} version */
|
|
31
34
|
// ### `baseVersion` strips the `-messages` suffix of a version label
|
|
32
35
|
export function baseVersion(version) {
|
|
33
36
|
return isMessagesVersion(version) ? version.slice(0, -'-messages'.length) : version;
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
/** @param {string} version */
|
|
36
40
|
// ### `isSupportedVersion` checks whether a (base) version label is supported
|
|
37
41
|
export function isSupportedVersion(version, supported = BASE_VERSIONS) {
|
|
38
42
|
return supported.includes(version);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type * as RDF from '@rdfjs/types';
|
|
2
|
+
export interface ParserSink<Q extends RDF.BaseQuad> {
|
|
3
|
+
message(quads: Q[], index: number): unknown;
|
|
4
|
+
end(prefixes?: Record<string, RDF.NamedNode>): unknown;
|
|
5
|
+
error(error: Error): unknown;
|
|
6
|
+
quad?(quad: Q, index: number): unknown;
|
|
7
|
+
prefix?(prefix: string, term: RDF.NamedNode): unknown;
|
|
8
|
+
version?(label: string): unknown;
|
|
9
|
+
}
|
|
10
|
+
export type Done = (error?: Error | null) => void;
|
|
11
|
+
export type End = (error?: Error | null, result?: string) => void;
|
|
12
|
+
export interface Output {
|
|
13
|
+
write(chunk: string, encoding: string, done: Done): unknown;
|
|
14
|
+
end(done: Done): unknown;
|
|
15
|
+
}
|