rdf-writer-ts
v0.1.1
Published
Fast RDF-JS writer for Turtle, TriG, N-Triples, N-Quads, and RDF Messages for NodeJS.
Maintainers
Readme
rdf-writer.ts
Fast RDF-JS writer for Turtle, TriG, N-Triples, N-Quads, RDF 1.2 triple terms, and RDF Message Logs in Node.js.
This package contains the writer that used to live next to rdf-parser.ts. It is now published separately so parser and writer users can depend on only the package they need.
[!NOTE] This package follows familiar RDF-JS and N3.js-style APIs while keeping the implementation compact. It would not have been possible without the RDF tooling ecosystem, especially Blake Regalia’s work on Graphy and Ruben Verborgh’s work on N3.js.
Supported output
- Turtle-style compact output with subject/predicate grouping.
- TriG graph blocks for named graphs.
- N-Triples and N-Quads line formats through the
formatoption. - Prefix declarations, prefixed names,
aforrdf:type, and base IRI shortening in pretty formats. - RDF-JS named nodes, blank nodes, literals, variables, default graphs, and quads.
- Language and direction literals, explicit datatype literals, numeric/boolean abbreviations, escaped IRIs/literals, blank-node property lists, RDF lists, and RDF 1.2 triple terms.
- RDF Message Logs through
VERSION "...-messages",MESSAGE,@version, and@message .delimiters. - String output, supplied output streams, and Node.js object-mode transform streams.
Install
npm install rdf-writer-tsNode.js 22 or newer is required.
For local development:
npm install
npm run build
npm testBuild and validation scripts
npm run build # Build CommonJS, ESM, and declaration files
npm run lint # Type-check with tsc --noEmit
npm test # Run unit tests
npm run check # Type-check, build, then testThe writer currently ships unit tests for serialization behavior. Unlike the parser package, this repository does not yet ship an official RDF Working Group compliance badge or a dedicated N3.js/Graphy writer benchmark. Parser compliance and parser performance comparisons live in rdf-parser.ts.
Basic usage
import { DataFactory, Writer } from 'rdf-writer.ts';
const { namedNode, literal, quad } = DataFactory;
const writer = new Writer({ prefixes: { ex: 'https://example.org/' } });
writer.addQuad(quad(
namedNode('https://example.org/s'),
namedNode('https://example.org/p'),
literal('object'),
));
writer.end((error, output) => {
if (error) throw error;
console.log(output);
// @prefix ex: <https://example.org/>.
//
// ex:s ex:p "object".
});Writer writes Turtle/TriG-style output by default. Use format: 'N-Triples' or format: 'N-Quads' for line-based output.
const writer = new Writer({ format: 'N-Quads' });
writer.addQuad(quad(
namedNode('https://example.org/s'),
namedNode('https://example.org/p'),
literal('42', namedNode('http://www.w3.org/2001/XMLSchema#integer')),
namedNode('https://example.org/g'),
));
writer.end((error, output) => {
if (error) throw error;
console.log(output);
// <https://example.org/s> <https://example.org/p> "42"^^<http://www.w3.org/2001/XMLSchema#integer> <https://example.org/g> .
});Writer API
new Writer(options) / new Writer(outputStream, options)
Construct a writer that either collects output into a final string or writes chunks to a supplied stream-like object.
Options:
format: output format hint. N-Triples/N-Quads values enable one-statement-per-line mode; other values use Turtle/TriG-style pretty output.prefixes: map from prefix labels to IRI strings or named nodes. Prefixes are emitted in pretty formats and ignored in line formats.baseIRI: base IRI used to shorten matching named nodes in pretty formats.end: when an output stream is supplied, controls whetherWriter#end()calls the stream'send()method. Defaults totrue.lists: map blank node labels to RDF list contents for pretty-printing list heads.rdfMessages/messages: force RDF Messages output.version: set the RDF version label; messages versions such as1.2-messagesenable RDF Messages output.
quadToString(subject, predicate, object, graph?)
Serializes a single quad in line syntax and returns the string. This is useful for diagnostics and simple line-format output.
const line = new Writer().quadToString(namedNode('s'), namedNode('p'), namedNode('o'));
console.log(line); // <s> <p> <o> .quadsToString(quads)
Serializes an iterable of RDF-JS quads in line syntax and returns one concatenated string.
addQuad() and addQuads()
addQuad() accepts either a full RDF-JS quad, separated subject, predicate, object, optional graph terms, or an RDF Messages entry of the shape { quad, messageCounter }. addQuads() iterates over an iterable of quads or message entries.
writer.addQuad(quad(namedNode('s'), namedNode('p'), namedNode('o')));
writer.addQuad(namedNode('s'), namedNode('p'), literal('second'));
writer.addQuads([quad(namedNode('s'), namedNode('p'), namedNode('third'))]);addPrefix() and addPrefixes()
Adds prefix declarations in pretty formats. If writing has already started, the current statement is closed before the prefix declaration is emitted.
writer.addPrefix('ex', 'https://example.org/');
writer.addPrefixes({ foaf: 'http://xmlns.com/foaf/0.1/' });blank() and list()
Creates serialized terms for Turtle blank-node property lists and RDF lists.
const knows = namedNode('https://example.org/knows');
const alice = namedNode('https://example.org/alice');
writer.addQuad(namedNode('https://example.org/me'), knows, writer.blank(knows, alice));
writer.addQuad(namedNode('https://example.org/me'), namedNode('https://example.org/items'), writer.list([alice, literal('label')]));blank() accepts no arguments for [], a single { predicate, object } child, an array of children, or shorthand blank(predicate, object).
addMessage()
Writes one RDF Message from an iterable of quads. Empty iterables preserve empty messages.
const writer = new Writer({ prefixes: { ex: 'http://example.org/' }, version: '1.2-messages' });
writer.addMessage([
quad(namedNode('http://example.org/s1'), namedNode('http://example.org/p'), namedNode('http://example.org/o1')),
]);
writer.addMessage([]);
writer.addMessage([
quad(namedNode('http://example.org/s2'), namedNode('http://example.org/p'), namedNode('http://example.org/o2')),
]);
writer.end((error, serialized) => {
if (error) throw error;
console.log(serialized);
});end(callback)
Closes any open statement and finalizes the writer. When no output stream is supplied, the callback receives the complete serialized string as its second argument. Writing after end() reports an error through the write callback.
Output streams
Pass a stream-like object with write() and end() methods when you want chunks as they are produced.
let output = '';
const writer = new Writer({
write: (chunk, _encoding, callback) => {
output += chunk;
callback?.(null);
},
end: callback => callback?.(null),
}, { end: false });
writer.addQuad(quad(namedNode('s'), namedNode('p'), namedNode('o')));
writer.end(error => {
if (error) throw error;
console.log(output);
});StreamWriter
StreamWriter is a Node.js Transform stream in object mode. It accepts RDF-JS quads or { quad, messageCounter } entries and emits serialized text chunks.
import { Readable } from 'node:stream';
import { DataFactory, StreamWriter } from 'rdf-writer.ts';
const { namedNode, quad } = DataFactory;
Readable.from([
quad(namedNode('a'), namedNode('b'), namedNode('c')),
quad(namedNode('a'), namedNode('b'), namedNode('d')),
], { objectMode: true })
.pipe(new StreamWriter())
.pipe(process.stdout);The import() convenience method mirrors N3.js and forwards prefix events from an input stream:
const writer = new StreamWriter({ format: 'N-Quads' });
writer.import(quadStream).pipe(process.stdout);RDF Messages
RDF Messages can be written either by adding message entries directly or by calling addMessage().
import { DataFactory, Writer, type MessageQuad } from 'rdf-writer.ts';
const { namedNode, quad } = DataFactory;
const entries: MessageQuad[] = [
{ quad: quad(namedNode('http://example.org/s1'), namedNode('http://example.org/p'), namedNode('http://example.org/o1')), messageCounter: 0 },
{ quad: quad(namedNode('http://example.org/s2'), namedNode('http://example.org/p'), namedNode('http://example.org/o2')), messageCounter: 1 },
];
const writer = new Writer({ format: 'N-Quads' });
writer.addQuads(entries);
writer.end((error, serialized) => {
if (error) throw error;
console.log(serialized);
});Line formats write VERSION "1.2-messages" and MESSAGE. Turtle/TriG-style output writes @version "1.2-messages" . and @message .. Gaps in messageCounter values are preserved as empty messages.
RDF-JS data model and helpers
The package includes a small RDF-JS-compatible data model so it can be used without another data factory:
import { DataFactory } from 'rdf-writer.ts';
const s = DataFactory.namedNode('http://example.org/s');
const p = DataFactory.namedNode('http://example.org/p');
const o = DataFactory.literal('hello', 'en');
const q = DataFactory.quad(s, p, o);
console.log(q.termType); // Quad
console.log(q.object.termType); // Literal
console.log(q.equals(q)); // truePublic exports include:
Writer: string/stream writer withquadToString(),quadsToString(),addQuad(),addQuads(),addPrefix(),addPrefixes(),blank(),list(),addMessage(), andend().StreamWriter: Node.js object-mode transform stream for quad streams.DataFactory: RDF-JS factory fornamedNode(),blankNode(),literal(),variable(),defaultGraph(), andquad().NamedNode,BlankNode,Literal,Variable,DefaultGraph,Quad, andMessage: lightweight RDF-JS term classes.- Factory aliases:
namedNode,blankNode,literal,variable,defaultGraph, andquad. termToString(): serialize a term for diagnostics and IDs.quadToString(): serialize a quad in line syntax.termToId(): convert terms to stable string IDs.isMessageQuad(): type guard for{ quad, messageCounter }entries.toMessages(): group message entries intoMessage[].
Performance notes
The writer is designed for low overhead on common RDF-JS output paths: it groups Turtle/TriG statements incrementally, writes chunks directly to the target stream when provided, and avoids additional runtime dependencies beyond RDF-JS types.
Dedicated writer benchmarks against N3.js and Graphy are not yet part of this repository. The related parser package currently publishes this quick parsing snapshot against N3.js and Graphy, included here as ecosystem context rather than as writer throughput data:
| Statements | Parser | Time | Throughput | Input | RSS delta | | ---: | --- | ---: | ---: | ---: | ---: | | 1,000 | rdf-parser.ts | 0.001s | 773,045 q/s | 0.1 MiB | 0.5 MiB | | 1,000 | rdf-parser.ts/relax | 0.002s | 525,116 q/s | 0.1 MiB | 0.3 MiB | | 1,000 | N3.js | 0.006s | 166,228 q/s | 0.1 MiB | 1.9 MiB | | 1,000 | Graphy | 0.003s | 317,648 q/s | 0.1 MiB | 1.9 MiB | | 1,000 | Graphy/relax | 0.002s | 594,226 q/s | 0.1 MiB | -1.0 MiB | | 10,000 | rdf-parser.ts | 0.010s | 959,069 q/s | 0.9 MiB | 1.4 MiB | | 10,000 | rdf-parser.ts/relax | 0.010s | 982,829 q/s | 0.9 MiB | 4.7 MiB | | 10,000 | N3.js | 0.026s | 386,462 q/s | 0.9 MiB | 3.3 MiB | | 10,000 | Graphy | 0.011s | 881,601 q/s | 0.9 MiB | 7.5 MiB | | 10,000 | Graphy/relax | 0.005s | 1,857,713 q/s | 0.9 MiB | 2.4 MiB |
Run the parser benchmark report from the sibling parser repository with npm run perf, npm run perf:quick, or npm run perf:graphy.
License
© Ghent University - IMEC
MIT Licensed
