Skip to content

The Schema model & OSD ​

A Schema is Omnist's other central feature, alongside OML. It's written as OSD (Omnist Schema Definition) -- a small, closed text language for describing the shape a Document must have: record definitions -- a closed set of named fields, each with a cardinality -- plus a root saying which one a document validates against. There's no JSON Schema-style open-ended composition: a field's type is always exactly one fixed scalar or one reference to another record, never a union, enum, or literal value.

ts
import { parseSchema, doc } from "@omnist-dev/omnist";

const s = parseSchema(`
    record Database { "type": string, "server": string, "port": integer }
    record Service  { "host": string, "port": integer,
                       "databases" [1,]: Database, "tags" [0,]: string }
    root Service
`);
s.validate(doc({
  host: "api.internal", port: 8443,
  databases: [{ type: "prod", server: "db1.internal.example.com", port: 5432 }],
  tags: ["prod", "us-east"],
})).ok; // true

Shape ​

record Address { "street": string, "city": string }

record User {
    "name":          string,        # required (default cardinality [1,1])
    "nickname" [0,1]: string,        # optional
    "emails" [1,]:    string,        # one or more (an array)
    "address":       Address,        # Ref to a named record
    "note":          string?,       # nullable scalar
}
root User
  • Field labels are always quoted (they're data strings, and may contain spaces: "home address"). An unquoted identifier in type position is a schema name -- a scalar keyword (string, integer, ...) or a Ref to a record.
  • Cardinality [min,max] is the only multiplicity knob: [1,1] required (the default -- omit the brackets), [0,1] optional, [0,] zero-or-more, [1,] one-or-more, [2,5] bounded. There is no separate array type -- an array is just a field with max > 1.
  • A field's type is always exactly one of the seven fixed scalars (string, integer, number, boolean, date, time, datetime), optionally suffixed ? for nullable (string?), or a Ref (an unquoted name) to a named record. ? is independent of cardinality -- a required field can still be nullable (it must appear, but its value may be null). ? never applies to a Ref; a record that may be absent is [0,1], never Ref?.
  • Records are closed -- an unexpected label is a validation error, not silently ignored.

A Schema is checked however it was built. Programmatically, new Schema() rejects a record or reference name outside [A-Za-z_][A-Za-z0-9_]* (schema.invalid-name, path $, the name in the message only) and a field label that cannot encode to UTF-8, i.e. one holding a lone surrogate (schema.invalid-label, at the record path; field() and record() raise it without a path). A [0,0] field is representable but has no OSD spelling, so toOsd fails on one with write.unsupported-value at the record path; call prune() first. field(label, type, 0, 0) builds such a field (since 0.10.1-alpha; before it threw schema.invalid-cardinality); OSD text still rejects [0,0]. This port has no caller-supplied record ordering, so schema.unknown-record (S-23) cannot arise. None of these has a conformance vector (spec DIV-5); test/schema-v028.test.ts is the only pin.

All of this is defined formally, with proofs, in the spec's Schema Model chapter.

The any type ​

A field may be typed any: its value is accepted unchecked -- any scalar, null, or a nested subtree of any shape -- and validation does not descend into it. The field's label is still fixed, declared, and counted: cardinality applies exactly as for every other field. Only the value's shape is unconstrained. See the spec's any type section for the formal spec.

ts
import { doc, parseSchema } from "@omnist-dev/omnist";

const s = parseSchema(`
record Event {
    "id":      string,
    "type":    string,
    "data":    any,
}
root Event
`);

// Two payloads with completely different shapes -- one schema accepts both.
s.validate(doc({ id: "evt_1", type: "user.created",
                  data: { name: "Ann", email: "a@x.com" } })).ok;      // true
s.validate(doc({ id: "evt_2", type: "payment.settled",
                  data: { amountCents: 1250, currency: "EUR" } })).ok; // true

Validation ​

schema.validate(doc) returns a ValidationResult: .ok plus, on failure, an array of field-path-qualified errors. schema.validates(doc) is a shorthand for schema.validate(doc).ok.

Operations ​

  • schema.compatibleWith(other) -- true iff every document other accepts, schema also accepts (backward compatibility).
  • schema.equivalent(other) -- true iff the two schemas accept exactly the same set of documents.
  • schema.normalize() -- a canonical form: merges equivalent records, useful before comparing schemas structurally.
  • schema.prune() / schema.isEmpty() -- remove/detect unsatisfiable branches (a schema whose language is empty, e.g. contradictory cardinality).
  • extract(schema, keep) -- the sub-schema reachable from a subset of root fields.
  • isomorphic(a, b) -- true iff the two schemas are the same graph up to record renaming.
  • lint(schema) -- structural findings (unreachable records, redundant fields) short of an outright validation error.

See the API reference for exact signatures and the spec's Schema Model chapter for the formal semantics behind "compatible" and "equivalent."

OSD grammar ​

The full ABNF grammar, verified against the parser, lives at the spec's OSD grammar chapter.