LPG Modeler
VS Code extension

Design a property graph once. Generate every schema from it.

LPG Modeler turns one hand-editable model file into LadybugDB DDL, Neo4j constraints, SHACL shapes and an OWL ontology — and tells you, before you ship, exactly what each database is unable to enforce.

The model file is parsed into an intermediate representation, resolved across imports, validated, then emitted to Ladybug DDL, Neo4j constraints, SHACL shapes and an OWL ontology. Anything a target cannot enforce is reported as a diagnostic.
The YAML model is the only thing you edit. Everything downstream is generated, and nothing is generated from a model that has errors.

The problem it removes

A graph schema usually lives in four places at once: the DDL that creates the tables, the constraint script someone ran last quarter, a diagram in a wiki that stopped being true, and an ontology maintained by a different team. They drift. LPG Modeler makes three of those four generated artifacts.

Before

Four sources, one truth, no agreement

The diagram says a property is required. The database never enforced it. Nobody finds out until a null reaches production.

After

One source, generated outputs

The model states the constraint. Every target either enforces it or declares, in writing, that it cannot.

Kept honest

Checked in CI

The same validation the editor runs also runs as a command, so a pull request can be gated on the model being valid.


What you get

Six things the extension does that a diagram tool or a hand-written DDL file cannot.

A canvas that writes the file Create node types, draw edges, add properties and rename things directly on the diagram. Every action becomes a targeted edit to the YAML, applied as a workspace edit — so undo, dirty state and version control all behave normally.
Diffs you can actually review Edits are computed as text splices, never as a re-serialization. Renaming a type changes exactly the lines that name it. Diagram coordinates live in a separate sidecar, so rearranging a diagram produces no semantic diff at all.
Four generators, one model LadybugDB node and relationship tables, Neo4j constraints and indexes, SHACL node shapes, and an OWL ontology — all from the same type hierarchy, keys and properties, with no second definition to keep in sync.
Loss is reported, never silent Each target publishes what it can express. Everything it cannot becomes a warning in the Problems panel and a comment at the exact line of the generated file, so the operator reading the DDL learns what the author already knew.
Schema-driven YAML editing The model format ships as a JSON Schema the extension contributes, so completion, hover documentation and structural errors come from the YAML tooling you already have.
Views, so diagrams stay readable A view names a subset of types plus an optional neighbourhood expansion. One model can carry an overview beside several focused diagrams, and validation tells you which types have fallen out of every view.

From this model, these artifacts

A small social domain: an abstract Party contributing the key, two concrete subtypes, a mixin, and an edge that carries a property. Every tab below is real generator output for the model on the left.

social.lpg.yaml

namespace:
  prefix: social
  iri: https://example.org/vocab/social#

mixins:
  Timestamped:
    props:
      createdAt: { type: datetime, required: true }

nodes:
  # Abstract root: gives the key to every descendant.
  Party:
    abstract: true
    key: [id]
    props:
      id: { type: string, required: true }

  Person:
    extends: Party
    mixins: [Timestamped]
    props:
      email: { type: string, required: true, unique: true }
      born: { type: date }

  Company:
    extends: Party
    props:
      vat: { type: string }

  Car:
    key: [vin]
    props:
      vin: { type: string, required: true }
      seats: { type: int }

edges:
  # Abstract endpoint: expands per concrete subtype.
  OWNS:
    from: Party
    to: Car
    props:
      since: { type: date }

  # No properties: stays a plain relation in RDF.
  LIKES:
    from: Person
    to: Company

Generated output

// Abstract node types are flattened to one table per
// concrete type, with inherited properties copied down.

// Abstract, no table emitted: Party

CREATE NODE TABLE IF NOT EXISTS Company (
  vat STRING,
  id STRING,
  PRIMARY KEY(id)
);

CREATE NODE TABLE IF NOT EXISTS Person (
  // UNENFORCED: 'email' is required in the model;
  // LadybugDB has no NOT NULL.
  // UNENFORCED: 'email' is unique in the model;
  // only the primary key is unique.
  email STRING,
  born DATE,
  createdAt TIMESTAMP,
  id STRING,
  PRIMARY KEY(id)
);

CREATE REL TABLE IF NOT EXISTS OWNS (
  // 'Party' is abstract; expanded to 2 endpoint pairs.
  FROM Company TO Car,
  FROM Person TO Car,
  since DATE
);

CREATE REL TABLE IF NOT EXISTS LIKES (
  FROM Person TO Company
);

Nothing disappears quietly

Three kinds of loss exist before a line of generator code is written: LadybugDB has no multi-label nodes, Neo4j existence and node-key constraints are Enterprise-only, and OWL is open-world where a schema is closed-world. Each one is stated rather than absorbed.

A matrix of five model features against four targets, showing which are enforced natively, which are downgraded, and which are transformed.
A required property that quietly vanishes is a data-integrity bug that surfaces in production. Here it surfaces in the Problems panel instead. See Targets for the full matrix.

The canvas edits the file, not a copy of it

The diagram is a companion view beside the YAML, in the manner of Markdown preview — not a replacement editor. It holds no model state: it posts an intent, the extension host computes the edits, and a fresh diagram comes back from the file.

The canvas posts an intent to the extension host, which splices the YAML file, which is re-resolved into a fresh projection sent back to the canvas.
Because the round trip always ends at the file, nothing on the canvas can diverge from what your teammates will review.
Three files: the model holds semantics, a views sidecar holds diagram scope, and a layout sidecar holds coordinates keyed by stable element ids.
Coordinates never touch the model file, and layout is keyed by a stable element id — so renaming a type moves nothing on screen, and a rename stays distinguishable from a delete plus an add.

Serious about RDF

A property graph schema and an OWL ontology do not mean the same thing, so the RDF export is split in two. SHACL carries the constraints because it is closed-world validation. OWL asserts only what is safe to assert.

An edge with no properties becomes a plain object property; an edge carrying properties becomes an n-ary relation class plus a shortcut property.
Reification is applied only where it is needed. Emitting rdfs:domain for an edge does not constrain anything — it instructs a reasoner to reclassify unrelated individuals, which inverts the meaning of the model rather than merely losing some of it.

Start here