VS Code extension · ORM 2

Model the facts,
not the tables.

Object-Role Modeling in VS Code. Draw a conceptual schema, read it back as sentences a domain expert can confirm, and map it to a relational or property graph schema — then keep the whole thing in git, where your build can check it and your coding agent can read it.

reads & writes .orm .fbm Apache Ossie UMS free & MIT

The Factum diagram editor showing an ORM schema for Person, Company and Skill, with the live FORML verbalization panel beside it.
An ORM 2 diagram and its live verbalization. Every sentence is generated from the model.

Three ways in

The same model, the same rules, three surfaces. Nothing is locked inside the editor — the core has no dependency on VS Code at all, which is why the other two exist.

The editor

A real ORM 2 diagram editor as a VS Code custom editor, with live verbalization, validation in the Problems panel, and both mappings in a side panel.

$

The command line

factum validate, verbalize, diff, drift, ddl, derive. Run it in CI and the build fails when someone breaks the model.

Your coding agent

factum-mcp is an MCP server over the model, so Claude Code or Copilot reads the conceptual schema instead of guessing it from your table names.

Why fact-based modeling

ORM describes a domain as elementary factsPerson works for Company — rather than as tables or classes. Because those facts carry no attributes, every constraint has to be stated explicitly, and the whole model can be read back in plain language.

Validated by people, not guesswork

A schema you can read aloud is a schema a domain expert can approve or reject, before a single table exists.

No premature attributes

Deciding what becomes a column is a design step, not a modeling step. ORM keeps the two apart, so the model outlives the schema.

One model, two targets

The same conceptual schema maps to relational tables and to a labeled property graph. Compare them side by side and pick.

What it does

Everything runs locally, in a VS Code custom editor over a plain JSON file.

Full ORM 2 notation

Entity and value types with reference modes, unary through n-ary fact types, multiple readings, subtyping, objectification and derived fact types.

Every constraint

Internal and external uniqueness, preferred identifiers, simple and disjunctive mandatory, frequency, all ten ring types, subset, exclusion, equality, value and cardinality — each with an alethic or deontic modality.

Live verbalization

A FORML rendering of the model that updates as you draw. Click a sentence to select what it describes.

Validation as you go

Missing reference schemes, fact types without uniqueness, constraints too narrow to keep a fact type elementary, subtype cycles — all in the Problems panel.

Four formats, both ways

NORMA .orm, the FBM Exchange MetaModel, Apache Ossie ontologies and the Unified Modelling Schema — imported and exported, not just read.

Sample populations

Real example rows stored with the model. The verbalizer substitutes them into the readings, and the validator checks your constraints against them.

Export & layout

SVG and PNG export for papers and docs, plus deterministic force-directed auto-layout.

Read your model back in English

Verbalization is what makes a conceptual schema reviewable. Factum generates it from the constraints themselves, so the sentences cannot drift from the diagram.

Each Person works for exactly one Company.
In each population of "Person has Skill", each Person, Skill combination occurs at most once.
It is necessary that the possible values of GenderCode are {'M', 'F'}.
Each Manager is a kind of Person.

A mandatory role plus a uniqueness constraint reads as exactly one; drop the mandatory dot and the same fact type reads at most one. The wording follows the model.

Two mappings from one schema

A conceptual schema is not a database design. Factum derives both, and explains every choice it made rather than leaving you to reverse-engineer the output.

Relational — Rmap

Functional fact types are absorbed as columns, compound-unique fact types get their own table, unaries become booleans, mandatory roles become NOT NULL, and value constraints become CHECK constraints. Emits PostgreSQL, SQL Server, MySQL, SQLite or ANSI SQL.

CREATE TABLE "Person" (
    "personNr" integer NOT NULL,
    "companyName" varchar(255) NOT NULL, -- From "Person works for Company"
    "genderCode" varchar(1) NOT NULL,
    CONSTRAINT "PK_Person" PRIMARY KEY ("personNr"),
    CONSTRAINT "CK_Person_genderCode" CHECK ("genderCode" IN ('M', 'F'))
);

Property graph — LadybugDB

ORM knows more than a hand-drawn graph model does, and the mapping uses it: uniqueness constraints become relationship multiplicities, lexical value types become properties, and an n-ary fact type — which no edge can hold — is reified into a node.

CREATE NODE TABLE Person(nr INT64 PRIMARY KEY, genderCode STRING);
CREATE REL TABLE WORKS_FOR(FROM Person TO Company, MANY_ONE);
CREATE REL TABLE HAS_STUDENT(FROM Enrolment TO Student, MANY_ONE);

// Constraints the schema cannot enforce:
//   [mandatory] It is necessary that each Person works for some Company.
The Graph tab listing node tables, relationship tables with their multiplicities, and the constraints the schema cannot enforce.
The Graph tab: node tables, relationships with multiplicities, and the rules a property graph cannot hold.

Learn the method, not just the tool

Factum ships with a short book on ORM 2 — elementary facts, the constraint family, subtyping and objectification, and Halpin's seven-step design procedure worked end to end. Every diagram in it was drawn by the extension, and every figure links to the model behind it, so you can open any example and take it apart.

02

Elementary facts

What an elementary fact is, the and-test for splitting compound ones, and the population check that catches invented fact types.

05

Uniqueness

The constraint that carries the most meaning, the three binary patterns, and the arity check that tells you when an n-ary fact type should be split.

09

The design procedure

The seven steps of the CSDP, applied to a real staff directory report from first sentence to finished schema.

You don’t have to pick a format

The fact-based modelling community has no agreed exchange standard, and three candidates are in play. Rather than back one, Factum reads and writes all of them — so a model can move between tools instead of being trapped in whichever one drew it.

FormatImportExportWhat it is
.ormYesYesNORMA’s ORM 2 XML, the de-facto archive format
.fbmYesYesThe FBM Exchange MetaModel, built for interchange
OssieYesYesApache Ossie’s ontology, an incubating ASF standard
UMSYesYesThe Unified Modelling Schema, a logical target

Every conversion reports what it could not carry rather than dropping it quietly. Exporting a model with an objectified fact type to Ossie tells you Ossie has no objectification; importing UMS tells you that what came back is the shape of the data rather than the facts behind it.

It reviews like code

A model stored as text can go through the same gate as everything else you ship. The bundled GitHub Action validates it and comments on the pull request with what the model now says — not with the JSON that changed.

- Each Person works for at most one Company.
+ Each Person works for exactly one Company.
+ It is necessary that each Employee has a Salary.

That is a reviewer reading one sentence and knowing whether to approve it. The same comparison runs locally with factum diff, and --exit-code lets a job fail on it.

In your workflow

- uses: Volland/factum-orm@v0
  with:
    model: model/domain.orm.json
    base: /tmp/base.orm.json
    strict: 'true'

Or in any shell

factum validate model.orm.json --format github
factum diff before.orm.json after.orm.json
factum drift model.orm.json schema.sql
factum derive people.csv -o people.orm.json

Check the model against reality

Against your own examples

Fact types carry real sample rows, stored in the same file. The verbalizer substitutes them back into the readings, so a domain expert reads “101 works for Acme” rather than a placeholder.

The validator then checks the constraints you drew against the examples you gave. A uniqueness, mandatory, frequency or value constraint your own data contradicts is reported — not believed.

Against the database you deployed

factum drift compares the schema your model maps to against SQL that already exists — a pg_dump, a migration — and prints the differences with the ALTER statements that would reconcile them.

It reads SQL text rather than connecting, so there is no driver to install and nothing to grant access to.

Start from data, not a blank diagram

The hardest part of modelling is the first fact type. Point Factum at a spreadsheet export and it proposes a first draft — the step both Halpin’s design procedure and FCO-IM actually begin with.

$ factum derive employees.csv

note: "employee_nr" is unique and never empty across all 240 rows,
      so it became the reference mode of Employee.
note: "department" takes only 4 distinct values, so a value
      constraint was proposed. Confirm the list is closed.

An identifying column becomes the entity’s reference mode, types and enumerations are inferred from the values, and every row is kept as a sample fact. Nothing is proposed that the data does not support, and each assumption comes back as a note for you to confirm.

Models are just files

A model is one .orm.json file holding the schema, its layout and its examples, described by a published JSON Schema so any tool can validate it. It diffs, merges and reviews like the rest of your source, and it is safe to hand-edit — the diagram is a custom editor over the text document, so VS Code's dirty state, undo stack and file watching all work normally.

Every element can carry metadata and per-target generation hints, and any x- prefixed key is yours to use — preserved untouched, so another tool can round-trip data we know nothing about.

Start modeling

Install the extension and run ORM: New ORM Model, or point factum derive at a CSV you already have. Either way you have a working schema in a minute — and it is a file you can commit.