Node versioning
A saved workflow stores each node's type string literally. If you change a
node's schema or behaviour incompatibly, workflows already built against the old
version would break. Node versioning lets both versions coexist.
How a version is expressed
A node type belongs to a family and has a version. Two equivalent ways to declare version 2:
// A: suffix the type string
public static function type(): string
{
return 'app::send-slack-message{v2}';
}
// B: leave the type bare, override version()
public static function version(): int
{
return 2;
}
With option B the registry rewrites the stored type to
app::send-slack-message{v2} for you. A type with no {vN} suffix and no
version() override is version 1 of its own family.
Registering two classes that both claim the same version of the same family
throws InvalidArgumentException.
Shipping a breaking change
- Keep the existing class registered, unchanged.
- Add a new class — same family,
version()returning the next number — with the new schema /handle(). - Register both.
$registry->register([
SendSlackMessageNode::class, // family app::send-slack-message, v1
SendSlackMessageNodeV2::class, // same family, v2
], 'action');
What each version sees
| Behaviour | |
|---|---|
Existing workflows (type app::send-slack-message) | Keep resolving to the v1 class, forever. Their handle(), schema, and label are unchanged. |
Existing workflows saved with …{v2} | Resolve to the v2 class. |
| The builder palette / node listings | Show only the latest version per family — new workflows are built against v2. |
Exact-type resolution — running a saved workflow, NodeRegistry::resolve(),
has(), getLabel(), getResolver() — always honours the literal stored type.
Only the listing methods (all(), byCategory(), palette(), search(), …)
collapse to the latest version.
Compatibility note
If you have code that enumerates a category to wire up side effects (the pattern
the engine uses internally for triggers), be aware that once a v2 of something
in that category ships, the public listing methods stop returning the superseded
v1. The engine handles its own triggers correctly; audit any equivalent code of
your own.