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.
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 bodyUse 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)');
The TBox emits:
{
"@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
| Method | Predicate | TypeScript narrowing | OWL semantics |
|---|---|---|---|
someValuesFrom(prop, classIri) | owl:someValuesFrom | non-empty tuple | At least one value of prop is an instance of classIri |
allValuesFrom(prop, classIri) | owl:allValuesFrom | readonly array of element type | Every value of prop is an instance of classIri |
hasValue(prop, value) | owl:hasValue | property type is the literal value | prop carries the literal value (string/number/boolean) |
cardinality(prop, n) | owl:cardinality | length-n readonly tuple (cap 16) | Exactly n values |
minCardinality(prop, n) | owl:minCardinality | n mandatory prefix elements | At least n values |
maxCardinality(prop, n) | owl:maxCardinality | union of tuples length 0..n | At 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']);
The TBox carries two owl:Restriction blank nodes attached via rdfs:subClassOf.
Don't use this when
- Use
Compose.equivalentto declare two classes have identical extension. That maps toowl:equivalentClass, not a property restriction. - Use
Compose.extendfor 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/maxCardinalityto drive structural validation. JSON Schema'srequired,minItems, andmaxItemsalready 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
// }]
// }
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']);
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']);
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']);
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)');
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);
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)');
Comparison
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.// 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.import * as v from 'valibot';
const InPrintBook = v.object({ inStock: v.literal(true) });
// Limitation: same as Zod — structural narrowing only, no OWL output.import * as t from 'io-ts';
const InPrintBook = t.type({ inStock: t.literal(true) });
// Limitation: codec-based narrowing; no ontological semantics; no TBox output.import { Type } from '@sinclair/typebox';
const InPrintBook = Type.Object({ inStock: Type.Literal(true) });
// Limitation: JSON Schema literal constraint; no owl:Restriction; no TBox.const inPrintBookSchema = {
$id: 'https://bookstore.example/InPrintBook',
type: 'object',
properties: { inStock: { const: true } },
};
// Limitation: JSON Schema const; validates instances but emits no OWL.from typing import Literal
class InPrintBook(BaseModel):
in_stock: Literal[True]
# Limitation: Python Literal type; no owl:Restriction; no RDF/OWL output.// Limitation: feature not directly supported in Yup. See /comparisons for the matrix.// Limitation: feature not directly supported in Joi. See /comparisons for the matrix.// Limitation: feature not directly supported in Effect Schema. See /comparisons for the matrix.// Limitation: feature not directly supported in ArkType. See /comparisons for the matrix.// Limitation: feature not directly supported in Runtypes. See /comparisons for the matrix.Related
Compose.equivalent-owl:equivalentClassfor class identityCompose.extend-rdfs:subClassOfvia allOf + $ref- Graph concepts (TBox / ABox)
sameAs- the ABox counterpart to class restrictions
See also
- Bookstore domain - schema definitions used in examples
- OWL TBox output - how restrictions appear in the emitted TBox