Headless runtime
Use an adapter entrypoint’s igniteCore(...) for source-backed construction.
The root source-free const core = igniteCore() remains registrar-only.
The canonical flow is source → native snapshot → derived states → renderer view.
Shared-source cores prepare during construction and support terminal disposal after registration.
See Events & effects for native event forwarding and effect delivery.
import { createMachine } from 'xstate';import { igniteCore } from 'ignite-element/xstate';
const counterMachine = createMachine({ initial: 'off', states: { off: { on: { TOGGLE: 'on' } }, on: { on: { TOGGLE: 'off' } }, },});
const counter = igniteCore({ source: counterMachine, states: (snapshot) => ({ isOn: snapshot.matches('on') }), commands: ({ source: actor }) => ({ toggle: () => actor.send({ type: 'TOGGLE' }) }), events: (event) => ({ toggled: event<{ isOn: boolean }>() }), effects: ({ emit, select }) => { const isOn = select((snapshot) => snapshot.matches('on')); if (isOn.changed) emit({ type: 'toggled', isOn: isOn.current }); },});try { const subscription = counter.watch((next, previous) => { console.log(previous.isOn, '->', next.isOn); }); const result = await counter.execute({ command: 'toggle' }); console.log(result.snapshot, result.states, result.events); console.log(counter.get('states')); subscription.unsubscribe();} finally { counter.dispose();}Tag-and-renderer registration requires DOM; imports and headless construction do not create browser globals or hidden elements.
Existing opaque non-DOM projection targets remain supported.
A retained DOM element is not a projection target.
Methods
Section titled “Methods”Keyed reads
Section titled “Keyed reads”get(key) accepts exactly "states", "schema", "commands", and "events".
There is no zero-argument form, raw-snapshot key, path language or function lookup.
get("states")reads the optional derived projection; omitted states return a stable empty object. On an isolated core, it also acquires the headless runtime if needed, including commands and the observation cache.get("schema")returns the immutable minimal catalogue described below.get("commands")andget("events")select catalogue data, not functions or event history.
Existing-source cores are ready at construction and need no preparation read before useIgnite(core).
Independent hooks also work directly, without a preparation read. Explicit headless access acquires a separate runtime; it does not inspect a mounted hook’s private state.
Native source APIs such as actor.getSnapshot() remain source-owned.
Native snapshots also remain in projections, effects and execution results.
Separate public reads are not an atomic multi-read transaction.
watch(handler)
Section titled “watch(handler)”Observes derived states as (next, previous) with no initial user delivery.
Synchronous source replay seeds the previous value.
Every delivered source observation follows the adapter’s existing notification/error boundary; Ignite does not add global deep equality.
The returned { unsubscribe() } handle is independently idempotent.
For an imperative initial render, subscribe first, then call get('states'), and unsubscribe if that render throws.
When a read first acquires an isolated headless runtime, it may return its captured initial value while synchronous replay has already updated the framework cache.
on(name, handler)
Section titled “on(name, handler)”Observes flat outward occurrences { type, ...fields }, including source-native XState emits.
Source event names and payloads retain their inferred types.
This is not replay or history.
Release its { unsubscribe() } handle when the observer ends.
execute({ command, input? })
Section titled “execute({ command, input? })”Calls one named command with exactly one positional argument, input, including undefined when omitted.
It never spreads an array.
Strict types exclude serialized calls requiring multiple positional arguments; direct callable commands retain those signatures.
Results and asynchronous work
Section titled “Results and asynchronous work”The promise awaits the callback and its queued-microtask observation window.
Its { snapshot, states, events } result pairs native snapshot and derived states from the same observation and includes ordered window events.
It does not promise business/network completion or independently correlated receipts for overlapping calls.
Wait for application-correlated source outcomes when work continues asynchronously.
The result does not contain the command’s resolved value.
Direct useIgnite(core) commands preserve that ordinary value/promise instead.
Fulfillment can still mean a guarded no-op or an unknown/rejected business outcome.
Disposal invalidates an outstanding execution observation, not its underlying I/O or an already-returned direct command promise.
Disposal during execution
Section titled “Disposal during execution”A callback rejection preserves the exact rejection value, even after disposal.
A callback that fulfills after disposal cannot acquire another result and rejects with the disposed-result error.
Already-returned direct command promises keep their ordinary outcomes; Ignite does not cancel entered source work.
dispose()
Section titled “dispose()”Source-backed owning cores release their own observation, event, effect, projection, binding and command-window handles.
They mark ended before cleanup, prevent reentrant acquisition, attempt all releases in acquisition order, and rethrow the first exact cleanup value, including undefined or null.
Repeated disposal is inert, even after a throwing first call.
Individual handles remain safe to release.
Live state reads, watches, listeners, commands and binding acquisition reject after disposal.
Catalogue reads remain available without reacquisition.
Final disposal also drains successfully registered views, making later connections inert without unregistering or rebinding tag definitions.
Failed registration does not permanently lock the core, and registration-in-progress remains guarded.
Ordinary element disconnect/reconnect keeps its existing lifecycle.
Source ownership
Section titled “Source ownership”Borrowed XState actors, Redux stores and MobX values remain caller-owned.
Disposal removes Ignite observations, never arbitrary source shutdown.
An Ignite-created private XState actor is stopped exactly once after observations drain; an unused core never creates one.
Discovery
Section titled “Discovery”Before binding a configured commands callback:
{ "schemaVersion": 1, "states": { "schema": null }, "commands": null, "events": [{ "type": "toggled", "payload": null }]}After real binding, commands contains actual own names, for example { "toggle": { "input": null } }.
With no commands callback, the known catalogue is {} from the outset.
Unknown is different from known empty.
Read unknown schemas
Section titled “Read unknown schemas”The three null schema positions mean that the runtime schema is unknown.
They do not mean that input is absent, validated, or unrestricted.
Omitted states retain their known empty-projection description.
Catalogue reads acquire no source, run no commands against fabricated actors, start no effects and initialize no DOM.
Previously returned objects never mutate.
The latest catalogue survives disposal.
Declared event names are not an exhaustive list of native source emits.
Observe actual occurrences with on and execution results.
Explicit tool descriptions, input schemas and availability predicates belong at the tool/application boundary.
The core does not infer them.
Project availability into public states while preserving source enforcement; availability is not authorization.
React and React Native
Section titled “React and React Native”useIgnite(core) from ignite-element/react borrows a core created by its owner outside rendering.
With an existing actor, store or observable, pass the core directly to the hook without a preparation read.
A machine, slice or fresh source factory gives each hook a private runtime.
Existing actor, store or observable instances share state. Explicit headless operations on independent cores use a separate runtime.
The hook returns inferred derived states and stable callable commands.
It uses cached reads. Unmount releases the private runtime for independent inputs, or only the view subscription for shared inputs. Neither unmount disposes the core or stops a borrowed source.
Snapshot values must be suitable immutable projections: nested plain data is detached without freezing the source; arbitrary class instances and accessors are not serialized.
Function-valued states remain states.
Browser interop and effect timing
Section titled “Browser interop and effect timing”Import igniteReact and web-only wrapper types from ignite-element/react/web; they target actual registered elements, not a hidden headless runtime.
No SSR fallback or React commit barrier is provided.
Effects use one evaluator per core/source instance.
Their queued execution is not a renderer or framework commit barrier and may precede a view update.
An activated shared evaluator retains its baseline through zero-view intervals until core.dispose(); isolated evaluators follow their instance lifetime.
Effect errors use the core’s console fallback, not an element’s error hook.
See the event guide for timing and ownership; view resources belong to the actual framework lifecycle.
Private inspection
Section titled “Private inspection”Document/speech projections retain a private coherent inspection for each validation/commit attempt.
It is not a public inspection bundle, and an external asynchronous commit is not transactional with later source transitions.