Docs

Value Types & Interfaces

Three mechanisms compose the building blocks from Object Types, Properties, and Links: value types share a property shape and extends inherits a parent's structure — both real reuse — while interfaces classify a type by the cross-cutting roles it plays (a label, not reuse).

Value Types

A value type is a named, reusable property shape. Define it once with defineValueType, then point many properties at it with valueTypeRef. The schema and semantics live in one place, so they stay consistent and evolve together.

TS
import { defineValueType } from "@sixb/core/ontology"

export const MoneyAmount = defineValueType({
  id: "MoneyAmount",
  name: "Money Amount",
  description: "A monetary value, paired with a currency property.",
  schema: "double",
})

defineValueType fields

FieldTypeRequiredDescription
idstringyesUnique value-type id, referenced by valueTypeRef.
namestringyesDisplay name.
schemaSchemayesThe shared shape — a primitive, enum, object, or array.
descriptionstringnoHuman-readable notes.
semanticTypeQuantitativeTypeIdnoA physical quantity; constrains valid units. See Units & Semantics.

Money is modeled as an amount (double) plus a currency enum — never with semanticType/units. semanticType is only for physical readings; see Units & Semantics.

When semanticType is set, every property that references the value type inherits the constraint, and only units belonging to that quantity are valid.

Referencing with valueTypeRef

valueTypeRef produces a schema you pass to prop. It has three forms:

TS
import { defineObjectType, prop, valueTypeRef } from "@sixb/core/ontology"
import { MoneyAmount } from "./value-types"

export const Project = defineObjectType({
  id: "Project",
  name: "Project",
  properties: [
    // 1. Pass the value type — schema resolves inline, no registry lookup
    prop("budget", valueTypeRef(MoneyAmount)),
    // 2. Reference by id — resolved from the registered value types
    prop("forecast", valueTypeRef("MoneyAmount")),
  ],
})
FormWhen to use
valueTypeRef(ValueType)You have the object in scope. Schema resolves inline — no registration needed.
valueTypeRef("id")Reference by id; resolved against the value types in the ontology.
valueTypeRef("id", schema)Escape hatch: supply the resolved schema explicitly alongside the id.

Refs by id string must be able to resolve. Register the value type in defineOntology({ valueTypes: [...] }), or rely on convention-based discovery. The object form carries its own schema and needs no registration.

Interfaces

An interface classifies the cross-cutting roles a type plays — "is auditable", "is billable" — that don't fit a single inheritance chain. Unlike value types and extends, an interface does not add structure: it is declarative classification metadata, not code reuse.

Define the role, and optionally document the shape you expect it to carry:

TS
import { defineInterface, prop, link } from "@sixb/core/ontology"

export const Auditable = defineInterface({
  id: "auditable",
  name: "Auditable",
  description: "Anything that tracks who created it and when.",
  properties: [prop("createdAt", "timestamp"), prop("updatedAt", "timestamp")],
  links: [link("createdBy", "Employee", { cardinality: "one" })],
})

properties and links describe the intended contract for readers and tooling (both default to []); Sixb does not inject them into implementing types or validate that a type satisfies them.

Implementing an interface

Object types declare the roles they play by id via implements:

TS
import { defineObjectType, prop } from "@sixb/core/ontology"

export const Invoice = defineObjectType({
  id: "Invoice",
  name: "Invoice",
  implements: ["auditable"],
  properties: [
    prop("id", "string", { required: true, primary: true }),
    prop("number", "string"),
    // Declare the interface's members yourself — they are not inherited.
    prop("createdAt", "timestamp"),
    prop("updatedAt", "timestamp"),
  ],
})

implements is a plain list of ids recorded on the object type, so one type can carry several roles: implements: ["auditable", "billable"]. Because it is classification only, the interface's createdAt/updatedAt/createdBy members must still be declared on each implementing type (or inherited via extends). Reach for an interface when you want a shared label; reach for extends when you want shared structure.

Inheritance with extends

extends makes an object type inherit the properties and links of a parent. Pass the parent object (recommended — properties and links merge at build time) or its id string.

TS
import { defineObjectType, link, prop, stringEnum } from "@sixb/core/ontology"
import { Employee } from "./employee"
import { Project } from "./project"

export const Document = defineObjectType({
  id: "Document",
  name: "Document",
  properties: [
    prop("id", "string", { required: true, primary: true }),
    prop("title", "string", { required: true }),
    prop("type", stringEnum(["proposal", "contract", "specification", "report"])),
    prop("createdAt", "timestamp"),
  ],
  links: [
    link("project", Project, { cardinality: "one" }),
    link("author", Employee, { cardinality: "one" }),
  ],
})

// Inherits all Document properties and links, then adds its own.
export const Contract = defineObjectType({
  id: "Contract",
  name: "Contract",
  extends: Document,
  properties: [prop("signedAt", "timestamp"), prop("value", "double")],
})

Contract.properties is the merged set: id, title, type, createdAt, signedAt, and value. Merge is by id, so an own definition overrides an inherited one with the same id.

extends vs parents

FieldTypePurpose
extendsstring | ObjectTypeThe primary structural parent. Its properties and links merge in.
parentsstring[]Extra parent ids for multi-parent classification (no merge).

Passing an object to extends also records its id in parents. Use parents when a type belongs to several classifications but inherits structure from one.

Choosing between them

MechanismReusesUse when
Value typeOne property shapeMany properties share a schema (and maybe a semantic).
InterfaceNothing — classificationYou want to label a cross-cutting role; each type still declares its own structure.
ExtendsA full parent typeA subtype is-a parent and should inherit its structure.

Search docs

Search the documentation