- Extend
skills/signalize-signals/SKILL.mdwith new pitfall:.set()stores functions as values — there is no updater-function pattern like React'ssetState- Clarify that
signal.set(fn)stores the function itself, not the result of calling it - Document the correct pattern:
signal.set(signal.value + x) - Document the
{lazy: true}special case where the function is evaluated on next read
- Clarify that
- Add test case documenting the updater-function pitfall:
set()stores function as value - Add test case for
.set(fn, {lazy: true})deferred evaluation behavior
- remove
AGENTS.mdfrom npm package output
- remove
.githubfolder from npm package output
- The npm build .js fragments are now bundled with rollup.
- chore: cleanup obsolete scripts
- chore: update build dependencies
- Nested effects cleanup: When an outer effect re-runs, nested (child) effects are now properly destroyed before being recreated. This ensures that cleanup callbacks of nested effects are correctly invoked.
- Previously, cleanup callbacks of nested effects were only called when the outer effect was destroyed, not when it re-ran
- Now,
destroyChildEffects()is called inrun()before the effect callback executes
- Test refactoring: Replace deprecated Jest matcher aliases with recommended alternatives
.toBeCalledWith()→.toHaveBeenCalledWith()(31 occurrences).toBeCalledTimes()→.toHaveBeenCalledTimes()(15 occurrences)- Remove unnecessary
donecallback in synchronous test (1 occurrence) - Affected files:
unsubscribeEffect.spec.ts,createSignal.spec.ts,createSignal.compareFn.spec.ts,batch.spec.ts,effects.onCreateEffect.spec.ts,globalEffectStack.spec.ts
- Restructure documentation:
README.mdis now a concise entry point with links to detaileddocs/ - Add comprehensive documentation in
docs/folder:introduction.md- Library overview and core conceptsquickstart.md- Installation and basic usageguide.md- Comprehensive tutorial with all featuresfull-api.md- Complete API referencecheat-sheet.md- Quick reference for common patterns
- Add AI agent skills in
skills/folder for assisted development - Add
CONTRIBUTING.mdwith development guidelines - Add JSDoc comments to all public API functions and classes
- Document
beforeReadsignal option - Clarify that static effects (with explicit dependencies) do NOT autorun
- Add EXPERIMENTAL warning for
@signaland@memodecorators
- Add
hibernate(callback)function to temporarily suspend all context states during callback execution- Clears batch, beQuiet, and effect stack contexts within the callback
- All API calls function as if called without any context
- Automatically restores previous states after callback completes (even if an exception occurs)
- Supports nesting for complex use cases
- Setting a memo value (the return value of a memo hook) now always happens automatically as a batch
- Rename
SignalLink#toggle()toSignalLink#toggleMute()for clarity - Add comprehensive documentation for
SignalGroupin README - Add comprehensive tests for
SignalGroupAPI covering all code paths
- Optimize dynamic signal unsubscriptions for effects
- Add a priority option to effects
- Memos by default have a higher prio then plain effects
- Fixed an issue that prevented signals that were no longer used from being removed from the subscription list for dynamic effects.
Memos are now non-lazy by default.
- Non-lazy memos are automatically recalculated when dependent signal values change. This also automatically updates any further effects that depend on the memo.
- Non-lazy memos are therefore a fully-fledged equivalent to a computed signal.
- Non-lazy is the new standard because that is most likely the behavior most users expect from a computed signal.
Lazy memos (as they were the default in previous library releases) are still available and can be created with the lazy: true option.
- Lazy memos only recalculate when they are explicitly called (and the signal dependencies have changed).
- Unlike computed signals (or non-lazy memos), effects that have a memo as a dependency are not automatically triggered. This only happens when the memo is read and the memo value changes as a result.
- Lazy memos are of course still available and can be quite effective.
- improve documentation
- remove docs/ folder and hero image from npm package archive
minor quality of live update
- use
ES2023as target for the build - update dependencies (patch and minor versions)
- build: use isolated modules in tsconfig.json
- improve
value(sig)types: allowSignalLikeandSignalReader
- deprecated
SignalGroup.destroy(obj)andSignalGroup#.destroy()functions- a group can not be destroyed anymore — just clear it
- use the new
SignalGroup.delete(obj)andSignalGroup#clear()functions instead
- improve
SignalAutoMapfrom props behavior:- always create signals even if values are undefined when using the
fromPropsorupdateFromPropsfunctions
- always create signals even if values are undefined when using the
- update
SignalAutoMapkey types (which is now string or symbol — period.)
- add
SignalAutoMapclass
- add
SignalGroup#hasSignal(name)helper - refactor naming of internal constants
- rename
SignalGroup#getSignal(name)helper toSignalGroup#signal(name) - remove obsolete type SignalFuncs
- improve README and CHANGELOG → Migration Guide to v0.17.0
- minor maintenance release
- exclude unused images from npm package output
❗BREAKING CHANGES❗
- refactor
createSignal()andcreateEffect()api calls- introduce the
Signalclass (formerlySignalObject)- as return result of
createSignal(): Signal - rename previous
Signaltype →ISignalImpl
- as return result of
- introduce a new
Effectclass- as return result of
createEffect(): Effect - rename previous
Effectclass →EffectImpl
- as return result of
- rename some
createSignal()options- rename
compareFn→compare - rename
beforeReadFn→beforeRead
- rename
- introduce the
- introduce the new
SignalGroupAPI - remove some awkward and mistakable decorators
- remove
@signalReader() - remove
@effect()
- remove
- refactor public api exports
- rename
queryObjectSignal()→findObjectSignalByName() - rename
getObjectSignalKeys()→findObjectSignalKeys() - rename
getObjectSignals()→findObjectSignals() - rename
destroySignals()→destroyObjectSignals()
- rename
- cleanup types
- remove
connect(),unconnect()andclass Connection - introduce
link(),unlink()andclass SignalGroup- as a more general approach and replacement of the previous connection api
The signature of the call to createSignal() has changed; a signal object is now returned.
The previous calls in the form const [val, setVal] = createSignal() can be transformed into the form const {get: val, set: setVal} = createSignal(). Alternatively, you can now simply call const val = createSignal() and read the signal using val.get() or val.value and write it using val.set().
Similarly, the createEffect() function now also returns an effect object.
The previous call const [run, destroy] = createEffect() should be rewritten as follows: const {run, destroy} = createEffect(). Alternatively, simply use the effect object:
const effect = createEffect(...)
...
effect.destroy()The SignalGroup API now replaces the awkward @signalReader decorator.
For each object that uses the @signal() decorator, a SignalGroup is automatically created, in which the signals are stored according to their name.
It is therefore possible to retrieve the signal api object via group.getSignal(name).
Before:
class Foo {
@signal() accessor bar = 123;
@signalReader() accessor bar$;
}
const f = new Foo();
f.bar$((val) => {
console.log('bar changed to', val);
});After:
class Foo {
@signal() accessor bar = 123;
}
const f = new Foo();
const bar = findObjectSignalByName(f, 'bar');
bar.onChange((val) => {
console.log('bar changed to', val);
});The SignalGroup API now replaces the mistakable @effect decorator.
The necessity to call the methods annotated as @effect() in the constructor once has led to misunderstandings and ambiguities, especially when it was an effect with static dependencies. With the new attach option for effects, the behavior is now explicit and clear.
Before:
class Foo {
@signal() accessor bar = 123;
@signal() accessor plah = 'abc';
constructor() {
this.staticEffect();
this.dynamicEffect();
}
@effect(['bar', 'plah'])
staticEffect() {
console.log('bar, plah :=', this.bar, this.plah);
}
@effect() dynamicEffect() {
console.log('plah, bar :=', this.plah, this.bar);
}
destroy() {
destroySignalsAndEffects(this);
}
}After:
class Foo {
@signal() accessor bar = 123;
@signal() accessor plah = 'abc';
constructor() {
createEffect(() => this.dynamicEffect(), {attach: this});
createEffect(() => this.staticEffect(), ['bar', 'plah'], {
attach: this,
}).run();
}
staticEffect() {
console.log('bar, plah :=', this.bar, this.plah);
}
dynamicEffect() {
console.log('plah, bar :=', this.plah, this.bar);
}
destroy() {
destroyObjectSignals(this);
}
}Replace all occurrences of SignalObject (which was introduced in version v0.14.0) with Signal. The methods have not changed.
The legacy connection api is now replaced by the signal group feature and the link() and unlink() utility functions:
In most cases, it should be sufficient to simply replace the connect() calls with link() calls. Similarly, unlink() replaces the function unconnect(), although unlink() is often not necessary at all; links between signals are automatically cleaned up when one of the signals is destroyed.
Links to object signals must be adapted, e.g. with:
link(sigFoo, findObjectSignalByName('bar'));.. or by using the new group api:
link(groupA.getSignal('foo'), groupB.getSignal('bar'));- update to
@spearwolf/eventize@4.0.1 - use
Symbol.forfor constants
maintenance update
- no new feature inside!
- just updated most build dependencies
- BUT also updated the (only) runtime dependency @spearwolf/eventize to v4.x: and this is a ❗BREAKING CHANGE❗ since the new eventize api switches to the functional api by default
- so you may need to make adjustments to your codebase if you use the eventize api directly (independently of signalize)
createSignal()now returns a polymorphic api- a new object-based api is returned, see the SignalObject class for details
- but the returned api can still be used as an array of [reader, writer] functions
- so you don't need to change existing code that uses the reader and writer function syntax
- but you can use the new object-based api, which may be more convenient (depending on your coding style and context)
- more docs will follow later ;)
- upgrade build dependencies
maintenance release
- upgrade build dependencies
- remove unnecessary optional dependencies
createEffect()now also supports async callbacks. if an async effect callback creates a cleanup callback as return value, it will be executed like a normal cleanup callback when the effect is re-executed
- add the
beQuiet()helper for dynamic effects. within the beQuiet callback, an active dynamic effect will not be noticed when a signal is read. - add another test to demonstrate the dynamic nature of effects
- fix
@effectdecorator types
- the
@effectdecorator now supports the specification of static signal dependencies (viasignalordepsoptions)- in this case, you can use the
autostart: falseoption to control whether the effect is executed immediately when the effect method is called for the first time - or only later when one of the static signal dependencies changes - by default (if it is not specified), then
autostartis activated
- in this case, you can use the
- if no name is specified in the
@signalReaderdecorator, then the name is automatically determined from the accessor field name. with the special feature that the field name is cut off at the end if the field has a$in the name. for example, the signal namefoois extracted from the field namefoo$
- ensure that each object has its own signal instance when using the
@signaldecorator - add
nameandreadAsValue: trueoptions to@signaldecorator - introduce
@signalReader({name: 'foo'})class accessor decorator - export
getObjectSignalKeys(obj)helper
- the createEffect api was enhanced
createEffect(callback, [sigA, sigB, ..])- similar to react's createEffect hook, you can now (optionally) specify a dependency array. in the dependency array, you specify the signals that will execute the effect on change. the signals do not have to match the signals used in the effect callback. if such static dependencies are specified, the effect callback will no longer be executed automatically when you create the effect. it will only be executed later if at least one signal changes.
- a signal reader callback is no longer called immediately ..
- only when the signal changes
- the callback is no longer called as a dynamic effect
- it only uses the original signal as a static effect dependency
- introduce the type helper
SignalFuncs<Type>— the return value type ofcreateSignal() - the pre-compile step for jest is omitted, now ts-jest is used and jest can be called directly without any indirection 🥳
- the decorators are no longer included in the default export (index.js)
- to use the decorators, the user must import them from `@spearwolf/signalize/decorators'
- fix package type definitions
- no commonjs format is delivered anymore
- the esm format is no longer bundled
- use
import type ..syntax
- switch package to
type: module- this hopefully solves the problem that typescript cannot resolve the types correctly when
signalize.mjsis loaded 😵 - the final package output will now completely omit
.mjsfile endings
- this hopefully solves the problem that typescript cannot resolve the types correctly when
- mark package as side effects free
- update (mainly dev) dependencies
- upgrade dev depenedencies
- this includes an upgrade from typescript 5.1 to 5.2, which brings with it new build artefacts
- upgrade dependency
@spearwolf/eventizetov3.0.0 - remove
type=modulefrom package.json- instead, use
*.mjsfile extension for esm output
- instead, use
- introduce CHANGELOG 😉
- upgrade to typescript@5
- refactor build pipeline
- mute, unmute and destroy signals
muteSignal(get)unmuteSignal(get)destroySignal(get)
- fix effect cleanup callback
- if an effect is executed again, the cleanup callback from the last effect is called first (the behavior is similar to the react.useEffect() cleanup function)
- add
getEffectsCount()andonDestroyEffect()helpers - auto cleanup/unsubscription of effects and memos when all their signals are destroyed
- change signature of the
createEffect()helper: an array with a run and unsubscribe function is now returned - refactor child effects
- typescript: export all types