OMGDB supports lightweight, declarative schema validation per collection. A collection can carry a spec that lists which fields are required, the expected type of named fields, foreign-_id references to other collections, and unique fields (single or compound). Rules are enforced when documents are written, and they live in the op-log as a define operation — so a packed or replayed store carries its own validation rules and remains self-describing.
Validation is deliberately narrow: the vocabulary is required / fields / refs / unique, nothing more. There is no JSON-Schema-style constraint language (no patterns, ranges, enums, or nested sub-schemas). See data model for the value types these rules reference.
Defining a collection spec
A spec is a JSON (or YAML) object describing one or more collections:
{
"collections": {
"users": {
"required": ["name"],
"fields": { "age": "long", "bio": "markdown" },
"unique": ["email", ["tenant", "email"]]
},
"posts": {
"fields": { "author": { "type": "objectId", "ref": "users" } },
"refs": { "reviewer": "users" }
}
}
}
required— an array of field names that must be present on every document. Each entry must be a string.fields— an object mapping a field name to its expected type: either a type-name string (see the type table) or an object{ "type": ..., "ref": ... }combining a type with a reference.refs— an object mapping a field name to the collection whose_idit must reference (a collection-name string, or{ "collection": "users", "field": "_id" }— only_idis supported as the target field).unique— an array of uniqueness constraints: a field-name string for a single-field constraint, or an array of field names for a compound one.
All keys are optional. A collection with none of them imposes no constraints.
You attach a spec to a store at creation time with --from:
omgdb create app.omgdb --from spec.json
The CLI reads the spec file, defines each entry in the top-level collections object on the new store, and creates a unique index for every unique entry. On success it prints what it did:
created store at app.omgdb (2 collection(s) defined, 2 unique index(es))
Note: The spec file may be JSON or YAML — a
.yaml/.ymlextension selects a minimal YAML subset (mappings, sequences, inline arrays,#comments; tabs and multi-line block scalars are not supported). The top level must contain acollectionsobject; otherwiseomgdb create --fromfails withspec must contain a `collections` object.
Defining a collection is idempotent with respect to replay: each define is recorded as a define op in the log, and replaying the log rebuilds the validation catalog. Re-defining a collection replaces its rules. Because the rules are persisted in the log, reopening, packing, or compacting the store preserves them — the database describes its own constraints. omgdb describe renders each collection’s spec (and its indexes) as part of the live manual — see introspection.
Supported field type names
A field’s expected type is given as one of the type-name strings below. The vocabulary is checked when the spec is defined: an unknown or misspelled type fails at define time with field `age` has unknown type `lng` (expected one of: array, binData, bool, date, double, long, markdown, null, number, object, objectId, string) — instead of silently producing a spec no insert could ever satisfy.
| Type name | Value kind | Example value |
|---|---|---|
null | JSON null | null |
bool | Boolean | true |
long | 64-bit signed integer | 42 |
double | 64-bit float | 3.14 |
number | Any numeric value — matches both long and double | 42 or 3.14 |
string | UTF-8 string | "ada" |
markdown | Markdown text — validated as a string | "# Title\n\nBody" |
binData | Binary data | {"$binary":"..."} |
array | Array | [1, 2, 3] |
object | Embedded document | {"k": 1} |
objectId | 12-byte ObjectId | {"$oid":"..."} |
date | Timestamp (ms since epoch) | {"$date": 0} |
Note: A bare JSON integer such as
36is along, while36.0is adouble— they are distinct types, and a field declaredlongrejects a value written with a decimal point. Declarenumberwhen either numeric kind is acceptable.markdowndeclares intent — the field holds Markdown text, as produced by Markdown import — and the type check enforces that the value is a string.
How rules are enforced
Validation runs on write, at the moment a document enters a collection that has a spec:
Store::insert_onevalidates the document before it is appended to the log. A violation returns a validation error and writes nothing.- Inserts inside a transaction (
Txn::insert_one) are validated the same way; a violation makes the closure return an error, which aborts the whole transaction. Store::replace_one(andTxn::replace_one) validate the replacement document, so an update cannot move a document into an invalid state.
The check covers:
- Required fields — every name in
requiredmust be present on the document. A missing one fails withmissing required field `name`. - Field types — for each field present on the document that also appears in
fields, the value’s actual type must match the declared type. A mismatch fails withfield `age` should be `long` but is `string`. - References — for each field with a
ref, the value must be the_idof an existing document in the referenced collection. Anullvalue passes (an optional reference), an array is checked element by element, and a dangling reference fails withfield `author` references missing `users._id` .... - Uniqueness — each
uniqueentry is enforced by a unique secondary index (single-field or compound), so a write that duplicates an existing key is rejected. See indexes for unique-index semantics.
Limitation: Type rules apply only to fields that are present. A field listed in
fieldsbut not inrequiredis unconstrained when absent — declaring a type does not make a field mandatory. Userequiredfor presence andfieldsfor typing. There is no constraint on fields that appear in a document but not in the spec; extra fields are always allowed.
A passing and a failing insert
Given the users spec above (required: ["name"], fields: {"age": "long"}):
# Passes: name is present, age is a long.
omgdb insert app.omgdb users '{"name":"ada","age":36}'
# Fails: missing the required field `name`.
omgdb insert app.omgdb users '{"age":5}'
# Fails: `age` is declared `long` but the value is a string.
omgdb insert app.omgdb users '{"name":"x","age":"old"}'
A passing insert prints the assigned _id (an ObjectId, e.g. {"$oid":"..."}). A failing insert is rejected with a validation error and leaves the store unchanged.
Auditing existing documents
Specs are enforced going forward, but a collection may already hold documents — for example a spec defined after data was inserted, or documents introduced by an external producer that appended to the log. The validate command scans a collection and reports every document that does not satisfy the current rules (required fields, types, and references):
omgdb validate app.omgdb users
When every document is valid (or the collection has no rules) it prints a single success line:
valid: no violations in `users`
When there are offenders it prints one line per violating document — the document’s _id as canonical JSON, followed by the reason — and exits non-zero:
{"$oid":"6630c0..."}: missing required field `name`
{"$oid":"6630c1..."}: field `age` should be `long` but is `string`
2 document(s) violate validation rules
Each document is reported with the first rule it violates. validate is read-only: it never modifies documents or the log, so it is safe to run as an audit step in CI or before compaction.
End-to-end example
# spec.json: {"collections":{"users":{"required":["name"],"fields":{"age":"long"},"unique":["email"]}}}
omgdb create app.omgdb --from spec.json
omgdb insert app.omgdb users '{"name":"ana","age":30,"email":"[email protected]"}'
omgdb insert app.omgdb users '{"name":"bob","email":"[email protected]"}' # rejected: duplicate email
omgdb validate app.omgdb users # -> valid: no violations in `users`
The rules survive reopening the store: because the define op is in the log, a fresh omgdb process replays it and enforces the same constraints. To see the inferred shape of a collection alongside its declared rules and indexes, use introspection via omgdb describe.
Limitations
Limitation: Validation is intentionally narrow and covers only what is listed here.
- The vocabulary is
required(presence),fields(types),refs(foreign_idreferences), andunique(single/compound uniqueness). No ranges, regex/patterns, enums, length limits, or conditional rules — this is not JSON Schema. - Type checks apply to the top-level field’s value type. There is no coercion (a
doubleis not accepted where alongis declared — declarenumberfor either) and no validation of array element types or nested sub-document fields. - References target only the
_idof the referenced collection, and referential integrity is checked on write — deleting a referenced document is not blocked retroactively. - Rules apply to
insertandreplace. They are not re-checked retroactively when a spec changes — useomgdb validateto find pre-existing offenders.
For more on the value types referenced by fields, see data model. For a hands-on walkthrough of creating a store and inserting documents, see the quickstart.