Assembly.attach ​Contract 2.0.0 · package sha256
a857eca95b35921109faf997094bb4192f12527d9d0fcd4be7e97f06425bad6e· generated fromconfigurator-runtime.d.tsbyservices/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
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').
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.
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
txthe 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'.
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):
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;
}
}
| 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) |