State ids derived from block ids by a pure function (ADR-0004 decision 3).
state_id(block_id) = "s_" <> block_id
state_id(block_id, role) = "s_" <> block_id <> "__" <> roleA block id is already stable, document-unique, opaque and never reused (ADR-0001 decision 3), so a state id derived from it inherits all of that per block rather than per document: editing one block's config, or inserting a step at the top of a sequence, changes the ids of nothing else. That is what keeps a stored provenance map valid and a publish diff readable.
Roles
A role is a short, block-type-chosen local name for an auxiliary state
a block emits inside its own state. Roles are minted here rather than
by string concatenation inside a block type so this module owns the
namespacing, and it refuses any role it could not invert:
- it must match
~r/\A[a-z][a-z0-9_]*\z/, and - it must not contain
__, which is the separator itself.
The second rule is not implied by the first - a__b matches the pattern -
and without it unstate_id/1 would have two readings of the same string.
The three properties
- Uniqueness. Block ids are document-unique, a
blk_-prefixed UXID contains no__, and a role cannot either, so no two generated ids collide whatever a block type does. Thes_prefix additionally keeps generated state ids out of the namespace an author's own config writes into, which matters because statifier checks uniqueness over allID-typed attributes in one set. - Invertibility.
state_id/1andstate_id/2are injective andunstate_id/1inverts them without consulting the provenance map, so a human reading generated SCXML in a diff can still see which block a state came from. - Totality. Every generated state carries an id. Statifier permits
nameless states, but
Statifier.Position.export/1refuses an export containing one ({:error, {:unnameable_states, indexes}}) andStatifier.active_leaf_states/1drops it, so this package emits none.
Summary
Types
A block-type-chosen local name for an auxiliary state.
A generated SCXML state id: "s_" <> block_id, optionally "__" <> role.
Functions
The done.state event a compound state raises when it enters a <final>
child (ADR-0004 decision 2). This is the whole of what a parent needs to
know about a child it did not compile.
Whether role is a role this module will mint an id for.
The state id of the state block_id's block compiles to.
The state id of an auxiliary state block_id's block mints under role.
Inverts state_id/1 and state_id/2.
Types
Functions
The done.state event a compound state raises when it enters a <final>
child (ADR-0004 decision 2). This is the whole of what a parent needs to
know about a child it did not compile.
iex> StatifierBlocks.Compiler.StateId.done_event("s_blk_SEQ")
"done.state.s_blk_SEQ"
Whether role is a role this module will mint an id for.
iex> StatifierBlocks.Compiler.StateId.role?("lane_capture")
true
iex> StatifierBlocks.Compiler.StateId.role?("Done")
false
@spec state_id(StatifierBlocks.Block.id()) :: t()
The state id of the state block_id's block compiles to.
iex> StatifierBlocks.Compiler.StateId.state_id("blk_ROOT")
"s_blk_ROOT"
@spec state_id(StatifierBlocks.Block.id(), role()) :: {:ok, t()} | {:error, {:invalid_role, StatifierBlocks.Block.id(), role()}}
The state id of an auxiliary state block_id's block mints under role.
Returns {:error, {:invalid_role, block_id, role}} rather than raising
for a role this module cannot invert - an ordinary Emit-stage finding,
since emit/2 is a total function like every other callback.
iex> StatifierBlocks.Compiler.StateId.state_id("blk_SEQ", "done")
{:ok, "s_blk_SEQ__done"}
iex> StatifierBlocks.Compiler.StateId.state_id("blk_SEQ", "a__b")
{:error, {:invalid_role, "blk_SEQ", "a__b"}}
@spec unstate_id(t()) :: {:ok, {StatifierBlocks.Block.id(), role() | nil}} | :error
Inverts state_id/1 and state_id/2.
iex> StatifierBlocks.Compiler.StateId.unstate_id("s_blk_SEQ")
{:ok, {"blk_SEQ", nil}}
iex> StatifierBlocks.Compiler.StateId.unstate_id("s_blk_SEQ__done")
{:ok, {"blk_SEQ", "done"}}
iex> StatifierBlocks.Compiler.StateId.unstate_id("blk_SEQ")
:error