lincd
v1.0.3
Published
LINCD is a JavaScript library for building user interfaces with linked data (also known as 'structured data', or RDF)
Maintainers
Readme
LINCD.js
Linked INteropeable Code & Data
All the tools you need to build Linked Data applications with React
LINCD.js is a modern TypeScript library for building React applications powered by RDF and SHACL Shapes. Create clean, type-safe code that queries, creates, updates, and deletes linked data through object-oriented APIs, with automatic data loading and reactive components.
New to Linked Data? It is a W3C standard based on RDF used to build interconnected knowledge graphs. It is also known as Structured Data which can help search engines to present rich snippets in search results.
LINCD.js is compatible with the RDFJS data model
See also
LINCD offers:
- Schema-Parameterized Query DSL: TypeScript-embedded declarative query language that compiles to backends like SPARQL
- Shape Classes: TypeScript classes that generate SHACL shapes for validation and OO data access
- Object-Oriented Data Operations: Query, create, update, and delete RDF data using clean OO code
- Automatic Data Loading: Flexible storage layer supporting multiple backends with automatic data fetching
- React Linked Components: Components tied to queries that automatically load data and convert results to props
- Reactive Queries: Query-level reactivity with automatic component updates when shared data changes
- Automatic Data Validation: Data validated against SHACL shapes automatically
- A registry of plug & play UI components, shapes & ontologies
Why?
LINCD is built from the ground up with a modern tech stack and integrates the latest developments. This allows us to offer advanced features that go beyond other existing RDF JavaScript libraries. Whilst also offering solutions for some major challenges in the world of Linked Data:
Making Linked Data app development easy
Graph databases are being adopted everywhere. Tons of Open Linked Data is being published. Tools have matured, but the learning curve for developers is still steep.
LINCD significantly reduces the amount of learning required and makes it much easier to work with Linked Data.
Reusable code for Linked Data
There are tons of ontologies (reusable linked data structures) available. But without a good searchable registry, it's hard to find the right one. And starting to use a specific ontology can be time-consuming still. Reusable UI components built specifically for Linked Data are virtually non-existent.
LINCD.org offers an open registry of quality ontologies and UI components built for those ontologies. Each of these ontology and components can be imported and used with just a few lines of code. This library makes it easy to develop and share such ontologies and components in the registry.
Solving data validation & more
Another hurdle in the adoption of Linked Data has been the openness of the RDF model. With a lack of proper data restriction and data validation tools, maintaining a clean dataset in real life applications has been a challenge. W3C has since published SHACL, which is an excellent standard to tackle this. However, the tools to work with SHACL have been minimal.
LINCD goes full in on SHACL and places SHACL's data "Shapes" right at the center of development. This creates much simpler and cleaner code, frees developers up from thinking about which classes & properties to use and allows us to offer advanced features like automatic data loading and data validation that to our knowledge no other Linked Data library or framework offers.
Installation
npm + node.js
npm install lincdimport { Shape, linkedComponent } from 'lincd';See this tutorial on how to build linked data applications with LINCD.js
Schema-Parameterized Query DSL
LINCD Queries is a TypeScript-embedded declarative query language that compiles its query AST to backends like SPARQL. You define your own SHACL shape schemas, making the DSL schema-parameterized and domain-agnostic — you apply the generic DSL to your specific domain.
The query language provides a type-safe, object-oriented interface that works with any domain through custom Shape classes:
// Basic property selection
let names = await Person.select(p => p.name);
// Filtering with where clauses
let filtered = await Person.select().where(p => p.name.equals('Semmy'));
// Nested property access
let friendsNames = await Person.select(p => p.friends.name);
// Complex filtering with AND/OR
let friends = await Person.select(p =>
p.friends.where(f =>
f.name.equals('Moa').and(f.hobby.equals('Jogging'))
)
);
// Aggregation
let friendCounts = await Person.select(p => p.friends.size());
// Sub-queries
let detailedFriends = await Person.select(p =>
p.friends.select(f => ({ name: f.name, hobby: f.hobby }))
);The query DSL automatically converts to backend query languages (like SPARQL) and executes against your configured storage layer.
Shapes
LINCD introduces Shape classes that generate SHACL Shapes.
These classes enable automatic data validation and abstract away RDF implementation details.
Consider this example shape:
Definition
@linkedShape
export class Person extends Shape {
/**
* indicates that instances of this shape need to have this rdf.type
*/
static targetClass: NamedNode = foaf.Person;
/**
* instances of this person shape are REQUIRED to have exactly one foaf.name property
*/
@literalProperty({
path: foaf.name,
required: true,
maxCount: 1,
})
get name() {
return this.getValue(foaf.name);
}
set name(val: string) {
this.overwrite(foaf.name, new Literal(val));
}
/**
* values of the property foaf.knows needs to be valid instances of this Person shape as well
*/
@objectProperty({
path: foaf.knows,
shape: Person,
})
get knows(): ShapeSet<Person> {
return Person.getSetOf(this.getAll(foaf.knows));
}
}Usage
Now when we use the shape above, we can simply use plain and simple object-oriented code. The code below creates RDF triples in the graph, but it does not need to know about any specific RDF properties or classes to use.
Instead, it simply uses the accessors from the Shape.
Furthermore, the shape class allows us to validate our graph. This happens automatically when visualizing specific shapes from the graph with Linked Components
let person = new Person();
person.name = "Rene";
let person2 = new Person();
person2.name = "Jenny";
person.knows.add(person2);
//both persons are valid instances of Person
console.log(Person.validate(person)); //true
console.log(Person.validate(person2));//trueAutomatic Data Loading
LINCD provides a flexible storage layer that can connect to multiple backend types. Components automatically load the data they need based on their queries — you just specify what data to load, and LINCD handles fetching it from your configured storage.
Storage Configuration
Storage is configured via storage-config.ts or storage-config.js:
// storage-config.js
import { FusekiStore } from 'lincd-fuseki/shapes/FusekiStore';
import { LinkedStorage } from 'lincd/utils/LinkedStorage';
let quadStore = new FusekiStore('cn-core', process.env.FUSEKI_BASE_URL);
LinkedStorage.setDefaultStore(quadStore);
LinkedStorage.init();Storage Backend Options
LINCD supports multiple storage backends:
Node.js Backend → Enterprise Graph Databases: Connect to Virtuoso, GraphDB, Jena/Fuseki via LINCD server packages:
lincd-fuseki- Jena Fuseki connectorlincd-server- Node.js server with file storage- Other connectors as available
In-Memory Database: Node.js in-memory graph database for development and testing
Remote SPARQL Endpoint: Connect frontend directly to SPARQL endpoints (e.g., Fluree with advanced access rights)
Browser Storage: Store graph in browser localStorage for client-side applications
How Automatic Loading Works
When you use a Linked Component with a query, LINCD:
- Analyzes the component's query to determine what data is needed
- Checks if the data is already cached
- Automatically fetches missing data from your configured storage backend
- Converts the data into props for your component
- Caches results using a shape-aware query cache
The subject of the query depends on the of prop you pass to the component. For example, <PersonCard of={{id: me.id}} /> will automatically load data for the node with that URI.
React Linked Components
React Linked Components are UI components tied to LINCD queries. They automatically load data from your configured storage, convert query results to props, and handle smart caching.
Currently LINCD exclusively supports React components, though other libraries may be added in the future.
Creating a Linked Component
import { linkedComponent } from 'lincd';
import { Person } from './shapes/Person';
const PersonCard = linkedComponent(
Person.query(p => ({
name: p.name,
friends: p.friends.name,
isLoading
})),
({ name, friends }) => {
return (
<div>
<h1>{name}</h1>
<ul>
{friends?.map(friend => (
<li key={friend.id}>{friend.name}</li>
))}
</ul>
</div>
);
}
);Using a Linked Component
// Automatically loads data for the specified node
<PersonCard of={{id: 'http://example.com/#me'}} />
// Or with a Shape instance
let person = new Person('http://example.com/#me');
<PersonCard of={person} />How It Works
Automatic Data Loading: When the component mounts, LINCD analyzes the query and automatically fetches the required data from your storage backend.
Data Conversion: Query results are automatically converted to plain JavaScript objects and passed as
linkedDataprops.Smart Caching: LINCD uses a shape-aware query cache. Components sharing the same shape data will share cached results.
Subject from Props: The
ofprop determines which node to load data for. You can pass:- A
QResultobject:{id: 'http://example.com/#me'} - A Shape instance:
new Person('http://example.com/#me') - A Node:
NamedNode.getOrCreate('http://example.com/#me')
- A
Reactive Queries
LINCD provides a reactive data layer at the query level — similar to what Svelte, Solid, or Apollo call "reactivity," but operating at the query level rather than component state level.
How Reactive Queries Work
Reactive Queries automatically update all components that depend on the same shape data whenever that data changes — powered by LINCD's shape-aware query cache.
When two components query the same shape data:
// Component 1
<PersonCard of={{id: me.id}} />
// Component 2 (elsewhere in your app)
<PersonProfile of={{id: me.id}} />If one component updates the data (e.g., changes the person's name), the other component will automatically rerender with the updated data because they share the same cached query result.
This is made possible by LINCD's shape-aware query cache, which tracks which components depend on which shape properties and invalidates the appropriate caches when data changes.
Note: Reactive Queries implementation is coming soon. Once implemented, this will enable true reactive data synchronization across components.
Automatic Data Validation
Once you have linked your component to specific shapes (data structures), LINCD.js ensures that only those nodes in the graph that match with this shape will be allowed to be used with this component. Because of this, you can rest assured that all the data will be there and in the right format.
That is, PersonView will only render once LINCD has confirmed that the provided person instance is a valid instance of the Person shape. With the Person example above, this would mean it has exactly one name defined under person.name and person.knows returns a set of valid Person instances.
Validation happens automatically when:
- Creating new Shape instances
- Loading data from storage
- Using Linked Components
- Executing queries
Queries: Create, Update, Delete
LINCD provides object-oriented methods for creating, updating, and deleting RDF data:
Create
let newPerson = await Person.create({
name: 'Alice',
knows: [{id: existingPerson.id}]
});Update
await Person.update({id: personId}, {
name: 'Alice Updated',
hobby: 'Reading'
});Delete
await Person.delete({id: personId});
// or delete multiple
await Person.delete([{id: id1}, {id: id2}]);All operations use your Shape classes and are validated against SHACL constraints.
A registry of reusable UI components, Shapes & ontologies
LINCD.org is a registry of components, shapes and ontologies built with LINCD.js. Each of these can be imported and used with a few lines. This is the best place to get started to build an application powered by linked data.
Documentation
See docs.lincd.org for the full documentation plus helpful tutorials for LINCD.js
Examples
See lincd.org/examples for a list of examples with source code on github.
Contributing
To make changes to lincd.js, clone this repository and install dependencies with npm install.
Then run npm run dev to start the development process which watches for source file changes in src and automatically
updates the individual transpiled files in the lib and the bundles in the dist folder.
Alternatively run npm run build to build the project just once.
We welcome pull requests.
