
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
@embra/reactivity
Advanced tools
A lightweight, composable and explicit reactivity system.
It does not convert the value with Object.defineProperty nor Proxy. Keeping everything as plain JavaScript value makes it easier to work with other libraries and easier for the JavaScript engine to optimize.
import { writable, readable, type Readable } from "@embra/reactivity";
const count$ = writable(0);
count$.set(1);
const [count2$, setCount] = readable(0);
setCount(1);
Unlike signal-based libraries, @embra/reactivity does not automatically track dependencies. You explicitly define what to watch and how to react to changes. This is easier to reason about dependencies and also reduce the cost of complex implicit dependency calculation.
With React hook-like API, computations can be pure functions which is more compatible with general non-reactive functions.
import { writable, derive } from "@embra/reactivity";
const count$ = writable(0);
const isPositive = (value: number): boolean => value > 0;
const positiveCount$ = derive(count$, isPositive);
Dynamic dependencies can be collected using get in compute or watch.
import { writable, compute, watch } from "@embra/reactivity";
const count$ = writable(0);
const doubleCount$ = compute(get => get(count$) * 2);
watch(get => {
const count = get(count$);
console.log(`Count is ${count}, double is ${get(doubleCount$)}`);
});
In practice, one of the biggest problems we encountered with reactivity libraries is the lifecycle management of reactive values. In @embra/reactivity, the reactive dependencies are weakly referenced, so generally you don't need to manually dispose of them. However, explicit lifecycle management is still a good practice to avoid unexpected behaviors.
@embra/reactivity provides a zero-cost ownership model that allows you to create reactive values with explicit ownership.
By default, created reactive values are with type OwnedReadable or OwnedWritable, which exposes a dispose() method to clear their subscribers. When passing the reactive value to other modules, you can use Readable or Writable types to hide the dispose() method, ensuring that the value is not disposed of accidentally.
import { writable, readable, type Readable, type OwnedWritable } from "@embra/reactivity";
const count$: OwnedWritable<number> = writable(0);
count$.set(1);
// Hide the setter by typing
function logCount(count$: Readable<number>) {
count$.subscribe(console.log);
// @ts-expect-error
count$.set(2);
}
logCount(count$);
// Hide the setter in runtime
const [count2$, setCount] = readable(0);
setCount(1);
// @ts-expect-error
count2$.set(2);
In the days of Flux reducer model, we often used a single store to hold the state and actions to mutate the state. This was nice for reasoning about the state, but it also introduced a lot of boilerplate code.
Later on, a pattern with state and action glued together was introduced, like redux-actions. @embra/reactivity takes this a step further by providing a simple and flexible abstraction of state and actions.
In the following example, we create a Writable count$ which looks like a Writable<number>, but internally it is derived from a larger application state appState$. This allows other modules to depend on a Writable<number> without knowing the details of the application state.
import { writable, derive, toWritable } from "@embra/reactivity";
import { trace } from "@embra/reactivity/debug";
const appState$ = writable({
count: 0,
user: null,
});
const count$ = toWritable(
derive(appState$, state => state.count),
count => appState$.set({ ...appState$.value, count }),
);
// when debugging, you can trace the reactive value
trace(count$);
@embra/reactivity includes a scheduler mechanism and built-in schedulers that lets you control when reactive updates are processed.
This is useful for batching updates and deferring computations.
import { writable, MicrotaskScheduler } from "@embra/reactivity";
const rapidChangeCount$ = writable(0);
rapidChangeCount$.reaction(console.log, MicrotaskScheduler);
count$.set(1);
count$.set(2);
await Promise.resolve();
// Logs "2" once after a microtask tick, reducing unnecessary computations.
You can also provide your owned custom scheduler function easily.
import { writable, asyncScheduler } from "@embra/reactivity";
const MicrotaskScheduler = asyncScheduler(flush => Promise.resolve().then(flush));
const AnimationFrameScheduler = asyncScheduler(requestAnimationFrame);
@embra/reactivity is designed to be framework agnostic. It can be used with any framework or library that supports JavaScript. It also provides first-class support for React.
value = Readablevalue + onChange = Writablestate + action = toWritable(derive(rootState$, selector), action) (See State and Actions)import { writable } from "@embra/reactivity";
import { useValue, useDerived, useCombined } from "@embra/reactivity/react";
const count$ = writable(0);
function Counter({ count$ }) {
const count = useValue(count$);
const countDouble = useDerived(count$, count => count * 2);
return (
<div>
<button onClick={() => count$.set(count - 1)}>-</button>
<span>{count}</span>
<button onClick={() => count$.set(count + 1)}>+</button>
<span>{countDouble}</span>
</div>
);
}
npm add @embra/reactivity
@embra/reactivity provides a trace() function to help debug reactive values and watches. It tracks value and dependency changes and logs them to the console.
import { writable, watch } from "@embra/reactivity";
import { trace } from "@embra/reactivity/debug";
const count$ = writable(0);
// trace a reactive value
trace(count$);
// trace a watch function
watch(trace(get => get(count$)));
count$.set(1);
@embra/reactivity supports Chrome DevTools custom formatters. You may enable it by checking the "Enable custom formatters" option in the "Console" section of DevTools general settings.
It is enabled in development by default. You can also enable it manually by calling customFormatter().
import { customFormatter } from "@embra/reactivity/debug";
customFormatter();
FAQs
A lightweight, composable and explicit reactivity system.
We found that @embra/reactivity demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.