TS SHEX-ORM Schema

CLI tool to convert SHEX shapes to schemas and TypeScript definitions (“shape types”) that can be used for creating RDF ORM objects.

You need this utility for compiling schemas for use with the TypeScript RDF ORM.

About RDF

RDF (Resource Description Framework) is a standard to describe data. Rather than organizing data as tables (e.g. SQL) or trees (e.g. JSON), RDF represents data as a non-hierarchical, unstructured set of triples (a graph aka network).

Each triple consists of a subject (about which you are describing something), a predicate (the property of the relationship, e.g. the first name), and an object (the value of that property or a reference). Triples belong to documents, also called graphs in the context of RDF. Subjects, predicates and graphs are all IRIs (the generalization of URLs). There are many specifications for describing data, to aid application interoperability.

RDF’s flexible and schema-less design aids in schema-evolution, interoperability, and data relationships. You are advised to take a moment to get yourself familiar with RDF if you are new to it.

To work with RDF in applications and bring structure to it, you need to define schemas.

Schemas define what data you want to query, validate, and see materialized in a TypeScript object.

Setup

Install @ng-org/shex-orm as dev dependency.

npm install --save-dev @ng-org/shex-orm

Then run

npx rdf-orm build --input ./src/shapes/shex --output ./src/shapes/orm

In your app’s package.json, you are advised to add something like the following:

{
  "scripts": {
    "build:orm": "rdf-orm build --input ./src/shapes/shex --output ./src/shapes/orm",
    "dev": "npm run build:orm && vite dev",
    "build": "npm run build:orm && vite build"
  }
}

Writing SHEX Schemas

Below, you can see an example SHEX schema as an orientation. You can also check out the SHEX schema of an example app.

PREFIX ex: <did:ng:z:>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

ex:ExpenseShape {
  # `a` is a shorthand for the standard RDF type predicate `rdfs:type` or in the long form: `http://www.w3.org/1999/02/22-rdf-syntax-ns#type`
  a [ex:Person]                    # Required type <did:ng:z:Person>
      // rdfs:comment "This is a comment that will appear in the generated TypeScript type" ;
  ex:name xsd:string ;             # Required string
  ex:email xsd:string * ;          # Zero or more strings (set)
  ex:height xsd:float ;            # Required number
  ex:age xsd:integer ;             # Required integer. NOTE: TS does not support integers and you are recommended to use xsd:float unless you know what you are doing.
  ex:friends IRI * ;               # Set of IRIs
  ex:isRecurring xsd:boolean ;     # A boolean value
  ex:address @ex:AddressShape ;    # A nested object shape.
  ex:paymentStatus [ex:Paid ex:Pending ex:Overdue] ; # Enum
}

# `EXTRA a` means that the property `a` may have other values in addition to `ex.Address`
ex:AddressShape EXTRA a {
  a [ ex:Address ] ;
  ex:name xsd:string ;
}

See what output was generated below.

SHEX Quick Reference

SyntaxMeaningTypeScript Type
prop xsd:stringRequired, exactly onestring
prop xsd:boolean ?Optional, zero or oneboolean | undefined
prop xsd:float *Zero or moreSet<number>
prop xsd:string +One or moreSet<string> (non-empty)
prop IRIReference to another objectstring (IRI)
@ex:PersonShapenested objectPerson
prop xsd:string OR xsd:floatmultiple types allowedstring | number
@ex:AudioAsset OR @ex:VideoAssetmultiple nested object types allowedAudioAsset | VideoAsset

You will then pass the shape type of a shape definition to the ng sdk:

Note: If you specify more than one allowed nested shape and the data matches both shapes, the first shape will “win”. In general though, you are advised to define shapes so that this does not happen. Specify different types for each shape instead (e.g. a [ex:DistinguishingTypeOnlyAvailableInThisData] ;).

Generated Output

For each SHEX file, the tool creates three TypeScript files:

  • A schema file like person.schema.ts
  • A typings file like person.typings.ts
  • A shape type file like person.shapeTypes.ts which contains a ShapeType that consists of the schema, the type, and the IRI of the main shape. This is what you pass to the ORM.

The transformers for converting SHEX to schema and typings files are based on @ldo/traverser-shexj.

Representation in TypeScript

Every type has the following two readonly properties:

  • @id the subject IRI
  • @graph the graph (document) NURI

In addition to that you will usually find the @type property: The RDF type IRI (from rdfs:type) is always converted to the property name @type by default. You are strongly encouraged to specify a type in your schema.

Property names are derived of the last part of the predicate IRI. In case of name collisions the 2nd, 3rd, etc. last part is used added as well.

Predicates with a cardinality higher than 1 (i.e., maxCardinality > 1 or maxCardinality === -1 for unlimited) are represented as TypeScript Set<T> types. Note that SHEX allows you to specify cardinalities not representable in TypeScript, for example greater than 2 and less than 4. When you modify an object so that it does not fulfil those requirements anymore, it will disappear since it doesn’t match the shape anymore.

Currently all RDF types are mapped to string, number, or boolean, including date which is mapped to an ISO 8601 date string.

Use in the ORM

In the ORM, you pass the generated ShapeType to the respective functions (getObjects(), insertObject(), useShape(), OrmSubscription.getOrCreate()), for example:

import { useShape } from "@ng-org/orm/react";
import { TestObjectShapeType } from "../shapes/orm/testShape.shapeTypes";

export function TestComponent() {
    const {data: testObjects} = useShape(TestObjectShapeType, {graphs: ["did:ng:i"]});
    ...
}

Nested Object Validity

Note that when a nested object is invalid, the whole parent object is invalid too (i.e. it won’t be loaded). If you want to allow for invalid children (that are then not materialized in TypeScript), you can mark the property as EXTRA, e.g.:

ex:ExpenseShape EXTRA ex:address {
  ex:address @ex:AddressShape ;
}

Reference

Interfaces

BaseType

Defined in: types.ts:23

The base type that all generated objects inherit from.

Extends

  • Record<string, any>

Indexable

[key: string]: any

Properties

@graph

@graph: string

Defined in: types.ts:27

The document NURI of the subject. By default, it will be set to the parent’s graph.

@id

@id: string

Defined in: types.ts:25

The IRI of the subject.


Predicate

Defined in: types.ts:53

The schema of a property.

Properties

dataTypes

dataTypes: DataType[]

Defined in: types.ts:55

Allowed type of object. If more than one is present, either of them is allowed.

extra?

optional extra: boolean

Defined in: types.ts:65

If other (additional) values are permitted. Useful for literals.

iri

iri: string

Defined in: types.ts:57

The RDF predicate URI.

maxCardinality

maxCardinality: number

Defined in: types.ts:61

Maximum allowed number of values. -1 means infinite.

minCardinality

minCardinality: number

Defined in: types.ts:63

Minimum required number of values.

readablePredicate

readablePredicate: string

Defined in: types.ts:59

The alias of the predicateUri when serialized to a JSON object.


Shape

Defined in: types.ts:35

Shape of an object.

Properties

iri

iri: string

Defined in: types.ts:37

The ID (IRI) of the shape.

predicates

predicates: Predicate[]

Defined in: types.ts:39

The predicates (properties) of the shape.


ShapeType

Defined in: types.ts:15

The ORM shape type generated from a SHEX schema with rdf-orm build --input ./path/to/shex-files --output ./path/to/shape-types

Type Parameters

T

T extends BaseType

Properties

schema

schema: Schema

Defined in: types.ts:17

The schema object of the shape.

shape

shape: string

Defined in: types.ts:19

The ID (IRI) of the shape.

Type Aliases

DataType

DataType = object

Defined in: types.ts:43

An allowed data type or literal.

Properties

literals?

optional literals: number[] | string[] | boolean[]

Defined in: types.ts:45

The required literal value(s). Additional values are allowed, if extra is true.

shape?

optional shape: string

Defined in: types.ts:47

If valType is "shape", the IRI of the nested shape.

valType

valType: "number" | "string" | "boolean" | "iri" | "shape"

Defined in: types.ts:49

The type of object value for a triple constraint.


Schema

Schema = object

Defined in: types.ts:30

Index Signature

[id: string]: Shape