{"id":13605611,"url":"https://github.com/vobyjs/oby","last_synced_at":"2026-04-06T07:02:02.540Z","repository":{"id":40555813,"uuid":"425032634","full_name":"vobyjs/oby","owner":"vobyjs","description":"A rich Observable/Signal implementation, the brilliant primitive you need to build a powerful reactive system.","archived":false,"fork":false,"pushed_at":"2024-04-03T21:15:41.000Z","size":989,"stargazers_count":244,"open_issues_count":5,"forks_count":8,"subscribers_count":4,"default_branch":"master","last_synced_at":"2026-01-15T03:40:21.802Z","etag":null,"topics":["fast","observable","rich","small"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vobyjs.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"license","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-05T17:30:13.000Z","updated_at":"2025-08-27T13:19:05.000Z","dependencies_parsed_at":"2023-10-10T16:42:17.355Z","dependency_job_id":"994055dc-f1c0-4380-b199-77013e3cfa35","html_url":"https://github.com/vobyjs/oby","commit_stats":{"total_commits":790,"total_committers":3,"mean_commits":263.3333333333333,"dds":"0.0025316455696202667","last_synced_commit":"9fa3e24692a4d42c540fa05a635ceb235692b2f4"},"previous_names":["fabiospampinato/oby"],"tags_count":159,"template":false,"template_full_name":null,"purl":"pkg:github/vobyjs/oby","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vobyjs%2Foby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vobyjs%2Foby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vobyjs%2Foby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vobyjs%2Foby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vobyjs","download_url":"https://codeload.github.com/vobyjs/oby/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vobyjs%2Foby/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31463015,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"online","status_checked_at":"2026-04-06T02:00:07.287Z","response_time":112,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fast","observable","rich","small"],"created_at":"2024-08-01T19:01:00.776Z","updated_at":"2026-04-06T07:02:02.523Z","avatar_url":"https://github.com/vobyjs.png","language":"JavaScript","funding_links":[],"categories":["State Managers","Uncategorized","Core"],"sub_categories":["Signals","Uncategorized"],"readme":"# Oby\n\nA rich Observable/Signal implementation, the brilliant primitive you need to build a powerful reactive system.\n\n## Install\n\n```sh\nnpm install --save oby\n```\n\n## APIs\n\n| [Core](#core)                     | [Flow](#flow)             | [Utilities](#utilities)     | [Types](#types)                                     |\n| --------------------------------- | ------------------------- | --------------------------- | --------------------------------------------------- |\n| [`$()`](#core)                    | [`$.if`](#if)             | [`$.boolean`](#boolean)     | [`EffectOptions`](#effectoptions)                   |\n| [`$.batch`](#batch)               | [`$.for`](#for)           | [`$.disposed`](#disposed)   | [`ForOptions`](#foroptions)                         |\n| [`$.cleanup`](#cleanup)           | [`$.suspense`](#suspense) | [`$.get`](#get)             | [`MemoOptions`](#memooptions)                       |\n| [`$.context`](#context)           | [`$.switch`](#switch)     | [`$.readonly`](#readonly)   | [`Observable`](#observable)                         |\n| [`$.effect`](#effect)             | [`$.ternary`](#ternary)   | [`$.resolve`](#resolve)     | [`ObservableLike`](#observablelike)                 |\n| [`$.isBatching`](#isbatching)     | [`$.tryCatch`](#trycatch) | [`$.selector`](#selector)   | [`ObservableReadonly`](#observablereadonly)         |\n| [`$.isObservable`](#isobservable) |                           | [`$.suspended`](#suspended) | [`ObservableReadonlyLike`](#observablereadonlylike) |\n| [`$.isStore`](#isstore)           |                           | [`$.untracked`](#untracked) | [`ObservableOptions`](#observableoptions)           |\n| [`$.memo`](#memo)                 |                           |                             | [`StoreOptions`](#storeoptions)                     |\n| [`$.observable`](#observable)     |                           |                             |                                                     |\n| [`$.owner`](#owner)               |                           |                             |                                                     |\n| [`$.root`](#root)                 |                           |                             |                                                     |\n| [`$.store`](#store)               |                           |                             |                                                     |\n| [`$.tick`](#tick)                 |                           |                             |                                                     |\n| [`$.untrack`](#untrack)           |                           |                             |                                                     |\n| [`$.with`](#with)                 |                           |                             |                                                     |\n\n## Usage\n\nThe following functions are provided. They are just grouped and ordered alphabetically, the documentation for this library is relatively dry at the moment.\n\n### Core\n\nThe following core functions are provided. These are functions which can't be implemented on top of the library itself, and on top of which everything else is constructed.\n\n#### `$()`\n\nThe main exported function wraps a value into an Observable, basically wrapping the value in a reactive shell.\n\nAn Observable is a function that works both as a getter and as a setter, and it can be writable or read-only, it has the following interface:\n\n```ts\ntype Observable\u003cT\u003e = {\n  (): T,\n  ( value: T ): T,\n  ( fn: ( value: T ) =\u003e T ): T\n};\n\ntype ObservableReadonly\u003cT\u003e = {\n  (): T\n};\n```\n\nThe `$()` function has the following interface:\n\n```ts\ntype ObservableOptions\u003cT\u003e = {\n  equals?: (( value: T, valuePrev: T ) =\u003e boolean) | false\n};\n\nfunction $ \u003cT\u003e (): Observable\u003cT | undefined\u003e;\nfunction $ \u003cT\u003e ( value: undefined, options?: ObservableOptions\u003cT | undefined\u003e ): Observable\u003cT | undefined\u003e;\nfunction $ \u003cT\u003e ( value: T, options?: ObservableOptions\u003cT\u003e ): Observable\u003cT\u003e;\n```\n\nThis is how to use it:\n\n```ts\nimport $ from 'oby';\n\n// Create an Observable without an initial value\n\n$\u003cnumber\u003e ();\n\n// Create an Observable with an initial value\n\n$(1);\n\n// Create an Observable with an initial value and a custom equality function\n\nconst equals = ( value, valuePrev ) =\u003e Object.is ( value, valuePrev );\n\nconst o = $( 1, { equals } );\n\n// Create an Observable with an initial value and a special \"false\" equality function, which is a shorthand for `() =\u003e false`, which causes the Observable to always notify its observers when its setter is called\n\nconst oFalse = $( 1, { equals: false } );\n\n// Getter\n\no (); // =\u003e 1\n\n// Setter\n\no ( 2 ); // =\u003e 2\n\n// Setter via a function, which gets called with the current value\n\no ( value =\u003e value + 1 ); // =\u003e 3\n\n// Setter that sets a function, it has to be wrapped in another function because the above form exists\n\nconst noop = () =\u003e {};\n\no ( () =\u003e noop );\n```\n\n#### `$.batch`\n\nSynchronous calls to Observables' setters are batched automatically for you, so unless you explicitly call a memo, or you explicitly use a synchronous effect, no computations will be re-executed until the next microtask, providing you with a pretty convenient performance guarantee. So 99.9% of the time you don't really have think about batching updates at all.\n\nAsynchronous calls to Observable's setters though are not batched automatically, so in the niche use case where you want to pause re-executions of effects for an arbitrary period of time, until the provided function resolves, that's when you will want to use this function.\n\n- **Note**: Calling this function with a function that doesn't return a Promise is pointless, but that's supported for convenience too, it will just not do anything special.\n- **Note**: This is an advanced function that you may very well never need to call, and at most may just improve performance in some edge cases.\n\nInterface:\n\n```ts\nfunction batch \u003cT\u003e ( fn: () =\u003e Promise\u003cT\u003e | T ): Promise\u003cAwaited\u003cT\u003e\u003e;\nfunction batch \u003cT\u003e ( fn: T ): Promise\u003cAwaited\u003cT\u003e\u003e;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Batch updates until the provided async function resolves\n\nconst o = $(0);\n\n$.effect ( () =\u003e {\n\n  console.log ( o () );\n\n});\n\n$.batch ( async () =\u003e {\n\n  o ( 1 );\n  o ( 2 );\n  o ( 3 );\n\n  // Here the effect has not been called yet, because setters were called synchronously\n  // Even without explicitly batching, synchronous calls to setters will be batched for you automatically\n\n  await someAsyncAction ();\n\n  // Here the effect has still not been called, because we are explicitly batching on an async function\n  // Without batching the effect would have been called by now\n\n});\n```\n\n#### `$.cleanup`\n\nThis function allows you to register cleanup functions, which are executed automatically whenever the parent memo/effect/root is disposed of, which also happens before re-executing it.\n\nInterface:\n\n```ts\nfunction cleanup ( fn: () =\u003e void ): void;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Attaching some cleanup functions to an effect\n\nconst callback = $( () =\u003e {} );\n\n$.effect ( () =\u003e {\n\n  const cb = callback ();\n\n  document.body.addEventListener ( 'click', cb );\n\n  $.cleanup ( () =\u003e { // Registering a cleanup function with the parent\n\n    document.body.removeEventListener ( 'click', cb );\n\n  });\n\n  $.cleanup ( () =\u003e { // You can have as many cleanup functions as you want\n\n    console.log ( 'cleaned up!' );\n\n  });\n\n});\n\nawait nextTask (); // Giving the effect a chance to run\n\ncallback ( () =\u003e () =\u003e {} ); // Causing the effect to be scheduled for re-execution\n\nawait nextTask (); // Giving the effect a chance to run. Once it runs it will call the previous cleanup functions and register new ones\n```\n\n#### `$.context`\n\nThis function provides a dependency injection mechanism, you can use it to provide arbitrary values to its inner scope, and those values can be read, or overridden, at any point inside that inner scope.\n\nInterface:\n\n```ts\nfunction context \u003cT\u003e ( symbol: symbol ): T | undefined; // Read\nfunction context \u003cT\u003e ( context: Record\u003csymbol, any\u003e, fn: () =\u003e T ): T; // Write\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Reading and writing some values in the context\n\nconst token = Symbol ( 'Some Context Key' );\n\n$.context ( { [token]: 123 }, () =\u003e { // Writing a value to the context for the inner scope\n\n  const value = $.context ( token ); // Reading a value from the context\n\n  console.log ( value ); // =\u003e 123\n\n  $.context ( { [token]: 321 }, () =\u003e { // Overriding some context for the inner scope\n\n    const value = $.context ( token ); // Reading again\n\n    console.log ( value ); // =\u003e 321\n\n  });\n\n});\n```\n\n#### `$.effect`\n\nAn effect is similar to a memo, but it returns a function for manually disposing of it instead of an Observable, and if you return a function from inside it that's automatically registered as a cleanup function.\n\nAlso effects are asynchronous by default, they will be re-executed automatically on the next microtask, when necessary. It's possible to make an effect synchronous also, but you are strongly discouraged to do that, because synchronous effects bypass any form of batching and are easy to misuse. It's also possible to make an effect that's asynchronous but executed immediately when created, that's way less problematic, but you probably still won't need it.\n\nAlso effects can be paused inside a `$.suspense` boundary by default. It's possible to make an effect that won't be paused inside a `$.suspense` boundary also, which is mostly useful if the effect is synchronous also, but you are discouraged to do that, unless you really need that.\n\nThere are no restrictions, you can nest these freely, create new Observables inside them, whatever you want.\n\n- **Note**: Effects are intended for encapsulating functions that interact with the outside world, or for writing to Observables in response to a user input, which is strongly discouraged to do inside memos instead.\n\nInterface:\n\n```ts\ntype EffectOptions = {\n  suspense?: boolean,\n  sync?: boolean\n};\n\nfunction effect ( fn: () =\u003e (() =\u003e void) | void, options?: EffectOptions ): (() =\u003e void);\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Create an asynchronous effect with an automatically registered cleanup function\n\nconst callback = $( () =\u003e {} );\n\n$.effect ( () =\u003e {\n\n  const cb = callback ();\n\n  document.body.addEventListener ( 'click', cb );\n\n  return () =\u003e { // Automatically-registered cleanup function\n\n    document.body.removeEventListener ( 'click', cb );\n\n  };\n\n});\n\n// Creating a synchronous effect, which is executed and re-executed immediately when needed\n\n$.effect ( () =\u003e {\n\n  // Do something...\n\n}, { sync: true } );\n\n// Creating an asynchronous effect, but that is executed immediately on creation\n\n$.effect ( () =\u003e {\n\n  // Do something...\n\n}, { sync: 'init' } );\n\n// Creating an effect that will not be paused by suspense\n\n$.effect ( () =\u003e {\n\n  // Do something...\n\n}, { suspense: false } );\n```\n\n#### `$.isBatching`\n\nThis function tells you if explicit batching is currently active, or if there are any effects currently scheduled for execution via other means.\n\n- **Note**: This is an advanced function that you may very well never need to call.\n\nInterface:\n\n```ts\nfunction isBatching (): boolean;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Checking if currently batching\n\n$.isBatching (); // =\u003e false\n\nawait $.batch ( async () =\u003e {\n\n  $.isBatching (); // =\u003e true\n\n});\n\n$.isBatching (); // =\u003e false\n```\n\n#### `$.isObservable`\n\nThis function allows you to tell apart Observables from other values.\n\n- **Note**: This function is intended mostly for internal usage, in user code you'll almost always want to unwrap whatever value you get with `$.get` instead, abstracting away the checks needed for understanding if something is an Observable or not.\n\nInterface:\n\n```ts\nfunction isObservable \u003cT = unknown\u003e ( value: unknown ): value is Observable\u003cT\u003e | ObservableReadonly\u003cT\u003e;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Checking\n\n$.isObservable ( $() ); // =\u003e true\n$.isObservable ( {} ); // =\u003e false\n```\n\n#### `$.isStore`\n\nThis function allows you to tell apart Stores from other values.\n\n- **Note**: This function is intended mostly for internal usage, in user code it's almost always better to not treat objects differently if they are stores or not, you can just treat them the same way and let the reactive system react to changes when needed.\n\nInterface:\n\n```ts\nfunction isStore ( value: unknown ): boolean;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Checking\n\n$.isStore ( $.store ( {} ) ); // =\u003e true\n$.isStore ( {} ); // =\u003e false\n```\n\n#### `$.memo`\n\nThis is the function where most of the magic happens, it generates a new read-only Observable with the result of the function passed to it, the function is automatically marked as stale whenever its dependencies change, and its dependencies are tracked automatically. The value of the Observable is refreshed, if needed, by re-executing the memo when you ask the returned Observale for its current value, by calling it, or when any other computation depends on this memo.\n\nMemos are asynchronous by default, they will be re-executed automatically only when/if needed. It's possible to make a memo synchronous also, but you are strongly discouraged to do that, because synchronous memos can use over-executions and are easy to misuse. Though in some edge cases they could have their usefulness, hence why they are supported.\n\nUsually you can just pass a plain function around, in those cases the only thing you'll get out of `$.memo` is memoization, which is a performance optimization, hence the name.\n\nThere are no restrictions, you can nest these freely, create new Observables inside them, whatever you want.\n\n- **Note**: The provided function is expected to be pure. For side effects, including for writing to other Observables, you should use `$.effect` instead.\n\nInterface:\n\n```ts\nfunction memo \u003cT\u003e ( fn: () =\u003e T, options?: MemoOptions\u003cT\u003e ): ObservableReadonly\u003cT\u003e;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Make a new memoized Observable\n\nconst a = $(1);\nconst b = $(2);\nconst c = $(3);\n\nconst sum = $.memo ( () =\u003e {\n  return a () + b () + c ();\n});\n\nsum (); // =\u003e 6\n\na ( 2 );\n\nsum (); // =\u003e 7\n\nb ( 3 );\n\nsum (); // =\u003e 8\n\nc ( 4 );\n\nsum (); // =\u003e 9\n\n// Make a new synchronous memo, which is executed and re-executed immediately when needed\n\nconst sumSync = $.memo ( () =\u003e {\n  return a () + b () + c ();\n}, { sync: true } );\n```\n\n#### `$.observable`\n\nThis is just an alias for the `$` function, without all the extra functions attached to it, for better tree-shaking.\n\nUsage:\n\n```ts\nimport {observable} from 'oby';\n\n// Creating an Observable\n\nconst o = observable ( 1 );\n\n// Getter\n\no (); // =\u003e 1\n\n// Setter\n\no ( 2 ); // =\u003e 2\n\n// Setter via a function, which gets called with the current value\n\no ( value =\u003e value + 1 ); // =\u003e 3\n```\n\n#### `$.owner`\n\nThis function tells you some metadata about the current owner/observer. There's always an owner.\n\n- **Note**: This is an advanced function intended mostly for internal usage, you almost certainly don't have a use case for using this function.\n\nInterface:\n\n```ts\ntype Owner = {\n  isSuperRoot: boolean, // This tells you if the nearest owner of your current code is a super root, which is kind of a default root that everything gets wrapped with\n  isRoot: boolean, // This tells you if the nearest owner of your current code is a root\n  isSuspense: boolean, // This tells you if the nearest owner of your current code is a suspense\n  isComputation: boolean // This tells you if the nearest owner of your current code is an effect or a memo\n};\n\nfunction owner (): Owner;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Check if you are right below the super root or an effect\n\n$.owner ().isSuperRoot; // =\u003e true\n$.owner ().isComputation; // =\u003e false\n\n$.effect ( () =\u003e {\n\n  $.owner ().isSuperRoot; // =\u003e false\n  $.owner ().isComputation; // =\u003e true\n\n});\n```\n\n#### `$.root`\n\nThis function creates a computation root, computation roots are detached from parent roots/memos/effects and will outlive them, so they must be manually disposed of, disposing them ends all the reactvity inside them, except for any eventual nested roots.\n\nThe value returned by the function is returned by the root itself.\n\nInterface:\n\n```ts\nfunction root \u003cT\u003e ( fn: ( dispose: () =\u003e void ) =\u003e T ): T;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Create a root and dispose of it\n\n$.root ( dispose =\u003e {\n\n  let calls = 0;\n\n  const a = $(0);\n  const b = $.memo ( () =\u003e {\n    calls += 1;\n    return a ();\n  });\n\n  console.log ( calls ); // =\u003e 0, memos are not refreshed until necessary\n  b (); // =\u003e 0\n  console.log ( calls ); // =\u003e 1, now the memo got refreshed, because we asked for its value and it didn't have a fresh value\n\n  a ( 1 );\n\n  console.log ( calls ); // =\u003e 1, not refreshed, because we don't need the new value yet\n  b (); // =\u003e 1\n  console.log ( calls ); // =\u003e 2, refreshed, because we asked for a new value\n\n  dispose (); // Now all the reactivity inside this root stops\n\n  a ( 2 );\n\n  console.log ( calls ); // =\u003e 2, not refreshed, because we don't need the new value yet\n  b (); // =\u003e 1\n  console.log ( calls ); // =\u003e 2, still not refreshed, because we disposed of the memo so its value will never change anymore\n\n});\n```\n\n#### `$.store`\n\nThis function returns a deeply reactive version of the passed object, where property accesses and writes are automatically interpreted as Observables reads and writes for you.\n\nYou can just use the reactive object like you would with a regular non-reactive one, every aspect of the reactivity is handled for you under the hood, just remember to perform reads in a computation if you want to subscribe to them.\n\n- **Note**: Only the following types of values will be handled automatically by the reactivity system: plain objects, plain arrays, primitives.\n- **Note**: Assignments to the following properties won't be reactive, as making those reactive would have more cons than pros: `__proto__`, `prototype`, `constructor`, `hasOwnProperty`, `isPrototypeOf`, `propertyIsEnumerable`, `toLocaleString`, `toSource`, `toString`, `valueOf`, all `Array` methods.\n- **Note**: Getters and setters that are properties of arrays, if for whatever reason you have those, won't be reactive.\n- **Note**: Getters and setters that are assigned to symbols, if for whatever reason you have those, won't be reactive.\n- **Note**: A powerful function is provided, `$.store.on`, for listening to any changes happening _inside_ a store. Changes are batched automatically within a microtask for you. If you use this function it's advisable to not have multiple instances of the same object inside a single store, or you may hit some edge cases where a listener doesn't fire because another path where the same object is available, and where it was edited from, hasn't been discovered yet, since discovery is lazy as otherwise it would be expensive.\n- **Note**: A powerful function is provided, `$.store.reconcile`, that basically merges the content of the second argument into the first one, preserving wrapper objects in the first argument as much as possible, which can avoid many unnecessary re-renderings down the line. Currently getters/setters/symbols from the second argument are ignored, as supporting those would make this function significantly slower, and you most probably don't need them anyway if you are using this function.\n- **Note**: The `$.store.unwrap` function unwraps the top-most proxy layer of the store only, which in most situations is equivalent to deeply unwrapping the store, and the fastest way to do it, except in one important edge case: if you are doing something that causes a proxy to be directly assigned to a property on the underlying unproxied plain object/array, which can happen when writing code like this: `myStore.foo = [myStore.obj]` for example, which should instead be written as `myStore.foo = [store.unwrap ( myStore.obj )]`. If you stumbled on this and you don't want to change your code refer to [this `deepUnwrap` function](https://github.com/vobyjs/oby/issues/8#issuecomment-1755509198).\n\nInterface:\n\n```ts\ntype StoreListenableTarget = Record\u003cstring | number | symbol, any\u003e | (() =\u003e any);\ntype StoreReconcileableTarget = Record\u003cstring | number | symbol, any\u003e | Array\u003cany\u003e;\n\ntype StoreOptions = {\n  equals?: (( value: unknown, valuePrev: unknown ) =\u003e boolean) | false\n};\n\nfunction store \u003cT\u003e ( value: T, options?: StoreOptions ): T;\n\nstore.on = function on ( target: ArrayMaybe\u003cStoreListenableTarget\u003e, callback: () =\u003e void ): (() =\u003e void);\nstore.reconcile = function reconcile \u003cT extends StoreReconcileableTarget\u003e ( prev: T, next: T ): T;\nstore.untrack = function untrack \u003cT\u003e ( value: T ): T;\nstore.unwrap = function unwrap \u003cT\u003e ( value: T ): T;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Make a reactive plain object\n\nconst obj = $.store ({ foo: { deep: 123 } });\n\n$.effect ( () =\u003e {\n\n  obj.foo.deep; // Subscribe to \"foo\" and \"foo.deep\"\n\n});\n\nawait nextTask (); // Giving the effect a chance to run\n\nobj.foo.deep = 321; // Cause the effect to re-run, eventually\n\n// Make a reactive array\n\nconst arr = $.store ([ 1, 2, 3 ]);\n\n$.effect ( () =\u003e {\n\n  arr.forEach ( value =\u003e { // Subscribe to the entire array\n    console.log ( value );\n  });\n\n});\n\nawait nextTask (); // Giving the effect a chance to run\n\narr.push ( 123 ); // Cause the effect to re-run, eventually\n\n// Make a reactive object, with a custom equality function, which is inherited by children also\n\nconst equals = ( value, valuePrev ) =\u003e Object.is ( value, valuePrev );\n\nconst eobj = $.store ( { some: { arbitrary: { velue: true } } }, { equals } );\n\n// Untrack parts of a store, bailing out of automatic proxying\n\nconst uobj = $.store ({\n  foo: {} // This object will become a store automatically\n  bar: $.store.untrack ( {} ) // This object will stay a plain object\n});\n\n// Get a non-reactive object out of a reactive one\n\nconst pobj = $.store.unwrap ( obj );\n\n// Get a non-reactive array out of a reactive one\n\nconst parr = $.store.unwrap ( arr );\n\n// Reconcile a store with new data\n\nconst rec = $.store ({ foo: { deep: { value: 123, other: '123' } } });\nconst dataNext = { foo: { deep: { value: 321, other: '321' } } };\n\n$.store.reconcile ( rec, dataNext ); // Now \"rec\" contains the data from \"dataNext\", but none of its internal objects, in this case, got deleted or created, they just got updated\n\n// Listen for changes inside a store using a selector, necessary if you want to listen to a primitive\n\n$.store.on ( () =\u003e obj.foo.deep, () =\u003e {\n  console.log ( '\"obj.foo.deep\" changed!' );\n});\n\n// Listen for changes inside a whole store\n\n$.store.on ( obj, () =\u003e {\n  console.log ( 'Something inside \"obj\" changed!' );\n});\n\n// Listen for changes inside a whole sub-store, which is just another store created automatically for you really\n\n$.store.on ( obj.foo, () =\u003e {\n  console.log ( 'Something inside \"obj.foo\" changed!' );\n});\n\n// Listen for changes inside multiple targets, the callback will still be fired once if multiple targets are edited within the same microtask\n\n$.store.on ( [obj, arr], () =\u003e {\n  console.log ( 'Something inside \"obj\" and/or \"arr\" changed!' );\n});\n```\n\n#### `$.tick`\n\nThis function forces effects scheduled for execution to be executed immediately, bypassing automatic or manual batching.\n\nInterface:\n\n```ts\nfunction tick (): void;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n$.effect ( () =\u003e {\n  console.log ( 'effect called' );\n});\n\n// Here the effect has not been called yet\n\n$.tick ();\n\n// Here the effect has been called\n```\n\n#### `$.untrack`\n\nThis function allows for reading Observables without creating dependencies on them, temporarily turning off tracking basically.\n\n- **Note**: This function turns off tracking for any arbitrary function, the function doesn't have to be an Observable necessarily.\n\nInterface:\n\n```ts\nfunction untrack \u003cT\u003e ( fn: () =\u003e T ): T;\nfunction untrack \u003cT\u003e ( value: T ): T;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Untracking a single Observable\n\nconst o = $(0);\n\n$.untrack ( o ); // =\u003e 0\n\n// Untracking multiple Observables\n\nconst a = $(1);\nconst b = $(2);\nconst c = $(3);\n\nconst sum = $.untrack ( () =\u003e {\n  return a () + b () + c ();\n});\n\nconsole.log ( sum ); // =\u003e 6\n\na ( 2 );\nb ( 3 );\nc ( 4 );\n\nconsole.log ( sum ); // =\u003e 6, it's just a value, not a reactive Observable\n\n// Untracking a non function, it's just returned as is\n\n$.untrack ( 123 ); // =\u003e 123\n```\n\n#### `$.with`\n\nThis function allows you to create a function for executing code as if it was a child of the owner/computation active when the function was originally created.\n\n- **Note**: This is an advanced function intended mostly for internal usage.\n\nInterface:\n\n```ts\nfunction with (): (\u003cT\u003e ( fn: () =\u003e T ): T);\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Reading some values from the context as if the code was executing inside a different computation\n\n$.root ( () =\u003e {\n\n  const token = Symbol ( 'Some Context Key' );\n\n  $.context ( { [token]: 123 }, () =\u003e { // Writing a value to the context for the inner scope\n\n    const runWithOuter = $.with ();\n\n    $.effect ( () =\u003e {\n\n      $.context ( { [token]: 321 }, () =\u003e { // Overriding some context for the inner scope\n\n        const value = $.context ( token ); // Reading the context\n\n        console.log ( value ); // =\u003e 321\n\n        runWithOuter ( () =\u003e { // Executing the function as if it was where `$.with` was called\n\n          const value = $.context ( token ); // Reading the context\n\n          console.log ( value ); // =\u003e 123\n\n        });\n\n      });\n\n    });\n\n  });\n\n});\n```\n\n### Flow\n\nThe following control flow functions are provided. These functions are like the reactive versions of native constructs in the language.\n\n#### `$.if`\n\nThis is the reactive version of the native `if` statement. It returns a read-only Observable that resolves to the passed value if the condition is truthy, or to the optional fallback otherwise.\n\nInterface:\n\n```ts\nfunction if \u003cT, F\u003e ( when: (() =\u003e boolean) | boolean, valueTrue: T, valueFalse?: F ): ObservableReadonly\u003cT | F | undefined\u003e;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Toggling an if\n\nconst bool = $(false);\n\nconst result = $.if ( bool, 123, 321 );\n\nresult (); // =\u003e 321\n\nbool ( true );\n\nresult (); // =\u003e 123\n```\n\n#### `$.for`\n\nThis is the reactive version of the native `Array.prototype.map`, it maps over an array of values while caching results when possible.\n\nThis function is crucial for achieving great performance when mapping over an array, as just calling `Array.prototype.map` inside a memo can be super expensive.\n\nThere are multiple strategies that this function may use internally for caching results:\n\n- **Keyed**: with the default options results are cached for values that didn't change, and thrown away when those values are no longer used. So for example when going from mapping over `[1, 2, 3]` to mapping over `[1, 2, 4]` the results for `1` and `2` are entirely cached, the old result for `3` is discarded, and an entirely new result for `4` is created. This is the easiest and safest strategy to use (especially in a DOM context, read [this](https://www.stefankrause.net/wp/?p=342) for more info). It's strongly recommended that the array of values to map over doesn't contain dulicates though.\n- **Unkeyed**: with the `unkeyed` option set to `true` results are cached for values that didn't change, and results for old values are transformed into results for new values, if possibile, or otherwise thrown away. So for example when going from mapping over `[1, 2, 3]` to mapping over `[1, 2, 4]` the results for `1` and `2` are entirely cached, and the result for `3` is transformed into the result for `4`. Basically the function that you pass `$.for` receives an observable to the value rather than the value itself, so it can update itself, it's no longer necessary to dispose of the old result and make an entirely new result. This option is the recommended one if your array may contain duplicated primitive values, or if you want to achieve maximum performance, though it's harder to use because you will receive an observable to the value rather than the value itself, and it can be easy to misuse (especially in a DOM context, read [this](https://www.stefankrause.net/wp/?p=342) for more info).\n- **Unkeyed+Pooled**: with both `unkeyed` set to `true`, and `pooled` set to `true` results are cached not only between runs, but they are also put in a suspended pool when not currently used. This is useful to trade some memory usage for potentially better runtime performance.\n\nInterface:\n\n```ts\nfunction for \u003cT, R, F\u003e ( values: (() =\u003e readonly T[]) | readonly T[] | undefined, fn: (( value: T, index: FunctionMaybe\u003cnumber\u003e ) =\u003e R), fallback?: F | [], options?: { pooled?: false, unkeyed?: false } ): ObservableReadonly\u003cR[] | F\u003e;\nfunction for \u003cT, R, F\u003e ( values: (() =\u003e readonly T[]) | readonly T[] | undefined, fn: (( value: ObservableReadonly\u003cT\u003e, index: FunctionMaybe\u003cnumber\u003e ) =\u003e R), fallback?: F | [], options?: { pooled?: boolean, unkeyed?: true } ): ObservableReadonly\u003cR[] | F\u003e;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Map over an array of values\n\nconst o1 = $(1);\nconst o2 = $(2);\nconst os = $([ o1, o2 ]);\n\nconst mapped = $.for ( os, o =\u003e {\n\n  return someExpensiveFunction ( o () );\n\n});\n\n// Update the \"mapped\" Observable\n\nos ([ o1, o2, o3 ]);\n```\n\n#### `$.suspense`\n\nThis function allows you to recursively pause and resume the execution of all, current and future, effects created inside it. Unless they are explicitly created with `suspense: false`.\n\nThis is very useful in some scenarios, for example you may want to keep a particular branch of computation around, if it'd be expensive to dispose of it and re-create it again, but you may not want its effects to be executing as they would probably interact with the rest of your application.\n\nA parent suspense boundary will also recursively pause children suspense boundaries.\n\nInterface:\n\n```ts\nfunction suspense \u003cT\u003e ( suspended: FunctionMaybe\u003cunknown\u003e, fn: () =\u003e T ): T;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Create a suspendable branch of computation\n\nconst title = $('Some Title');\nconst suspended = $(false);\n\n$.suspense ( suspended, () =\u003e {\n\n  $.effect ( () =\u003e {\n\n    document.title = title (); // Changing something in the outside world, in other words performing a side effect\n\n  });\n\n});\n\n// Pausing effects inside the suspense boundary\n\nsuspended ( true );\n\ntitle ( 'Some Other Title' ); // This won't cause the effect to be re-executed, since it's paused\n\n// Resuming effects inside the suspense boundary\n\nsuspended ( false ); // This will cause the effect to be re-executed, as it had pending updates\n```\n\n#### `$.switch`\n\nThis is the reactive version of the native `switch` statement. It returns a read-only Observable that resolves to the value of the first matching case, or the value of the default condition, or `undefined` otherwise.\n\nInterface:\n\n```ts\ntype SwitchCase\u003cT, R\u003e = [T, R];\ntype SwitchDefault\u003cR\u003e = [R];\ntype SwitchValue\u003cT, R\u003e = SwitchCase\u003cT, R\u003e | SwitchDefault\u003cR\u003e;\n\nfunction switch \u003cT, R, F\u003e ( when: (() =\u003e T) | T, values: SwitchValue\u003cT, R\u003e[], fallback?: F ): ObservableReadonly\u003cR | F | undefined\u003e;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Switching cases\n\nconst o = $(1);\n\nconst result = $.switch ( o, [[1, '1'], [2, '2'], [1, '1.1'], ['default']] );\n\nresult (); // =\u003e '1'\n\no ( 2 );\n\nresult (); // =\u003e '2'\n\no ( 3 );\n\nresult (); // =\u003e 'default'\n```\n\n#### `$.ternary`\n\nThis is the reactive version of the native ternary operator. It returns a read-only Observable that resolves to the first value if the condition is truthy, or the second value otherwise.\n\nInterface:\n\n```ts\nfunction ternary \u003cT, F\u003e ( when: (() =\u003e boolean) | boolean, valueTrue: T, valueFalse: T ): ObservableReadonly\u003cT | F\u003e;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Toggling an ternary\n\nconst bool = $(false);\n\nconst result = $.ternary ( bool, 123, 321 );\n\nresult (); // =\u003e 321\n\nbool ( true );\n\nresult (); // =\u003e 123\n```\n\n#### `$.tryCatch`\n\nThis is the reactive version of the native `try..catch` block. If no errors happen the regular value function is executed, otherwise the fallback function is executed, whatever they return is returned wrapped in a read-only Observable.\n\nThis is also commonly referred to as an \"error boundary\".\n\nInterface:\n\n```ts\nfunction tryCatch \u003cT, F\u003e ( value: T, catchFn: ({ error, reset }: { error: Error, reset: () =\u003e void }) =\u003e F ): ObservableReadonly\u003cT | F\u003e;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Create an tryCatch boundary\n\nconst o = $(false);\n\nconst fallback = ({ error, reset }) =\u003e {\n  console.log ( error );\n  setTimeout ( () =\u003e { // Attempting to recovering after 1s\n    o ( false );\n    reset ();\n  }, 1000 );\n  return 'fallback!';\n};\n\nconst regular = () =\u003e {\n  if ( o () ) throw 'whoops!';\n  return 'regular!';\n};\n\nconst result = $.tryCatch ( fallback, regular );\n\nresult (); // =\u003e 'regular!'\n\n// Cause an error to be thrown inside the boundary\n\no ( true );\n\nresult (); // =\u003e 'fallback!'\n```\n\n### Utilities\n\nThe following utilities functions are provided. These functions are either simple to implement and pretty handy, or pretty useful in edge scenarios and hard to implement, so they are provided for you.\n\n#### `$.boolean`\n\nThis function is like the reactive equivalent of the `!!` operator, it returns you a boolean, or a function to a boolean, depending on the input that you give it.\n\nInterface:\n\n```ts\nfunction boolean ( value: FunctionMaybe\u003cunknown\u003e ): FunctionMaybe\u003cboolean\u003e;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Implementing a custom if function\n\nfunction if ( when: FunctionMaybe\u003cunknown\u003e, whenTrue: FunctionMaybe\u003cunknown\u003e, whenFalse: FunctionMaybe\u003cunknown\u003e ) {\n\n  const condition = $.boolean ( when );\n\n  return $.memo ( () =\u003e {\n\n    return $.resolve ( $.get ( condition ) ? whenTrue : whenFalse );\n\n  });\n\n}\n```\n\n#### `$.disposed`\n\nThis function returns a read-only Observable that tells you if the parent computation got disposed of or not.\n\nInterface:\n\n```ts\nfunction disposed (): ObservableReadonly\u003cboolean\u003e;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Create an effect whose function knows when it's disposed\n\nconst url = $( 'htts://my.api' );\n\n$.effect ( () =\u003e {\n\n  const disposed = $.disposed ();\n\n  const onResolve = ( response: Response ): void =\u003e {\n\n    if ( disposed () ) return; // The effect got disposed, no need to handle the response anymore\n\n    // Do something with the response\n\n  };\n\n  const onReject = ( error: unknown ): void =\u003e {\n\n    if ( disposed () ) return; // The effect got disposed, no need to handle the error anymore\n\n    // Do something with the error\n\n  };\n\n  fetch ( url () ).then ( onResolve, onReject );\n\n});\n\nawait nextTask (); // Giving the effect a chance to run\n\nurl ( 'https://my.api2' ); // This causes the effect to be re-executed, and the previous `disposed` Observable will be set to `true`\n```\n\n#### `$.get`\n\nThis function gets the value out of something, if it gets passed an Observable or a function then by default it calls it, otherwise it just returns the value. You can also opt-out of calling plain functions, which is useful when dealing with callbacks.\n\nInterface:\n\n```ts\nfunction get \u003cT\u003e ( value: T, getFunction?: true ): (T extends (() =\u003e infer U) ? U : T);\nfunction get \u003cT\u003e ( value: T, getFunction: false ): (T extends ObservableReadonly\u003cinfer U\u003e ? U : T);\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Getting the value out of an Observable\n\nconst o = $(123);\n\n$.get ( o ); // =\u003e 123\n\n// Getting the value out of a function\n\n$.get ( () =\u003e 123 ); // =\u003e 123\n\n// Getting the value out of an Observable but not out of a function\n\n$.get ( o, false ); // =\u003e 123\n$.get ( () =\u003e 123, false ); // =\u003e () =\u003e 123\n\n// Getting the value out of a non-Observable and non-function\n\n$.get ( 123 ); // =\u003e 123\n```\n\n#### `$.readonly`\n\nThis function makes a read-only Observable out of any Observable you pass it. It's useful when you want to pass an Observable around but you want to be sure that they can't change it's value but only read it.\n\nInterface:\n\n```ts\nfunction readonly \u003cT\u003e ( observable: Observable\u003cT\u003e | ObservableReadonly\u003cT\u003e ): ObservableReadonly\u003cT\u003e;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Making a read-only Observable\n\nconst o = $(123);\nconst ro = $.readonly ( o );\n\n// Getting\n\nro (); // =\u003e 123\n\n// Setting throws\n\nro ( 321 ); // An error will be thrown, read-only Observables can't be set\n```\n\n#### `$.resolve`\n\nThis function recursively resolves reactivity in the passed value. Basically it replaces each function it can find with the result of `$.memo ( () =\u003e $.resolve ( fn () ) )`.\n\nYou may never need to use this function yourself, but it's necessary internally at times to make sure that a child value is properly tracked by its parent computation.\n\nThis function is used internally by `$.if`, `$.for`, `$.switch`, `$.ternary`, `$.tryCatch`, as they need to resolve values to make sure the memo they give you can properly keep track of dependencies.\n\nInterface:\n\n```ts\ntype ResolvablePrimitive = null | undefined | boolean | number | bigint | string | symbol;\ntype ResolvableArray = Resolvable[];\ntype ResolvableObject = { [Key in string | number | symbol]?: Resolvable };\ntype ResolvableFunction = () =\u003e Resolvable;\ntype Resolvable = ResolvablePrimitive | ResolvableObject | ResolvableArray | ResolvableFunction;\n\nconst resolve = \u003cT\u003e ( value: T ): T extends Resolvable ? T : never;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Resolve a plain value\n\n$.resolve ( 123 ); // =\u003e 123\n\n// Resolve a function\n\n$.resolve ( () =\u003e 123 ); // =\u003e ObservableReadonly\u003c123\u003e\n\n// Resolve a nested function\n\n$.resolve ( () =\u003e () =\u003e 123 ); // =\u003e ObservableReadonly\u003cObservableReadonly\u003c123\u003e\u003e\n\n// Resolve a plain array\n\n$.resolve ( [123] ); // =\u003e [123]\n\n// Resolve an array containing a function\n\n$.resolve ( [() =\u003e 123] ); // =\u003e [ObservableReadonly\u003c123\u003e]\n\n// Resolve an array containing arrays and functions\n\n$.resolve ( [() =\u003e 123, [() =\u003e [() =\u003e 123]]] ); // =\u003e [ObservableReadonly\u003c123\u003e, [ObservableReadonly\u003c[ObservableReadonly\u003c123\u003e]\u003e]]\n\n// Resolve a plain object\n\n$.resolve ( { foo: 123 } ); // =\u003e { foo: 123 }\n\n// Resolve a plain object containing a function, plain objects are simply returned as is\n\n$.resolve ( { foo: () =\u003e 123 } ); // =\u003e { foo: () =\u003e 123 }\n```\n\n#### `$.selector`\n\nThis function is useful for optimizing performance when you need to, for example, know when an item within a set is the selected one.\n\nIf you use this function then when a new item should be the selected one the old one is unselected, and the new one is selected, directly, without checking if each element in the set is the currently selected one. This turns a `O(n)` operation into an `O(1)` one.\n\nInterface:\n\n```ts\ntype SelectorFunction\u003cT\u003e = ( value: T ) =\u003e ObservableReadonly\u003cboolean\u003e;\n\nfunction selector \u003cT\u003e ( source: () =\u003e T ): SelectorFunction\u003cT\u003e;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Making a selector\n\nconst values = [1, 2, 3, 4, 5];\nconst selected = $(-1);\n\nconst select = value =\u003e selected ( value );\nconst selector = $.selector ( selected );\n\nvalues.forEach ( value =\u003e {\n\n  $.effect ( () =\u003e {\n\n    const selected = selector ( value );\n\n    if ( selected () ) return;\n\n    console.log ( `${value} selected!` );\n\n  });\n\n});\n\nawait nextTask (); // Giving the effects a chance to run\n\nselect ( 1 ); // It causes only 2 effect to re-execute, not 5 or however many there are\n\nawait nextTask (); // Giving the effects a chance to run\n\nselect ( 5 ); // It causes only 2 effect to re-execute, not 5 or however many there are\n```\n\n#### `$.suspended`\n\nThis function returns a read-only Observable that tells you if the closest suspense boundary is currently suspended or not.\n\nYou may never need this function, but it's useful to pause or skip the execution of effectfull code scheduled outside of effects while suspense is active, since you shouldn't execute any side effects while the computation you are on is suspended, and in general you want suspended computations to stay as idle as possible.\n\nInterface:\n\n```ts\nfunction suspended (): ObservableReadonly\u003cboolean\u003e;\n```\n\nUsage:\n\n```ts\nimport {$} from 'oby';\n\n// Scheduling an interval that won't be executed while the nearest suspense boundary is suspended\n\nconst suspended = $.suspended ();\n\n$.effect ( () =\u003e {\n\n  if ( suspended () ) return; // Do nothing while suspended\n\n  const intervalId = setInterval ( doSomething, 1000 );\n\n  return () =\u003e {\n\n    clearInterval ( intervalId );\n\n  };\n\n}, { suspense: false } );\n```\n\n#### `$.untracked`\n\nThis function creates an untracked version of a value.\n\nIt's functionally equivalent to a simple `() =\u003e untrack ( value )`, but the returned function is also marked as being untracked, which allows for some optimizations internally.\n\nInterface:\n\n```ts\nfunction untracked \u003cT\u003e ( fn: () =\u003e T ): () =\u003e T;\nfunction untracked \u003cT\u003e ( value: T ): () =\u003e T;\n```\n\nUsage:\n\n```ts\nimport $ from 'oby';\n\n// Creating an untracked function\n\nconst a = $(1);\nconst b = $(2);\nconst c = $(3);\n\nconst sum = $.untracked ( () =\u003e {\n  return a () + b () + c ();\n});\n\nconsole.log ( sum () ); // =\u003e 6\n\na ( 2 );\nb ( 3 );\nc ( 4 );\n\nconsole.log ( sum () ); // =\u003e 9\n```\n\n### Types\n\nThe following TypeScript types are provided.\n\n#### `EffectOptions`\n\nThis type describes the options object that effects can accept to tweak how they work.\n\nInterface:\n\n```ts\ntype EffectOptions = {\n  suspense?: boolean,\n  sync?: boolean | 'init'\n};\n```\n\n#### `ForOptions`\n\nThis type describes the options object that `$.for` can accept to tweak how it works.\n\nInterface:\n\n```ts\ntype ForOptions = {\n  pooled?: boolean,\n  unkeyed?: boolean\n};\n```\n\n#### `MemoOptions`\n\nThis type describes the options object that memos can accept to tweak how they work.\n\nInterface:\n\n```ts\ntype MemoOptions\u003cT = unknown\u003e = {\n  equals?: (( value: T, valuePrev: T ) =\u003e boolean) | false\n  sync?: boolean\n};\n```\n\n#### `Observable`\n\nThis type describes a regular writable Observable, like what you'd get from `$()`.\n\nInterface:\n\n```ts\ntype Observable\u003cT\u003e = {\n  (): T,\n  ( value: T ): T,\n  ( fn: ( value: T ) =\u003e T ): T,\n  readonly [ObservableSymbol]: true\n};\n```\n\n#### `ObservableLike`\n\nThis type describes an object with the same interface as a regular writable Observable, but which may not actually be an Observable.\n\nInterface:\n\n```ts\ntype ObservableLike\u003cT\u003e = {\n  (): T,\n  ( value: T ): T,\n  ( fn: ( value: T ) =\u003e T ): T\n};\n```\n\n#### `ObservableReadonly`\n\nThis type describes a read-only Observable, like what you'd get from `$.memo` or `$.readonly`.\n\nInterface:\n\n```ts\ntype ObservableReadonly\u003cT\u003e = {\n  (): T,\n  readonly [ObservableSymbol]: true\n};\n```\n\n#### `ObservableReadonlyLike`\n\nThis type describes an object with the same interface as a read-only Observable, but which may not actually be an Observable.\n\nInterface:\n\n```ts\ntype ObservableReadonlyLike\u003cT\u003e = {\n  (): T\n};\n```\n\n#### `ObservableOptions`\n\nThis type describes the options object that various functions can accept to tweak how the underlying Observable works.\n\nInterface:\n\n```ts\ntype ObservableOptions\u003cT\u003e = {\n  equals?: (( value: T, valuePrev: T ) =\u003e boolean) | false\n};\n```\n\n#### `StoreOptions`\n\nThis type describes the options object that the `$.store` function can accept.\n\nInterface:\n\n```ts\ntype StoreOptions = {\n  equals?: (( value: unknown, valuePrev: unknown ) =\u003e boolean) | false\n};\n```\n\n## Thanks\n\n- **[reactively](https://github.com/modderme123/reactively)**: for teaching me the awesome push/pull hybrid algorithm that this library is currently using.\n- **[S](https://github.com/adamhaile/S)**: for paving the way to this awesome reactive way of writing software.\n- **[sinuous/observable](https://github.com/luwes/sinuous/tree/master/packages/sinuous/observable)**: for making me fall in love with Observables and providing a good implementation that this library was originally based on.\n- **[solid](https://www.solidjs.com)**: for being a great sort of reference implementation, popularizing Signal-based reactivity, and having built a great community.\n- **[trkl](https://github.com/jbreckmckye/trkl)**: for being so inspiringly small.\n\n## License\n\nMIT © Fabio Spampinato\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvobyjs%2Foby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvobyjs%2Foby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvobyjs%2Foby/lists"}