@inflexa-ai/tsprov
v0.5.1
Published
An idiomatic TypeScript implementation of the W3C PROV Data Model (a port of Python prov): author provenance documents and round-trip them through PROV-JSON and PROV-N.
Readme
tsprov
An idiomatic TypeScript implementation of the W3C PROV Data Model —
a port of the Python prov library. Author provenance documents
with a fully-typed fluent API, and round-trip them through PROV-JSON and PROV-N.
📖 New to PROV or want the full tour? See the tsprov guide — provenance concepts, a worked example, the data model in depth, and design notes.
- Value-equality that works.
doc.equals(other)is content-based — validated against the full 398-file Python PROV-JSON conformance corpus (every fixture round-trips:deserialize → serialize → deserializeis.equals()-stable). - Fluent, typed authoring.
doc.entity("ex:report"),e.wasGeneratedBy(a).wasAttributedTo(agent), with the camelCase PROV vocabulary as the primary API. - Dependency-light core. Only luxon (datetime fidelity). Browser- safe and tree-shakeable.
- Dual ESM + CJS with
.d.tsdeclarations — works in Node, Bun, and bundlers.
Install
tsprov is published to the public npm registry with provenance attestations — every release is built and signed on GitHub Actions from this repository.
npm install @inflexa-ai/tsprov # or: bun add @inflexa-ai/tsprovQuick start
import { ProvDocument } from "@inflexa-ai/tsprov";
const doc = new ProvDocument();
doc.addNamespace("ex", "http://example.org/");
// Elements
const article = doc.entity("ex:article", { "prov:type": "ex:Article" });
const compile = doc.activity("ex:compile", "2024-01-01T09:00:00+00:00", "2024-01-01T09:05:00+00:00");
const author = doc.agent("ex:alice");
// Relations — container form
doc.wasGeneratedBy(article, compile, "2024-01-01T09:05:00+00:00");
doc.wasAttributedTo(article, author);
// …or the fluent record form (chainable; returns the record)
article.wasDerivedFrom(doc.entity("ex:draft")).wasAttributedTo(author);
console.log(doc.serialize("provn"));
// document
// prefix ex <http://example.org/>
// …
// endDocumentSerialization
const json = doc.serialize("json"); // PROV-JSON
const provn = doc.serialize("provn"); // PROV-N (text)
const parsed = ProvDocument.deserialize(json, "json");
doc.equals(parsed); // true
// read() auto-detects the format
import { read } from "@inflexa-ai/tsprov";
const back = read(json);PROV-JSON supports both serialize and deserialize. PROV-N is serialize-only (matching the
reference library — there is no standard PROV-N parser).
Bundles
const doc = new ProvDocument();
doc.addNamespace("ex", "http://example.org/");
const bundle = doc.bundle("ex:bundle1"); // a named sub-bundle (inherits the document's namespaces)
bundle.entity("ex:nested");
doc.hasBundles(); // true
doc.flattened(); // a new document with all bundle records lifted to the top levelTyped literals
JavaScript has a single number type, so to preserve the XSD datatype distinction, wrap typed values
in a Literal:
import { Literal, XSD_INT } from "@inflexa-ai/tsprov";
doc.entity("ex:dataset", { "ex:rows": new Literal(10_000, XSD_INT) });A bare number defaults to xsd:double on encode; a bare string is an xsd:string.
Graph & lineage
Provenance is a graph, so tsprov ships a graph view and a lineage walker under the optional
@inflexa-ai/tsprov/graph subpath — zero extra dependencies (the core stays luxon-only). The
headline: a lineage answer is itself a PROV document you can serialize and feed to any PROV
tool.
import { ProvDocument } from "@inflexa-ai/tsprov";
import { ProvGraph, resolve, lineage, toProvDocument } from "@inflexa-ai/tsprov/graph";
const doc = new ProvDocument();
doc.addNamespace("ex", "http://example.org/");
const article = doc.entity("ex:article", { "prov:type": "ex:Article" });
const compile = doc.activity("ex:compile", "2024-01-01T09:00:00+00:00", "2024-01-01T09:05:00+00:00");
article.wasGeneratedBy(compile).wasDerivedFrom(doc.entity("ex:draft"));
const graph = ProvGraph.of(doc); // flattened().unified() multi-digraph
const found = resolve(graph, { localpart: "article" }); // git-style resolve → all matches
if (found.kind !== "matched") throw new Error("no such record");
const ancestry = lineage(graph, found.records, { direction: "backward" }); // "where did this come from?"
const { document } = toProvDocument(graph, ancestry); // …the answer is itself a PROV document
console.log(document.serialize("provn"));
// document
// prefix ex <http://example.org/>
//
// entity(ex:article, [prov:type="ex:Article"])
// activity(ex:compile, 2024-01-01T09:00:00+00:00, 2024-01-01T09:05:00+00:00)
// entity(ex:draft)
// wasGeneratedBy(ex:article, ex:compile, -)
// wasDerivedFrom(ex:article, ex:draft, -, -, -)
// endDocumentThe default backward/"dataflow" walk answers ancestry; direction: "forward" and "both"
answer descendants, depth bounds the hops (every cutoff surfaces as explicit frontier data,
never a silent truncation). toFlatGraph(result) gives a JSON-safe { nodes, edges } projection,
and lineagePaths(graph, result, target) enumerates the connecting paths. See
§8 of the guide for the full tour.
What's included
- The full PROV-DM in-memory model:
Identifier/QualifiedName/Namespace/Literal, all 3 elements + 15 relation classes,NamespaceManager,ProvBundle,ProvDocument. - The complete fluent authoring API (camelCase PROV vocabulary primary; descriptive aliases).
- PROV-JSON (serialize + deserialize) and PROV-N (serialize), with
read()auto-detection. - Content-based
equals(), plusflattened()and sub-bundles. - Graph & lineage under the optional
@inflexa-ai/tsprov/graphsubpath — a multi-digraph view, composable recordresolve(), and a directional, boundedlineage()walk, with no extra dependencies.
PROV-XML, PROV-RDF, DOT (graph-visualisation) rendering, and the CLI are out of scope for now.
Develop
bun install
bun run bootstrap # fetch the 398-file PROV-JSON conformance corpus into reference/
bun test # run the test suite (incl. the corpus oracle)
bun run build # emit dist/ (ESM + CJS + .d.ts)The corpus is the Python reference implementation's, and is deliberately not vendored — see
CONTRIBUTING.md for why. Without bootstrap, the two
suites that read it fail with ENOENT.
Contributing
Contributions are welcome, and PROV is a standard we implement rather than own — so if you work on PROV elsewhere in the ecosystem, we would rather have you inside this project than outside it. Start with CONTRIBUTING.md; GOVERNANCE.md explains how decisions get made and what is openly missing (PROV-XML, PROV-RDF, a PROV-N parser, PROV-CONSTRAINTS validation). Vulnerabilities go through SECURITY.md, never a public issue.
License
Apache-2.0. tsprov is a port of the Python prov library,
used under the MIT License — see NOTICE.
