A group owns its blocks. An arrow references its endpoints. Both are relationships between models, but moving or detaching a group should affect its blocks differently from the arrows that point to them.

Choose the relationship #

Relationship Declaration Behavior
Ownership @syncing.child and child collections One parent; adopting into another parent removes the old ownership
Reference @syncing and ordinary collections Points at a model without taking ownership; cycles are allowed

Ownership enriches a model with a parent-child structure: projects, pages, groups, and their contents. These relationships work locally and keep the same behavior when the model joins a document. Use references for links across that structure, shared definitions, and back-pointers.

Move a child #

This complete local example has two groups and a block. Moving the block updates its parent and both collections:

import { PlexusModel, syncing } from "@here.build/plexus";

@syncing("Block")
class Block extends PlexusModel<Group> {
  @syncing accessor text = "";
}

@syncing("Group")
class Group extends PlexusModel<Board> {
  @syncing.child.list accessor blocks!: Block[];
}

@syncing("Board")
class Board extends PlexusModel {
  @syncing.child.list accessor groups!: Group[];
  @syncing accessor selected: Block | null = null;
}

const left = new Group();
const right = new Group();
const board = new Board({ groups: [left, right] });

const block = new Block({ text: "Move me" });
left.blocks.push(block);
board.selected = block;

right.blocks.push(block);

console.log(left.blocks.length);       // 0
console.log(block.parent === right);   // true
console.log(board.selected === block); // true

The move changes ownership. The block keeps its identity, and selected continues to reference it. The generic PlexusModel<Group> types the block's .parent accessor.

Add a document when needed #

To share this existing graph, continue the example:

import { Plexus } from "@here.build/plexus";

const plexus = Plexus.bootstrap(board);
console.log(board.selected === block); // true; the same local object
console.log(block.parent === right);   // true; the same ownership
block.uuid;                           // shared identity is now available

plexus.destroy(); // when finished using the document

The document adds replicated state and UUIDs to the existing graph. A provider can now exchange its state with other environments. From local to shared demonstrates the transition with an observer and a second replica.

Detach without losing identity #

block.detach() removes the block from its owner. A detached model remains in its document and can still be referenced. Detaching a group makes its owned subtree unreachable from the root; plain references can still point into it.

Reattach a model by adopting it into another owner in the same document. See Document lifecycle for the difference between a new, document-free model and a detached model.

Respect the document boundary #

Once materialized, an entity belongs to one document. Adopting it into a model in another document throws PlexusDocMismatchError. Detaching it does not remove that affiliation.

Ownership also rejects self-adoption, cycles, and parenting the document root. Errors and troubleshooting lists the error types and recovery guidance.

Model collections #

Child relationships support scalar accessors, lists, sets, records, and maps. In a child map, values are owned; keys are not. Models and fields covers the decorator forms and structural map keys.