
Research
NPM targeted by malware campaign mimicking familiar library names
Socket uncovered npm malware campaign mimicking popular Node.js libraries and packages from other ecosystems; packages steal data and execute remote code.
A lightweight (750B) library for creating reactive observables via functions.
fn.obs
fn.obs
is a tiny (750B minzipped) library for creating reactive observables via functions. You
can use observables to store state, create computed properties (y = mx + b
), and subscribe to
updates as its value changes.
$computed
$subscribe
$dispose
Here's a simple demo to see how it works:
Note
Interact with the demo live on StackBlitz.
import { $observable, $computed, $subscribe } from 'fn.obs';
// Create - all types supported (string, array, object, etc.)
const $m = $observable(1);
const $x = $observable(1);
const $b = $observable(0);
// Compute - this will run whenever `$m`, `$x`, or `$b` changes.
const $y = $computed(() => $m() * $x() + $b());
// Subscribe - this will run whenever `$y` is updated.
const unsubscribe = $subscribe(() => console.log($y()));
// `10` will be logged by subscription.
$m.set(10);
// `15` will be logged by subscription.
$b.update((prev) => prev + 5);
// Observable is up-to-date (`15`).
console.log($y());
unsubscribe();
$: npm i fn.obs
$: pnpm i fn.obs
$: yarn add fn.obs
$observable
Wraps the given value into an observable function. The observable function will return the
current value when invoked fn()
, and provide a simple write API via set()
and update()
. The
value can now be observed when used inside other functions such as $computed
and $subscribe
.
import { $observable } from 'fn.obs';
const $a = $observable(10);
$a(); // read
$a.set(20); // write (1)
$a.update((prev) => prev + 10); // write (2)
Warning Read the
$tick
section below to understand batched updates.
$computed
Creates a new observable whose value is computed by invoking the given function. Any observables that are read during execution are considered a dependency; they will trigger the compute function to re-run if changed.
import { $observable, $computed } from 'fn.obs';
const $a = $observable(10);
const $b = $observable(10);
const $c = $computed(() => $a() + $b());
console.log($c()); // logs 20
$a.set(20);
console.log($c()); // logs 30
$b.set(20);
console.log($c()); // logs 40
import { $observable, $computed } from 'fn.obs';
const $a = $observable(10);
const $b = $observable(10);
const $c = $computed(() => $a() + $b());
// Computed observables can be deeply nested.
const $d = $computed(() => $a() + $b() + $c());
const $e = $computed(() => $d());
$subscribe
Invokes the given function each time any of the observables that are read inside are updated (i.e., their value changes). The subscription is immediately invoked on initialization.
import { $observable, $computed, $subscribe } from 'fn.obs';
const $a = $observable(10);
const $b = $observable(20);
const $c = $computed(() => $a() + $b());
// This subscription will run each time `$a` or `$b` is updated.
const unsubscribe = $subscribe(() => console.log($c()));
// Dispose of the subscription.
unsubscribe();
$peek
Returns the current value stored inside an observable without triggering a dependency.
import { $observable, $computed, $peek } from 'fn.obs';
const $a = $observable(10);
$computed(() => {
// `$a` changes will not trigger any updates of this computed function.
const value = $peek($a);
});
$readonly
Takes in the given observable and makes it read only by removing access to write
operations (i.e., set()
and update()
).
import { $observable, $readonly } from 'fn.obs';
const $a = $observable(10);
const $b = $readonly($a);
console.log($b()); // logs 10
// We can still update value through `$a`.
$a.set(20);
console.log($b()); // logs 20
$tick
Tasks are batched onto the microtask queue. This means only the last write of multiple write actions performed in the same execution window is applied. You can wait for the microtask queue to be flushed before writing a new value so it takes effect.
import { $observable } from 'fn.obs';
const $a = $observable(10);
$a.set(10);
$a.set(20);
$a.set(30); // only this write is applied
import { $observable, $tick } from 'fn.obs';
const $a = $observable(10);
// All writes are applied.
$a.set(10);
await $tick();
$a.set(20);
await $tick();
$a.set(30);
$dispose
Marks the given observable as disposed. Each observable that this observer depends on will delete it on their respective next run (i.e., lazily disposed). This means it will eventually be garbage collected once all observables dependencies are run.
import { $observable, $dispose } from 'fn.obs';
const $a = $observable(10);
$dispose($a);
The $observable
, $computed
, and $subscribe
functions accept a debugging ID (string) as
their second argument. This can be helpful when logging a cyclic dependency chain to understand
where it's occurring.
import { $observable, $computed } from 'fn.obs';
const $a = $observable(10, 'a');
// Cyclic dependency chain.
const $b = $computed(() => $a() + $c(), 'b');
const $c = $computed(() => $a() + $b(), 'c');
// This will throw an error in the form:
// $: Error: cyclic dependency detected
// $: a -> b -> c -> b
Note This feature is only available in a development or testing Node environment (i.e.,
NODE_ENV
).
We provide the underlying microtask scheduler incase you'd like to use it:
import { createScheduler } from 'fn.obs';
// Creates a scheduler which batches tasks and runs them in the microtask queue.
const scheduler = createScheduler();
// Queue tasks.
scheduler.enqueue(() => {});
scheduler.enqueue(() => {});
// Schedule a flush - can be invoked more than once.
scheduler.flush();
// Wait for flush to complete.
await scheduler.microtask;
import { $computed, type Observable, type ComputedObservable } from 'fn.obs';
const observable: Observable<number>;
const computed: ComputedObservable<number>;
// Provide generic if TS fails to infer correct type.
const $a = $computed<string>(() => /* ... */);
fn.obs
was made possible based on my learnings from:
Special thanks to Wesley, Julien, and Svelte contributors for all their work 🎉
FAQs
A lightweight (750B) library for creating reactive observables via functions.
We found that fn.obs demonstrated a not healthy version release cadence and project activity because the last version was released 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
Socket uncovered npm malware campaign mimicking popular Node.js libraries and packages from other ecosystems; packages steal data and execute remote code.
Research
Socket's research uncovers three dangerous Go modules that contain obfuscated disk-wiping malware, threatening complete data loss.
Research
Socket uncovers malicious packages on PyPI using Gmail's SMTP protocol for command and control (C2) to exfiltrate data and execute commands.