https://github.com/maverick-js/observables Skip to content Sign up * Product + Features + Mobile + Actions + Codespaces + Copilot + Packages + Security + Code review + Issues + Integrations + GitHub Sponsors + Customer stories * Team * Enterprise * Explore + Explore GitHub + Learn and contribute + Topics + Collections + Trending + Skills + GitHub Sponsors + Open source guides + Connect with others + The ReadME Project + Events + Community forum + GitHub Education + GitHub Stars program * Marketplace * Pricing + Plans + Compare plans + Contact Sales + Education [ ] * # In this repository All GitHub | Jump to | * No suggested jump to results * # In this repository All GitHub | Jump to | * # In this organization All GitHub | Jump to | * # In this repository All GitHub | Jump to | Sign in Sign up {{ message }} maverick-js / observables Public * Notifications * Fork 2 * Star 140 A tiny (~850B minzipped) library for creating reactive observables via functions. Works in browsers and Node. License MIT license 140 stars 2 forks Star Notifications * Code * Issues 1 * Pull requests 0 * Actions * Projects 0 * Wiki * Security * Insights More * Code * Issues * Pull requests * Actions * Projects * Wiki * Security * Insights maverick-js/observables This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. main Switch branches/tags [ ] Branches Tags Could not load branches Nothing to show {{ refName }} default View all branches Could not load tags Nothing to show {{ refName }} default View all tags 1 branch 0 tags Code Latest commit @mihar-22 mihar-22 chore: add size badge ... 522c526 Jun 26, 2022 chore: add size badge 522c526 Git stats * 4 commits Files Permalink Failed to load latest commit information. Type Name Latest commit message Commit time .scripts feat: initial commit Jun 25, 2022 src chore: release 1.0 Jun 26, 2022 tests chore: release 1.0 Jun 26, 2022 .editorconfig feat: initial commit Jun 25, 2022 .gitignore feat: initial commit Jun 25, 2022 .prettierrc.cjs feat: initial commit Jun 25, 2022 LICENSE feat: initial commit Jun 25, 2022 README.md chore: add size badge Jun 26, 2022 export-sizes.png chore: update export sizes Jun 26, 2022 package.json chore: update export sizes Jun 26, 2022 pnpm-lock.yaml feat: initial commit Jun 25, 2022 tsconfig-build.json feat: initial commit Jun 25, 2022 tsconfig.json feat: initial commit Jun 25, 2022 types.json feat: initial commit Jun 25, 2022 vite.config.ts feat: initial commit Jun 25, 2022 View code [ ] Observables Export Sizes Installation API $observable $computed $effect $peek $readonly $tick $dispose isComputed Debugging Scheduler Types Inspiration README.md Observables package-badge license-badge size-badge The goal of this library is to provide a lightweight reactivity API for other UI libraries to be built on top of. It follows the "lazy principle" that Svelte adheres to - don't do any unnecessary work and don't place the burden of figuring it out on the developer. This is a tiny (~850B 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. * Light (~850B minzipped) * Works in both browsers and Node.js * All types are observable (i.e., string, array, object, etc.) * [?][?] Only updates when value has changed * [?][?] Batched updates via microtask scheduler * Lazy by default - efficiently re-computes only what's needed * Computations via $computed * Effect subscriptions via $effect * [?][?] Detects cyclic dependencies * Debugging identifiers * Strongly typed - built with TypeScript [?][?] Skip to API Here's a simple demo to see how it works: Note Interact with the demo live on StackBlitz. import { $observable, $computed, $effect, $tick } from '@maverick-js/observables'; // Create - all types supported (string, array, object, etc.) const $m = $observable(1); const $x = $observable(1); const $b = $observable(0); // Compute - only re-computed when `$m`, `$x`, or `$b` changes. const $y = $computed(() => $m() * $x() + $b()); // Effect - this will run whenever `$y` is updated. const stop = $effect(() => console.log($y())); $m.set(10); // logs `10` inside effect // Wait a tick so update is applied and effect is run. await $tick(); $b.update((prev) => prev + 5); // logs `15` inside effect // Wait a tick so effect runs last update. await $tick(); // Nothing has changed - no re-compute. $y(); // Stop running effect. stop(); Export Sizes Library export sizes Total: if you import everything it'll be ~850B. You can also check out the library size on Bundlephobia (less accurate). Installation $: npm i @maverick-js/observables $: pnpm i @maverick-js/observables $: yarn add @maverick-js/observables API * $observable * $computed * $effect * $peek * $readonly * $tick * $dispose * isComputed $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 computations created with $computed and $effect. import { $observable } from '@maverick-js/observables'; 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 and returned by the given function. The given compute function is only re-run when one of it's dependencies are updated. Dependencies are are all observables that are read during execution. import { $observable, $computed, $tick } from '@maverick-js/observables'; const $a = $observable(10); const $b = $observable(10); const $c = $computed(() => $a() + $b()); console.log($c()); // logs 20 $a.set(20); await $tick(); console.log($c()); // logs 30 $b.set(20); await $tick(); console.log($c()); // logs 40 // Nothing changed - no re-compute. console.log($c()); // logs 40 import { $observable, $computed } from '@maverick-js/observables'; 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()); $effect Invokes the given function each time any of the observables that are read inside are updated (i.e., their value changes). The effect is immediately invoked on initialization. import { $observable, $computed, $effect } from '@maverick-js/observables'; const $a = $observable(10); const $b = $observable(20); const $c = $computed(() => $a() + $b()); // This effect will run each time `$a` or `$b` is updated. const stop = $effect(() => console.log($c())); // Stop observing. stop(); You can optionally destroy all inner observables when stopping the effect by passing in true to the stop effect function: // `$c` is from the example above. const stop = $effect(() => console.log($c())); // This will dispose of `$a`, `$b`, `$c`, and the effect itself. stop(true); // <- deep flag $peek Returns the current value stored inside an observable without triggering a dependency. import { $observable, $computed, $peek } from '@maverick-js/observables'; const $a = $observable(10); $computed(() => { // `$a` will not be considered a dependency. 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 '@maverick-js/observables'; 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. Note You can read more about microtasks on MDN. import { $observable } from '@maverick-js/observables'; const $a = $observable(10); $a.set(10); $a.set(20); $a.set(30); // only this write is applied import { $observable, $tick } from '@maverick-js/observables'; const $a = $observable(10); // All writes are applied. $a.set(10); await $tick(); $a.set(20); await $tick(); $a.set(30); $dispose Unsubscribes the given observable and optionally all inner computations. Disposed functions will retain their current value but are no longer reactive. import { $observable, $dispose } from '@maverick-js/observables'; const $a = $observable(10); const $b = $computed(() => $a()); // `$b` will no longer update if `$a` is updated. $dispose($a); $a.set(100); console.log($b()); // still logs `10` The second argument to $dispose is a deep flag which specifies whether all inner computations should also be disposed of: const $a = $observable(); const $b = $computed(() => $a()); const $c = $effect(() => $b()); $dispose($c, true); // <- deep flag // `$a`, `$b`, and `$c` are all disposed. isComputed Whether the given function is a computed observable. import { $observable, $computed, isComputed } from '@maverick-js/observables'; isComputed(() => {}); // false const $a = $observable(10); isComputed($a); // false const $b = $computed(() => $a() + 10); isComputed($b); // true Debugging The $observable, $computed, and $effect 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 '@maverick-js/observables'; 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). Scheduler We provide the underlying microtask scheduler incase you'd like to use it: import { createScheduler } from '@maverick-js/observables'; // 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.tick; Note You can read more about microtasks on MDN. Types import { $computed, type Observable, type Computation } from '@maverick-js/observables'; const observable: Observable; const computed: Computation; // Provide generic if TS fails to infer correct type. const $a = $computed(() => /* ... */); Inspiration @maverick-js/observables was made possible based on my learnings from: * Solid JS * Sinuous * Hyperactiv * Svelte Scheduler Special thanks to Wesley, Julien, and Solid/Svelte contributors for all their work About A tiny (~850B minzipped) library for creating reactive observables via functions. Works in browsers and Node. Resources Readme License MIT license Stars 140 stars Watchers 1 watching Forks 2 forks Releases No releases published Packages 0 No packages published Languages * TypeScript 95.6% * JavaScript 4.4% * (c) 2022 GitHub, Inc. * Terms * Privacy * Security * Status * Docs * Contact GitHub * Pricing * API * Training * Blog * About You can't perform that action at this time. You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.