n3.js-messages 0.1.2 → 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.
@@ -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
- this._factory = options.factory || DataFactory;
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' ? this._scopeQuad(term) : term;
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();
@@ -1,21 +1,29 @@
1
1
  // **MessageStreamWriter** is a Node.js stream that serializes RDF Messages.
2
2
  //
3
- // It consumes messages (arrays of quads) and produces text.
3
+ // It consumes messages (arrays of quads), or `{ quad, messageIndex }` objects, and produces text.
4
4
  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
- this._writer.addMessage(message, done);
21
+ // A message (array of quads), or a quad of a message: `{ quad, messageIndex }`
22
+ if (Array.isArray(message)) this._writer.addMessage(message, done);
23
+ else this._writer.addQuad(message.quad, message.messageIndex, done);
17
24
  }
18
25
 
26
+ /** @param {import('node:stream').TransformCallback} done */
19
27
  _flush(done) {
20
28
  this._writer.end(done);
21
29
  }
@@ -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,45 +42,105 @@ 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;
56
+ this._open = false;
57
+ this._count = 0; // messages begun so far: the index of the next message
40
58
  this._declared = Object.assign({}, options.prefixes);
59
+
60
+ /** @type {Record<string, string>} */
41
61
  this._newPrefixes = {};
42
62
  // The N3.js writer writes to this sink, which it never ends
63
+ /** @type {Pick<Output, 'write'>} */
43
64
  this._sink = { write: (chunk, encoding, done) => this._write(chunk, done) };
65
+
66
+ /** @type {LogWriter | null} */
44
67
  this._writer = null;
45
68
  }
46
69
 
47
70
  // ### `addMessage` writes the quads of one message, followed by a delimiter.
48
71
  // The callback (or returned promise) completes once the output is flushed.
72
+ /** @param {import('@rdfjs/types').BaseQuad[]} quads
73
+ * @param {Done} [done] */
49
74
  addMessage(quads, done) {
50
75
  return this._complete(done, () => {
51
- this._start();
52
- this._writeMessage(quads || []);
76
+ if (this._open) throw new Error('A message is already open');
77
+ this._begin();
78
+ for (const quad of quads || []) this._add(quad);
79
+ this._close();
80
+ });
81
+ }
82
+
83
+ // ### `beginMessage` opens a message. It is optional: the first `addQuad` opens one implicitly.
84
+ /** @param {Done} [done] */
85
+ beginMessage(done) {
86
+ return this._complete(done, () => {
87
+ if (this._open) throw new Error('A message is already open');
88
+ this._begin();
89
+ });
90
+ }
91
+
92
+ // ### `addQuad` writes a quad into the open message right away (a message is opened if there is none).
93
+ // With a `messageIndex` (0-based), a change of index ends the current message and writes the empty
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] */
98
+ addQuad(quad, messageIndex, done) {
99
+ if (typeof messageIndex === 'function') done = messageIndex, messageIndex = undefined;
100
+ return this._complete(done, () => {
101
+ if (messageIndex !== undefined && messageIndex !== null) this._moveTo(messageIndex);
102
+ else if (!this._open) this._begin();
103
+ this._add(quad);
104
+ });
105
+ }
106
+
107
+ // ### `endMessage` ends the open message with a delimiter. Without an open message, it writes an empty one.
108
+ /** @param {Done} [done] */
109
+ endMessage(done) {
110
+ return this._complete(done, () => {
111
+ if (!this._open) this._begin();
112
+ this._close();
53
113
  });
54
114
  }
55
115
 
56
116
  // ### `addPrefix` declares a prefix, which is written before the next message
57
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] */
58
121
  addPrefix(prefix, iri, done) {
59
122
  return this._complete(done, () => {
60
- if (!this._format.lineMode && this._declared[prefix] !== (iri.value || iri))
61
- this._newPrefixes[prefix] = iri.value || 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;
62
125
  });
63
126
  }
64
127
 
65
128
  // ### `end` ends the log and, unless disabled, the output stream.
66
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} */
67
137
  end(done) {
68
138
  // Ending twice is harmless
69
139
  if (this._ended)
70
140
  return typeof done === 'function' ? done(null) : Promise.resolve();
71
141
  this._start();
142
+ if (this._open)
143
+ this._close();
72
144
  if (typeof done === 'function')
73
145
  return this._finish(done);
74
146
  return new Promise((resolve, reject) => this._finish((error, result) => error ? reject(error) : resolve(result)));
@@ -77,6 +149,9 @@ export default class MessageWriter {
77
149
  // ## Private methods
78
150
 
79
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} */
80
155
  _complete(done, action) {
81
156
  if (this._ended)
82
157
  this._error = this._error || new Error('Cannot write after end');
@@ -85,7 +160,7 @@ export default class MessageWriter {
85
160
  action();
86
161
  }
87
162
  catch (error) {
88
- this._error = this._error || error;
163
+ this._error = this._error || /** @type {Error} */ (error);
89
164
  }
90
165
  if (typeof done === 'function')
91
166
  return this._whenFlushed(done);
@@ -93,25 +168,29 @@ export default class MessageWriter {
93
168
  }
94
169
 
95
170
  // Ends the output once all queued writes were accepted
171
+ /** @param {End} callback */
96
172
  _finish(callback) {
97
173
  this._ended = true;
98
174
  this._whenFlushed(error => this._endOutput(error, callback));
99
175
  }
100
176
 
177
+ /** @param {Error | null | undefined} error
178
+ * @param {End} callback */
101
179
  _endOutput(error, callback) {
102
180
  if (error || !this._endStream)
103
181
  return callback(error);
104
182
  if (!this._stream)
105
183
  return callback(null, this._output);
106
184
  try {
107
- this._stream.end(endError => callback(endError));
185
+ (/** @type {Required<Output>} */ (this._stream)).end(endError => callback(endError));
108
186
  }
109
187
  catch (endError) {
110
- callback(endError);
188
+ callback(/** @type {Error} */ (endError));
111
189
  }
112
190
  }
113
191
 
114
192
  // Calls back once all queued writes were accepted by the output
193
+ /** @param {Done} done */
115
194
  _whenFlushed(done) {
116
195
  if (this._pending === 0)
117
196
  this._settle(done);
@@ -119,12 +198,15 @@ export default class MessageWriter {
119
198
  this._waiting.push(done);
120
199
  }
121
200
 
201
+ /** @param {Done} done */
122
202
  _settle(done) {
123
203
  const error = this._error;
124
204
  this._error = null;
125
205
  done(error || null);
126
206
  }
127
207
 
208
+ /** @param {string} chunk
209
+ * @param {Done} [done] */
128
210
  _write(chunk, done) {
129
211
  if (!this._stream) {
130
212
  this._output += chunk;
@@ -135,11 +217,13 @@ export default class MessageWriter {
135
217
  this._stream.write(chunk, 'utf8', error => this._written(error, done));
136
218
  }
137
219
  catch (error) {
138
- this._written(error, done);
220
+ this._written(/** @type {Error} */ (error), done);
139
221
  }
140
222
  }
141
223
 
142
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] */
143
227
  _written(error, done) {
144
228
  this._pending--;
145
229
  this._error = this._error || error || null;
@@ -165,32 +249,66 @@ export default class MessageWriter {
165
249
  }
166
250
  }
167
251
 
168
- _writeMessage(quads) {
169
- const writer = this._writer;
252
+ // Opens a message: declares the prefixes that were added since the previous one
253
+ _begin() {
254
+ this._start();
255
+ const writer = /** @type {LogWriter} */ (this._writer);
170
256
  for (const prefix of Object.keys(this._newPrefixes)) {
171
- writer.addPrefix(prefix, this._newPrefixes[prefix]);
172
- this._declared[prefix] = this._newPrefixes[prefix];
257
+ const iri = /** @type {string} */ (this._newPrefixes[prefix]);
258
+ writer.addPrefix(prefix, iri);
259
+ this._declared[prefix] = iri;
173
260
  }
174
261
  this._newPrefixes = {};
262
+ this._open = true;
263
+ this._count++;
264
+ }
265
+
266
+ /** @param {import('@rdfjs/types').BaseQuad} quad */
267
+ _add(quad) {
268
+ /** @type {Done} */
175
269
  const record = error => error && (this._error = this._error || error);
176
- for (const quad of quads)
177
- writer.addQuad(quad.subject, quad.predicate, quad.object, quad.graph, record);
178
- writer.addDelimiter();
270
+ (/** @type {LogWriter} */ (this._writer)).addQuad(quad.subject, quad.predicate, quad.object, quad.graph, record);
271
+ }
272
+ _close() {
273
+ this._open = false;
274
+ (/** @type {LogWriter} */ (this._writer)).addDelimiter();
275
+ }
276
+
277
+ // Makes message `index` the open message, writing empty messages for the gap
278
+ /** @param {number} index */
279
+ _moveTo(index) {
280
+ const current = this._count - 1;
281
+ if (!Number.isInteger(index) || index < 0) throw new RangeError(`Invalid message index: ${index}`);
282
+ if (this._open && index === current) return;
283
+ if (index <= current) throw new RangeError(`Message index ${index} is lower than the current message index ${current}`);
284
+ if (this._open) this._close();
285
+ while (this._count < index) {
286
+ this._begin();
287
+ this._close();
288
+ }
289
+ this._begin();
179
290
  }
180
291
  }
181
292
 
293
+ /** @param {Iterable<import('@rdfjs/types').BaseQuad[]>} messages
294
+ * @param {Options} [options]
295
+ * @returns {string} */
182
296
  // ### `writeMessages` synchronously serializes an array of messages to a string
183
297
  export function writeMessages(messages, options) {
184
298
  const writer = new MessageWriter(options);
299
+
300
+ /** @type {Error[]} */
185
301
  const errors = [];
186
- let output;
302
+ let output = '';
303
+
304
+ /** @param {Error | null | undefined} error */
187
305
  function collect(error) {
188
306
  if (error)
189
307
  errors.push(error);
190
308
  }
191
309
  for (const message of messages)
192
310
  writer.addMessage(message, collect);
193
- writer.end((error, result) => (collect(error), output = result));
311
+ writer.end((error, result) => (collect(error), output = /** @type {string} */ (result)));
194
312
  if (errors.length > 0)
195
313
  throw errors[0];
196
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().split(';')[0].trim()
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
+ }