npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rdf-parser-ts

v0.3.1

Published

Fast RDF-JS parser for Turtle, TriG, N-Triples, N-Quads, and RDF Messages for NodeJS and browsers.

Readme

RDF Parser for TypeScript

W3C RDF1.2 spec compliance

Fast RDF/JS parsing for Turtle, TriG, N-Triples, N-Quads, RDF 1.2 triple terms, and RDF Message Logs in Node.js and browsers.

Try the interactive browser demo at https://www.pieter.pm/rdf-parser.ts/.

This implementation has been built with a clear scope in mind: RDF1.2 compliance for parsing these 4 formats with RDF Messages support using the RDF/JS data model. We will explicitly never support storing data or reasoning.

We have a sibling package for writing data called rdf-writer-ts.

[!NOTE] I built this as an agentic coding experiment for myself. I’m happy to see spec compliance, RDF Messages support, and a significant performance improvement over N3.js on the generated benchmarks, but integration tests with other software will need to show whether this work is as maintainable and useful as other libraries. This project would not have been possible without Blake Regalia’s work on Graphy and Ruben Verborgh’s work on N3.js.

Install

npm install rdf-parser-ts

Package layout

  • src/index.ts contains the RDF-JS data model, parser, stream parser, incremental parser, and parser helpers.
  • src/bin/rdf-parser.ts provides the rdf-parser-ts CLI.
  • test/ contains Vitest unit tests.
  • spec/ contains the rdf-test-suite adapter and EARL metadata, matching the N3.js spec-test setup.
  • perf/ contains synthetic performance benchmarks against N3.js and Graphy.
  • dist/ is generated by npm run build, including Node.js builds and browser bundles.

Build and validation scripts

npm run build          # Build CommonJS, ESM, declarations, CLI, and browser bundles
npm run build:browser  # Build dist/browser/index.mjs and dist/browser/index.global.js
npm run lint           # Type-check with tsc --noEmit
npm test               # Run unit tests
npm run check          # Type-check, build, then test
npm run ci             # Run check plus the performance regression warning check
npm run spec           # Run RDF 1.1 and RDF 1.2 spec suites
npm run perf           # Benchmark 10^4, 10^5, and 10^6 generated statements
npm run perf:quick     # Smaller benchmark for local iteration
npm run perf:regression # Compare current build to a git baseline and warn on >20% throughput drops
npm run perf:graphy    # Graphy-compatible benchmark without RDF 1.2 triple terms

The package exports CommonJS, ESM, and browser builds:

{
	"main": "./dist/index.js",
	"module": "./dist/index.mjs",
	"types": "./dist/index.d.ts",
	"browser": "./dist/browser/index.mjs"
}

Browser usage

With a browser-aware bundler, import the browser entry explicitly:

import { Parser, StreamParser, quadToString } from 'rdf-parser.ts/browser';

const quads = new Parser({ baseIRI: 'https://example.org/' }).parse('<s> <p> <o>.') ?? [];
console.log(quadToString(quads[0]!));

For direct browser usage through a CDN, use the ESM bundle:

<script type="module">
	import { Parser, quadToString } from 'https://cdn.jsdelivr.net/npm/rdf-parser.ts/dist/browser/index.mjs';

	const quads = new Parser({ baseIRI: 'https://example.org/' }).parse('<s> <p> <o>.') ?? [];
	console.log(quadToString(quads[0]));
</script>

Or use the global bundle, which exposes RDFParserTS:

<script src="https://unpkg.com/rdf-parser.ts/dist/browser/index.global.js"></script>
<script>
	const { Parser, quadToString } = RDFParserTS;
	const quads = new Parser({ baseIRI: 'https://example.org/' }).parse('<s> <p> <o>.') || [];
	console.log(quadToString(quads[0]));
</script>

For streaming in browsers, StreamParser works with Web Streams. It can be passed to pipeThrough() or used through its import() convenience method:

import { StreamParser, quadToString } from 'rdf-parser.ts/browser';

const parser = new StreamParser({ baseIRI: 'https://example.org/' });
const rdfStream = new Blob(['<s> <p>', ' <o>.']).stream();

for await (const quad of rdfStream.pipeThrough(parser)) {
	console.log(quadToString(quad));
}

Current minified browser bundle sizes after npm run build, measured with gzip -9 for the compressed column:

| Bundle | Minified | gzip compressed | | --- | ---: | ---: | | dist/browser/index.mjs | 31,617 bytes (30.9 KiB) | 8,267 bytes (8.1 KiB) | | dist/browser/index.global.js | 32,099 bytes (31.3 KiB) | 8,462 bytes (8.3 KiB) |

The example/ folder contains a browser-only playground. It parses RDF with this package, reports quads/messages per second, and currently uses N3 in the browser for optional serialization until the standalone rdf-writer.ts package is wired in.

Parsing strings

import { Parser, quadToString } from 'rdf-parser.ts';

const parser = new Parser({ baseIRI: 'http://example.org/' });
const quads = parser.parse(`
	@prefix ex: <http://example.com/>.
	ex:s ex:p "hello"@en;
			 ex:n 42;
			 a ex:Thing.
`);

for (const quad of quads ?? []) {
	console.log(quad.subject.termType, quad.predicate.value, quad.object.value);
	console.log(quadToString(quad));
}

Parser#parse() returns RDF-JS quads when no callback is provided. With a callback, it follows the N3.js-style callback flow and calls the callback once per quad, then once with quad === null and the prefix map.

const parser = new Parser();

parser.parse('<s> <p> <o>.', (error, quad, prefixes) => {
	if (error) throw error;
	if (quad) console.log(quad);
	else console.log('done', prefixes);
});

Parser options

  • baseIRI / baseIRIPath: resolve relative IRIs.
  • format: hint the input format, such as text/turtle, application/n-triples, application/n-quads, or application/trig.
  • factory: custom RDF-JS data factory.
  • comments: emit comment events in streaming mode.
  • relax: enable the faster relaxed line-format path for generated input.
  • rdfMessages / messages: force RDF Messages mode.
  • version: set the RDF version label; messages versions such as 1.2-messages enable RDF Messages mode.
  • parseUnsupportedVersions: accept unsupported version labels for compatibility testing.

RDF Messages

RDF Messages mode is enabled automatically when the input contains a messages version label, such as VERSION "1.2-messages" or @version "1.2-messages" .. It can also be enabled explicitly with rdfMessages: true or messages: true.

When RDF Messages mode is active, Parser#parse() returns entries that contain both the parsed quad and the message counter. Counters start at 0 and increase at each MESSAGE or @message . delimiter.

import { Parser, isMessageQuad, quadToString } from 'rdf-parser.ts';

const output = new Parser().parse(`
	VERSION "1.2-messages"
	<http://example.org/s1> <http://example.org/p> <http://example.org/o1> .
	MESSAGE
	<http://example.org/s2> <http://example.org/p> <http://example.org/o2> .
`);

for (const entry of output ?? []) {
	if (isMessageQuad(entry)) {
		console.log(entry.messageCounter, quadToString(entry.quad));
	}
}

The callback form still emits quads, with an additional optional message-counter argument when RDF Messages mode is active:

new Parser().parse(input, (error, quad, prefixes, messageCounter) => {
	if (error) throw error;
	if (quad) console.log(messageCounter, quadToString(quad));
});

Use toMessages() to group parser output into Message instances. Message extends Array and contains the quads belonging to one RDF Message. Empty messages are preserved when the input contains delimiters before the first quad or between two delimiters.

import { Parser, toMessages } from 'rdf-parser.ts';

const output = new Parser({ rdfMessages: true }).parse(`
	MESSAGE
	<http://example.org/s> <http://example.org/p> <http://example.org/o> .
`);

const messages = toMessages(output ?? []);
console.log(messages[0]?.length); // 0
console.log(messages[1]?.length); // 1

For direct message-level parsing, use parseMessages():

const messages = new Parser({ baseIRI: 'http://example.org/' }).parseMessages(`
	VERSION "1.2-messages"
	<s1> <p> <o1> .
	MESSAGE
	<s2> <p> <o2> .
`);

Blank node labels are scoped per message in RDF Messages mode, so the same blank node label in two messages produces distinct blank node terms.

Streaming and incremental parsing

StreamParser is a Node.js Transform stream in object mode. It accepts string or Buffer chunks and emits RDF-JS quads. In RDF Messages mode, it emits { quad, messageCounter } entries and a messageCounter event for each parsed quad.

import { createReadStream } from 'node:fs';
import { StreamParser } from 'rdf-parser.ts';

const parser = new StreamParser({
	baseIRI: 'http://example.org/',
	format: 'application/n-quads',
});

createReadStream('data.nq')
	.pipe(parser)
	.on('data', quad => {
		console.log(quad.subject.value, quad.predicate.value, quad.object.value);
	})
	.on('prefix', (prefix, iri) => {
		console.log('prefix', prefix, iri.value);
	})
	.on('comment', comment => {
		console.log('comment', comment);
	});

The import() convenience method mirrors N3.js:

const parser = new StreamParser();
parser.import(createReadStream('data.ttl')).on('data', quad => console.log(quad));

IncrementalParser exposes the same chunking logic without Node streams. Call write(chunk) for each partial input chunk and end(optionalFinalChunk) when the input is complete; both methods return any complete RDF-JS output items parsed from the accumulated text.

RDF-JS data model

The default DataFactory creates RDF-JS-compatible terms:

import { DataFactory } from 'rdf-parser.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));       // true

Public exports include:

  • Parser: string parser with parse() and parseMessages().
  • StreamParser: Node/browser stream parser with import().
  • IncrementalParser: chunked parser with write() and end().
  • DataFactory: RDF-JS factory for namedNode(), blankNode(), literal(), variable(), defaultGraph(), and quad().
  • NamedNode, BlankNode, Literal, Variable, DefaultGraph, Quad, and Message: lightweight RDF-JS term classes.
  • Factory aliases: namedNode, blankNode, literal, variable, defaultGraph, and quad.
  • termToString(): serialize a term for diagnostics and IDs.
  • quadToString(): serialize a quad in line syntax.
  • termToId() / termFromId(): convert terms to and from stable string IDs.
  • isMessageQuad(): type guard for { quad, messageCounter } entries.
  • toMessages(): group parser output into Message[].

Custom RDF-JS factories

Pass a custom factory to produce terms owned by another RDF-JS implementation, such as Comunica’s data factory.

import { StreamParser } from 'rdf-parser.ts';

const parser = new StreamParser({
	factory: dataFactory,
	baseIRI: action.metadata?.baseIRI,
	format: mediaType,
	parseUnsupportedVersions: true,
	version: action.metadata?.version,
});

This option shape matches the usage pattern in Comunica's ActorRdfParseN3: a consumer can replace import { StreamParser } from 'n3' with import { StreamParser } from 'rdf-parser.ts' for evaluation.

CLI

After building or installing the package, the rdf-parser-ts binary reads RDF from a file or stdin and writes N-Quads-style output.

rdf-parser-ts --base http://example.org/ data.ttl
cat data.nq | rdf-parser-ts --format application/n-quads

Options:

  • --format, -f: format hint, such as text/turtle or application/n-quads.
  • --base, -b: base IRI for relative IRIs.
  • --help, -h: print usage.

RDF Working Group test suites

The spec/ setup mirrors N3.js:

  • spec/parser.cjs implements the rdf-test-suite parser interface by piping streamify-string(data) into new StreamParser(...) and collecting with arrayify-stream.
  • spec/earl-meta.json contains metadata for EARL report generation.
  • .rdf-test-suite-cache/ is used for downloaded manifests.
  • Library-specific RDF Messages tests cover VERSION and @version, MESSAGE and @message ., message counters, empty messages, final delimiters, repeated prefixes, named graphs, blank-node scoping, and delimiter errors.

Run all configured compliance suites with:

npm run spec

Run focused suites when iterating:

npm run spec-1-1-ntriples
npm run spec-1-1-nquads
npm run spec-1-1-turtle
npm run spec-1-1-trig
npm run spec-1-2-ntriples
npm run spec-1-2-nquads
npm run spec-1-2-turtle
npm run spec-1-2-trig

Generate EARL reports with npm run spec-1-1-earl or npm run spec-1-2-earl, and use npm run spec-clean to remove the manifest cache.

The N-Triples and N-Quads RDF 1.1/RDF 1.2 scripts run without skips. The Turtle and TriG scripts run the same official manifests with explicit --skip patterns for currently unsupported edge cases such as full PN_CHARS Unicode coverage, escaped prefixed names, some IRI-resolution cases, RDF 1.2 annotation/reifier syntax, and Turtle/TriG version directives. This keeps npm run spec reproducible and green while making remaining conformance work visible in package.json.

Performance benchmarks

The benchmark generates synthetic RDF 1.2 N-Quads-like input with a mix of default-graph triples, named-graph quads, IRI objects, string literals, language-tagged literals, numeric/boolean literals, and triple terms as objects. Default sizes are 10,000, 100,000, and 1,000,000 statements.

npm run perf
npm run perf:quick
node perf/bench.js --sizes 10000,50000 --no-n3
node perf/bench.js --sizes 10000,50000 --no-triple-terms

Graphy 4.x's N-Quads reader does not parse RDF 1.2 triple terms, so the default triple-term benchmark prints a skipped Graphy row. Use --no-triple-terms or npm run perf:graphy for direct rdf-parser.ts, N3.js, Graphy, and Graphy relaxed-mode numbers on the same generated line-format input.

Quick benchmark snapshot

The following results were captured with Node.js v25.9.0 on Linux x64 using the quick benchmark commands. They are intended as a local performance snapshot, not as stable release guarantees; larger runs with npm run perf and node --expose-gc are more representative.

Default RDF 1.2 triple-term input, from npm run perf:quick:

| Statements | Parser | Time | Throughput | Input | RSS delta | | ---: | --- | ---: | ---: | ---: | ---: | | 1,000 | rdf-parser.ts | 0.002s | 439,540 q/s | 0.1 MiB | 2.1 MiB | | 1,000 | rdf-parser.ts/relax | 0.001s | 782,497 q/s | 0.1 MiB | 0.4 MiB | | 1,000 | N3.js | 0.008s | 127,099 q/s | 0.1 MiB | 1.9 MiB | | 10,000 | rdf-parser.ts | 0.023s | 435,162 q/s | 1.1 MiB | 5.6 MiB | | 10,000 | rdf-parser.ts/relax | 0.012s | 839,620 q/s | 1.1 MiB | 5.0 MiB | | 10,000 | N3.js | 0.047s | 214,567 q/s | 1.1 MiB | 6.6 MiB |

Line-format input without RDF 1.2 triple terms, from node perf/bench.js --sizes 1000,10000 --no-triple-terms:

| 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 |

On these generated inputs, the strict parser is ahead of N3.js, and relax: true improves the RDF 1.2 triple-term case by reducing validation overhead on hot line-format paths. The no-triple-term run shows the intended fast-path shape most clearly: common escapeless N-Quads statements are parsed with direct index scanning, bounded named-node caching, and fallback only when the specialized parser cannot handle a line. Graphy remains a strong baseline for ordinary N-Quads and benefits from its own relaxed mode, but the current Graphy reader is skipped for the default RDF 1.2 triple-term workload. Memory deltas in the quick run are noisy because the process is short-lived and includes JIT, parser warmup, and garbage-collection timing.

For cleaner memory measurements, run Node with explicit garbage collection:

npm run build
node --expose-gc perf/bench.js

Performance-oriented implementation notes

  • The parser uses a single pass over the input string.
  • It tracks positions with numeric indexes and avoids token objects.
  • It emits quads directly from parse routines.
  • For strict N-Triples/N-Quads input, it uses a Graphy-inspired fast path for common escapeless statements before falling back to the general parser.
  • The fast path caches recurring predicate, datatype, and graph named nodes in a bounded cache.
  • A relax: true option mirrors Graphy's relaxed mode by skipping part of the validation cost on hot line-format paths and by fast-parsing common RDF 1.2 triple-term objects; spec tests use strict validation by default.
  • The default data model has small classes with simple equals() implementations.
  • StreamParser incrementally parses complete statement prefixes and only retains incomplete trailing input between chunks; very large single statements still need to be held until their terminating boundary arrives.

License

© Ghent University - IMEC

MIT Licensed