Skip to content

Collaboration Engine API

IFClite supports real-time collaborative BIM editing. The @ifc-lite/collab package puts the model in a CRDT (a Yjs document mirroring the IFCX data model), so any number of peers can edit concurrently and converge without a central lock, online or offline. @ifc-lite/collab-server is the reference websocket sync server.

Collaboration is IFCX/IFC5-canonical: the CRDT schema mirrors the IFCX wire shape (path-keyed nodes, flat namespaced attributes, layer composition), so seeding from and snapshotting to IFCX are lossless, and every peer's edits form a composable IFCX layer. STEP-flavored IFC (IFC2X3/IFC4) is converted through IFCX to participate.

Quick Start

import { createCollabSession, setAttribute } from '@ifc-lite/collab';

const session = await createCollabSession({
  roomId: 'project-abc/model.ifcx',
  user: { id: 'louis', name: 'Louis', color: '#5b8def' },
  provider: 'indexeddb+websocket',   // 'memory' (default) | 'indexeddb' | 'websocket' | 'indexeddb+websocket'
  serverUrl: 'ws://localhost:1234',  // required for websocket providers
});

// Wait for persistence + websocket sync to complete
await session.whenSynced;

// Seed the shared document from an IFCX file (string, buffer, or parsed
// IfcxFile). ifcxContent is caller-provided, e.g.
// await fetch('model.ifcx').then(r => r.text())
session.seed(ifcxContent);

// Capture a baseline state vector now if you later want to extract just
// this peer's edits as a layer (see extractUserLayer below).
const baseline = session.captureBaseline();

// Make an edit; all peers see it in real time
session.transact(() => {
  setAttribute(session.doc, 'wall-uuid', 'bsi::ifc::v5a::Pset_WallCommon::FireRating', 'EI60');
});

// Live presence: selections and cursors of every peer
session.presence.setSelection(['wall-uuid']);
session.presence.onUpdate((peers) => {
  // render avatars / cursors / selection outlines
});

// Undo is scoped to your own edits only
session.undo();
session.redo();

// React to concurrent-edit conflicts (last-writer-wins has already resolved them)
const off = session.onConflict((conflict) => {
  console.log('conflict on', conflict);
});

// Snapshot the whole document back to IFCX
const file = session.snapshot();

// Or extract just this peer's edits as an IFCX layer. The baseline is the
// state vector captured with session.captureBaseline() before the edits.
const myLayer = session.extractUserLayer(baseline);

session.dispose();

Key points:

  • createCollabSession is async and returns a CollabSession
  • session.whenSynced is a promise property, not a method
  • session.presence carries live cursors and selections between peers
  • token in the options is forwarded to the server as a bearer token for authentication

Running the Sync Server

The reference server is a plain Node websocket server with file persistence:

npx @ifc-lite/collab-server
# [collab-server] listening at ws://0.0.0.0:1234 (data: ./.collab-data)

Configuration is via environment variables:

Variable Default Description
COLLAB_PORT 1234 Listen port
COLLAB_HOST 0.0.0.0 Bind address
COLLAB_DATA_DIR ./.collab-data Room persistence directory
COLLAB_MAX_ROOMS 1024 Maximum loaded rooms
COLLAB_METRICS_TOKEN unset Token protecting the /metrics endpoint

The server also exposes /healthz, Prometheus /metrics, and a content-addressed blob store under /blobs/.

For programmatic use, import startCollabServer(opts) from @ifc-lite/collab-server. Pluggable persistence backends include FilePersistence, MemoryPersistence, S3Persistence, and RedisPersistence. For production hardening (TLS, JWT authentication, per-role rate limits, path locks, JSONL audit logging), use startSecureCollabServer - see Testing collab end-to-end for a full example.

Beyond Basic Sync

These ship in @ifc-lite/collab today (mention-level; see the package README for details):

  • Branching and history - forkSession forks a session into a branch, mergeBranch merges it back (op-based or layer-based strategies), and buildBranchTree reconstructs the branch graph from a history sidecar (MemoryHistorySidecar or AutomergeHistorySidecar, attached with attachHistorySidecar)
  • Federation - createFederationSession composes one session per model plus a shared federation document for cross-model relationships
  • End-to-end encryption - AES-GCM-256 per-room keys: generateRoomKey / deriveRoomKey, encryptFrame / decryptFrame, and createKeyRing for rotation
  • Privacy/GDPR - exportAndLeave and redactAuthorMeta

Try It: Two Runnable Demos

From the repository root:

pnpm collab:demo

Builds the packages, starts the sync server on ws://localhost:1234 and a demo app on http://localhost:5174. Open it in two browser tabs: live peer cursors, shared selection, "Add wall" entity edits mirrored instantly, forced conflicts with keep-mine/accept-theirs, per-tab undo, and IFCX history snapshots.

pnpm collab:demo:3d

Same server, but serves the Three.js variant (examples/threejs-collab) on http://localhost:5175: every entity is a real 3D box whose position, size, rotation, and color are CRDT attributes. Drag a wall in tab A and it slides in tab B.

Conflict Semantics

Concurrent edits converge automatically. Attribute writes resolve last-writer-wins by Lamport clock; the conflict detector then surfaces what happened through session.onConflict so the UI can offer keep-mine/accept-theirs. Geometry replacement keeps both candidates for explicit resolution, and delete-versus-edit prompts a restore. The full semantics table is in the package README.

Testing

For everything from unit suites to latency simulation, curl smoke tests, audit-log inspection, and the determinism harness, see the dedicated guide: Testing @ifc-lite/collab end-to-end.

Status

The collab stack is early (0.x). APIs are functional and tested, but expect breaking changes between minor versions.

Model slot records may include an immutable spatialContext object. The collaboration library preserves this consumer-defined metadata through Yjs updates and persisted room reloads without depending on a geometry or IFC parser. Old slot records omit it. Consumers must validate the object's schema and numbers before using it; snapshotToIfcx exports entity snapshots and does not export these slot records.