n3.js Documentation

repository·main·Indexed 21 days ago

https://github.com/rdfjs/n3.js

A high-performance, asynchronous, and streaming RDF library for JavaScript that implements the RDF.js low-level specification. It provides tools to parse and write RDF formats including Turtle, TriG, N-Triples, N-Quads, and Notation3. Key components include N3.Parser and N3.StreamParser for reading data, N3.Writer and N3.StreamWriter for serialization, N3.Store for in-memory storage, and N3.Reasoner for logical inference using rules.

Tokens
12.9K
Snippets
47
Records
51
Agent score
73%

What's inside n3.js

  1. Use N3.js in the browser

    main

    N3.js can be used in browsers via bundlers like Webpack or Browserify (creating a UMD bundle). Alternatively, you can load it via CDN using a UMD bundle or as an ES module.

    UMD Bundle (Global N3):

    <script src="https://unpkg.com/n3/browser/n3.min.js"></script>

    ES Module:

    <script type="module">
      import { Store, Parser, Writer } from 'https://unpkg.com/n3/browser/n3.esm.min.js';
    </script>
    <script src="https://unpkg.com/n3/browser/n3.min.js"></script>
  2. Parse Web Streams with N3.StreamParser

    main

    Since N3.StreamParser implements a standard writable stream interface, you can consume Web Streams (like those from fetch) directly.

    In Node.js (v17+): Convert the Web Stream to a Node.js stream using Readable.fromWeb().

    In Browsers: Manually write chunks from the Web Stream reader to the StreamParser and handle backpressure using the drain event.

    // Node.js 17+
    const streamParser = new N3.StreamParser(),
          { Readable } = require('stream');
    Readable.fromWeb(response.body).pipe(streamParser);
    
    // Browser
    const streamParser = new N3.StreamParser(),
          reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
    (async () => {
      for (let result; !(result = await reader.read()).done;) {
        if (!streamParser.write(result.value))
          await new Promise(resolve => streamParser.once('drain', resolve));
      }
      streamParser.end();
    })();
  3. ESLint configuration for n3.js

    main

    The project uses a flat-config ESLint setup (eslint.config.mjs) to enforce code quality and style. The configuration is divided into several scopes based on file patterns:

    • Global/Base (**/*.js): Applies to all JavaScript files. It uses import-x for module linting and enforces strict rules for possible errors, best practices, strict mode, variables, Node.js, and stylistic issues.
    • Tests (test/**): Applies eslint-plugin-jest recommended rules and provides Jest globals (describe, it, expect, etc.). It also includes specific overrides for Mocha compatibility (e.g., allowing max-nested-callbacks) and relaxed import rules.
    • Performance (perf/**): Disables no-console, no-process-exit, and certain import-x rules to allow for performance measurement scripts.
    • Spec Runner (spec/**): Disables specific rules like max-nested-callbacks, no-console, no-loop-func, no-process-exit, no-shadow, and no-sync to support the spec runner environment.
  4. Match quads using DatasetCoreAndReadableStream

    main

    When you call .match(subject, predicate, object, graph) on an N3Store (or via the internal DatasetCoreAndReadableStream logic), it returns a DatasetCoreAndReadableStream.

    This object acts as both a Dataset and a ReadableStream. It allows you to perform dataset operations (like filter, map, union, toArray) on a specific subset of the data defined by the match pattern, while also being able to stream those results.

    // match() returns a streamable dataset subset
    const subsetStream = store.match('http://example.org/s', 'http://example.org/p', null, null);
    
    // You can still use Dataset methods on the stream
    const subsetArray = subsetStream.toArray();
    
    // Or consume it as a stream
    subsetStream.on('data', (quad) => {
      console.log('Matched quad:', quad);
    });
  5. Manually create blank nodes and lists in N3.Writer

    main

    Because streaming writers cannot automatically determine when to use shorthand Turtle/TriG notations (like [...] or (...)) without knowing the future of the stream, you must use the blank and list methods on the N3.Writer instance to create them manually.

    const writer = new N3.Writer({ prefixes: { c: 'http://example.org/cartoons#',
                                           foaf: 'http://xmlns.com/foaf/0.1/' } });
    writer.addQuad(
      writer.blank(
        namedNode('http://xmlns.com/foaf/0.1/givenName'),
        literal('Tom', 'en')),
      namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
      namedNode('http://example.org/cartoons#Cat')
    );
    writer.addQuad(quad(
      namedNode('http://example.org/Jerry'),
      namedNode('http://xmlns.com/foaf/0.1/knows'),
      writer.blank([{
        predicate: namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
        object:    namedNode('http://example.org/cartoons#Cat'),
      },{
        predicate: namedNode('http://xmlns.com/foaf/0.1/givenName'),
        object:    literal('Tom', 'en'),
      }])
    ));
    writer.addQuad(
      namedNode('http://example.org/Mammy'),
      namedNode('http://example.org/hasPets'),
      writer.list([
        namedNode('http://example.org/cartoons#Tom'),
        namedNode('http://example.org/cartoons#Jerry'),
      ])
    );
    writer.end((error, result) => console.log(result));
  6. Serialize quads to a string using N3.Writer

    main

    Use N3.Writer to serialize quads into an RDF document string. By default, it writes Turtle (or TriG if named graphs are present). You can specify other formats like N-Triples or application/trig via the format option in the constructor. Use addQuad to provide data and end to finalize the serialization.

    const writer = new N3.Writer({ prefixes: { c: 'http://example.org/cartoons#' } });
    writer.addQuad(quad(
      namedNode('http://example.org/cartoons#Tom'),   // Subject
      namedNode('http://example.org/cartoons#name'),  // Predicate
      literal('Tom')                                  // Object
    ));
    writer.end((error, result) => console.log(result));
  7. Pipe a quad stream to an RDF stream using N3.StreamWriter

    main

    N3.StreamWriter is a Node.js stream and RDF.js Sink implementation. It allows you to pipe parsed quads directly into a writer, which can then be piped to an output stream (e.g., process.stdout).

    const streamParser = new N3.StreamParser(),
          inputStream = fs.createReadStream('cartoons.ttl'),
          streamWriter = new N3.StreamWriter({ prefixes: { c: 'http://example.org/cartoons#' } });
    inputStream.pipe(streamParser);
    streamParser.pipe(streamWriter);
    streamWriter.pipe(process.stdout);
  8. Create triples and quads with N3.DataFactory

    main

    N3.js follows the RDF.js low-level specification. Use N3.DataFactory to access factory functions for creating terms (like namedNode and literal) and quads.

    const { DataFactory } = N3;
    const { namedNode, literal, defaultGraph, quad } = DataFactory;
    const myQuad = quad(
      namedNode('https://ruben.verborgh.org/profile/#me'), // Subject
      namedNode('http://xmlns.com/foaf/0.1/givenName'),    // Predicate
      literal('Ruben', 'en'),                              // Object
      defaultGraph(),                                      // Graph
    );
    console.log(myQuad.termType);              // Quad
    console.log(myQuad.value);                 // ''
    console.log(myQuad.subject.value);         // https://ruben.verborgh.org/profile/#me
    console.log(myQuad.object.value);          // Ruben
    console.log(myQuad.object.datatype.value); // http://www.w3.org/1999/02/22-rdf-syntax-ns#langString
    console.log(myQuad.object.language);       // en
  9. Parse Node.js streams with N3.Parser and N3.StreamParser

    main

    Using N3.Parser with Node.js streams

    N3.Parser can parse Node.js streams as they grow, returning quads as soon as they are ready.

    Using N3.StreamParser

    N3.StreamParser is a Node.js stream and RDF.js Sink implementation. It is ideal when the consumer is slower than the source, as it implements backpressure (data is only read when the consumer is ready).

    Note on event order: In StreamParser, prefix and comment events are emitted as soon as they are parsed, while quads might be buffered. If document order is critical, use N3.Parser with callbacks instead.

    // N3.Parser with stream
    const parser = new N3.Parser(),
          rdfStream = fs.createReadStream('cartoons.ttl');
    parser.parse(rdfStream, console.log);
    
    // N3.StreamParser for backpressure
    const streamParser = new N3.StreamParser(),
          rdfStream = fs.createReadStream('cartoons.ttl');
    rdfStream.pipe(streamParser);
    streamParser.pipe(new SlowConsumer());
  10. Store triples in memory using N3.Store

    main

    N3.Store allows for fast in-memory storage and retrieval of triples. It implements the Dataset interface. To reduce memory consumption when using multiple stores, you can have them share an N3.EntityIndex.

    const store = new N3.Store();
    store.add(
      quad(
        namedNode('http://ex.org/Pluto'),
        namedNode('http://ex.org/type'),
        namedNode('http://ex.org/Dog')
      )
    );
    store.add(
      quad(
        namedNode('http://ex.org/Mickey'),
        namedNode('http://ex.org/type'),
        namedNode('http://ex.org/Mouse')
      )
    );
    
    // Retrieve all quads
    for (const quad of store)
      console.log(quad);
    // Retrieve Mickey's quads
    for (const quad of store.match(namedNode('http://ex.org/Mickey'), null, null))
      console.log(quad);
  11. Serialize quads to a Node.js stream using N3.Writer

    main

    You can direct N3.Writer output to a Node.js stream (like process.stdout) by passing the stream as the first argument to the constructor. Set { end: false } in the options if you want to keep the stream open for further writing.

    const writer = new N3.Writer(process.stdout, { end: false, prefixes: { c: 'http://example.org/cartoons#' } });
    writer.addQuad(
      namedNode('http://example.org/cartoons#Tom'),                   // Subject
      namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),   // Predicate
      namedNode('http://example.org/cartoons#Cat')                    // Object
    );
    writer.addQuad(quad(
      namedNode('http://example.org/cartoons#Tom'),     // Subject
      namedNode('http://example.org/cartoons#name'),  // Predicate
      literal('Tom')                                    // Object
    ));
    writer.end();