Ir al contenido

Migración a 2.1

2.1.0-rc.0 corrige capacidades existentes: cancelación, concurrencia, async y validación. Se prepara primero como release candidate; todavía no se ha publicado. Mantiene Node 20+, decoradores TC39 estándar, cero dependencias de runtime y los entrypoints existentes. Revisa estos cambios observables antes de actualizar.

Si la señal proporcionada a run(receiver, args, operation, { signal }) aborta, la promesa de ese llamante rechaza con el mismo signal.reason, aunque sea un valor que no herede de Error. Una señal ya abortada impide comenzar su trabajo.

La cancelación del llamante:

  • No activa fallback.
  • No incrementa ni resetea los fallos del CircuitBreaker, ni cuenta como éxito.
  • No queda almacenada como resultado rechazado de caché.
  • No cancela a otros consumidores de una ejecución compartida.

Un AbortError ajeno a la señal del llamante sigue siendo un error normal. Los errores de negocio y los TimeoutError internos conservan su tratamiento habitual: por ejemplo, fallback puede recuperar un timeout interno y retry puede volver a intentar según el orden de composición.

Abortar deja de esperar ese resultado; no detiene físicamente código que no coopere. El trabajo subyacente puede seguir ejecutándose y sus efectos pueden ocurrir después del rechazo del llamante.

memoize, cachedFor y overlap({ mode: 'share', ... }) separan la ejecución compartida de las señales de sus consumidores. Si A inicia y B se une, cancelar A rechaza solo A: B puede recibir el resultado. Si ambos cancelan, el trabajo puede continuar; 2.1 no incorpora cancelación por conteo de consumidores.

La cancelación de A no se cachea. El resultado válido de la ejecución compartida sí puede quedar cacheado y reutilizarse después.

Se conservan los contratos previos:

  • Los decoradores Memoize/CachedFor aíslan la caché por instancia.
  • Los operadores funcionales comparten su caché entre receivers de una misma pipeline aplicada; las claves por defecto siguen basándose en los argumentos.
  • El TTL comienza al invocar, también para promesas pendientes; no comienza al resolver.
  • Los rechazos del trabajo cacheado se expulsan para permitir otro intento.
  • cachedFor(0) funcional desactiva la reutilización; Infinity significa que la entrada no caduca.

pipe(fn, timeout(...), retry(...)) propaga la señal interna del timeout al retry contextual aunque la invocación no use run(). Los contextos internos no consumen IDs públicos de operación.

Una policy con timeout global y retry ya no exige timeout.abortSignal estático. Puede reutilizarse con una señal nueva en cada run(). La opción estática sigue admitida; si reutilizas una señal ya abortada, seguirá abortada.

Los operadores contextuales externos, los operadores identidad y las pipelines anidadas conservan el contexto. Un operador personalizado ordinario que envuelva su función en otra función opaca sigue siendo una frontera de contexto para sus operadores internos: 2.1 no introduce contexto ambiental para atravesar arbitrariamente wrappers async. El contexto externo continúa disponible.

Una espera abortada de overlap(queue) se retira de la cola, libera capacidad de maxQueue, rechaza con su reason y no ejecuta el método. También se comprueba el aborto entre la admisión y la ejecución. La cola mantiene FIFO sin permitir que una llamada nueva adelante a una ya admitida.

El holder activo conserva el permiso hasta que termine su ejecución interna. Cancelar la espera del llamante no libera prematuramente un permiso cuyo trabajo no cooperativo sigue activo. @Semaphore(n) transfiere permisos directamente y respeta siempre el límite de concurrencia. keyedLock mantiene sus contratos existentes y los nombres públicos de eventos se conservan.

Cada probe HALF_OPEN tiene un lease de resetMs desde su inicio. Si se cuelga, una llamada posterior puede iniciar otra probe al expirar ese intervalo. No se crea un scheduler ni se fuerza la terminación del trabajo antiguo. Una generación identifica a la probe vigente: el resultado o la cancelación de una probe obsoleta no puede cerrar, reabrir ni liberar la posición de la nueva. El llamante de la probe antigua conserva su propio resultado o error eventual. Cancelar la probe vigente libera su posición sin decidir éxito ni fallo.

Los valores inválidos fallan antes de iniciar trabajo. Las clases de error y los paths de InvalidPolicyError se conservan; el código que dependía de valores inválidos aceptados en silencio debe corregir su configuración.

Dominio APIs Valores admitidos
Timer con cero timeout(), delay de Retry, delay de hedge, timeout de RemoteAsyncStore Finito, entre 0 y 2_147_483_647, ambos incluidos
Timer positivo @Timeout, timeout de definePolicy, @Debounce Finito, mayor que 0 y como máximo 2_147_483_647
Contador positivo Retry attempts, CircuitBreaker failures, Semaphore permits, RateLimit maxCalls Entero seguro mayor o igual que 1
Límite de cola explícito overlap y keyedLock maxQueue Entero seguro mayor o igual que 0; omitirlo conserva la cola sin límite configurado
Backoff Retry backoffFactor Finito, mayor o igual que 1
Ventana de tiempo transcurrido Throttle interval, RateLimit window, CircuitBreaker reset Finito y mayor que 0, sin máximo de timer
TTL con cero cachedFor(), cachedForWithHandle(), AsyncMemoryStore TTL Finito mayor o igual que 0, o Infinity
TTL positivo @CachedFor Finito mayor que 0, o Infinity

Los delays y las ventanas admiten fracciones válidas; los contadores no. NaN y -Infinity nunca son válidos. Infinity solo se admite en TTL. Retry aplica un clamp interno al delay calculado, incluso cuando el backoff desborda, para no superar el máximo portable del timer. AsyncMemoryStore incorpora TTL Infinity como entrada sin caducidad; cero conserva expiración inmediata.

@Revertible registra éxito síncrono inmediatamente y éxito async al resolver, incluidos thenables. Si la operación lanza o rechaza, no se registra. Dos operaciones concurrentes se registran en orden de finalización, conservando los argumentos originales. undoLast() ejecuta undo síncrono antes de retirar la entrada; si undo lanza, la entrada permanece disponible. No se añade undo async.

Subject.emit() toma una instantánea de listeners al comenzar cada emisión y los llama en orden de suscripción. Añadir, eliminar o volver a añadir listeners durante la emisión no cambia esa instantánea. Una emisión reentrante toma una instantánea nueva; un listener añadido durante la emisión exterior puede recibir la emisión reentrante, pero no la exterior ya iniciada.

El builder usa una regla léxica: si el sufijo empieza con dos mayúsculas ASCII, lo conserva; en los demás casos, pone su primer carácter en minúscula. withName escribe name, withURL escribe URL y withHTTPPort escribe HTTPPort. uRL y URL son ambiguos porque ambos generan withURL; se elige URL. También Name y name comparten withName, que elige name. No se inspecciona ni ejecuta el constructor hasta build(), ni cambian readonly o los métodos incluidos en el tipo Builder.

Con un mapa cerrado de eventos, @On comprueba el nombre y el payload recibido; @Emits comprueba el nombre y el valor resuelto del método. Subject sin genérico mantiene nombres string genéricos y payload unknown: el listener debe aceptar y estrechar ese valor antes de usarlo.

Con StateMachine<'draft' | 'published'>, tanto @When('publised') como @TransitionTo('publised') producen un error de compilación. Usa StateMachine<string> cuando los estados deban ser abiertos y dinámicos.

Son cambios de declaraciones observables: código con nombres o payloads antes aceptados puede dejar de compilar. Corrige el contrato o usa deliberadamente el modelo genérico correspondiente; no presupongas que runtime idéntico implica compatibilidad de tipos.

StateMachine sigue sin serializar operaciones async. Dos métodos pueden comprobar el mismo estado antes de que cualquiera termine y confirmar transiciones en orden de finalización. El tipado cerrado no corrige esa carrera; coordina esas operaciones en tu aplicación si necesitas exclusión mutua.

2.1 no cambia el crecimiento de las cachés ni sustituye Date.now por un reloj monótono. Tampoco añade isFailure, maxDelayMs, jitter, refcount de cancelación o nuevos servicios distribuidos.