Session.beforeCommit ​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 Session · inherited by testing.FakeSession · labs, @experimental · since 2.0.0 · called as session.beforeCommit · optional: reach it through has()
beforeCommit?(name: string, fn: (commit: BeforeCommitContext) => void | Promise<void>, options?: SignalOption): () => void;
Registers a hook that runs inside every recorded commit (writes, queued writes, undo, redo,
restore, reload), after loads and before the commit point. An equal write (it commits nothing)
runs no hook: equality is decided before the first hook; a reload still runs them. It is
decided again after the last hook, so a commit the hooks write back to the current state
records nothing (changed: false, seq: null).
Its writes go through commit.tx and join that commit. The display draws nothing, and no other write commits (spec section 6.1, O2b), from the first hook call of a commit to its
commit point. Keep it under 5 ms ('beforeCommit-slow'); never load assets (engine queries such
as labs.geometry.read are allowed, so it may be async; cache their results); mint ids from
state, never at random. A throw undoes only its own writes, object by object as a savepoint's
(spec section 6.1, O7), and records 'beforeCommit-threw';
the commit proceeds. A hook still pending after 10 s is treated as thrown. If the commit ends
before its commit point (an abort, a supersede, a timeout, destroy()), commit.tx closes and
its signal aborts: a later write through it throws HoloTransactionClosedError and records
nothing, nor does the hook's later throw. Check commit.tx.open after each await before you
change your own state. A collection handle created while the hooks run (commit.tx.collection
or anywhere else) lands after the commit point and runs no hook again; a dispose (a lifecycle
change) while they run closes their commit.tx with 'other' and runs them again on the state
it left, so a hook may run more than once for one commit. Hooks run in
registration order; the same name again replaces the earlier hook. Lifecycle call: immediate.
Returns the unregister function; aborting signal unregisters the same way, and nothing
rejects. Registering replays nothing: it runs for later commits, not one made before (the
page's first setSceneModel). Lay out the current state once after registering, through one
transaction (look-only writes record nothing, so no hook runs for it), as the example does.
Cleanup: unregistering stops the hook; rules its writes created stay until you reset them
(find(q).set({ pose: null })) or dispose their owner. Keep a list of what the hook wrote.
Errors: an asset load through commit.tx throws HoloScopeError, and so does
session.capture(), which would wait for this commit (spec section 6.1, O3).
Not for: reacting after a commit, which is on('committed').
Experimental, so optional in this declaration: reach it through has(session, 'beforeCommit').
| Option | Type | Default | What it does |
|---|---|---|---|
signal |
AbortSignal |
undefined | Cancels this call; the member's JSDoc says what an abort does. |
signal) ​This call is a registration (SignalOption.signal, the kinds of call).
A registration (
on,beforeCommit): aborting removes it and nothing rejects.A creator (
collectionfor a new name,materials.create,on,onFrame,beforeCommit,follow,input.drag,labs.lightLinking) given a signal already aborted returns at once, already ended: aCollectionorMaterialreadingdisposed: truethat holds no content and no live name, or a registration that never runs and whose disposer does nothing.It records no Problem for the abort and throws only what it would throw anyway, as the DOM's
addEventListenerwith an aborted signal adds nothing (spec section 6.5).
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/05-Session.beforeCommit.ts):
import { has } from '@holostep-configurators/runtime-client';
import type { DeepReadonly, PoseInput, SceneModel, Session, Writer } from '@holostep-configurators/runtime-client/contract';
declare const session: Session;
declare const signal: AbortSignal;
// Install a layout hook; the returned uninstall also resets the rules the hook wrote.
declare function legLayout(
model: DeepReadonly<SceneModel> | null,
): Array<{ node: string; pose: PoseInput }>;
export function installLegLayout(): () => void {
const posed = new Set<string>(); // every node the layout posed
const layout = async (writer: Writer): Promise<void> => {
for (const { node, pose } of legLayout(writer.getSceneModel())) {
await writer.node(node).set({ pose });
posed.add(node);
}
};
const off = has(session, 'beforeCommit')
// joins each commit, so it never shows one frame stale
? session.beforeCommit('leg-layout', ({ tx }) => layout(tx), { signal })
// keep this branch: a runtime without the hook gets the same writes, one frame later
: session.on('committed', ({ recorded }) => { if (recorded) void layout(session); }, { signal });
void session.transaction((tx) => layout(tx)); // the current state, once: registering replays nothing
return () => {
off(); // stops the layout; its rules stay
for (const node of posed) void session.node(node).set({ pose: null }); // so reset them
posed.clear();
};
}
| Rule | Result |
|---|---|
| 1. Neutral name | pass: beforeCommit names no product |
| 2. A precedent | pass: Lexical registerNodeTransform (row G4); ProseMirror PluginSpec.appendTransaction (row G4) |
| 3. Two apps, or labs | labs (@experimental at a stable path, reached through has() or supports()): it graduates when a second app uses it; apps today: app-04c2774145, app-0c773a4ee3, app-378d190f1b, app-4762caacdc, app-4f16d2f6da, app-510d750508, app-59c5b5751e, app-b362a81c85, app-b4ae2fce25, app-bcb7ef21da, app-ceb4eccc72, app-ddfb017681, app-f9a32142f1, app-fc07850558, app-fc306ec3dc, app-fd09090420 |
| 4. The cheaper fix first | decided per need in Appendix B: rows G4 at tier labs (a member only where no rule, data, asset or app code serves the need) |