Assembly.attach ​

Contract 2.0.0 · package sha256 a857eca95b35921109faf997094bb4192f12527d9d0fcd4be7e97f06425bad6e · generated from configurator-runtime.d.ts by services/onsite-scripter/scripts/build-contract-docs.mjs; edit the source, not this file.

Method of Assembly · stable · since 2.0.0 · called as session.assembly.attach

ts
attach(node: string, options?: RecordedOptions<C> & AttachOptions): Promise<AttachReport>;

Attaches node by its connectors (recorded; merging; in the write queue): moves its model root and records the connection. Spec section 6.12 is the normative table. In short: placement honours the stored plane config (align, frontDir, allowRotatedSnap) exactly as 1.4 snapTo did; it refuses only a disabled connector or one occupied by another pair (re-attaching a connected pair re-places node), an accepts mismatch where accepts is declared, or planes that don't meet; it never moves another node, and it drops node's other connections that no longer meet after the move ('connection-stale'). A refused attach changes nothing. dryRun reports without changing. Through a tx: 'staged', and a dropped connection that the same transaction attaches again is not a problem. Not for: moving nodes by pose, which is never recorded as a connection. Errors: a refusal resolves (attached: false), never rejects, and so does an internal failure ('rolled-back', 'internal-error'; spec section 6.4); invalid input rejects HoloInputError. Other connections it drops are problems ('connection-stale').

Options ​

Through Session:

Option Type Default What it does
actor Actor the expectGroup's actor when one is given, else 'app' Who made the write.
checkpoint JsonValue unchanged Page state stored atomically with this step; returned in state().checkpoint and the feed.
dryRun boolean false Runs and reports, then rolls back by design ('dry-run').
expectGroup HistoryGroup undefined A guard, checked at the call: this write belongs to the undo group you are running.
expectRevision number undefined (no check) If-match on session.revision.
from string the best free one The connector of node to use (a ConnectorRef.connector).
near Point3 the node's position Which of node's connectors to prefer when several have partners: the one nearest this world point.
orient 'auto' | 'keep' 'auto' 'auto': yaw node about world +Y so the two normals oppose, then translate.
origin string null Your tag, echoed unchanged in Report.origin and cause.origin.
signal AbortSignal undefined Cancels this call; the member's JSDoc says what an abort does.
supersede boolean false (the write is queued behind the transaction and applies after it closes) While a transaction is open: roll it back (it resolves 'superseded'); the writes queued behind it apply in call order, then this one, which supersedes an…
to ConnectorRef | { node: string } the best free connector of any other node A connector ({ node, connector }: used at any distance), or a node (its best free connector).
undo 'rebase' | (string & {}) a recorded-state change gets one step labelled 'Change'; a look-only write gets none; the session's first model (cause 'load') gets none, whatever the label: it is the floor of history An undo label makes this write one undo step.

Through Tx:

Option Type Default What it does
from string the best free one The connector of node to use (a ConnectorRef.connector).
near Point3 the node's position Which of node's connectors to prefer when several have partners: the one nearest this world point.
orient 'auto' | 'keep' 'auto' 'auto': yaw node about world +Y so the two normals oppose, then translate.
signal AbortSignal undefined Cancels this call; the member's JSDoc says what an abort does.
to ConnectorRef | { node: string } the best free connector of any other node A connector ({ node, connector }: used at any distance), or a node (its best free connector).

Refused here (typed never, so passing one fails to compile): actor, checkpoint, dryRun, expectGroup, expectRevision, origin, supersede, undo.

Abort (signal) ​

This call is a write (SignalOption.signal, the kinds of call).

A write: before the commit point it rolls back and rejects AbortError; after it, no effect, except that a running camera move stops where the last frame drew it (CameraMoveOptions).

Through a tx the commit point is the transaction's: aborting a write still loading rejects AbortError and stages nothing (catch it: unobserved, it rolls the transaction back, spec section 6.1, O6); aborting one already staged unstages it, its paths keeping the latest other write to them as after a failed savepoint (O7), and its Promise stays resolved 'staged'.

Example ​

Imports: values (has, validate, the Holo*Error classes, the pure helpers) from @holostep-configurators/runtime-client, types from @holostep-configurators/runtime-client/contract, /math and /testing names from those entries; boot from the runtime module the root's loadRuntimeAtFloor resolves to. A type your page defines is your app's: declare it there, never import it from the package.

Compiled under the contract's strict settings (examples/declaration/12-Assembly.attach.ts):

ts
import type { DeepReadonly, IModelNode, Reason, SceneModel, Session } from '@holostep-configurators/runtime-client/contract';
declare const session: Session;
declare const signal: AbortSignal;
// Add an arm and attach it, in one undo step; a refusal rolls the add back too. For an app whose
// scene model is its source of truth: a store app adds in its store and lets its one sync write
// the model (spec section 11.2), or a sync rolls this back, or its next sync drops the arm.
class AttachRefused extends Error {
  constructor(readonly reason: Reason) { super(`attach refused: ${reason}`); }
}
declare function withNode(model: DeepReadonly<SceneModel> | null, node: IModelNode): SceneModel;
export async function addArm(arm: IModelNode, seatId: string): Promise<Reason | 'superseded' | null> {
  try {
    const r = await session.transaction(async (tx) => {
      // staged and loaded, hidden
      await tx.setSceneModel(withNode(tx.getSceneModel(), arm));
      const a = await tx.assembly.attach(arm.nodeId, {
        from: 'mp-left',
        to: { node: seatId, connector: 'mp-right' },
      });
      // 'occupied' | 'rejected' | 'no-candidate' | 'not-loaded'
      if (!a.attached) throw new AttachRefused(a.reason ?? 'other');
    }, { undo: 'Add arm', origin: 'arm-ui' });
    if (r.outcome === 'superseded') return 'superseded';    // a newer write won: not a refusal
    return r.outcome === 'committed' ? null : (r.reason ?? 'other');
  } catch (error) {
    // the transaction rolled back
    if (error instanceof AttachRefused) return error.reason;
    throw error;
  }
}

Generic-API test (spec 3.1) ​

Rule Result
1. Neutral name pass: attach names no product
2. A precedent pass: Onshape mate connectors (row K63)
3. Two apps, or labs pass: 6 apps (ledger: app-04c2774145, app-37356bf09f, app-4762caacdc, app-c1384707fc, app-f9a32142f1; fleet cross-check: app-04c2774145, app-37356bf09f, app-4762caacdc, app-ceb4eccc72, app-f9a32142f1)
4. The cheaper fix first decided per need in Appendix B: rows K63 at tier app, stable (a member only where no rule, data, asset or app code serves the need)