Upgrading from v0.9
@triggery/core@0.10.0 and its sibling packages are an additive release.
Existing v0.9 code keeps compiling and running unchanged. The new APIs land
alongside the old ones; the old ones gain @deprecated JSDoc in v0.11 and
are removed in v1.0.
What changed in v0.10
Section titled “What changed in v0.10”Four new things, all opt-in:
| Change | Effect on your code |
|---|---|
Inline conditions: config on createTrigger({...}) | Less wiring per trigger; values mutate through a typed setter |
Action channels via trigger.action(name).subscribe(cb) | Multi-subscriber action handlers without hand-rolled Set + for-of fan-out |
Builder API createTrigger<S>().require(...).handle(...) | Required conditions become NonNullable<...> — no more ! and no manual if (!conditions.x) return; |
Inspector subpath @triggery/core/inspect | Opt-in factory pattern that keeps the inspector out of the main bundle |
There are also a few semantic changes worth noting:
| Behaviour | v0.9 | v0.10 |
|---|---|---|
Two components both call useAction(trigger, 'x', fn) | last-mount-wins (only the second fn runs) | both fns run on each emit (fan-out) |
runtime.subscribeAction(...) | not available | new method, additive (every subscriber invoked) |
Two consecutive runtime.registerCondition(t.id, 'x', g) calls | stack-based — unregister of top falls back to previous | last write wins; stale unregister() is a no-op |
Builder form createTrigger<S>() (no args) | exported from @triggery/core | moved to @triggery/core/builder subpath |
Migrating the builder import is a one-line change — see step 5 below.
Migrating a typical trigger
Section titled “Migrating a typical trigger”A canonical v0.9 trigger looks like this:
Same trigger in v0.10:
What dropped:
- The
let user: User | null = null;storage cell — values live inside the trigger. - The two
runtime.registerConditioncalls —conditions:config handles registration. - The
conditions.user!non-null assertion — the builder narrows the type. - The
if (!conditions.settings) return;guard — same. - The
new Set<(p) => void>()+for offan-out —t.action('X').subscribedoes it.
LOC drop on a realistic engine (the comparison repo’s notifications-pipeline): 181 → ~155.
Step-by-step
Section titled “Step-by-step”1. Start in a clean working tree
Section titled “1. Start in a clean working tree”Before running the codemod (or doing manual edits) — commit or stash your work. The migration touches many files at once; an untracked diff makes review easier.
2. Move let + registerCondition pairs into the trigger config
Section titled “2. Move let + registerCondition pairs into the trigger config”Identify each pattern of the form:
Rewrite into:
The trigger now owns the cell. Older runtime.registerCondition calls for
other condition keys (e.g. values that live in a store or signal) keep
working unchanged — they’re the recommended low-level path for
externally-owned values.
3. Adopt action channels for fan-out
Section titled “3. Adopt action channels for fan-out”Replace hand-rolled Set<callback> fan-out with trigger.action('name').
The channel is cached per (trigger, name), so calling
trigger.action('showToast') repeatedly returns the same channel.
The channel’s subscribe and any runtime.registerAction handler for the
same key now coexist — both fire on every action emit. This is the
main behavioural change in v0.10; framework bindings (useAction in
React/Solid/Vue) benefit automatically because they switched to
subscribeAction internally.
4. (Optional) Move to the builder API to drop ! and if-return
Section titled “4. (Optional) Move to the builder API to drop ! and if-return”If you prefer the imperative form, enable the
no-non-null-assertion-in-handler rule to flag
conditions.X! automatically (autofix removes the !).
5. Switch the builder import to @triggery/core/builder
Section titled “5. Switch the builder import to @triggery/core/builder”If you used the chainable form createTrigger<S>().require(...).handle(...),
update the import — the builder moved to its own subpath in v0.10 so apps
that only use the imperative createTrigger({...}) config form don’t pay
for the builder machinery:
The imperative createTrigger({ id, events, handler }) form remains exported
from @triggery/core — only the no-arg chainable overload moved.
6. (Optional) Switch the inspector to the factory pattern
Section titled “6. (Optional) Switch the inspector to the factory pattern”createRuntime({ inspector: true }) continues to work — it just keeps a
static reference to the inspector code in the main bundle. The factory
pattern is the bundle-friendly way going forward.
React / Solid / Vue components
Section titled “React / Solid / Vue components”You normally don’t need to change a thing. useEvent, useCondition,
useAction keep the same signatures and behaviour with one subtle
improvement: multiple useAction calls for the same (trigger, name)
all run on every emit (instead of last-mount-wins). If you have a code
path that relied on overwriting, switch it to
runtime.registerAction(trigger.id, 'name', fn).
One new hook landed:
It’s a thin wrapper over useEffect(() => trigger.setCondition(...), [user])
— a one-liner replacement for “I have a React state and I want to feed it
to a v0.10 inline condition”.
Do I have to migrate?
No. v0.9 patterns keep working through v1.0. v0.11 will start surfacing
@deprecated JSDoc on the older paths so editors flag them, but the
runtime semantics stay the same. v1.0 removes the deprecated paths.
When does v0.9 stop being supported?
v0.9 stays in the legacy dist-tag and receives critical security fixes
until v1.0 ships. Bug fixes that are easily backportable get cherry-picked;
new features land on latest only.
What if my codebase mixes v0.9 and v0.10 patterns? Fine — they coexist. The migration is opt-in per trigger.
Will the bundle actually shrink?
Yes. The @triggery/core main entry drops from ~5.2 KB gz (v0.9) to
~4.2 KB gz in v0.10 (production minification), thanks to DEV-only
warnings being stripped via process.env.NODE_ENV and the builder API
moving to @triggery/core/builder. If your app uses both entries the
bundler deduplicates shared helpers and the combined cost lands at
~3.8 KB gz.