Skip to content

OWL class restrictions Compile-time

Validation modes: Validation modes reference

Declaration. OWL property restrictions narrow the inferred TypeScript type of the restricted property at compile time. The TBox output carries owl:Restriction blank nodes for reasoners.

ts
Compose.someValuesFrom(propIri, rangeClassIri): RestrictionRefType
Compose.allValuesFrom(propIri, rangeClassIri): RestrictionRefType
Compose.hasValue(propIri, value): RestrictionRefType
Compose.cardinality(propIri, n): RestrictionRefType
Compose.minCardinality(propIri, n): RestrictionRefType
Compose.maxCardinality(propIri, n): RestrictionRefType

Compose.subClassOf(restriction, body): typeof body

Use this when you want to express OWL property-restriction class axioms - anonymous classes that constrain how a property is used. Each restriction becomes an owl:Restriction blank node in the TBox, referenced from the body class via rdfs:subClassOf. Restrictions compose: chaining Compose.subClassOf accumulates jt:restrictions on the body schema.

/**
 * OWL property restrictions — Example 1: cardinality / minCardinality / maxCardinality
 *
 * Exercises the six `Compose` restriction builders against the
 * canonical `Book.authors` property — the same property the
 * registered `RareBookSchema` already constrains via
 * `maxCardinality(authors, 1)` and `someValuesFrom(authors, AuthorName)`.
 * Demonstrates how additional restriction-narrowed sibling classes
 * register onto the canonical bookstore.
 *
 * Each derived schema attaches to `` via
 * `jt.set()` so the canonical registry remains the
 * single source of truth.
 */

import { Compose } from '../../../src/index.js';
import {
  AuthorNameSchema, BookSchema,
  createBookstoreDocRegistry
} from '../bookstore/index.js';

// createBookstoreDocRegistry seeds a permissive copy of the bookstore — docs examples extend
// it with ad-hoc demo schemas; strict-graph checking is intentionally off here.
const jt = createBookstoreDocRegistry();

// Restriction onProperty is the property IDENTIFIER (`Class#prop`); the
// projection resolves it to the flat `https://bookstore.example/authors`
// predicate in the emitted TBox/SHACL, matching the property declaration.
const AUTHORS_PROP = 'urn:bookstore:Book#authors';

// cardinality — Book with exactly one author.
const OneAuthorBookSchema = Compose.subClassOf(
  Compose.cardinality(AUTHORS_PROP, 1),
  Compose.subClassOf(BookSchema, {
    '$id': 'https://bookstore.example/OneAuthorBook',
    'type': 'object'
  } as const)
);

// minCardinality + allValuesFrom — Book with two-or-more named authors.
const MultiAuthoredBookSchema = Compose.subClassOf(
  Compose.minCardinality(AUTHORS_PROP, 2),
  Compose.subClassOf(
    Compose.allValuesFrom(AUTHORS_PROP, AuthorNameSchema.$id),
    Compose.subClassOf(BookSchema, {
      '$id': 'https://bookstore.example/MultiAuthoredBook',
      'type': 'object'
    } as const)
  )
);

const jt2 = jt.set(OneAuthorBookSchema).set(MultiAuthoredBookSchema);

// A solo-authored Michael Ende title passes OneAuthorBook.
const momo = {
  'authors': ['Michael Ende'],
  'inStock': true,
  'isbn': '9783522115056',
  'price': {
    'amount': 16.99,
    'currency': 'EUR'
  },
  'printStatus': 'inPrint',
  'title': 'Momo'
} as const;

const oneAuthorErrs = jt2.validate(OneAuthorBookSchema.$id, momo);

console.assert(oneAuthorErrs.length === 0);
console.log('OneAuthorBook accepts Momo (1 author):', oneAuthorErrs.length === 0, '| cardinality(authors, 1)');

// A multi-author anthology (two authors) passes MultiAuthoredBook.
const anthology = {
  'authors': [
    'Michael Ende',
    'Cornelia Funke'
  ],
  'inStock': true,
  // Märchen-Sammelband anthology by Cornelia Funke & Michael Ende
  'isbn': '9783522115070',
  'price': {
    'amount': 22,
    'currency': 'EUR'
  },
  'printStatus': 'inPrint',
  'title': 'Märchen-Sammelband'
} as const;

const multiErrs = jt2.validate(MultiAuthoredBookSchema.$id, anthology);

console.assert(multiErrs.length === 0);
console.log('MultiAuthoredBook accepts anthology (2 authors):', multiErrs.length === 0, '| minCardinality(authors, 2)');
Output
Press Execute to run this example against the real library.

The TBox emits:

json
{
  "@id": "urn:example:PersonWithExactlyTwoParents",
  "@type": "owl:Class",
  "rdfs:subClassOf": [
    {
      "@type": "owl:Restriction",
      "owl:onProperty": { "@id": "https://example.com/parent" },
      "owl:cardinality": 2
    }
  ]
}

The six restriction methods

MethodPredicateTypeScript narrowingOWL semantics
someValuesFrom(prop, classIri)owl:someValuesFromnon-empty tupleAt least one value of prop is an instance of classIri
allValuesFrom(prop, classIri)owl:allValuesFromreadonly array of element typeEvery value of prop is an instance of classIri
hasValue(prop, value)owl:hasValueproperty type is the literal valueprop carries the literal value (string/number/boolean)
cardinality(prop, n)owl:cardinalitylength-n readonly tuple (cap 16)Exactly n values
minCardinality(prop, n)owl:minCardinalityn mandatory prefix elementsAt least n values
maxCardinality(prop, n)owl:maxCardinalityunion of tuples length 0..nAt most n values

The compile-time narrowing applies to the property named in the restriction. cardinality(prop, N) produces an N-length tuple capped at 16; minCardinality(prop, N) produces a tuple with N mandatory prefix elements; maxCardinality(prop, N) produces a union of tuples of length 0..N.

Chaining

Compose.subClassOf is composable. Each call appends to the body's jt:restrictions:

/**
 * OWL restrictions — Chaining min and max cardinality
 *
 * Each `Compose.subClassOf` call appends a restriction blank node to
 * the body's `jt:restrictions`. Chaining the minCardinality(1) and
 * maxCardinality(2) restrictions onto an Adult class produces two
 * `owl:Restriction` blank nodes attached via `rdfs:subClassOf`.
 *
 * Demonstrates the chain composition shape used by the bookstore's
 * `RareBookSchema` (which layers `someValuesFrom` and
 * `maxCardinality` on `authors`).
 */

import { Compose } from '../../../src/index.js';

const PARENT_PROP = 'urn:example:parent';

const AdultSchema = Compose.subClassOf(
  Compose.minCardinality(PARENT_PROP, 1),
  Compose.subClassOf(
    Compose.maxCardinality(PARENT_PROP, 2),
    {
      '$id': 'urn:example:Adult',
      'type': 'object'
    } as const
  )
);

const adultId: string = AdultSchema.$id;

console.assert(adultId.endsWith('Adult'));
console.log('Adult schema with chained min/max cardinality restrictions:', adultId);
console.log('restrictions:', (AdultSchema as Record<string, unknown>)['jt:restrictions']);
Output
Press Execute to run this example against the real library.

The TBox carries two owl:Restriction blank nodes attached via rdfs:subClassOf.

Don't use this when

  • Use Compose.equivalent to declare two classes have identical extension. That maps to owl:equivalentClass, not a property restriction.
  • Use Compose.extend for structural inheritance (allOf + $ref). Restrictions are property-level axioms - they say "values of this property satisfy X", not "this class also has these properties".
  • Don't use cardinality/minCardinality/maxCardinality to drive structural validation. JSON Schema's required, minItems, and maxItems already cover those at instance time. Restrictions are purely TBox semantic content for reasoners.

Examples

Example 1: Exact cardinality: PersonWithExactlyTwoParents

import {
  Compose, JsonTology
} from '../../../src/index.js';

const PARENT = 'https://bookstore.example/parent';

const PersonWithExactlyTwoParents = Compose.subClassOf(
  Compose.cardinality(PARENT, 2),
  {
    '$id': 'https://bookstore.example/PersonWithExactlyTwoParents',
    'type': 'object'
  } as const
);

// doc example with synthetic fixture schemas (strict-graph default does not throw because no inline duplicates)
const jt = JsonTology.create({
  'baseIri': 'https://bookstore.example',
  'schemas': [PersonWithExactlyTwoParents] as const
});

console.log(jt.toTbox().jsonLd());
// {
//   "@id": "https://bookstore.example/PersonWithExactlyTwoParents",
//   "@type": "owl:Class",
//   "rdfs:subClassOf": [{
//     "@type": "owl:Restriction",
//     "owl:onProperty": { "@id": "https://bookstore.example/parent" },
//     "owl:cardinality": 2
//   }]
// }
Output
Press Execute to run this example against the real library.

Example 2: someValuesFrom: book authored by at least one author

import { Compose } from '../../../src/index.js';

const AUTHORED_BY = 'https://bookstore.example/authoredBy';
const AUTHOR_IRI = 'https://bookstore.example/Author';

const AuthoredBookSchema = Compose.subClassOf(
  Compose.someValuesFrom(AUTHORED_BY, AUTHOR_IRI),
  {
    '$id': 'https://bookstore.example/AuthoredBook',
    'type': 'object'
  } as const
);

console.log('AuthoredBook schema $id:', AuthoredBookSchema.$id);
console.log('someValuesFrom restriction on authoredBy:', (AuthoredBookSchema as Record<string, unknown>)['jt:restrictions']);
Output
Press Execute to run this example against the real library.

Example 3: Chaining restrictions: at least one author, all authors are Author instances

import { Compose } from '../../../src/index.js';

const AUTHORED_BY = 'https://bookstore.example/authoredBy';
const AUTHOR_IRI = 'https://bookstore.example/Author';

// TBox carries two owl:Restriction blank nodes on rdfs:subClassOf
const VerifiedAuthoredBookSchema = Compose.subClassOf(
  Compose.minCardinality(AUTHORED_BY, 1),
  Compose.subClassOf(
    Compose.allValuesFrom(AUTHORED_BY, AUTHOR_IRI),
    {
      '$id': 'https://bookstore.example/VerifiedAuthoredBook',
      'type': 'object'
    } as const
  )
);

console.log('VerifiedAuthoredBook schema $id:', VerifiedAuthoredBookSchema.$id);
console.log('chained restrictions:', (VerifiedAuthoredBookSchema as Record<string, unknown>)['jt:restrictions']);
Output
Press Execute to run this example against the real library.

Example 4: hasValue: mark in-print books

import { Compose } from '../../../src/index.js';

const IN_STOCK = 'https://bookstore.example/inStock';

// TypeScript narrows the inStock property type to the literal `true`
const InPrintBookSchema = Compose.subClassOf(
  Compose.hasValue(IN_STOCK, true),
  {
    '$id': 'https://bookstore.example/InPrintBook',
    'type': 'object'
  } as const
);

console.log('InPrintBook schema $id:', InPrintBookSchema.$id);
console.log('hasValue restriction on inStock:', (InPrintBookSchema as Record<string, unknown>)['jt:restrictions']);
Output
Press Execute to run this example against the real library.

Bad examples: what NOT to do

Anti-pattern 1: Using cardinality restrictions to drive instance validation

import {
  Compose, JsonTology
} from '../../../src/index.js';

// ✗ Don't do this — owl:cardinality is a TBox semantic axiom for reasoners,
// NOT a runtime validation constraint on instance data
const _StrictBook = Compose.subClassOf(
  Compose.cardinality('https://bookstore.example/authors', 1),
  {
    '$id': 'https://bookstore.example/StrictBook',
    'type': 'object'
  } as const
);
// doc example with synthetic fixture schemas (strict-graph default does not throw because no inline duplicates)
const jt = JsonTology.create({
  'baseIri': 'https://bookstore.example',
  'schemas': [_StrictBook] as const
});

const restrictionResult = jt.validate('https://bookstore.example/StrictBook', {
  'authors': [
    'A',
    'B'
  ]
});

// Does NOT fail — restrictions are TBox-only, not checked at validate/instantiate time
console.log('owl:cardinality does NOT enforce at validate time:', restrictionResult.ok, '(passes even with 2 authors)');

// ✓ Do this — use JSON Schema keywords for instance validation
const StrictBook2Schema = {
  '$id': 'https://bookstore.example/StrictBook2',
  'properties': {
    'authors': {
      'maxItems': 1,
      'minItems': 1,
      'type': 'array'
    }
  },
  'type': 'object'
} as const;

const jt2 = JsonTology.create({
  'baseIri': 'https://bookstore.example',
  'schemas': [StrictBook2Schema] as const
});

const cardinalityResult = jt2.validate('https://bookstore.example/StrictBook2', {
  'authors': [
    'A',
    'B'
  ]
});

// Fails — maxItems is a JSON Schema keyword checked at validate time
console.log('maxItems/minItems enforce at validate time:', cardinalityResult.ok, '(rejects 2 authors)');
Output
Press Execute to run this example against the real library.

Anti-pattern 2: Using Compose.equivalent to express a property restriction

import { Compose } from '../../../src/index.js';

// ✗ Don't do this — equivalent expresses class identity, not property constraints
const _InPrintBook = Compose.equivalent(
  {
    '$id': 'https://bookstore.example/Book',
    'type': 'object'
  } as const,
  {
    '$id': 'https://bookstore.example/InPrintBook'
    // can't express owl:hasValue here — equivalent only supports $id / description / title
  }
);

// ✓ Do this — use Compose.subClassOf + Compose.hasValue
const _InPrintBook2 = Compose.subClassOf(
  Compose.hasValue('https://bookstore.example/inStock', true),
  {
    '$id': 'https://bookstore.example/InPrintBook2',
    'type': 'object'
  } as const
);

console.log('equivalent anti-pattern $id:', _InPrintBook.$id, '| preferred subClassOf+hasValue $id:', _InPrintBook2.$id);

Output
Press Execute to run this example against the real library.

Anti-pattern 3: Confusing minCardinality and JSON Schema minItems

import {
  Compose, JsonTology
} from '../../../src/index.js';

// ✗ Don't do this — minCardinality on a multi-valued property is an OWL axiom;
// it does NOT add a minItems constraint on the JSON Schema array
const authoredBook = Compose.subClassOf(
  Compose.minCardinality('https://bookstore.example/authors', 2),
  {
    '$id': 'https://bookstore.example/AuthoredBook',
    'type': 'object'
  } as const
);
// jt.validate('AuthoredBook', { authors: [] }) → passes (no minItems in JSON Schema)

// ✓ Do this — use minItems in the JSON Schema definition for runtime enforcement
const AuthoredBook2Schema = {
  '$id': 'https://bookstore.example/AuthoredBook2',
  'properties': {
    'authors': {
      'minItems': 2,
      'type': 'array'
    }
  },
  'type': 'object'
} as const;

const jt = JsonTology.create({
  'baseIri': 'https://bookstore.example',
  'schemas': [AuthoredBook2Schema] as const
});

const minItemsResult = jt.validate('https://bookstore.example/AuthoredBook2', { 'authors': [] });

console.log('anti-pattern — minCardinality does not add runtime enforcement (authoredBook.$id):', authoredBook.$id);
// Fails — minItems is a JSON Schema keyword checked at validate time
console.log('minCardinality is TBox-only — minItems enforces at validate time:', minItemsResult.ok, '(rejects empty authors)');
Output
Press Execute to run this example against the real library.

Comparison

ts
const InPrintBook = Compose.subClassOf(
  Compose.hasValue('https://bookstore.example/inStock', true),
  { $id: 'https://bookstore.example/InPrintBook', type: 'object' } as const
);
// Emits owl:Restriction blank node in TBox; TypeScript narrows inStock to literal true.
ts
// Zod has no OWL restriction concept. Compile-time narrowing is expressed
// directly via literal types:
const InPrintBook = z.object({ inStock: z.literal(true) });
// Limitation: no owl:Restriction emitted; no TBox; narrowing is structural,
// not ontological.
ts
import * as v from 'valibot';
const InPrintBook = v.object({ inStock: v.literal(true) });
// Limitation: same as Zod — structural narrowing only, no OWL output.
ts
import * as t from 'io-ts';
const InPrintBook = t.type({ inStock: t.literal(true) });
// Limitation: codec-based narrowing; no ontological semantics; no TBox output.
ts
import { Type } from '@sinclair/typebox';
const InPrintBook = Type.Object({ inStock: Type.Literal(true) });
// Limitation: JSON Schema literal constraint; no owl:Restriction; no TBox.
ts
const inPrintBookSchema = {
  $id: 'https://bookstore.example/InPrintBook',
  type: 'object',
  properties: { inStock: { const: true } },
};
// Limitation: JSON Schema const; validates instances but emits no OWL.
py
from typing import Literal
class InPrintBook(BaseModel):
    in_stock: Literal[True]
# Limitation: Python Literal type; no owl:Restriction; no RDF/OWL output.
ts
// Limitation: feature not directly supported in Yup. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Joi. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Effect Schema. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in ArkType. See /comparisons for the matrix.
ts
// Limitation: feature not directly supported in Runtypes. See /comparisons for the matrix.

See also

Released under the MIT License.