New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

alien-signals

Package Overview
Dependencies
Maintainers
1
Versions
46
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

alien-signals - npm Package Compare versions

Comparing version 1.0.0-alpha.3 to 1.0.0

2

package.json
{
"name": "alien-signals",
"version": "1.0.0-alpha.3",
"version": "1.0.0",
"sideEffects": false,

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -9,40 +9,36 @@ <p align="center">

<h3 align="center">
<p>[<a href="https://github.com/YanqingXu/alien-signals-in-lua">Alien Signals in Lua</a>]</p>
<p>[<a href="https://github.com/medz/alien-signals-dart">Alien Signals in Dart</a>]</p>
<p>[<a href="https://github.com/Rajaniraiyn/react-alien-signals">React Binding</a>]</p>
</h3>
## Derived Projects
- [YanqingXu/alien-signals-in-lua](https://github.com/YanqingXu/alien-signals-in-lua): Lua implementation of alien-signals
- [medz/alien-signals-dart](https://github.com/medz/alien-signals-dart): alien-signals Dart implementation of alien-signals
- [Rajaniraiyn/react-alien-signals](https://github.com/Rajaniraiyn/react-alien-signals): React bindings for the alien-signals API
- [CCherry07/alien-deepsignals](https://github.com/CCherry07/alien-deepsignals): Use alien-signals with the interface of a plain JavaScript object
# alien-signals
The goal of `alien-signals` is to create a ~~push-pull~~ [push-pull-push model](https://github.com/stackblitz/alien-signals/pull/19) based signal library with the lowest overhead.
This project explores a push-pull based signal algorithm. Its current implementation is similar to or related to certain other frontend projects:
We have set the following constraints in scheduling logic:
- Propagation algorithm of Vue 3
- Preact’s double-linked-list approach (https://preactjs.com/blog/signal-boosting/)
- Inner effects scheduling of Svelte
- Graph-coloring approach of Reactively (https://milomg.dev/2022-12-01/reactivity)
1. No dynamic object fields
2. No use of Array/Set/Map
3. No recursion calls
4. Class properties must be fewer than 10 (https://v8.dev/blog/fast-properties)
We impose some constraints (such as not using Array/Set/Map and disallowing function recursion) to ensure performance. We found that under these conditions, maintaining algorithmic simplicity offers more significant improvements than complex scheduling strategies.
Experimental results have shown that with these constraints, it is possible to achieve excellent performance for a Signal library without using sophisticated scheduling strategies. The overall performance of `alien-signals` is approximately 400% that of Vue 3.4's reactivity system.
Even though Vue 3.4 is already optimized, alien-signals is still noticeably faster. (I wrote code for both, and since they share similar algorithms, they’re quite comparable.)
For more detailed performance comparisons, please visit: https://github.com/transitive-bullshit/js-reactivity-benchmark
<img width="1210" alt="Image" src="https://github.com/user-attachments/assets/88448f6d-4034-4389-89aa-9edf3da77254" />
## Motivation
## Background
To achieve high-performance code generation in https://github.com/vuejs/language-tools, I needed to write some on-demand computed logic using Signals, but I couldn't find a low-cost Signal library that satisfied me.
I spent considerable time [optimizing Vue 3.4’s reactivity system](https://github.com/vuejs/core/pull/5912), gaining experience along the way. Since Vue 3.5 [switched to a pull-based algorithm similar to Preact](https://github.com/vuejs/core/pull/10397), I decided to continue researching a push-pull based implementation in a separate project. Our end goal is to implement fully incremental AST parsing and virtual code generation in Vue language tools, based on alien-signals.
In the past, I accumulated some knowledge of reactivity systems in https://github.com/vuejs/core/pull/5912, so I attempted to develop `alien-signals` with the goal of creating a Signal library with minimal memory usage and excellent performance.
## Adoption
Since Vue 3.5 switched to a Pull reactivity system in https://github.com/vuejs/core/pull/10397, I continued to research the Push-Pull reactivity system here. It is worth mentioning that I was inspired by the doubly-linked concept, but `alien-signals` does not use a similar implementation.
- [vuejs/core](https://github.com/vuejs/core): The core algorithm has been ported to 3.6 or higher (PR:https://github.com/vuejs/core/pull/12349)
- [vuejs/language-tools](https://github.com/vuejs/language-tools): Used in the language-core package for virtual code generation
## Adoptions
- Used in Vue language tools (https://github.com/vuejs/language-tools) for virtual code generation.
- The core reactivity system code was ported to Vue 3.6 and later. (https://github.com/vuejs/core/pull/12349)
## Usage
### Basic
#### Basic APIs

@@ -53,16 +49,16 @@ ```ts

const count = signal(1);
const doubleCount = computed(() => count.get() * 2);
const doubleCount = computed(() => count() * 2);
effect(() => {
console.log(`Count is: ${count.get()}`);
console.log(`Count is: ${count()}`);
}); // Console: Count is: 1
console.log(doubleCount.get()); // 2
console.log(doubleCount()); // 2
count.set(2); // Console: Count is: 2
count(2); // Console: Count is: 2
console.log(doubleCount.get()); // 4
console.log(doubleCount()); // 4
```
### Effect Scope
#### Effect Scope

@@ -73,17 +69,24 @@ ```ts

const count = signal(1);
const scope = effectScope();
scope.run(() => {
const stopScope = effectScope(() => {
effect(() => {
console.log(`Count in scope: ${count.get()}`);
console.log(`Count in scope: ${count()}`);
}); // Console: Count in scope: 1
count.set(2); // Console: Count in scope: 2
count(2); // Console: Count in scope: 2
});
scope.stop();
stopScope();
count.set(3); // No console output
count(3); // No console output
```
#### Creating Your Own Public API
You can reuse alien-signals’ core algorithm via `createReactiveSystem()` to build your own signal API. For implementation examples, see:
- https://github.com/stackblitz/alien-signals/blob/master/src/index.ts
- https://github.com/proposal-signals/signal-polyfill/pull/44
## About `propagate` and `checkDirty` functions

@@ -98,3 +101,3 @@

```ts
export function propagate(link: Link, targetFlag: SubscriberFlags = SubscriberFlags.Dirty): void {
function propagate(link: Link, targetFlag = SubscriberFlags.Dirty): void {
do {

@@ -106,14 +109,15 @@ const sub = link.sub;

(
!(subFlags & (SubscriberFlags.Tracking | SubscriberFlags.Recursed | SubscriberFlags.InnerEffectsPending | SubscriberFlags.ToCheckDirty | SubscriberFlags.Dirty))
&& (sub.flags = subFlags | targetFlag, true)
!(subFlags & (SubscriberFlags.Tracking | SubscriberFlags.Recursed | SubscriberFlags.Propagated))
&& (sub.flags = subFlags | targetFlag | SubscriberFlags.Notified, true)
)
|| (
(subFlags & (SubscriberFlags.Tracking | SubscriberFlags.Recursed)) === SubscriberFlags.Recursed
&& (sub.flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag, true)
(subFlags & SubscriberFlags.Recursed)
&& !(subFlags & SubscriberFlags.Tracking)
&& (sub.flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag | SubscriberFlags.Notified, true)
)
|| (
!(subFlags & (SubscriberFlags.InnerEffectsPending | SubscriberFlags.ToCheckDirty | SubscriberFlags.Dirty))
!(subFlags & SubscriberFlags.Propagated)
&& isValidLink(link, sub)
&& (
sub.flags = subFlags | SubscriberFlags.Recursed | targetFlag,
sub.flags = subFlags | SubscriberFlags.Recursed | targetFlag | SubscriberFlags.Notified,
(sub as Dependency).subs !== undefined

@@ -127,9 +131,9 @@ )

subSubs,
isEffect(sub)
? SubscriberFlags.InnerEffectsPending
: SubscriberFlags.ToCheckDirty
subFlags & SubscriberFlags.Effect
? SubscriberFlags.PendingEffect
: SubscriberFlags.PendingComputed
);
} else if (isEffect(sub)) {
} else if (subFlags & SubscriberFlags.Effect) {
if (queuedEffectsTail !== undefined) {
queuedEffectsTail.nextNotify = sub;
queuedEffectsTail.depsTail!.nextDep = sub.deps;
} else {

@@ -140,9 +144,16 @@ queuedEffects = sub;

}
} else if (!(subFlags & (SubscriberFlags.Tracking | targetFlag))) {
sub.flags = subFlags | targetFlag | SubscriberFlags.Notified;
if ((subFlags & (SubscriberFlags.Effect | SubscriberFlags.Notified)) === SubscriberFlags.Effect) {
if (queuedEffectsTail !== undefined) {
queuedEffectsTail.depsTail!.nextDep = sub.deps;
} else {
queuedEffects = sub;
}
queuedEffectsTail = sub;
}
} else if (
!(subFlags & (SubscriberFlags.Tracking | targetFlag))
|| (
!(subFlags & targetFlag)
&& (subFlags & (SubscriberFlags.InnerEffectsPending | SubscriberFlags.ToCheckDirty | SubscriberFlags.Dirty))
&& isValidLink(link, sub)
)
!(subFlags & targetFlag)
&& (subFlags & SubscriberFlags.Propagated)
&& isValidLink(link, sub)
) {

@@ -154,6 +165,2 @@ sub.flags = subFlags | targetFlag;

} while (link !== undefined);
if (targetFlag === SubscriberFlags.Dirty && !batchDepth) {
drainQueuedEffects();
}
}

@@ -165,3 +172,3 @@ ```

```ts
export function checkDirty(link: Link): boolean {
function checkDirty(link: Link): boolean {
do {

@@ -171,4 +178,4 @@ const dep = link.dep;

const depFlags = dep.flags;
if (depFlags & SubscriberFlags.Dirty) {
if (isComputed(dep) && updateComputed(dep)) {
if ((depFlags & (SubscriberFlags.Computed | SubscriberFlags.Dirty)) === (SubscriberFlags.Computed | SubscriberFlags.Dirty)) {
if (updateComputed(dep)) {
const subs = dep.subs!;

@@ -180,5 +187,5 @@ if (subs.nextSub !== undefined) {

}
} else if (depFlags & SubscriberFlags.ToCheckDirty) {
if (isComputed(dep) && checkDirty(dep.deps!)) {
if (dep.update()) {
} else if ((depFlags & (SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) === (SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) {
if (checkDirty(dep.deps!)) {
if (updateComputed(dep)) {
const subs = dep.subs!;

@@ -191,3 +198,3 @@ if (subs.nextSub !== undefined) {

} else {
dep.flags = depFlags & ~SubscriberFlags.ToCheckDirty;
dep.flags = depFlags & ~SubscriberFlags.PendingComputed;
}

@@ -202,10 +209,1 @@ }

```
## Roadmap
| Version | Savings |
|---------|-----------------------------------------------------------------------------------------------|
| 0.3 | Satisfy all 4 constraints |
| 0.2 | Correctly schedule computed side effects |
| 0.1 | Correctly schedule inner effect callbacks |
| 0.0 | Add APIs: `signal()`, `computed()`, `effect()`, `effectScope()`, `startBatch()`, `endBatch()` |

@@ -7,3 +7,2 @@ import { Subscriber } from './system.js';

export declare function createDefaultSystem(): {
readonly batchDepth: number;
activeSub: Subscriber | undefined;

@@ -10,0 +9,0 @@ startBatch(): void;

export * from './system.js';
export declare function getDefaultSystem(): {
readonly batchDepth: number;
activeSub: import("./system.js").Subscriber | undefined;
startBatch(): void;
endBatch(): void;
pauseTracking(): void;
resumeTracking(): void;
signal: {
<T>(): {
(): T | undefined;
(value: T | undefined): void;
};
<T>(oldValue: T): {
(): T;
(value: T): void;
};
};
computed: <T>(getter: (cachedValue?: T) => T) => () => T;
effect: <T>(fn: () => T) => () => void;
type WriteableSignal<T> = {
(): T;
(value: T): void;
};
export declare function startBatch(): void;
export declare function endBatch(): void;
export declare function pauseTracking(): void;
export declare function resumeTracking(): void;
export declare function signal<T>(): WriteableSignal<T | undefined>;
export declare function signal<T>(oldValue: T): WriteableSignal<T>;
export declare function computed<T>(getter: (cachedValue?: T) => T): () => T;
export declare function effect<T>(fn: () => T): () => void;
export declare function effectScope<T>(fn: () => T): () => void;

@@ -18,29 +18,115 @@ export interface Dependency {

export declare const enum SubscriberFlags {
None = 0,
Tracking = 1,
Notified = 2,
Recursed = 4,
PendingInnerEffects = 8,
CheckRequired = 16,
Computed = 1,
Effect = 2,
Tracking = 4,
Notified = 8,
Recursed = 16,
Dirty = 32,
Propagated = 56
PendingComputed = 64,
PendingEffect = 128,
Propagated = 224
}
export declare function createSystem<Computed extends Dependency & Subscriber, Effect extends Subscriber>({ computed: { is: isComputed, update: updateComputed, }, effect: { is: isEffect, notify: notifyEffect, }, }: {
computed: {
is(sub: Dependency & Subscriber): sub is Computed;
update(computed: Computed): boolean;
};
effect: {
is(sub: Subscriber): sub is Effect;
notify(effect: Effect): boolean;
};
export declare function createReactiveSystem({ updateComputed, notifyEffect, }: {
/**
* Updates the computed subscriber's value and returns whether it changed.
*
* This function should be called when a computed subscriber is marked as Dirty.
* The computed subscriber's getter function is invoked, and its value is updated.
* If the value changes, the new value is stored, and the function returns `true`.
*
* @param computed - The computed subscriber to update.
* @returns `true` if the computed subscriber's value changed; otherwise `false`.
*/
updateComputed(computed: Dependency & Subscriber): boolean;
/**
* Handles effect notifications by processing the specified `effect`.
*
* When an `effect` first receives any of the following flags:
* - `Dirty`
* - `PendingComputed`
* - `PendingEffect`
* this method will process them and return `true` if the flags are successfully handled.
* If not fully handled, future changes to these flags will trigger additional calls
* until the method eventually returns `true`.
*/
notifyEffect(effect: Subscriber): boolean;
}): {
link: (dep: Dependency, sub: Subscriber) => boolean;
propagate: (link: Link) => void;
startTrack: (sub: Subscriber) => void;
endTrack: (sub: Subscriber) => void;
isDirty: (sub: Subscriber, flags: SubscriberFlags) => boolean;
processComputedUpdate: (computed: Computed, flags: SubscriberFlags) => void;
processInnerEffects: (link: Link) => void;
processQueuedEffects: () => void;
/**
* Links a given dependency and subscriber if they are not already linked.
*
* @param dep - The dependency to be linked.
* @param sub - The subscriber that depends on this dependency.
* @returns The newly created link object if the two are not already linked; otherwise `undefined`.
*/
link(dep: Dependency, sub: Subscriber): Link | undefined;
/**
* Traverses and marks subscribers starting from the provided link.
*
* It sets flags (e.g., Dirty, PendingComputed, PendingEffect) on each subscriber
* to indicate which ones require re-computation or effect processing.
* This function should be called after a signal's value changes.
*
* @param link - The starting link from which propagation begins.
*/
propagate(link: Link): void;
/**
* Prepares the given subscriber to track new dependencies.
*
* It resets the subscriber's internal pointers (e.g., depsTail) and
* sets its flags to indicate it is now tracking dependency links.
*
* @param sub - The subscriber to start tracking.
*/
startTracking(sub: Subscriber): void;
/**
* Concludes tracking of dependencies for the specified subscriber.
*
* It clears or unlinks any tracked dependency information, then
* updates the subscriber's flags to indicate tracking is complete.
*
* @param sub - The subscriber whose tracking is ending.
*/
endTracking(sub: Subscriber): void;
/**
* Updates the dirty flag for the given subscriber based on its dependencies.
*
* If the subscriber has any pending computeds, this function sets the Dirty flag
* and returns `true`. Otherwise, it clears the PendingComputed flag and returns `false`.
*
* @param sub - The subscriber to update.
* @param flags - The current flag set for this subscriber.
* @returns `true` if the subscriber is marked as Dirty; otherwise `false`.
*/
updateDirtyFlag(sub: Subscriber, flags: SubscriberFlags): boolean;
/**
* Updates the computed subscriber if necessary before its value is accessed.
*
* If the subscriber is marked Dirty or PendingComputed, this function runs
* the provided updateComputed logic and triggers a shallowPropagate for any
* downstream subscribers if an actual update occurs.
*
* @param computed - The computed subscriber to update.
* @param flags - The current flag set for this subscriber.
*/
processComputedUpdate(computed: Dependency & Subscriber, flags: SubscriberFlags): void;
/**
* Ensures all pending internal effects for the given subscriber are processed.
*
* This should be called after an effect decides not to re-run itself but may still
* have dependencies flagged with PendingEffect. If the subscriber is flagged with
* PendingEffect, this function clears that flag and invokes `notifyEffect` on any
* related dependencies marked as Effect and Propagated, processing pending effects.
*
* @param sub - The subscriber which may have pending effects.
* @param flags - The current flags on the subscriber to check.
*/
processPendingInnerEffects(sub: Subscriber, flags: SubscriberFlags): void;
/**
* Processes queued effect notifications after a batch operation finishes.
*
* Iterates through all queued effects, calling notifyEffect on each.
* If an effect remains partially handled, its flags are updated, and future
* notifications may be triggered until fully handled.
*/
processEffectNotifications(): void;
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc