skip to the page

Installation

Loom is two packages. @jam-overture/loom is the framework — the tree, the change model and the Gate — with several entry points behind it. @jam-overture/loom-primitives is the starter library: the one hundred and six primitives every example on this site is built from.

Install both, and a React renderer to mount what they produce.

pnpm add @jam-overture/loom @jam-overture/loom-primitives react react-dom

You can skip the second one. The framework has no opinion about which primitives exist — it is a vocabulary you register, and the starter library is one somebody already wrote. Every page here uses it because a book about a language needs words in it.

What you actually need

zod is the only hard dependency — every primitive declares its props as a Zod schema, and validation happens at the seam where a tree becomes an element.

Everything else is a peer and optional, because the runtime is deliberately usable without any of it:

PeerYou need it when
reactyou are rendering. The tree itself has no opinion about React.
@jam-overture/loom-primitivesyou want the starter library rather than a vocabulary of your own.
drizzle-orm + postgresyou are persisting trees rather than holding them in memory.
@anthropic-ai/sdkyou want a model to interpret a sentence into a proposal.

A deployment with none of the three still builds trees, applies deltas and gates proposals. That is not an accident of packaging — the model is a seam, not the centre.

The entry points

ImportWhat is behind itFor
@jam-overture/loomThe tree, the delta, the ids, the builders, the Gate and the pipeline. Start here.any app
@jam-overture/loom/reactRendering: a tree to React elements, theme mounting, addressing, render diagnostics.any app
@jam-overture/loom-primitivesThe starter library — the primitives every example on this site is built from. Its own package, installed beside the framework.any app
@jam-overture/loom-primitives/compositionsThe starter library's fifty-seven bands under their own names, for assembling a page without going through the catalogue.any app
@jam-overture/loom/sdkDefining and registering primitives of your own, and the catalogue a model is shown.hosts
@jam-overture/loom/storePersisting trees and revisions: the store contract, and an in-memory implementation.hosts
@jam-overture/loom/postgresThe Postgres store. Same contract, a database behind it.hosts
@jam-overture/loom/writeThe write path: proposing, holding, confirming and applying a change.hosts
@jam-overture/loom/signalsReader signals — what a published page reports about how it is read, and the parser a receiver checks them with.hosts
@jam-overture/loom/signals/broadcastThe reader-signal broadcaster alone, for a browser bundle — about 5 KB, with no schema library.hosts
@jam-overture/loom/signals/postgresThe Postgres buffer and counters, for a deployment that keeps what its readers did.hosts
@jam-overture/loom/telemetryThe journal — proposal, provenance, disposition and outcome, recorded as they happen.hosts
@jam-overture/loom/telemetry/postgresThe Postgres journal, for a deployment that keeps its telemetry.hosts
@jam-overture/loom/anthropicThe model seam: an interpreter that turns a sentence into a proposal. Optional.hosts
@jam-overture/loom/cliScaffolding and inspection from a terminal.tooling
@jam-overture/loom/testingFixtures and doubles: the sample trees, clocks and scripted interpreters Loom tests itself with.tooling
@jam-overture/loom/testing/contractsThe suites that tell you whether your own store, hold store or journal keeps its promises.tooling

The split is the important part. @jam-overture/loom is the tree and the change model and knows nothing about React, storage or any model provider. Each of the others is one of those things, behind its own door, so that importing the core never drags a database driver into your bundle.

The last row is the one that is not a door but a second package, and the reason is the same split one step out: the framework does not depend on the library, so the library does not ship inside it. It takes @jam-overture/loom as a peer, which is a package's way of saying there must be exactly one copy of this in your tree — two would be two registries that reject each other's entries.

The table above is the map you need to write your first import. The API reference lists the same doors with the number of names behind each, and says the thing this table cannot: they do not nest, so none of them has the whole package behind it.

TypeScript

The package ships its own types and is written for strict mode. Two settings are worth turning on if they are not already: exactOptionalPropertyTypes and noUncheckedIndexedAccess. The runtime's own source uses both, and its types distinguish "absent" from "present and undefined" in several places where the difference matters.

Nothing throws across a seam. Functions that can fail return a Result:

import { createStarterPrimitiveRegistry } from "@jam-overture/loom-primitives"
import { describeRegistryError } from "@jam-overture/loom/sdk"

const built = createStarterPrimitiveRegistry()

if (!built.ok) {
  throw new Error(`the registry was refused — ${describeRegistryError(built.error)}`)
}

const registry = built.value

That shape repeats everywhere: check ok, read value or error. Rendering is the one place that never fails at all — it always returns an element, and says what it could not honour in a list of diagnostics beside it.