Las notas de la versión se generan a partir del changelog, en inglés.

Notas de la versión 0.10 de Yoltra

#0.10.0 (2026-10-03)

#@yoltra/core

Funcionalidades

  • A typed array, DataView or ArrayBuffer in state is now one value at its own path, compared by reference like Map and Set. Replacing a 4-byte Uint8Array used to report four paths, one per byte, in changedPaths, prevValues and nextValues; it now reports the view's path once, and a subscription to an index inside a view is no longer notified. The development warning for a payload kept by reference now says that binary data cannot be frozen, so a later write changes the slice unseen, and it also catches a buffer kept from a field of the payload.
  • store.call() takes cancel: [channel, type], typed to events whose payload accepts a CallCancellation (new exports CallCancellation, CancelKey). When the call is cancelled, aborted by its signal, or times out, it emits that event with { requestId, reason, detail? } so the responder can stop working; never after a terminal reply, never on store disposal, never for a request that was not sent, and never throwing. meta.correlationId carries over when set.
  • store.call() takes correlation: "either" | "causal" | "id" (new type CallCorrelation). "either" is the default and unchanged: a reply matches by its parent link or an echoed correlationId. "id" matches on the echoed id alone, for a responder whose protocol carries its own request id and keeps several requests in flight on one channel, where a reply can descend from the wrong request and used to settle it. "causal" ignores an echoed id. "id" without a correlationId, or an unknown mode, throws when the call is made, before anything is emitted.
  • createStore takes clock and scheduler (new exports Clock, Scheduler, TimerHandle): the store reads the time and arms every timer through them, for deduplication windows, the dedup cache prune (now a self-rearming timeout) and the idle timeout of store.call(). persist takes a scheduler option. The defaults look the globals up at each use, so fake timers installed after a store was built still apply. InstrumentedEvent gains at, the clock time the event was processed, and its event carries parentId and depth. A payload too large to fingerprint is still never deduplicated, but no longer leaves an unmatchable entry in the dedup cache.
  • One diagnostics seam. createStore takes diagnostics, a DiagnosticSink (new exports Diagnostic, DiagnosticCode, DiagnosticSink), and stores gain onDiagnostic(observer). Every failure the store contains (effect, reducer, subscriber, connect handler, middleware, observer), every refusal (cascade, rejected write) and every development warning (key collision, payload by reference, dotted key, missing snapshot slice) is reported as a Diagnostic with a stable code. The sink replaces the console output; without one the console output is unchanged. Runtime observers are additive and never silence the console. The on* hooks still fire. EventBus and LooseEventBus take an optional handler-error callback, defaulting to the console.
  • New store.instrumentEffects(observer, options?) (with InstrumentedEffects, InstrumentedEffect, EffectsObserver): once every effect for an event has settled, reports each effect's name, origin, duration and whether it failed, plus the whole phase's duration and the clock time. Nothing is timed while no observer is registered; ephemeral events reach only observers that opt in; a throwing observer is reported as observer-error.
  • Effects receive a fourth argument, an EffectContext (new export), whose signal aborts when the effect stops being registered: its disposer runs, replaceEffects or hotReplace removes it ("effect replaced"), or the store is disposed. Created on first read, one per registration. onEffect handlers receive it as their fifth argument. Effects written with three parameters are unaffected.
  • createStore takes ephemeral, a list of channels whose events are traffic, not history: they are handled as usual but reach only instrumentation observers registered with store.instrument(observer, { ephemeral: true }) (new InstrumentOptions export), cost no instrumentation work while none is, and are skipped by replay. An ephemeral event that writes state is warned about once in development (ephemeral-write). persist opts in. The README documents the pattern for values that change many times a second.
  • A reducer or an effect registered with when: { channelPattern } now throws, naming the registration. channelPattern was only ever honoured by middleware: on a reducer or an effect it was accepted without a word and handled nothing, so the reducer never ran and the effect was not even reported to onRegistrationChange. ReducerSpec.when and EffectSpec.when are now typed ExactWhen (new export: When without its pattern form), so the mistake is a compile error too. A when of none of the five forms ({}, { any: false }, { keys: "x" }) also throws, on reducers, effects and middleware alike. Every entry point checks the whole batch before changing anything, so a refused replaceReducers, replaceEffects, replaceMiddleware or hotReplace leaves the store as it was. Breaking for code that registered either form, which never worked: name the channels, or keep the pattern on a middleware. See docs/en/UPGRADE_0.10.md.
  • matchesWhen(when, event) and describeWhenProblem(when, consumer) are exported, with the WhenConsumer type: the matcher every seam of the store uses and the check that refuses a malformed matcher, for code that filters events by a When outside the store. matchesWhen reads only channel and type, so an instrumented event's event is accepted as is.
  • New warnOnLargeValues(store, limits?) (with LargeValueLimits and SizeWatchedStore): a development-only check that warns once per event key and once per slice when a committed payload or a changed slice exceeds a node or estimated byte limit. A separate import, tree-shaken when unused, and a no-op in production. Built on instrument(..., { ephemeral: true }), with a bounded measurement that stops at the limit.
  • New store.metrics() (with StoreMetrics): queue depth, effects in flight, dedup hits and dedup entries, cheap enough to read on every metrics scrape. New store.whenIdle(): resolves when no event waits to be reduced and no effect runs, for a graceful shutdown; a call waiting for its reply and pending timers do not delay it, and every wait resolves on dispose.
  • persist never writes a partial state. State past the node budget (PersistOptions.maxNodes, new, default 100 000) is not written, storage keeps its previous value, and onError receives a PersistEncodeError (new export) with truncated: true and written: false; dehydrate returns "" in that case. Unsupported values are still written and reported as a PersistEncodeError with written: true. persist also writes only after an event that changed state, not after a vetoed, refused or no-op one.
  • The function persist() returns now returns a promise that resolves once the last write has settled, so the final write can be awaited before the store is torn down; it never rejects, and a failed write still goes to onError.
  • replaceReducers and hotReplace({ reducer }) now take ReducerReplacement (new export): every slice optional, each typed with its own slice's state. The argument used to require every slice name and type each reducer with the union of all slice states, so an annotated reducer failed on any store with two slices, a reducer could return another slice's state, and on a decorated store the only call that compiled named a slice mounted at runtime, which the runtime refuses. StoreSpec.reducer is typed per slice the same way. EventFromWhen gains its channelPattern arm, resolving to the whole event union a middleware receives instead of never.
  • A disposed store is inert: emit() resolves { committed: false } without running anything, call() rejects with CallAbortedError("store disposed") as does every call still pending, and register*, with*, onEffect, replace* and hotReplace throw naming the store. A late emit or call is reported once per method in development as use-after-dispose. dispose() is idempotent. New store.signal, an AbortSignal created on first read and aborted last in dispose(). A call() whose signal is already aborted no longer sends its request or arms a timer.

Correcciones

  • The error for an effect registered with channelPattern says "an effect takes", not "a effect takes".
  • The development warning for two (channel, type) pairs that join to one internal key is now remembered per store and names the store. It was remembered for the whole process, which got both directions wrong: once one store had reported a key, a second store with the same collision stayed silent, and two stores that each used one of the pairs, which cannot interfere, were reported as colliding.
  • The development warning for a state key containing a dot is kept per store and names the store and the slice; it used to be kept for the whole process, so one store reporting a key silenced every other. The entity adapter keeps its dotted-id warning per adapter for the same reason. EventFromWhen, ReducersMapAny, StateFromReducers and EMFromReducersStrict, already exported, are now documented in the API reference. Store gains a class description, LooseEventBus gets its description back, and InstrumentedEvent.reduceTimeMs is described as the monotonic duration it is. The README says that effects for one event run in sequence and that emit() waits for them, and gives current figures for the cost of diffing an array against a normalised collection.

#@yoltra/devtools-browser-agent

Funcionalidades

  • Several stores in one page work through the extension's bridge. Each postMessage socket stamps its frames with its own connection id, accepts only frames to the page that carry its id (or none), and posts a closed notice when it closes; BridgeMessage gains the optional connection and closed fields. Before, every store on a page reached the panel as one connection: only the first store registered, and a command for one store, time travel included, was applied by all of them.

#@yoltra/devtools-cli

Funcionalidades

  • --token <secret> and the YOLTRA_DEVTOOLS_TOKEN environment variable (exported as TOKEN_ENV; parseArgs takes the environment as an optional second argument): the embedded hub requires the token and the terminal panel presents it. In the Emit tab, q, [, ], Tab and r no longer act while a field has focus, so they can be typed into a channel, a type or a payload; Esc leaves the form and Enter returns to it.

#@yoltra/devtools-protocol

Funcionalidades

  • duplicateStoreIdError(storeId): the handshake error every hub uses to refuse a store whose id is already connected, so the refusal reads the same over a socket and through an in-memory broker.

#@yoltra/devtools-server

Funcionalidades

  • The standalone CLI takes the hub's token from --token <secret> or the YOLTRA_DEVTOOLS_TOKEN environment variable (the flag wins), and refuses --token without a value instead of starting an open hub. A store presenting a store id that is already connected is refused with a handshake error naming the id, instead of silently replacing the first store's registration (after which events from both shared one id, commands reached only the newer store, and either one leaving removed the other). The refusal log reads "Rejected an extension handshake".

#@yoltra/devtools-storeview

Correcciones

  • mountDevtools and DevtoolsApp pass authToken to the hub through their config (the hub connection config gained the field), so the panel can connect to a hub started with a token. The README (en, es) says so.
  • The event status toggle reads Uncommitted instead of Bounced, and the event detail badge reads uncommitted instead of vetoed, matching the store, which reports events that did not commit for reasons other than a veto too.

#@yoltra/devtools-ui

Funcionalidades

  • HubConnectionConfig.authToken: HubProvider sends it in the handshake, so a panel can connect to a hub started with a token. useStoreState no longer freezes when the first snapshot is version 0 (a store that has committed nothing yet): it tracks whether a snapshot has arrived separately from the version, so later patches apply instead of being buffered until the next snapshot.

Correcciones

  • The loopback hub refuses a second store presenting a connected store id, with the same message as the hub, instead of registering it beside the first.

#@yoltra/react

Correcciones

  • useAtomicProp(slice) called without a path accessor, or with a dotted path where the accessor goes, throws a sentence naming the hook and both calling forms instead of a TypeError from inside the path recorder. The README says that a wildcard path hands map the whole slice.
  • The Suspense hooks no longer serve a value loaded from state that has since changed. Their cache was invalidated only by the store subscription, which React attaches after commit, so a change landing between a render and that subscription was missed and the stale value stayed on screen indefinitely. Cache entries now record the values they were loaded from, and a read whose values differ loads again; suspenseCache.read takes them as an optional source. A load overtaken by a newer one no longer overwrites it.
Reportar un problema con esta página