{"id":13495226,"url":"https://github.com/fabiospampinato/store","last_synced_at":"2025-03-28T16:31:55.658Z","repository":{"id":39715013,"uuid":"240561586","full_name":"fabiospampinato/store","owner":"fabiospampinato","description":"A beautifully-simple framework-agnostic modern state management library.","archived":true,"fork":false,"pushed_at":"2022-06-24T21:51:17.000Z","size":588,"stargazers_count":229,"open_issues_count":1,"forks_count":7,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-10-01T19:48:44.612Z","etag":null,"topics":["clean","fast","library","management","node","proxy","react","reactive","state","store"],"latest_commit_sha":null,"homepage":null,"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/fabiospampinato.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}},"created_at":"2020-02-14T17:18:36.000Z","updated_at":"2024-08-22T02:57:55.000Z","dependencies_parsed_at":"2022-08-09T15:24:59.353Z","dependency_job_id":null,"html_url":"https://github.com/fabiospampinato/store","commit_stats":null,"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fabiospampinato%2Fstore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fabiospampinato%2Fstore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fabiospampinato%2Fstore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fabiospampinato%2Fstore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fabiospampinato","download_url":"https://codeload.github.com/fabiospampinato/store/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222395769,"owners_count":16977621,"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":["clean","fast","library","management","node","proxy","react","reactive","state","store"],"created_at":"2024-07-31T19:01:32.700Z","updated_at":"2024-10-31T10:30:46.888Z","avatar_url":"https://github.com/fabiospampinato.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# Store\n\nA beautifully-simple framework-agnostic modern state management library.\n\n![Debug](resources/demo.png)\n\n## Features\n\n- **Simple**: there's barely anything to learn and no boilerplate code required. Thanks to our usage of [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)s you just have to wrap your state with [`store`](#store), mutate it and retrieve values from it just like if it was a regular object, and listen to changes via [`onChange`](#onchange) or [`useStore`](#usestore).\n- **Framework-agnostic**: Store doesn't make any assumptions about your UI framework of choice, in fact it can also be used without one.\n- **React support**: an hook for React is provided, because that's the UI framework I'm using. Support for other UI frameworks can be added easily, PRs are very welcome.\n- **TypeScript-ready**: Store is written in TypeScript and enables you to get a fully typed app with no extra effort.\n\nRead more about how Store compares against other libraries in the [FAQ](#faq) section below.\n\n## Install\n\n```sh\nnpm install --save store@npm:@fabiospampinato/store\n```\n\n## Usage\n\n- Core\n  - [`store`](#store)\n  - [`isStore`](#isstore)\n  - [`isIdle`](#isidle)\n  - [`onChange`](#onchange)\n  - [`batch`](#batch)\n  - [`target`](#target)\n  - [`debug`](#debug)\n  - [`Hooks`](#hooks)\n- Extra/React\n  - [`useStore`](#usestore)\n  - [`useStores`](#usestores)\n\n### Core\n\n#### `store`\n\nThe first step is wrapping the objects containing the state of your app with the `store` function, this way Store will be able to transparently detect when mutations occur.\n\nExample usage:\n\n```ts\nimport {store} from 'store';\n\nconst CounterApp = {\n  store: store ({ value: 0 }),\n  increment: () =\u003e CounterApp.store.value += 1,\n  decrement: () =\u003e CounterApp.store.value -= 1\n};\n```\n\n- ℹ️ The object passed to `store` can contain a variety of values:\n  - These are fully supported: [primitives](https://developer.mozilla.org/en-US/docs/Glossary/Primitive), [functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions), [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get), [setters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set), [Dates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date), [RegExps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp), [Objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object), [Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array), [ArrayBuffers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer), [TypedArrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), [Maps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) and [Sets](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set).\n  - These are partially supported: [Promises](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise), [WeakMaps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap), [WeakSets](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) and custom classes. Basically mutations happening inside them won't be detected, however setting any of these as a value will be detected as a mutation.\n- ℹ️ `store` will wrap your object with a [`Proxy`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Proxy), which will detect mutations, and return a proxied object.\n- ℹ️ Never mutate the raw object passed to `store` directly, as those mutations won't be detected, always go through the proxied object returned by `store` instead. I'd suggest you to wrap your raw objects with `store` immediately so you won't even keep a reference to them.\n- ℹ️ In order to trigger a change simply mutate the proxied object returned by `store` as if it was a regular object.\n- ℹ️ Mutations happening at locations that need to be reached via a [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) aren't detected (e.g. `{ [Symbol ()]: { undetected: true }`).\n\n#### `isStore`\n\nThis function checks if the passed value is a recognized `Proxy` object or not.\n\nExample usage:\n\n```ts\nimport {store, isStore} from 'store';\n\nisStore ( store ( {} ) ); // =\u003e true\nisStore ( {} ); // =\u003e false\n```\n\n#### `isIdle`\n\nWhen no store is passed to it it checks if all known stores have no pending updates, i.e. some changes happened to them and at least one `onChange` listener has not been called yet.\n\nIf a store is passed it checks only if the passed store has no pending updates.\n\nThis is its interface:\n\n```ts\nfunction isIdle ( store?: Store ): boolean;\n```\n\nExample usage:\n\n```ts\nimport {store, isIdle} from 'store';\n\nconst proxy1 = store ( {} );\nconst proxy2 = store ( {} );\n\nisIdle (); // =\u003e true\nisIdle ( proxy1 ); // =\u003e true\nisIdle ( proxy2 ); // =\u003e true\n\nproxy1.foo = true;\n\nisIdle (); // =\u003e false\nisIdle ( proxy1 ); // =\u003e false\nisIdle ( proxy2 ); // =\u003e true\n```\n\n#### `onChange`\n\nNext you'll probably want to listen for changes to your stores, the `onChange` function is how you do that in a framework-agnostic way.\n\nThis is its interface:\n\n```ts\n// Single store, without selector, listen to all changes to the store\nfunction onChange ( store: Store, listener: ( data: Store ) =\u003e any ): Disposer;\n\n// Multiple stores, without selector, listen to all changes to any store\nfunction onChange ( stores: Store[], listener: ( ...data: Store[] ) =\u003e any ): Disposer;\n\n// Single store, with selector, listen to only changes that cause the value returned by the selector to change\nfunction onChange ( store: Store, selector: ( store: Store ) =\u003e Data, listener: ( data: Data ) =\u003e any ): Disposer;\n\n// Single store, with selector, with comparator, listen to only changes that cause the value returned by the selector to change and the comparator to return true\nfunction onChange ( store: Store, selector: ( store: Store ) =\u003e Data, comparator: ( dataPrev: Data, dataNext: Data ) =\u003e boolean, listener: ( data: Data ) =\u003e any ): Disposer;\n\n// Multiple stores, with selector, listen to only changes that cause the value returned by the selector to change\nfunction onChange ( stores: Store[], selector: ( ...stores: Store[] ) =\u003e Data, listener: ( data: Data ) =\u003e any ): Disposer;\n\n// Multiple stores, with selector, with comparator, listen to only changes that cause the value returned by the selector to change and the comparator to return true\nfunction onChange ( stores: Store[], selector: ( ...stores: Store[] ) =\u003e Data, comparator: ( dataPrev: Data, dataNext: Data ) =\u003e boolean, listener: ( data: Data ) =\u003e any ): Disposer;\n```\n\n- The `store`/`stores` argument is either a single proxied object retuned by the [`store`](#store) function or an array of those.\n- The `listener` argument is the function that will be called when a change to the provided stores occurs. It will be called with the value returned by the `selector`, if a selector was provided, or with all the provided stores as its arguments otherwise.\n- The `selector` optional argument is a function that computes some value that will be passed to the listener as its first argument. It's called with all the provided stores as its arguments.\n- The `comparator` optional argument is a function that checks for equality between the previous value returned by the selector and the current one.\n- The return value is a disposer, a function that when called will terminate this specific listening operation.\n\nExample usage:\n\n```ts\nimport areShallowEqual from 'are-shallow-equal';\nimport {store, onChange} from 'store';\n\nconst CounterApp = {\n  store: store ({ value: 0 }),\n  increment: () =\u003e CounterApp.store.value += 1,\n  decrement: () =\u003e CounterApp.store.value -= 1\n};\n\n// No selector\n\nconst disposer1 = onChange ( CounterApp.store, store =\u003e {\n  console.log ( 'Value changed, new value:', store.value );\n  disposer1 (); // Preventing this listener to be called again\n});\n\n// With selector\n\nconst disposer2 = onChange ( CounterApp.store, store =\u003e store.value % 2 === 0, isEven =\u003e {\n  console.log ( 'Is the new value even?', isEven );\n});\n\n// With selector, with comparator\n\nconst disposer = onChange ( CounterApp.store, store =\u003e ({ sqrt: Math.sqrt ( store.value ) }), areShallowEqual, ({ sqrt }) =\u003e {\n  console.log ( 'The new square root is:', sqrt );\n});\n\nCounterApp.increment (); // This will cause a mutation, causing the listeners to be called\nCounterApp.increment (); // This will cause another mutation, but the listeners will still be called once as these mutations are occurring in a single event loop tick\n\nsetTimeout ( CounterApp.increment, 100 ); // This will cause the remaining listener to be called again\n```\n\n- ℹ️ Using a selector that retrieves only parts of the store will improve performance.\n- ℹ️ It's possible that the listener will be called even if the object returned by the selector, or the entire store, didn't actually change.\n- ℹ️ Calls to `listener`s are automatically coalesced and batched together for performance, so if you synchronously, i.e. within a single event loop tick, mutate a store multiple times and there's a listener listening for those changes that listener will only be called once.\n- ℹ️ Using a comparator can improve performance if your selector only selects a small part of a large object.\n  - ℹ️ The comparator is not called if the library is certain that the value returned by the selector did or didn't change.\n    - ℹ️ As a consequence of this the comparator is never called with primitive values.\n  - ℹ️ When using a comparator the selector should return a new object, not one that might get mutated, or the comparator will effectively get called with the same objects.\n\n#### `batch`\n\nSynchronous mutations, i.e. mutations that happen within a single event loop tick, are batched and coalesced together automatically, if you sometimes also want to batch and coalesce mutations happening inside an asynchronous function or two arbitrary points in time you can use the `batch` function.\n\nThis is its interface:\n\n```ts\nfunction batch\u003cP extends Promise\u003cany\u003e\u003e ( fn: () =\u003e P ): P;\n\n// Helper methods\nbatch.start = function (): void;\nbatch.stop = function (): void;\n```\n\nExample usage:\n\n```ts\nimport {batch, store} from 'store';\n\nconst myStore = store ( { foo: 123 } );\n\n// Function-based batching\n\nbatch ( async () =\u003e {\n  myStore.foo = 0;\n  await someAsyncFunction ();\n  myStore.foo = 1;\n});\n\n// Manual batching\n\nbatch.start ();\nfor ( const nr of [1, 2, 3, 4, 5] ) {\n  myStore.foo = nr;\n  await someAsyncFunction ();\n}\nbatch.stop ();\n```\n\n- ℹ️ This function is critical for performance when performing a very large number of mutations in an asynchronous way.\n- ℹ️ When batching and coalescing asynchronous mutations by passing a function to `batch` everything is taken care of for you: if the passed function throws batching is stopped automatically, nested `batch` calls are not a problem either.\n- ℹ️ When batching and coalescing asynchronous mutations manually using `batch.start` and `batch.stop` you have to make sure that `batch.stop` is always called the same number of times that `batch.start` was called, or batching will never stop. So make sure that for instance thrown errors or early exits are not an issue.\n\n#### `target`\n\nThis function unwraps a store and returns the raw plain object used under the hood.\n\nThis is its interface:\n\n```ts\nfunction target\u003cT\u003e ( store: T ): T;\n```\n\nExample usage:\n\n```ts\nimport {store, target, isStore} from 'store';\n\nconst myStore = store ( { foo: 123 } );\nconst rawObject = target ( myStore );\n\nisStore ( myStore ); // =\u003e true\nisStore ( rawObject ); // =\u003e false\n```\n\n#### `debug`\n\n`debug` provides a simple way to access your stores and see at a glance how and when they change from the DevTools.\n\n![Debug](resources/debug.png)\n\nThis is its interface:\n\n```ts\ntype Global = {\n  stores: Store[], // Access all stores\n  log: () =\u003e void // Log all stores\n};\n\ntype Options = {\n  collapsed: true, // Whether the logged groups should be collapsed\n  logStoresNew: false, // Whether to log new store that have been created\n  logChangesDiff: true, // Whether to log diffs (added, updated, removed) state changes\n  logChangesFull: false // Whether to log the previous and current state in their entirity\n};\n\nfunction debug ( options?: Options ): Global;\n```\n\nExample usage:\n\n```ts\nimport {debug} from 'store';\n\ndebug ();\n```\n\nOnce called, `debug` defines a global object named `STORE`, which you can then access from the DevTools, and returns it.\n\nExample usage:\n\n```ts\nSTORE.stores[0].value += 1; // Manually triggering a mutation\nSTORE.log (); // Logging all stores to the console\n```\n\n- ℹ️ It's important to call `debug` before creating any stores.\n- ℹ️ It's important to call `debug` only during development, as it may perform some potentially slow computations.\n\n#### `Hooks`\n\n`Hooks` provides a simple way to \"hook\" into Store's internal events.\n\nEach hook has the following interface:\n\n```ts\nclass Hook {\n  subscribe ( listener: Function ): Disposer\n}\n```\n\n- `subscribe` registers a function for being called every time that hook is triggered.\n  - The returned value is a disposer, a function that when called will terminate this specific subscription.\n\nThese are all the currently available hooks:\n\n```ts\nconst Hooks = {\n  store: {\n    change: Hook, // Triggered whenever a store is mutated\n    changeBatch: Hook, // Triggered whenever a store is mutated (batched)\n    new: Hook // Triggered whenever a new store is created. This hook is used internally for implementing `debug`\n  }\n};\n```\n\nExample usage:\n\n```ts\nimport {Hooks} from 'store';\n\nconst disposer = Hooks.store.new.subscribe ( store =\u003e {\n  console.log ( 'New store:', store );\n});\n\ndisposer ();\n```\n\nIf you need some more hooks for your Store plugin let me know and I'll make sure to add them.\n\nWe currently don't have an official \"Store DevTools Extension\", but it would be super cool to have one. Perhaps it could provide a GUI for [`debug`](#debug)'s functionalities, and/or implement other features like time-travel debugging. If you're interested in developing this please do get in touch! 😃\n\n### Extra/React\n\nThese extra features, intended to be used with React, are available from a dedicated subpackage.\n\n#### `useStore`\n\n`useStore` is a React [hook](https://reactjs.org/docs/hooks-intro.html) for accessing a store's, or multiple stores's, values from within a functional component in a way that makes the component re-render whenever those values change.\n\nThis is its interface:\n\n```ts\n// Single store, without selector, re-render after any change to the store\nfunction useStore ( store: Store ): Store;\n\n// Multiple stores, without selector, re-render after any change to any store\nfunction useStore ( stores: Store[] ): Store[];\n\n// Single store, with selector, re-render only after changes that cause the value returned by the selector to change\nfunction useStore ( store: Store, selector: ( store: Store ) =\u003e Data, dependencies: ReadonlyArray\u003cany\u003e = [] ): Data;\n\n// Single store, with selector, with comparator, re-render only after changes that cause the value returned by the selector to change and the comparator to return true\nfunction useStore ( store: Store, selector: ( store: Store ) =\u003e Data, comparator: ( dataPrev: Data, dataNext: Data ) =\u003e boolean, dependencies: ReadonlyArray\u003cany\u003e = [] ): Data;\n\n// Multiple stores, with selector, re-render only after changes that cause the value returned by the selector to change\nfunction useStore ( stores: Store[], selector: ( ...args: Store[] ) =\u003e Data, dependencies: ReadonlyArray\u003cany\u003e = [] ): Data;\n\n// Multiple stores, with selector, with comparator, re-render only after changes that cause the value returned by the selector to change and the comparator to return true\nfunction useStore ( stores: Store[], selector: ( ...args: Store[] ) =\u003e Data, comparator: ( dataPrev: Data, dataNext: Data ) =\u003e boolean, dependencies: ReadonlyArray\u003cany\u003e = [] ): Data;\n```\n\n- The `store`/`stores` argument is either a single proxied object retuned by the [`store`](#store) function or an array of those.\n- The `selector` optional argument if a function that computes some value that will be the return value of the hook. It's called with all the passed stores as its arguments.\n- The `comparator` optional argument is a function that checks for equality between the previous value returned by the selector and the current one.\n- The `dependencies` optional argument is an array of dependencies used to inform React about any objects your selector function will reference from outside of its innermost scope, ensuring the selector gets called again if any of those change.\n- The return value is whatever `selector` returns, if a selector was provided, or the entire store if only one store was provided, or the entire array of stores otherwise.\n\nExample usage:\n\n```tsx\nimport areShallowEqual from 'are-shallow-equal';\nimport {store, onChange} from 'store';\nimport {useStore} from 'store/x/react';\n\nconst CounterApp = {\n  store: store ({ value: 0 }),\n  increment: () =\u003e CounterApp.store.value += 1,\n  decrement: () =\u003e CounterApp.store.value -= 1\n};\n\n// No selector\n\nconst CounterComponent1 = () =\u003e {\n  const {value} = useStore ( ConunterApp.store );\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003e{value}\u003c/div\u003e\n      \u003cbutton onClick={CounterApp.increment}\u003eIncrement\u003c/button\u003e\n      \u003cbutton onClick={CounterApp.decrement}\u003eDecrement\u003c/button\u003e\n    \u003c/div\u003e\n  )\n};\n\n// With selector\n\nconst CounterComponent2 = () =\u003e {\n  const isEven = useStore ( ConunterApp.store, store =\u003e store.value % 2 === 0 );\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003eIs the value even? {isEven}\u003c/div\u003e\n      \u003cbutton onClick={CounterApp.increment}\u003eIncrement\u003c/button\u003e\n      \u003cbutton onClick={CounterApp.decrement}\u003eDecrement\u003c/button\u003e\n    \u003c/div\u003e\n  )\n};\n\n// With selector, with comparator\n\nconst CounterComponent3 = () =\u003e {\n  const {sqrt} = useStore ( ConunterApp.store, store =\u003e ({ sqrt: Math.sqrt ( store.value ) }), areShallowEqual );\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003eThe square root is: {sqrt}\u003c/div\u003e\n      \u003cbutton onClick={CounterApp.increment}\u003eIncrement\u003c/button\u003e\n      \u003cbutton onClick={CounterApp.decrement}\u003eDecrement\u003c/button\u003e\n    \u003c/div\u003e\n  )\n};\n```\n\n- ℹ️ You basically just need to wrap the parts of your component that access any value from any store in a `useStore` hook, in order to make the component re-render whenever any of the retireved values change.\n- ℹ️ You don't need to use `useStore` for accessing methods that mutate the store, you can just reference them directly.\n- ℹ️ Using a selector that retrieves only parts of the store will improve performance.\n- ℹ️ It's possible that the component will be re-rendered even if the object returned by the selector, or the entire store, didn't actually change.\n- ℹ️ Re-renders are automatically coalesced and batched together for performance, so if synchronously, i.e. within a single event loop tick, the stores you're listening to are mutated multiple times the related components will only be re-rendered once.\n- ℹ️ Using a comparator can improve performance if your selector only selects a small part of a large object.\n  - ℹ️ The comparator is not called if the library is certain that the value returned by the selector did or didn't change.\n    - ℹ️ As a consequence of this the comparator is never called with primitive values.\n  - ℹ️ When using a comparator the selector should return a new object, not one that might get mutated, or the comparator will effectively get called with the same objects.\n\n#### `useStores`\n\n`useStores` is just an alias for `useStore`, this alias is provided in case passing multiple stores to an hook called `useStore` doesn't feel quite right to you.\n\nExample import:\n\n```tsx\nimport {useStores} from 'store/x/react';\n```\n\n## FAQ\n\n### Why not using [Redux](https://github.com/reduxjs/redux), [Unstated](https://github.com/jamiebuilds/unstated), [Overstated](https://github.com/fabiospampinato/overstated), [react-easy-state](https://github.com/RisingStack/react-easy-state) etc.?\n\nI'll personally use this library over more popular ones for a few reasons:\n\n- **Simpler APIs**: almost all other state management libraries I've encountered have APIs that don't resonate with me, often they feel unnecessarily bloated. I don't want to write \"actions\", I don't want to write \"reducers\", I don't want to litter my code with decorators or unnecessary boilerplate.\n- **Fewer footguns**: many other libraries I've encountered have multiple footguns to be aware of, some which may cause hard-to-debug bugs. With Store you won't update your stores incorrectly once you have wrapped them with [`store`](#store), you won't have to specially handle asynchronicity, and you won't have to carefully update your stores in an immutable fashion.\n- **Fewer restrictions**: most other libraries require you to structure your stores in a specific way, update them with library-specific APIs, perhaps require the usage of classes, and/or are tied to a specific UI framework. Store is more flexible in this regard: your stores are just proxied objects, you can manipulate them however you like, adopt a more functional coding style if you prefer, and the library isn't tied to any specific UI framework, in fact you can use it to manage your purely server-side state too.\n- **Easy type-safety**: some libraries don't play very well with TypeScript and/or require you to manually write some types, Store just works with no extra effort.\n\n### Why not using Store?\n\nYou might not want to use Store if: the design choices I made don't resonate with you, you need something more battle-tested, you need to support some of the ~5% of the outdated browsers where [`Proxy` isn't available](https://caniuse.com/#search=proxy), or you need the absolute maximum performance from your state management library since you know that will be your bottleneck, which is very unlikely.\n\n## License\n\nMIT © Fabio Spampinato\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffabiospampinato%2Fstore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffabiospampinato%2Fstore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffabiospampinato%2Fstore/lists"}