{"id":13454293,"url":"https://github.com/vobyjs/voby","last_synced_at":"2025-12-15T00:17:45.830Z","repository":{"id":39751099,"uuid":"455933049","full_name":"vobyjs/voby","owner":"vobyjs","description":"A high-performance framework with fine-grained observable-based reactivity for building rich applications.","archived":false,"fork":false,"pushed_at":"2024-04-06T18:32:21.000Z","size":5285,"stargazers_count":856,"open_issues_count":13,"forks_count":21,"subscribers_count":10,"default_branch":"master","last_synced_at":"2024-05-01T12:39:28.721Z","etag":null,"topics":["fast","fine-grained","framework","observable","performant","reactive","small","ui","voby"],"latest_commit_sha":null,"homepage":"https://voby.dev","language":"TypeScript","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":null,"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}},"created_at":"2022-02-05T17:11:03.000Z","updated_at":"2024-04-28T16:05:01.000Z","dependencies_parsed_at":"2023-12-21T17:37:47.152Z","dependency_job_id":"82239019-03f4-4fb2-bc64-ff2d309772b7","html_url":"https://github.com/vobyjs/voby","commit_stats":{"total_commits":926,"total_committers":5,"mean_commits":185.2,"dds":0.006479481641468721,"last_synced_commit":"e5a0a221e3badcfa1f0cc383174b35826ef36cbc"},"previous_names":["fabiospampinato/voby"],"tags_count":172,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vobyjs%2Fvoby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vobyjs%2Fvoby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vobyjs%2Fvoby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vobyjs%2Fvoby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vobyjs","download_url":"https://codeload.github.com/vobyjs/voby/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219843309,"owners_count":16556507,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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","fine-grained","framework","observable","performant","reactive","small","ui","voby"],"created_at":"2024-07-31T08:00:52.736Z","updated_at":"2025-12-15T00:17:45.776Z","avatar_url":"https://github.com/vobyjs.png","language":"TypeScript","readme":"\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://voby.dev\"\u003e\n    \u003cimg src=\"./resources/banner/svg/banner-light-rounded.svg\" alt=\"Voby's Banner\" width=\"640px\" height=\"320px\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://discord.gg/E6pK7VpnjC\"\u003e\n    \u003cimg src=\"./resources/discord/button.png\" alt=\"Join The Discord Chat\" width=\"175px\" height=\"56.5px\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codesandbox.io/s/voby-playground-7w2pxg\"\u003e\n    \u003cimg src=\"./resources/playground/button.png\" alt=\"Open The Playground\" width=\"175px\" height=\"56.5px\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://opencollective.com/voby\"\u003e\n    \u003cimg src=\"./resources/collective/button.png\" alt=\"Donate With Open Collective\" width=\"175px\" height=\"56.5px\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n# [Voby](https://voby.dev)\n\nA high-performance framework with fine-grained observable/signal-based reactivity for building rich applications.\n\n## Features\n\nThis works similarly to [Solid](https://www.solidjs.com), but without a custom Babel transform and with a different API.\n\n- **No VDOM**: there's no VDOM overhead, the framework deals with raw DOM nodes directly.\n- **No stale closures**: functions are always executed afresh, no need to worry about previous potential executions of the current function, ever.\n- **No rules of hooks**: hooks are just regular functions, which you can nest indefinitely, call conditionally, use outside components, whatever you want.\n- **No dependencies arrays**: the framework is able to detect what depends on what else automatically, no need to specify dependencies manually.\n- **No props diffing**: updates are fine grained, there's no props diffing, whenever an attribute/property/class/handler/etc. should be updated it's updated directly and immediately.\n- **No key prop**: you can just map over arrays, or use the `For` component with an array of unique values, no need to specify keys explicitly.\n- **No Babel**: there's no need to use Babel with this framework, it works with plain old JS (plus JSX if you are into that). As a consequence we have 0 transform function bugs, because we don't have a transform function.\n- **No magic**: what you see is what you get, your code is not transformed to actually do something different than what you write, there are no surprises.\n- **No server support**: for the time being this framework is focused on local-first rich applications, most server-related features are not implemented: no hydration, no server components, no streaming etc.\n- **Observable-based**: observables, also known as \"signals\", are at the core of our reactivity system. The way it works is very different from a React-like system, it may be more challenging to learn, but it's well worth the effort.\n- **Work in progress**: this is probably beta software, I'm working on it because I need something with great performance for [Notable](https://github.com/notable/notable), I'm allergic to third-party dependencies, I'd like something with an API that resonates with me, and I wanted to deeply understand how the more solid [Solid](https://www.solidjs.com), which you should also check out, works.\n\n## Demos\n\nYou can find some demos and benchmarks below, more demos are contained inside the repository.\n\n- Playground (CodeSandbox): https://codesandbox.io/s/voby-playground-7w2pxg\n- Playground (StackBlitz): https://stackblitz.com/edit/vitejs-vite-azlrzl?file=src%2Fcounter.tsx\n- Benchmark: https://krausest.github.io/js-framework-benchmark/current.html\n- Counter: https://codesandbox.io/s/voby-demo-counter-23fv5\n- Clock: https://codesandbox.io/s/voby-demo-clock-w1e7yb\n- Emoji Counter: https://codesandbox.io/s/voby-demo-emoji-counter-j91iz2\n- HyperScript: https://codesandbox.io/s/voby-demo-hyperscript-h4rf38\n- HTML Template Literal: https://codesandbox.io/s/voby-demo-html-lvfeyo\n- Single-file HTML: https://codesandbox.io/s/voby-demo-html-dueygt?file=/public/index.html\n- Spiral: https://codesandbox.io/s/voby-demo-spiral-ux33p6\n- Store Counter: https://codesandbox.io/s/voby-demo-store-counter-kvoqrw\n- Triangle: https://codesandbox.io/s/voby-demo-triangle-l837v0\n- Boxes: https://codesandbox.io/s/voby-demo-boxes-wx6rqb\n\n## APIs\n\n| [Methods](#methods)                   | [Components](#components)         | [Hooks](#hooks-core) \u003csub\u003ecore\u003c/sub\u003e | [Hooks](#hooks-web) \u003csub\u003eweb\u003c/sub\u003e          | [Types](#types)                                     | [Extras](#extras)               |\n| ------------------------------------- | --------------------------------- | ------------------------------------ | ------------------------------------------- | --------------------------------------------------- | ------------------------------- |\n| [`$`](#methods)                       | [`Dynamic`](#dynamic)             | [`useBoolean`](#useboolean)          | [`useAbortController`](#useabortcontroller) | [`Context`](#context)                               | [`Contributing`](#contributing) |\n| [`$$`](#methods)                      | [`ErrorBoundary`](#errorboundary) | [`useCleanup`](#usecleanup)          | [`useAbortSignal`](#useabortsignal)         | [`Directive`](#directive)                           | [`Globals`](#globals)           |\n| [`batch`](#batch)                     | [`For`](#for)                     | [`useContext`](#usecontext)          | [`useAnimationFrame`](#useanimationframe)   | [`DirectiveOptions`](#directiveoptions)             | [`JSX`](#jsx)                   |\n| [`createContext`](#createcontext)     | [`Fragment`](#fragment)           | [`useDisposed`](#usedisposed)        | [`useAnimationLoop`](#useanimationloop)     | [`EffectOptions`](#effectoptions)                   | [`Tree Shaking`](#tree-shaking) |\n| [`createDirective`](#createdirective) | [`If`](#if)                       | [`useEffect`](#useeffect)            | [`useEventListener`](#useeventlistener)     | [`FunctionMaybe`](#functionmaybe)                   | [`TypeScript`](#typescript)     |\n| [`createElement`](#createelement)     | [`KeepAlive`](#keepalive)         | [`useMemo`](#usememo)                | [`useFetch`](#usefetch)                     | [`MemoOptions`](#memooptions)                       |                                 |\n| [`h`](#h)                             | [`Portal`](#portal)               | [`usePromise`](#usepromise)          | [`useIdleCallback`](#useidlecallback)       | [`Observable`](#observable)                         |                                 |\n| [`hmr`](#hmr)                         | [`Suspense`](#suspense)           | [`useReadonly`](#usereadonly)        | [`useIdleLoop`](#useidleloop)               | [`ObservableLike`](#observablelike)                 |                                 |\n| [`html`](#html)                       | [`Switch`](#switch)               | [`useResolved`](#useresolved)        | [`useInterval`](#useinterval)               | [`ObservableReadonly`](#observablereadonly)         |                                 |\n| [`isBatching`](#isbatching)           | [`Ternary`](#ternary)             | [`useResource`](#useresource)        | [`useMicrotask`](#usemicrotask)             | [`ObservableReadonlyLike`](#observablereadonlylike) |                                 |\n| [`isObservable`](#isobservable)       |                                   | [`useRoot`](#useroot)                | [`useTimeout`](#usetimeout)                 | [`ObservableMaybe`](#observablemaybe)               |                                 |\n| [`isServer`](#isserver)               |                                   | [`useSelector`](#useselector)        |                                             | [`ObservableOptions`](#observableoptions)           |                                 |\n| [`isStore`](#isstore)                 |                                   | [`useSuspended`](#usesuspended)      |                                             | [`Resource`](#resource)                             |                                 |\n| [`lazy`](#lazy)                       |                                   | [`useUntracked`](#useuntracked)      |                                             | [`StoreOptions`](#storeoptions)                     |                                 |\n| [`render`](#render)                   |                                   |                                      |                                             |                                                     |                                 |\n| [`renderToString`](#rendertostring)   |                                   |                                      |                                             |                                                     |                                 |\n| [`resolve`](#resolve)                 |                                   |                                      |                                             |                                                     |                                 |\n| [`store`](#store)                     |                                   |                                      |                                             |                                                     |                                 |\n| [`template`](#template)               |                                   |                                      |                                             |                                                     |                                 |\n| [`tick`](#tick)                       |                                   |                                      |                                             |                                                     |                                 |\n| [`untrack`](#untrack)                 |                                   |                                      |                                             |                                                     |                                 |\n\n## Usage\n\nThis framework is simply a view layer built on top of the Observable library [`oby`](https://github.com/fabiospampinato/oby), knowing how that works is necessary to understand how this works.\n\nThis framework basically re-exports everything that `oby` exports, sometimes with a slightly different interface, adjusted for usage as components or hooks, plus some additional functions.\n\n### Methods\n\nThe following top-level functions are provided.\n\n#### `$`\n\nThis function is just the default export of `oby`, it can be used to wrap a value in an observable.\n\nNo additional methods are attached to this function. Everything that `oby` attaches to it is instead exported as components and hooks.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#core).\n\nInterface:\n\n```ts\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\nUsage:\n\n```tsx\nimport {$} from 'voby';\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 emit 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#### `$$`\n\nThis function unwraps a potentially observable value.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#get).\n\nInterface:\n\n```ts\nfunction $$ \u003cT\u003e ( value: T ): (T extends ObservableReadonly\u003cinfer U\u003e ? U : T);\n```\n\nUsage:\n\n```tsx\nimport {$$} from 'voby';\n\n// Getting the value out of an observable\n\nconst o = $(123);\n\n$$ ( o ); // =\u003e 123\n\n// Getting the value out of a function\n\n$$ ( () =\u003e 123 ); // =\u003e 123\n\n// Getting the value out of an observable but not out of a function\n\n$$ ( o, false ); // =\u003e 123\n$$ ( () =\u003e 123, false ); // =\u003e () =\u003e 123\n\n// Getting the value out of a non-observable and non-function\n\n$$ ( 123 ); // =\u003e 123\n```\n\n#### `batch`\n\nThis function prevents effects from firing until the function passed to it resolves. It's largely only useful when the passed function is asynchronous, as otherwise the reactivity system is lazy so effects won't be over-executed anyway.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#batch).\n\nInterface:\n\n```ts\nfunction batch \u003cT\u003e ( fn: () =\u003e Promise\u003cT\u003e | T ): Promise\u003cAwaited\u003cT\u003e\u003e;\nfunction batch \u003cT\u003e ( value: T ): Promise\u003cAwaited\u003cT\u003e\u003e;\n```\n\nUsage:\n\n```tsx\nimport {batch} from 'voby';\n\nbatch // =\u003e Same as require ( 'oby' ).batch\n```\n\n#### `createContext`\n\nThis function creates a context object, optionally with a default value, which can later be used to provide a new value for the context or to read the current value.\n\nA context's `Provider` will register the value of context with its children.\n\nInterface:\n\n```ts\ntype ContextProvider\u003cT\u003e = ( props: { value: T, children: JSX.Element } ) =\u003e JSX.Element;\ntype Context\u003cT\u003e = { Provider: ContextProvider\u003cT\u003e };\n\nfunction createContext \u003cT\u003e ( defaultValue?: T ): Context\u003cT\u003e;\n```\n\nUsage:\n\n```tsx\nimport {createContext, useContext} from 'voby';\n\nconst App = () =\u003e {\n  const Context = createContext ( 123 );\n  return (\n    \u003c\u003e\n      {() =\u003e {\n        const value = useContext ( Context );\n        return \u003cp\u003e{value}\u003c/p\u003e;\n      }}\n      \u003cContext.Provider value={312}\u003e\n        {() =\u003e {\n          const value = useContext ( Context );\n          return \u003cp\u003e{value}\u003c/p\u003e;\n        }}\n      \u003c/Context.Provider\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n#### `createDirective`\n\nThis function creates a directive provider, which can be used to register a directive with its children.\n\nA directive is a function that always receives an `Element` as its first argument, which is basically a ref to the target element, and arbitrary user-provided arguments after that.\n\nEach directive has a unique name and it can be called by simply writing `use:directivename={[arg1, arg2, ...argN]]}` in the JSX.\n\nDirectives internally are registered using context providers, so you can also override directives for a particular scope just by registering another directive with the same name closer to where you are reading it.\n\nA directive's `Provider` will register the directive with its children, which is always what you want, but it can lead to messy code due to nesting.\n\nA directive's `register` function will register the directive with the current parent observer, which is usually only safe to do at the root level, but it will lead to very readable code.\n\nInterface:\n\n```ts\ntype DirectiveFunction = \u003cT extends unknown[]\u003e ( ref: Element, ...args: T ) =\u003e void;\ntype DirectiveProvider = ( props: { children: JSX.Element } ) =\u003e JSX.Element;\ntype DirectiveRef\u003cT extends unknown[]\u003e = ( ...args: T ) =\u003e (( ref: Element ) =\u003e void);\ntype DirectiveRegister = () =\u003e void;\ntype Directive = { Provider: DirectiveProvider, ref: DirectiveRef, register: DirectiveRegister };\n\nfunction createDirective \u003cT extends unknown[] = []\u003e ( name: string, fn: DirectiveFunction\u003cT\u003e, options?: DirectiveOptions ): Directive;\n```\n\nUsage:\n\n```tsx\nimport {createDirective, useEffect} from 'voby';\n\n// First of all if you are using TypeScript you should extend the \"JSX.Directives\" interface, so that TypeScript will know about your new directive\n\nnamespace JSX {\n  interface Directives {\n    tooltip: [title: string] // Mapping the name of the directive to the array of arguments it accepts\n  }\n}\n\n// Then you should create a directive provider\n\nconst TooltipDirective = createDirective ( 'tooltip', ( ref, title: string ) =\u003e {\n\n  useEffect ( () =\u003e {\n\n    if ( !ref () ) return; // The element may not be available yet, or it might have been unmounted\n\n    // Code that implements a tooltip for the given element here...\n\n  });\n\n});\n\n// Then you can use the new \"tooltip\" directive anywhere inside the \"TooltipDirective.Provider\"\n\nconst App = () =\u003e {\n  return (\n    \u003cTooltipDirective.Provider\u003e\n      \u003cinput value=\"Placeholder...\" use:tooltip={['This is a tooltip!']} /\u003e\n    \u003c/TooltipDirective.Provider\u003e\n  );\n};\n\n// You can also use directives directly by padding them along as refs\n\nconst App = () =\u003e {\n  return \u003cinput ref={TooltipDirective.ref ( 'This is a tooltip!' )} value=\"Placeholder...\" /\u003e;\n};\n```\n\n#### `createElement`\n\nThis is the internal function that will make DOM nodes and call/instantiate components, it will be called for you automatically via JSX.\n\nInterface:\n\n```ts\nfunction createElement \u003cP = {}\u003e ( component: JSX.Component\u003cP\u003e, props: P | null, ...children: JSX.Element[] ): () =\u003e JSX.Element);\n```\n\nUsage:\n\n```tsx\nimport {createElement} from 'voby';\n\nconst element = createElement ( 'div', { class: 'foo' }, 'child' ); // =\u003e () =\u003e HTMLDivElement\n```\n\n#### `h`\n\nThis function is just an alias for the `createElement` function, it's more convenient to use if you want to use Voby in hyperscript mode just because it has a much shorter name.\n\nInterface:\n\n```ts\nfunction h \u003cP = {}\u003e ( component: JSX.Component\u003cP\u003e, props: P | null, ...children: JSX.Element[] ): () =\u003e JSX.Element);\n```\n\nUsage:\n\n```tsx\nimport {h} from 'voby';\n\nconst element = h ( 'div', { class: 'foo' }, 'child' ); // =\u003e () =\u003e HTMLDivElement\n```\n\n#### `hmr`\n\nThis function wraps a component and makes it HMR-aware, for implementations of HMR like Vite's, this makes the component refresh itself and its children without requiring a reload of the whole page.\n\nFor an automated way to make all your components HMR-aware check out [`voby-vite`](https://github.com/vobyjs/voby-vite) instead.\n\nInterface:\n\n```ts\nfunction hmr \u003cT extends Function\u003e ( accept: Function, component: T ): T;\n```\n\nUsage:\n\n```tsx\nimport {hmr} from 'voby';\n\n// Define a component\n\nconst Counter = ({ value }): JSX.Element =\u003e {\n  // Return something...\n};\n\n// Optionally attach components and other values to it\n\nCounter.Button = ({ onClick }): JSX.Element =\u003e {\n  // Return something...\n};\n\nCounter.INITIAL_VALUE = 0;\n\n// Lastly export it as \"default\", wrapped in \"hmr\"\n// Only components exported as \"default\" are supported\n\nexport default hmr ( import.meta.hot?.accept?.bind ( import.meta.hot ), Counter );\n```\n\n#### `html`\n\nThis function provides an alternative way to use the framework, without writing JSX or using the `h` function manually, it instead allows you to write your markup as tagged template literals.\n\n[`htm`](https://github.com/developit/htm) is used under the hood, read its documentation.\n\nInterface:\n\n```ts\nfunction html ( strings: TemplateStringsArray, ...values: any[] ): JSX.Element;\n```\n\nUsage:\n\n```tsx\nimport {html, If} from 'voby';\n\nconst Counter = (): JSX.Element =\u003e {\n  const value = $(0);\n  const increment = () =\u003e value ( prev =\u003e prev + 1 );\n  const decrement = () =\u003e value ( prev =\u003e prev - 1 );\n  return html`\n    \u003ch1\u003eCounter\u003c/h1\u003e\n    \u003cp\u003e${value}\u003c/p\u003e\n    \u003cbutton onClick=${increment}\u003e+\u003c/button\u003e\n    \u003cbutton onClick=${decrement}\u003e-\u003c/button\u003e\n  `;\n};\n\n// Using a custom component without registering it\n\nconst NoRegistration = (): JSX.Element =\u003e {\n  return html`\n    \u003c${If} when=${true}\u003e\n      \u003cp\u003econtent\u003c/p\u003e\n    \u003c/${If}\u003e\n  `;\n};\n\n// Using a custom component after registering it, so you won't need to interpolate it anymore\n\nhtml.register ({ If });\n\nconst NoRegistration = (): JSX.Element =\u003e {\n  return html`\n    \u003cIf when=${true}\u003e\n      \u003cp\u003econtent\u003c/p\u003e\n    \u003c/If\u003e\n  `;\n};\n```\n\n#### `isBatching`\n\nThis function tells you if batching is currently active or not.\n\nInterface:\n\n```ts\nfunction isBatching (): boolean;\n```\n\nUsage:\n\n```tsx\nimport {batch, isBatching} from 'voby';\n\n// Checking if currently batching\n\nisBatching (); // =\u003e false\n\nbatch ( () =\u003e {\n\n  isBatching (); // =\u003e true\n\n});\n\nisBatching (); // =\u003e false\n```\n\n#### `isObservable`\n\nThis function tells you if a variable 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```tsx\nimport {$, isObservable} from 'voby';\n\nisObservable ( 123 ); // =\u003e false\nisObservable ( $(123) ); // =\u003e true\n```\n\n#### `isServer`\n\nThis function tells you if your code is executing in a browser environment or not.\n\nInterface:\n\n```ts\nfunction isServer (): boolean;\n```\n\nUsage:\n\n```tsx\nimport {isServer} from 'voby';\n\nisServer (); // =\u003e true or false\n```\n\n#### `isStore`\n\nThis function tells you if a variable is a store or not.\n\nInterface:\n\n```ts\nfunction isStore ( value: unknown ): boolean;\n```\n\nUsage:\n\n```tsx\nimport {store, isStore} from 'voby';\n\nisStore ( {} ); // =\u003e false\nisStore ( store ( {} ) ); // =\u003e true\n```\n\n#### `lazy`\n\nThis function creates a lazy component, which is loaded via the provided function only when/if needed.\n\nThis function uses `useResource` internally, so it's significant for `Suspense` too.\n\nInterface:\n\n```ts\ntype LazyComponent\u003cP = {}\u003e = ( props: P ) =\u003e ObservableReadonly\u003cChild\u003e;\ntype LazyFetcher\u003cP = {}\u003e = () =\u003e Promise\u003c{ default: JSX.Component\u003cP\u003e } | JSX.Component\u003cP\u003e\u003e;\ntype LazyResult\u003cP = {}\u003e = LazyComponent\u003cP\u003e \u0026 ({ preload: () =\u003e Promise\u003cvoid\u003e });\n\nfunction lazy \u003cP = {}\u003e ( fetcher: LazyFetcher\u003cP\u003e ): LazyResult\u003cP\u003e;\n```\n\nUsage:\n\n```ts\nimport {lazy} from 'voby';\n\nconst LazyComponent = lazy ( () =\u003e import ( './component' ) );\n```\n\n#### `render`\n\nThis function mounts a component inside a provided DOM element and returns a disposer function for unmounting it and stopping all reactivity inside it.\n\nInterface:\n\n```ts\nfunction render ( child: JSX.Element, parent?: HTMLElement | null ): Disposer;\n```\n\nUsage:\n\n```tsx\nimport {render} from 'voby';\n\nconst App = () =\u003e \u003cp\u003eHello, World!\u003c/p\u003e;\n\nconst dispose = render ( \u003cApp /\u003e, document.body );\n\ndispose (); // Unmounted and all reactivity inside it stopped\n```\n\n#### `renderToString`\n\nThis works just like `render`, but it returns a Promise to the HTML representation of the rendered component.\n\nThis is currently implemented in a way that works only inside a browser-like environement, so you'll need to use [JSDOM](https://github.com/jsdom/jsdom) or similar for this to work server-side, but it can work server-side too potentially.\n\nThis function automatically waits for all `Suspense` boundaries to resolve before returning.\n\nInterface:\n\n```ts\nfunction renderToString ( child: JSX.Element ): Promise\u003cstring\u003e;\n```\n\nUsage:\n\n```tsx\nimport {renderToString} from 'voby';\n\nconst App = () =\u003e \u003cp\u003eHello, World!\u003c/p\u003e;\n\nconst html = await renderToString ( \u003cApp /\u003e );\n```\n\n#### `resolve`\n\nThis function basically resolves any reactivity inside the passed argument, basically replacing every function it finds with a memo to the value of that function.\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\n[Read upstream documentation](https://github.com/fabiospampinato/oby#resolve).\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\nfunction resolve \u003cT\u003e ( value: T ): T extends Resolvable ? T : never;\n```\n\nUsage:\n\n```tsx\nimport {resolve} from 'voby';\n\nresolve // =\u003e Same as require ( 'oby' ).resolve\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\n[Read upstream documentation](https://github.com/fabiospampinato/oby#store).\n\nInterface:\n\n```ts\nfunction store \u003cT\u003e ( value: T, options?: StoreOptions ): T;\n```\n\nUsage:\n\n```tsx\nimport {store} from 'voby';\n\nstore // =\u003e Same as require ( 'oby' ).store\n```\n\n#### `template`\n\nThis function enables constructing elements with [Solid](https://www.solidjs.com)-level performance without using the Babel transform, but also without the convenience of that.\n\nIt basically works like [sinuous](https://github.com/luwes/sinuous/tree/master)'s template function, but with a cleaner API, since you don't have to access your props any differently inside the template here.\n\nBasically you can use this to wrap a component that doesn't directly create any observables or call any hooks to significanly improve performance when instantiating that component.\n\nInterface:\n\n```ts\nfunction template \u003cP = {}\u003e ( fn: (( props: P ) =\u003e JSX.Element) ): (( props: P ) =\u003e () =\u003e Element);\n```\n\nUsage:\n\n```tsx\nimport {template} from 'voby';\n\nconst Row = template ( ({ id, cls, label, onSelect, onRemove }) =\u003e { // Now Row is super fast to instantiate\n  return (\n    \u003ctr class={cls}\u003e\n      \u003ctd class=\"col-md-1\"\u003e{id}\u003c/td\u003e\n      \u003ctd class=\"col-md-4\"\u003e\n        \u003ca onClick={onSelect}\u003e{label}\u003c/a\u003e\n      \u003c/td\u003e\n      \u003ctd class=\"col-md-1\"\u003e\n        \u003ca onClick={onRemove}\u003e\n          \u003cspan class=\"glyphicon glyphicon-remove\" ariaHidden={true}\u003e\u003c/span\u003e\n        \u003c/a\u003e\n      \u003c/td\u003e\n      \u003ctd class=\"col-md-6\"\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  );\n});\n\nconst Table = () =\u003e {\n  const rows = [ /* props for all your rows here */ ];\n  return rows.map ( row =\u003e \u003cRow {...row}\u003e );\n};\n```\n\n#### `tick`\n\nThis function forces effects scheduled for execution to be executed immediately, bypassing automatic or manual batching.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#tick).\n\nInterface:\n\n```ts\nfunction tick (): void;\n```\n\nUsage:\n\n```tsx\nimport {tick} from 'voby';\n\ntick // =\u003e Same as require ( 'oby' ).tick\n```\n\n#### `untrack`\n\nThis function executes the provided function without creating dependencies on observables retrieved inside it.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#untrack).\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```tsx\nimport {untrack} from 'voby';\n\nuntrack // =\u003e Same as require ( 'oby' ).untrack\n```\n\n### Components\n\nThe following components are provided.\n\nCrucially some components are provided for control flow, since regular JavaScript control flow primitives are not reactive, and we need to have reactive alternatives to them to have great performance.\n\n#### `Dynamic`\n\nThis component is just an alternative to `createElement` that can be used in JSX, it's useful to create a new element dynamically.\n\nInterface:\n\n```ts\nfunction Dynamic \u003cP = {}\u003e ( props: { component: ObservableMaybe\u003cJSX.Component\u003cP\u003e, props?: FunctionMaybe\u003cP | null\u003e, children?: JSX.Element }): JSX. Element;\n```\n\nUsage:\n\n```tsx\nimport {Dynamic} from 'voby';\n\nconst App = () =\u003e {\n  const heading = 'h2';\n  return (\n    \u003cDynamic component={heading}\u003e\n      Some content\n    \u003c/Dynamic\u003e\n  );\n};\n```\n\n#### `ErrorBoundary`\n\nThe error boundary catches errors thrown inside it, and renders a fallback component when that happens.\n\nInterface:\n\n```ts\nfunction ErrorBoundary ( props: { fallback: JSX.Element | (( props: { error: Error, reset: Callback } ) =\u003e JSX.Element), children: JSX.Element }): ObservableReadonly\u003cJSX.Element\u003e;\n```\n\nUsage:\n\n```tsx\nimport {ErrorBoundary} from 'voby';\n\nconst Fallback = ({ reset, error }: { reset: () =\u003e void, error: Error }) =\u003e {\n  return (\n    \u003c\u003e\n      \u003cp\u003eError: {error.message}\u003c/p\u003e\n      \u003cbutton onClick={error}\u003eRecover\u003c/button\u003e\n    \u003c/\u003e\n  );\n};\n\nconst SomeComponentThatThrows = () =\u003e {\n  throw 'whatever';\n};\n\nconst App = () =\u003e {\n  return (\n    \u003cErrorBoundary fallback={Fallback}\u003e\n      \u003cSomeComponentThatThrows /\u003e\n    \u003c/ErrorBoundary\u003e\n  );\n};\n```\n\n#### `For`\n\nThis component is the reactive alternative to natively mapping over an array.\n\nIt must be called with an array, or a function that returns an array, of _unique_ values, and each of them are passed to the child function to render something.\n\nIt can be used to map over values either with a keyed (default) or unkeyed (opt-in) strategy. Read [this](https://www.stefankrause.net/wp/?p=342) for some details about the differences between those, and the [upstream documentation](https://github.com/fabiospampinato/oby#for).\n\nInterface:\n\n```ts\nfunction For \u003cT\u003e ( props: { values?: FunctionMaybe\u003creadonly T[]\u003e, fallback?: JSX.ELement, children: (( value: T, index: FunctionMaybe\u003cnumber\u003e ) =\u003e JSX.Element) }): ObservableReadonly\u003cJSX.Element\u003e;\nfunction For \u003cT\u003e ( props: { values?: FunctionMaybe\u003creadonly T[]\u003e, fallback?: JSX.ELement, pooled?: true, unkeyed?: true, children: (( value: ObservableReadonly\u003cT\u003e, index: FunctionMaybe\u003cnumber\u003e ) =\u003e JSX.Element) }): ObservableReadonly\u003cJSX.Element\u003e;\n```\n\nUsage:\n\n```tsx\nimport {For} from 'voby';\n\nconst App = () =\u003e {\n  const numbers = [1, 2, 3, 4, 5];\n  return (\n    \u003cFor values={numbers}\u003e\n      {( value ) =\u003e {\n        return \u003cp\u003eValue: {value}\u003c/p\u003e\n      }}\n    \u003c/For\u003e\n  );\n};\n```\n\n#### `Fragment`\n\nThis is just the internal component used for rendering fragments: `\u003c\u003e\u003c/\u003e`, you probably would never use this directly even if you are not using JSX, since you can return plain arrays from your components anyway.\n\nInterface:\n\n```ts\nfunction Fragment ( props: { children: JSX.Element }): JSX.Element;\n```\n\nUsage:\n\n```tsx\nimport {Fragment} from 'voby';\n\nconst App = () =\u003e {\n  return (\n    \u003cFragment\u003e\n      \u003cp\u003echild 1\u003c/p\u003e\n      \u003cp\u003echild 2\u003c/p\u003e\n    \u003c/Fragment\u003e\n  );\n};\n```\n\n#### `If`\n\nThis component is the reactive alternative to the native `if`.\n\nIf a function is passed as the children then it will be called with a read-only observable that contains the current, always truthy, value of the \"when\" condition.\n\nInterface:\n\n```ts\ntype Truthy\u003cT = unknown\u003e = Extract\u003cT, number | bigint | string | true | object | symbol | Function\u003e;\n\nfunction If \u003cT\u003e ( props: { when: FunctionMaybe\u003cT\u003e, fallback?: JSX.Element, children: JSX.Element | (( value: (() =\u003e Truthy\u003cT\u003e) ) =\u003e JSX.Element) }): ObservableReadonly\u003cJSX.Element\u003e;\n```\n\nUsage:\n\n```tsx\nimport {If} from 'voby';\n\nconst App = () =\u003e {\n  const visible = $(false);\n  const toggle = () =\u003e visible ( !visible () );\n  return (\n    \u003c\u003e\n      \u003cbutton onClick={toggle}\u003eToggle\u003c/button\u003e\n      \u003cIf when={visible}\u003e\n        \u003cp\u003eHello!\u003c/p\u003e\n      \u003c/If\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n#### `KeepAlive`\n\nThis component allows you to create singleton instances of other components that survive their parent components being disposed, and can therefore be reused cheaply.\n\nComponents rendered inside a `KeepAlive` are detached from the rest of the reactivity graph, so they don't inherit any context provided outside of their parent `KeepAlive` wrapper.\n\nComponents rendered inside a `KeepAlive` are kept in memory until the wrapper `KeepAlive` is unmounted and `ttl` milliseconds have passed without another `KeepAlive` with the same `id` being mounted. Or never, if no `ttl` prop is provided.\n\nInterface:\n\n```ts\nfunction KeepAlive ( props: { id: FunctionMaybe\u003cstring\u003e, ttl?: FunctionMaybe\u003cnumber\u003e, children: JSX.Element } ): ObservableReadonly\u003cJSX.Element\u003e;\n```\n\nUsage:\n\n```tsx\nimport {KeepAlive} from 'voby';\n\n// Render some expensive component inside a KeepAlive\n\nconst App = () =\u003e {\n  return (\n    \u003cKeepAlive id=\"some-unique-id\" ttl={60_000}\u003e\n      \u003cSomeExpensiveComponent /\u003e\n    \u003c/KeepAlive\u003e\n  );\n};\n```\n\n#### `Portal`\n\nThis component mounts its children inside a provided DOM element, or inside `document.body` otherwise.\n\nThe `mount` prop can also be an observable, if its value changes the portal is reparented.\n\nThe `when` prop can be used to apply the portal conditionally, if it explicitly resolves to false then children are mounted normally, as if they weren't wrapped in a portal.\n\nEvents will propagate natively, according to the resulting DOM hierarchy, not the components hierarchy.\n\nInterface:\n\n```ts\nfunction Portal ( props: { when: boolean, mount?: JSX.Element, wrapper?: JSX.Element, children: JSX.Element }): (() =\u003e JSX.Element | null) \u0026 ({ metadata: { portal: HTMLDivElement } });\n```\n\nUsage:\n\n```tsx\nimport {Portal} from 'voby';\n\nconst Modal = () =\u003e {\n  // Some modal component maybe...\n};\n\nconst App = () =\u003e {\n  return (\n    \u003cPortal mount={document.body}\u003e\n      \u003cModal /\u003e\n    \u003c/Portal\u003e\n  );\n};\n```\n\n#### `Suspense`\n\nThis component is like `If`, the reactive alternative to the native `if`, but the fallback branch is shown automatically while there are some resources loading in the main branch, and the main branch is kept alive under the hood.\n\nSo this can be used to show some fallback content while the actual content is loading in the background.\n\nThis component relies on `useResource` to understand if there's a resource loading or not.\n\nThis component also supports a manual \"when\" prop for manually deciding whether the fallback branch should be rendered or not.\n\nInterface:\n\n```ts\nfunction Suspense ( props: { when?: FunctionMaybe\u003cunknown\u003e, fallback?: JSX.Element, children: JSX.Element }): ObservableReadonly\u003cJSX.Element\u003e;\n```\n\nUsage:\n\n```tsx\nimport {Suspense} from 'voby';\n\nconst App = () =\u003e {\n  const Content = () =\u003e {\n    const resource = useResource ( () =\u003e makeSomePromise () );\n    return (\n      \u003cIf when={() =\u003e !resource ().pending \u0026\u0026 !resource ().error}\u003e\n        {resource ().value}\n      \u003c/If\u003e\n    );\n  };\n  const Spinner = () =\u003e {\n    return \u003cp\u003eLoading...\u003c/p\u003e;\n  };\n  return (\n    \u003cSuspense fallback={\u003cSpinner /\u003e}\u003e\n      \u003cContent /\u003e\n    \u003c/Suspense\u003e\n  );\n};\n```\n\n#### `Switch`\n\nThis component is the reactive alternative to the native `switch`.\n\nInterface:\n\n```ts\nfunction Switch \u003cT\u003e ( props: { when: FunctionMaybe\u003cT\u003e, fallback?: JSX.Element, children: JSX.Element }): ObservableReadonly\u003cJSX.Element\u003e;\n\nSwitch.Case = function \u003cT\u003e ( props: { when: T, children: JSX.Element } ): (() =\u003e JSX.Element) \u0026 ({ metadata: [T, JSX.Element] });\nSwitch.Default = function ( props: { children: JSX.Element } ): (() =\u003e JSX.Element) \u0026 ({ metadata: [JSX.Element] });\n```\n\nUsage:\n\n```tsx\nimport {Switch} from 'voby';\n\nconst App = () =\u003e {\n  const value = $(0);\n  const increment = () =\u003e value ( value () + 1 );\n  const decrement = () =\u003e value ( value () - 1 );\n  return (\n    \u003c\u003e\n      \u003cSwitch when={value}\u003e\n        \u003cSwitch.Case when={0}\u003e\n          \u003cp\u003e0, the boundary between positives and negatives! (?)\u003c/p\u003e\n        \u003c/Switch.Case\u003e\n        \u003cSwitch.Case when={1}\u003e\n          \u003cp\u003e1, the multiplicative identity!\u003c/p\u003e\n        \u003c/Switch.Case\u003e\n        \u003cSwitch.Default\u003e\n          \u003cp\u003e{value}, I don't have anything interesting to say about that :(\u003c/p\u003e\n        \u003c/Switch.Default\u003e\n      \u003c/Switch\u003e\n      \u003cbutton onClick={increment}\u003e+\u003c/button\u003e\n      \u003cbutton onClick={decrement}\u003e-\u003c/button\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n#### `Ternary`\n\nThis component is the reactive alternative to the native ternary operator.\n\nThe first child will be rendered when the condition is truthy, otherwise the second child will be rendered.\n\nInterface:\n\n```ts\nfunction Ternary ( props: { when: FunctionMaybe\u003cunknown\u003e, children: [JSX.Element, JSX.Element] } ): ObservableReadonly\u003cJSX.Element\u003e;\n```\n\nUsage:\n\n```tsx\nimport {Ternary} from 'voby';\n\nconst App = () =\u003e {\n  const visible = $(false);\n  const toggle = () =\u003e visible ( !visible () );\n  return (\n    \u003c\u003e\n      \u003cbutton onClick={toggle}\u003eToggle\u003c/button\u003e\n      \u003cTernary when={visible}\u003e\n        \u003cp\u003eVisible :)\u003c/p\u003e\n        \u003cp\u003eInvisible :(\u003c/p\u003e\n      \u003c/Ternary\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n### Hooks \u003csub\u003ecore\u003c/sub\u003e\n\nThe following core hooks are provided.\n\nMost of these are just functions that `oby` provides, re-exported as `use*` functions.\n\n#### `useBoolean`\n\nThis hook 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\n[Read upstream documentation](https://github.com/fabiospampinato/oby#boolean).\n\nInterface:\n\n```ts\nfunction useBoolean ( value: FunctionMaybe\u003cunknown\u003e ): FunctionMaybe\u003cboolean\u003e;\n```\n\nUsage:\n\n```tsx\nimport {useBoolean} from 'voby';\n\nuseBoolean // =\u003e Same as require ( 'oby' ).boolean\n```\n\n#### `useCleanup`\n\nThis hook registers a function to be called when the parent computation is disposed.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#cleanup).\n\nInterface:\n\n```ts\nfunction useCleanup ( fn: () =\u003e void ): void;\n```\n\nUsage:\n\n```tsx\nimport {useCleanup} from 'voby';\n\nuseCleanup // =\u003e Same as require ( 'oby' ).cleanup\n```\n\n#### `useContext`\n\nThis hook retrieves the value out of a context object.\n\nInterface:\n\n```ts\nfunction useContext \u003cT\u003e ( context: Context\u003cT\u003e ): T | undefined;\n```\n\nUsage:\n\n```tsx\nimport {createContext, useContext} from 'voby';\n\nconst App = () =\u003e {\n  const ctx = createContext ( 123 );\n  const value = useContext ( ctx );\n  return \u003cp\u003e{value}\u003c/p\u003e;\n};\n```\n\n#### `useDisposed`\n\nThis hook returns a boolean read-only observable that is set to `true` when the parent computation gets disposed of.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#disposed).\n\nInterface:\n\n```ts\nfunction useDisposed (): ObservableReadonly\u003cboolean\u003e;\n```\n\nUsage:\n\n```tsx\nimport {useDisposed} from 'voby';\n\nuseDisposed // =\u003e Same as require ( 'oby' ).disposed\n```\n\n#### `useEffect`\n\nThis hook registers a function to be called when any of its dependencies change. If a function is returned it's automatically registered as a cleanup function.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#effect).\n\nInterface:\n\n```ts\nfunction useEffect ( fn: () =\u003e (() =\u003e void) | void ): (() =\u003e void);\n```\n\nUsage:\n\n```tsx\nimport {useEffect} from 'voby';\n\nuseEffect // =\u003e Same as require ( 'oby' ).effect\n```\n\n#### `useMemo`\n\nThis hook is the crucial other ingredient that we need, other than observables themselves, to have a powerful reactive system that can track dependencies and re-execute computations when needed.\n\nThis hook registers a function to be called when any of its dependencies change, and the return of that function is wrapped in a read-only observable and returned.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#memo).\n\nInterface:\n\n```ts\nfunction useMemo \u003cT\u003e ( fn: () =\u003e T, options?: MemoOptions\u003cT | undefined\u003e ): ObservableReadonly\u003cT\u003e;\n```\n\nUsage:\n\n```tsx\nimport {useMemo} from 'voby';\n\nuseMemo // =\u003e Same as require ( 'oby' ).memo\n```\n\n#### `usePromise`\n\nThis hook wraps a promise in an observable, so that you can be notified when it resolves or rejects.\n\nThis hook uses `useResource` internally, so it's significant for `Suspense` too.\n\nInterface:\n\n```ts\nfunction usePromise \u003cT\u003e ( promise: FunctionMaybe\u003cPromise\u003cT\u003e\u003e ): ObservableReadonly\u003cResource\u003cT\u003e\u003e;\n```\n\nUsage:\n\n```tsx\nimport {usePromise} from 'voby';\n\nconst App = () =\u003e {\n  const request = fetch ( 'https://my.api' ).then ( res =\u003e res.json ( 0 ) );\n  const resource = usePromise ( request );\n  return () =\u003e {\n    const state = resource ();\n    if ( state.pending ) return \u003cp\u003epending...\u003c/p\u003e;\n    if ( state.error ) return \u003cp\u003e{state.error.message}\u003c/p\u003e;\n    return \u003cp\u003e{JSON.stringify ( state.value )}\u003c/p\u003e\n  };\n};\n```\n\n#### `useReadonly`\n\nThis hook creates a read-only observable out of another observable.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#readonly).\n\nInterface:\n\n```ts\nfunction useReadonly \u003cT\u003e ( observable: Observable\u003cT\u003e | ObservableReadonly\u003cT\u003e ): ObservableReadonly\u003cT\u003e;\n```\n\nUsage:\n\n```tsx\nimport {useReadonly} from 'voby';\n\nuseReadonly // =\u003e Same as require ( 'oby' ).readonly\n```\n\n#### `useResolved`\n\nThis hook receives a value, or an array of values, potentially wrapped in functions and/or observables, and unwraps it/them.\n\nIf no callback is used then it returns the unwrapped value, otherwise it returns whatever the callback returns.\n\nThis is useful for handling reactive and non reactive values the same way. Usually if the value is a function, or always for convenience, you'd want to wrap the `useResolved` call in a `useMemo`, to maintain reactivity.\n\nThis is potentially a more convenient version of `$$`, made especially for handling nicely arguments passed that your hooks receive that may or may not be observables.\n\nInterface:\n\n\u003e The precise interface for this function is insane, you can find it here: https://github.com/fabiospampinato/voby/blob/master/src/hooks/use_resolved.ts\n\nUsage:\n\n```tsx\nimport {$, useResolved} from 'voby';\n\nuseResolved ( 123 ); // =\u003e 123\n\nuseResolved ( $(123) ); // =\u003e 123\n\nuseResolved ( () =\u003e 123 ); // =\u003e 123\n\nuseResolved ( () =\u003e 123, false ); // =\u003e () =\u003e 123\n\nuseResolved ( $(123), value =\u003e 321 ); // =\u003e 321\n\nuseResolved ( [$(123), () =\u003e 123], ( a, b ) =\u003e 321 ); // =\u003e 321\n```\n\n#### `useResource`\n\nThis hook wraps the result of a function call with an observable, handling the cases where the function throws, the result is an observable, the result is a promise or an observale that resolves to a promise, and the promise rejects, so that you don't have to worry about these issues.\n\nThis basically provides a unified way to handle sync and async results, observable and non observable results, and functions that throw and don't throw.\n\nThis function is also the mechanism through which `Suspense` understands if there are things loading under the hood or not.\n\nWhen the `value` property is read while fetching, or when the `latest` property is read the first time, or after an error, while fetching, then `Suspense` boundaries will be triggered.\n\nWhen the `value` property or the `latest` property are read after the fetch errored they will throw, triggering `ErrorBoundary`.\n\nThe passed function is tracked and it will be automatically re-executed whenever any of the observables it reads change.\n\nInterface:\n\n```ts\nfunction useResource \u003cT\u003e ( fetcher: (() =\u003e ObservableMaybe\u003cPromiseMaybe\u003cT\u003e\u003e) ): Resource\u003cT\u003e;\n```\n\nUsage:\n\n```tsx\nimport {useResource} from 'voby';\n\nconst fetcher = () =\u003e fetch ( 'https://my.api' );\n\nconst resource = useResource ( fetcher );\n```\n\n#### `useRoot`\n\nThis hook creates a new computation root, detached from any parent computation.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#root).\n\nInterface:\n\n```ts\nfunction useRoot \u003cT\u003e ( fn: ( dispose: () =\u003e void ) =\u003e T ): T;\n```\n\nUsage:\n\n```tsx\nimport {useRoot} from 'voby';\n\nuseRoot // =\u003e Same as require ( 'oby' ).root\n```\n\n#### `useSelector`\n\nThis hook massively optimizes `isSelected` kind of workloads.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#selector).\n\nInterface:\n\n```ts\ntype SelectorFunction\u003cT\u003e = ( value: T ) =\u003e ObservableReadonly\u003cboolean\u003e;\n\nfunction useSelector \u003cT\u003e ( source: () =\u003e T | ObservableReadonly\u003cT\u003e ): SelectorFunction\u003cT\u003e;\n```\n\nUsage:\n\n```tsx\nimport {useSelector} from 'voby';\n\nuseSelector // =\u003e Same as require ( 'oby' ).selector\n```\n\n#### `useSuspended`\n\nThis hook returns a read-only observable that tells you if the closest suspense boundary is currently suspended or not.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#suspended).\n\nInterface:\n\n```ts\nfunction useSuspended (): ObservableReadonly\u003cboolean\u003e;\n```\n\nUsage:\n\n```tsx\nimport {useSuspended} from 'voby';\n\nuseSuspended // =\u003e Same as require ( 'oby' ).suspended\n```\n\n#### `useUntracked`\n\nThis hook returns an untracked version of a value.\n\n[Read upstream documentation](https://github.com/fabiospampinato/oby#untracked).\n\nInterface:\n\n```ts\nfunction useUntracked \u003cT\u003e ( fn: () =\u003e T ): () =\u003e T;\nfunction useUntracked \u003cT\u003e ( value: T ): () =\u003e T;\n```\n\nUsage:\n\n```tsx\nimport {useUntracked} from 'voby';\n\nuseUntracked // =\u003e Same as require ( 'oby' ).untracked\n```\n\n### Hooks \u003csub\u003eweb\u003c/sub\u003e\n\nThe following web hooks are provided.\n\nMost of these are just reactive alternatives to native web APIs.\n\n#### `useAbortController`\n\nThis hook is just an alternative to `new AbortController ()` that automatically aborts itself when the parent computation is disposed.\n\nInterface:\n\n```ts\nfunction useAbortController ( signals?: ArrayMaybe\u003cAbortSignal\u003e ): AbortController;\n```\n\nUsage:\n\n```tsx\nimport {useAbortController} from 'voby';\n\nconst controller = useAbortController ();\n```\n\n#### `useAbortSignal`\n\nThis hook is just a convenient alternative to `useAbortController`, if you are only interested in its signal, which is automatically aborted when the parent computation is disposed.\n\nInterface:\n\n```ts\nfunction useAbortSignal ( signals?: ArrayMaybe\u003cAbortSignal\u003e ): AbortSignal;\n```\n\nUsage:\n\n```tsx\nimport {useAbortSignal} from 'voby';\n\nconst signal = useAbortSignal ();\n```\n\n#### `useAnimationFrame`\n\nThis hook is just an alternative to `requestAnimationFrame` that automatically clears itself when the parent computation is disposed.\n\nInterface:\n\n```ts\nfunction useAnimationFrame ( callback: ObservableMaybe\u003cFrameRequestCallback\u003e ): Disposer;\n```\n\nUsage:\n\n```tsx\nimport {useAnimationFrame} from 'voby';\n\nuseAnimationFrame ( () =\u003e console.log ( 'called' ) );\n```\n\n#### `useAnimationLoop`\n\nThis hook is just a version of `useAnimationFrame` that loops until the parent computation is disposed.\n\nInterface:\n\n```ts\nfunction useAnimationLoop ( callback: ObservableMaybe\u003cFrameRequestCallback\u003e ): Disposer;\n```\n\nUsage:\n\n```tsx\nimport {useAnimationLoop} from 'voby';\n\nuseAnimationLoop ( () =\u003e console.log ( 'called' ) );\n```\n\n#### `useEventListener`\n\nThis hook is just an alternative to `addEventListener` that automatically clears itself when the parent computation is disposed.\n\nInterface:\n\n```ts\nfunction useEventListener ( target: FunctionMaybe\u003cEventTarget\u003e, event: FunctionMaybe\u003cstring\u003e, handler: ObservableMaybe\u003c( event: Event ) =\u003e void\u003e, options?: FunctionMaybe\u003ctrue | AddEventListenerOptions\u003e ): Disposer;\n```\n\nUsage:\n\n```tsx\nimport {useEventListener} from 'voby';\n\nuseEventListener ( document, 'click', console.log );\n```\n\n#### `useFetch`\n\nThis hook wraps the output of a fetch request in an observable, so that you can be notified when it resolves or rejects. The request is also aborted automatically when the parent computation gets disposed of.\n\nThis hook uses `useResource` internally, so it's significant for `Suspense` too.\n\nInterface:\n\n```ts\nfunction useFetch ( request: FunctionMaybe\u003cRequestInfo\u003e, init?: FunctionMaybe\u003cRequestInit\u003e ): ObservableReadonly\u003cResource\u003cResponse\u003e\u003e;\n```\n\nUsage:\n\n```tsx\nimport {useFetch} from 'voby';\n\nconst App = () =\u003e {\n  const resource = useFetch ( 'https://my.api' );\n  return () =\u003e {\n    const state = resource ();\n    if ( state.pending ) return \u003cp\u003epending...\u003c/p\u003e;\n    if ( state.error ) return \u003cp\u003e{state.error.message}\u003c/p\u003e;\n    return \u003cp\u003eStatus: {state.value.status}\u003c/p\u003e\n  };\n};\n```\n\n#### `useIdleCallback`\n\nThis hook is just an alternative to `requestIdleCallback` that automatically clears itself when the parent computation is disposed.\n\nInterface:\n\n```ts\nfunction useIdleCallback ( callback: ObservableMaybe\u003cIdleRequestCallback\u003e, options?: FunctionMaybe\u003cIdleRequestOptions\u003e ): Disposer;\n```\n\nUsage:\n\n```tsx\nimport {useIdleCallback} from 'voby';\n\nuseIdleCallback ( () =\u003e console.log ( 'called' ) );\n```\n\n#### `useIdleLoop`\n\nThis hook is just a version of `useIdleCallback` that loops until the parent computation is disposed.\n\nInterface:\n\n```ts\nfunction useIdleLoop ( callback: ObservableMaybe\u003cIdleRequestCallback\u003e, options?: FunctionMaybe\u003cIdleRequestOptions\u003e ): Disposer;\n```\n\nUsage:\n\n```tsx\nimport {useIdleLoop} from 'voby';\n\nuseIdleLoop ( () =\u003e console.log ( 'called' ) );\n```\n\n#### `useInterval`\n\nThis hook is just an alternative to `setInterval` that automatically clears itself when the parent computation is disposed.\n\nInterface:\n\n```ts\nfunction useInterval ( callback: ObservableMaybe\u003cCallback\u003e, ms?: FunctionMaybe\u003cnumber\u003e ): Disposer;\n```\n\nUsage:\n\n```tsx\nimport {useInterval} from 'voby';\n\nuseInterval ( () =\u003e console.log ( 'called' ), 1000 );\n```\n\n#### `useMicrotask`\n\nThis hook is just an alternative to `queueMicrotask` that automatically clears itself when the parent computation is disposed, and that ensures things like contexts, error boundaries etc. keep working inside the microtask.\n\nInterface:\n\n```ts\nfunction useMicrotask ( fn: () =\u003e void ): void;\n```\n\nUsage:\n\n```tsx\nimport {useMicrotask} from 'voby';\n\nuseMicrotask ( () =\u003e console.log ( 'called' ) );\n```\n\n#### `useTimeout`\n\nThis hook is just an alternative to `setTimeout` that automatically clears itself when the parent computation is disposed.\n\nInterface:\n\n```ts\nfunction useTimeout ( callback: ObservableMaybe\u003cCallback\u003e, ms?: FunctionMaybe\u003cnumber\u003e ): Disposer;\n```\n\nUsage:\n\n```tsx\nimport {useTimeout} from 'voby';\n\nuseTimeout ( () =\u003e console.log ( 'called' ), 1000 );\n```\n\n### Types\n\n#### `Context`\n\nThis type describes the object that `createContext` gives you.\n\nInterface:\n\n```ts\ntype Context\u003cT = unknown\u003e = {\n  Provider ( props: { value: T, children: JSX.Element } ): JSX.Element\n};\n```\n\nUsage:\n\n```ts\nimport {useContext} from 'voby';\nimport type {Context} from 'voby';\n\n// Create an alternative useContext that throws if the context is not available\n\nconst useNonNullableContext = \u003cT\u003e ( context: Context\u003cT\u003e ): NonNullable\u003cT\u003e =\u003e {\n  const value = useContext ( context );\n  if ( value === null || value === undefined ) throw new Error ( 'Missing context' );\n  return value;\n};\n```\n\n#### `Directive`\n\nThis type describes the object that `createDirective` gives you.\n\nInterface:\n\n```ts\ntype Directive\u003cArguments extends unknown[] = []\u003e = {\n  Provider: ( props: { children: JSX.Element } ) =\u003e JSX.Element,\n  ref: ( ...args: Arguments ) =\u003e (( ref: Element ) =\u003e void),\n  register: () =\u003e void\n};\n```\n\nUsage:\n\n```ts\nimport {$$, useEffect} from 'voby';\nimport type {Directive, FunctionMaybe} from 'voby';\n\n// Example hook for turning a directive into a hook\n\nconst useDirective = \u003cT extends unknown[] = []\u003e ( directive: Directive\u003cT\u003e ) =\u003e {\n  return ( ref: FunctionMaybe\u003cElement | undefined\u003e, ...args: T ): void =\u003e {\n    useEffect ( () =\u003e {\n      const target = $$(ref);\n      if ( !target ) return;\n      directive.ref ( ...args )( target );\n    });\n  };\n};\n```\n\n#### `DirectiveOptions`\n\nThis type describes the options object that the `createDirective` function accepts.\n\nInterface:\n\n```ts\ntype DirectiveOptions = {\n  immediate?: boolean // If `true` the directive is called as soon as the node is created, otherwise it also waits for that node to be attached to the DOM\n};\n```\n\nUsage:\n\n```tsx\nimport {createDirective} from 'voby';\n\n// Create an regular, non-immediate, directive\n\nconst TooltipDirective = createDirective ( 'tooltip', ( ref, title: string ) =\u003e {\n  // Implementation...\n});\n\n// Create an immediate directive\n\nconst TooltipDirectiveImmediate = createDirective ( 'tooltip', ( ref, title: string ) =\u003e {\n  // Implementation...\n}, { immediate: true } );\n```\n\n#### `EffectOptions`\n\nThis type describes the options object that the `useEffect` hook accepts.\n\nInterface:\n\n```ts\ntype EffectOptions = {\n  suspense?: boolean,\n  sync?: boolean | 'init'\n};\n```\n\nUsage:\n\n```tsx\nimport {useEffect} from 'voby';\n\n// Make a regular asynchronous effect\n\nuseEffect ( () =\u003e {\n  // Do something...\n});\n\n// Make a synchronous effect, which is strongly discouraged\n\nuseEffect ( () =\u003e {\n  // Do something...\n}, { sync: true } );\n\n// Make an asynchronous effect that's executed immediately on creation, which is useful in edge cases\n\nuseEffect ( () =\u003e {\n  // Do something...\n}, { sync: 'init' } );\n\n// Make an effect that won't be paused by `Suspense`, which is useful in edge cases\n\nuseEffect ( () =\u003e {\n  // Do something...\n}, { suspense: false } );\n```\n\n#### `FunctionMaybe`\n\nThis type says that something can be the value itself or a function that returns that value.\n\nIt's useful at times since some components, like `If`, accept `when` conditions wrapped in `FunctionMaybe`.\n\nInterface:\n\n```ts\ntype FunctionMaybe\u003cT\u003e = (() =\u003e T) | T;\n```\n\nUsage:\n\n```tsx\nimport type {FunctionMaybe} from 'voby';\n\nconst SomeConditionalComponent = ( when: FunctionMaybe\u003cboolean\u003e, value: string ): JSX.Element =\u003e {\n  return (\n    \u003cIf when={when}\u003e\n      {value}\n    \u003c/If\u003e\n  );\n};\n```\n\n#### `Observable`\n\nThis type says that something is a regular observable, which can be updated via its setter.\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\nUsage:\n\n```tsx\nimport type {Observable} from 'voby';\n\nconst fn = ( value: Observable\u003cboolean\u003e ): void =\u003e {\n  value (); // Getting\n  value ( true ); // Setting\n};\n```\n\n#### `ObservableLike`\n\nThis type says that something has the same shape as a regular observable, but it 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\nUsage:\n\n```tsx\nimport type {ObservableLike} from 'voby';\n\nconst fn = ( value: ObservableLike\u003cboolean\u003e ): void =\u003e {\n  value (); // Getting\n  value ( true ); // Setting\n};\n```\n\n#### `ObservableReadonly`\n\nThis type says that something is a read-only observable, which can only be read but not updated.\n\nInterface:\n\n```ts\ntype ObservableReadonly\u003cT\u003e = {\n  (): T,\n  readonly [ObservableSymbol]: true\n};\n```\n\nUsage:\n\n```tsx\nimport type {ObservableReadonly} from 'voby';\n\nconst fn = ( value: ObservableReadonly\u003cboolean\u003e ): void =\u003e {\n  value (); // Getting\n  value ( true ); // This will throw!\n};\n```\n\n#### `ObservableReadonlyLike`\n\nThis type says that something hsa the same shape as a read-only observable, but it may not actually be an observable.\n\nInterface:\n\n```ts\ntype ObservableReadonlyLike\u003cT\u003e = {\n  (): T\n};\n```\n\nUsage:\n\n```tsx\nimport type {ObservableReadonlyLike} from 'voby';\n\nconst fn = ( value: ObservableReadonlyLike\u003cboolean\u003e ): void =\u003e {\n  value (); // Getting\n  value ( true ); // This is not supported!\n};\n```\n\n#### `ObservableMaybe`\n\nThis type says that something can be the value itself or an observable to that value.\n\nThis is super useful if you want to write components and hooks that can accept either plain values or observables to those values.\n\nInterface:\n\n```ts\ntype ObservableMaybe\u003cT\u003e = Observable\u003cT\u003e | ObservableReadonly\u003cT\u003e | T;\n```\n\nUsage:\n\n```tsx\nimport type {ObservableMaybe} from 'voby';\n\nconst Button = ({ label }: { label: ObservableMaybe\u003cstring\u003e }): JSX.Element =\u003e {\n  return \u003cbutton\u003e{label}\u003c/button\u003e;\n};\n```\n\n#### `MemoOptions`\n\nThis type describes the options object that the `useMemo` hook accepts.\n\nInterface:\n\n```ts\ntype MemoOptions\u003cT\u003e = {\n  equals?: (( value: T, valuePrev: T ) =\u003e boolean) | false,\n  sync?: boolean\n};\n```\n\nUsage:\n\n```tsx\nimport {useMemo} from 'voby';\n\n// Make a regular asynchronous memo\n\nuseMemo ( () =\u003e {\n  // Do something...\n});\n\n// Make a synchronous memo, which is strongly discouraged\n\nuseMemo ( () =\u003e {\n  // Do something...\n}, { sync: true } );\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\nUsage:\n\n```tsx\nimport type {Observable, ObservableOptions} from 'voby';\nimport {$} from 'voby';\n\nconst createTimestamp = ( options?: ObservableOptions ): Observable\u003cnumber\u003e =\u003e {\n  return $( Date.now (), options );\n};\n```\n\n#### `Resource`\n\nThis is the type of object that `useResource`, `usePromise` and `useFetch` will return you.\n\nIt's an object that tells if whether the resource is loading or not, whether an error happened or not, if what the eventual resulting value is.\n\nIt's a read-only observable that holds the resulting object, but it also comes with helper methods for retrieving specific keys out of the object, which can make some code much cleaner.\n\nHelper methods are memoized automatically for you.\n\nInterface:\n\n```ts\ntype ResourceStaticPending\u003cT\u003e = { pending: true, error?: never, value?: never, latest?: T };\ntype ResourceStaticRejected = { pending: false, error: Error, value?: never, latest?: never };\ntype ResourceStaticResolved\u003cT\u003e = { pending: false, error?: never, value: T, latest: T };\ntype ResourceStatic\u003cT\u003e = ResourceStaticPending\u003cT\u003e | ResourceStaticRejected | ResourceStaticResolved\u003cT\u003e;\ntype ResourceFunction\u003cT\u003e = { pending (): boolean, error (): Error | undefined, value (): T | undefined, latest (): T | undefined };\ntype Resource\u003cT\u003e = ObservableReadonly\u003cResourceStatic\u003cT\u003e\u003e \u0026 ResourceFunction\u003cT\u003e;\n```\n\nUsage:\n\n```tsx\nimport type {ObservableReadonly, Resource} from 'voby';\n\nconst resource: Resource\u003cResponse\u003e = useResource ( () =\u003e fetch ( 'https://my.api' ) );\n\n// Reading the static object\n\nresource ().pending; // =\u003e true | false\nresource ().error; // =\u003e Error | undefined\nresource ().value; // =\u003e Whatever the resource will resolve to\nresource ().latest; // =\u003e Whatever the resource will resolve to, or the previous known resolved value if the resource is pending\n\n// Using helper methods\n\nresource.pending (); // =\u003e true | false\nresource.error (); // =\u003e Error | undefined\nresource.value (); // =\u003e Whatever the resource will resolve to\nresource.latest (); // =\u003e Whatever the resource will resolve to, or the previous known resolved value if the resource is pending\n```\n\n#### `StoreOptions`\n\nThis type describes the options object that the `store` function accepts.\n\nInterface:\n\n```ts\ntype StoreOptions = {\n  unwrap?: boolean\n};\n```\n\nUsage:\n\n```ts\nimport type {StoreOptions} from 'voby';\nimport {store} from 'voby';\n\nconst createStore = \u003cT\u003e ( value: T, options?: StoreOptions ): T =\u003e {\n  return store ( value, options );\n};\n```\n\n### Extras\n\nExtra features and details.\n\n#### `Contributing`\n\nIf you'd like to contribute to this repo you should take the following steps to install Voby locally:\n\n```sh\ngit clone https://github.com/vobyjs/voby.git\ncd voby\nnpm install\nnpm run compile\n```\n\nThen you can run any of the demos locally like this:\n\n```sh\n# Playground\nnpm run dev\n# Counter\nnpm run dev:counter\n# Benchmark\nnpm run dev:benchmark\n```\n\n#### `Globals`\n\nThe following globals are supported.\n\n- `VOBY`: if `true`, then Voby is used in the current client page. This is also used internally to detect if Voby has been loaded multiple times within the same page, which is not supported.\n\n#### `JSX`\n\nJSX is supported out of the box, as a rule of thumb it's very similar to how React's JSX works, but with some differences.\n\n- The value provided to an attribute can always be either just the plain value itself, an observable to that value, or a function to that value. If an observable or a function is provided then that attribute will update itself in a fine-grained manner.\n- There's no \"key\" attribute because it's unnecessary.\n- Only refs in the function form are supported, so you are incentivized to simply use observables for them too.\n- The \"ref\" attribute can also accept an array of functions to call, for convenience.\n- Refs are called on the next microtask, making it so the node you'll get will probably be attached to the DOM already. For getting a more immediate reference you can use an \"immediate\" [directive](#createdirective).\n- You can simply just use \"class\" instead of \"className\".\n- The \"class\" attribute can also accept an object of classes or an array of classes, for convenience.\n- SVGs are supported out of the box and will also be updated in a fine-grained manner.\n- The \"innerHTML\", \"outerHTML\", \"textContent\" and \"className\" props are forbidden on native elements, as they are largely just footguns or non-idiomatic.\n- A React-like \"dangerouslySetInnerHTML\" attribute is supported for setting some raw HTML.\n- Numbers set as values for style properties that require a unit to be provided will automatically be suffixed with \"px\".\n- Using CSS variables in the \"style\" object is supported out of the box.\n- The following events are delegated, automatically: `beforeinput`, `click`, `dblclick`, `focusin`, `focusout`, `input`, `keydown`, `keyup`, `mousedown`, `mouseup`.\n- Events always bubble according to the natural DOM hierarchy, there's no special bubbling logic for `Portal`.\n- Class components, but with no lifecycle callbacks, are supported too. They got thrown away with the bath water by other frameworks, but organizing internal methods in a class and assigning that class to refs automatically is actually a really nice feature.\n\n#### `Tree Shaking`\n\nVoby is released as a tree-shakeable ESM module. The functions you don't use simply won't be included in the final bundle.\n\n#### `TypeScript`\n\nThere are two main actions needed to make Voby work with TypeScript.\n\n1. Voby is an ESM-only framework, so you _might_ need to mark your package as ESM too in order to use it, you can do that by putting the following in your `package.json`:\n   ```\n   \"type\": \"module\"\n   ```\n2. You should instruct TypeScript to load the correct JSX types by putting the following in your `tsconfig.json`:\n   ```json\n    {\n      \"compilerOptions\": {\n        \"jsx\": \"react-jsx\",\n        \"jsxImportSource\": \"voby\"\n      }\n    }\n   ```\n3. Optionally, if you don't want to use a bundler or if you are using a bundler for which a plugin hasn't been written yet you can just define a \"React\" variable in scope and just use the JSX transform for React:\n   ```ts\n   import * as React from 'voby';\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","funding_links":["https://opencollective.com/voby"],"categories":["Frameworks","TypeScript","Core"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvobyjs%2Fvoby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvobyjs%2Fvoby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvobyjs%2Fvoby/lists"}