# rdf-parser-ts

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

Latest version **0.3.2** (published 2026-09-08) · MIT license · 0 weekly downloads

## Install

```sh
npm install rdf-parser-ts
pnpm add rdf-parser-ts
yarn add rdf-parser-ts
bun add rdf-parser-ts
```

Provides the command `rdf-parser-ts`.

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.3.2 |
| Published | 2026-09-08 |
| First published | 2026-06-06 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Node | >=22.0.0 |
| Dependencies | 2 |
| Unpacked size | 390.5 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 1 |
| Author | Pieter Colpaert |
| Maintainers | pietercolpaert |
| Keywords | rdf, rdf-js, turtle, trig, n-triples, n-quads, parser |

## Links

- npm: https://www.npmjs.com/package/rdf-parser-ts
- Repository: https://github.com/pietercolpaert/rdf-parser.ts
- Homepage: https://github.com/pietercolpaert/rdf-parser.ts#readme
- Issues: https://github.com/pietercolpaert/rdf-parser.ts/issues
- npm.io page: https://npm.io/package/rdf-parser-ts

## Dependencies (2)

- [@rdfjs/types](https://npm.io/package/@rdfjs/types.md) ^2.0.1
- [rdf-writer-ts](https://npm.io/package/rdf-writer-ts.md) ^0.1.1

## Alternatives

- [babylon](https://npm.io/package/babylon.md) — 5.1M weekly downloads
- [csscolorparser](https://npm.io/package/csscolorparser.md) — 3.7M weekly downloads
- [expr-eval-fork](https://npm.io/package/expr-eval-fork.md) — 1.5M weekly downloads
- [@leeoniya/ufuzzy](https://npm.io/package/@leeoniya/ufuzzy.md) — 247.7K weekly downloads
- [xml-parser](https://npm.io/package/xml-parser.md) — 78.4K weekly downloads

## Recent versions

- 0.3.2 (latest) — 2026-09-08
- 0.3.1 — 2026-07-07
- 0.3.0 — 2026-07-06
- 0.2.6 — 2026-06-13
- 0.2.5 — 2026-06-12
- 0.2.4 — 2026-06-08
- 0.2.3 — 2026-06-08
- 0.2.2 — 2026-06-07
- 0.2.1 — 2026-06-07
- 0.2.0 — 2026-06-07
- 0.1.0 — 2026-06-06

## README

# RDF Parser for TypeScript

[![W3C RDF1.2 spec compliance](https://github.com/pietercolpaert/rdf-parser-ts/actions/workflows/ci.yml/badge.svg)](https://github.com/pietercolpaert/rdf-parser-ts/actions/workflows/ci.yml)

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 `rdf-parser-ts` in the [ldfetch playground](https://www.pieter.pm/ldfetch), which uses this parser library in the browser.

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](https://github.com/pietercolpaert/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

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

```sh
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:

```json
{
	"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:

```ts
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:

```html
<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`:

```html
<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:

```ts
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` | 42,908 bytes (41.9 KiB) | 11,032 bytes (10.8 KiB) |
| `dist/browser/index.global.js` | 43,393 bytes (42.4 KiB) | 11,223 bytes (11.0 KiB) |

## Parsing strings

```ts
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.

```ts
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.

```ts
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:

```ts
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.

```ts
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()`:

```ts
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.

```ts
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:

```ts
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:

```ts
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.

```ts
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.

```sh
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:

```sh
npm run spec
```

Run focused suites when iterating:

```sh
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.

```sh
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:

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

---
_Source: https://npm.io/package/rdf-parser-ts · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
