{"id":27414533,"url":"https://github.com/martyroque/apps-digest","last_synced_at":"2025-04-14T08:29:45.762Z","repository":{"id":58608769,"uuid":"494930313","full_name":"martyroque/apps-digest","owner":"martyroque","description":"Simple, atomic state management.","archived":false,"fork":false,"pushed_at":"2023-12-01T02:43:00.000Z","size":1088,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-29T20:45:21.745Z","etag":null,"topics":["atomic-state","hooks","react","state","state-management","store"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/martyroque.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-05-22T01:15:33.000Z","updated_at":"2023-12-01T02:43:42.000Z","dependencies_parsed_at":"2023-01-25T17:15:11.503Z","dependency_job_id":null,"html_url":"https://github.com/martyroque/apps-digest","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martyroque%2Fapps-digest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martyroque%2Fapps-digest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martyroque%2Fapps-digest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martyroque%2Fapps-digest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/martyroque","download_url":"https://codeload.github.com/martyroque/apps-digest/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248845510,"owners_count":21170776,"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":["atomic-state","hooks","react","state","state-management","store"],"created_at":"2025-04-14T08:29:44.819Z","updated_at":"2025-04-14T08:29:45.725Z","avatar_url":"https://github.com/martyroque.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# App's Digest (DEPRECATED)\n\n_Simple, atomic state management._\n\n**DEPRECATED**: This is no longer supported, please consider using [Nucleux](https://www.npmjs.com/package/nucleux) instead\n\n---\n\n## Introduction\n\nApp's Digest is a simple, atomic state management library based on the publisher-subscriber pattern and inversion-of-control (IoC) container design principle.\n\nApp's Digest allows you to have centralized locations (stores) with units of state (atoms) that your application can subscribe to. Unlike other state management libraries, App's Digest only triggers strictly-needed, isolated updates for computations (e.g. React components) subscribed to atoms.\n\nWith App's Digest you can manage your application state outside of any UI framework, making your code decoupled, portable, and testable.\n\n\u003e The library name was inspired by the general-interest subscription-based magazine, Reader's Digest.\n\n## Why App's Digest over other state management libraries?\n\n- Simple and un-opinionated\n- Makes hooks the primary means of consuming state\n- Less boilerplate and no provider wrapping\n- Centralized, atomic and subscription-based state management\n\n## Table of contents\n\n- [Prerequisites](#prerequisites)\n- [Installation](#installation)\n- [A Quick Example](#a-quick-example)\n- [Description](#description)\n- [Detailed Usage](#detailed-usage)\n- [Dependency](#dependency-injection)\n- [Persistency](#persistency)\n- [Computed Values](#computed-values)\n- [React Native](#react-native)\n- [Author](#author)\n- [License](#license)\n\n## Prerequisites\n\n- Node \u003e= 14\n- React \u003e= 16.9.0 (Optional)\n\n## Installation\n\n```sh\nnpm install apps-digest\n```\n\n## A quick example\n\n```javascript\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport { AppsDigestValue, useStore, useValue } from 'apps-digest';\n\nclass CounterStore {\n  count = new AppsDigestValue(0);\n\n  increment() {\n    const currentCount = this.count.value;\n    this.count.value = currentCount + 1;\n  }\n}\n\nconst CounterView = () =\u003e {\n  const counterStore = useStore(CounterStore);\n  const count = useValue(counterStore.count);\n\n  return (\n    \u003cbutton onClick={() =\u003e counterStore.increment()}\u003e\n      Current Count: {count}\n    \u003c/button\u003e\n  );\n};\n\nReactDOM.render(\u003cCounterView /\u003e, document.body);\n```\n\n## Description\n\nApp's Digest leverages on two software architecture patterns:\n\n- IoC Container pattern (a.k.a. DI Container) to manage store instantiation, dependency injection and lifecycle.\n- The publisher-subscriber pattern to implement values within the stores that any JavaScript context (including React components) can subscribe and publish to.\n\n![App's Digest Flow](https://emrock-app.s3.us-east-2.amazonaws.com/uploads/apps-digest-flow.png)\n\n### What's a Store?\n\nA store is essentially a bucket of values that other JavaScript objects can subscribe and publish to. The stores live as long as they have at least one reference in the container. Once the last reference of a store is removed, the store is disposed.\n\n## Detailed Usage\n\nLet's take a closer look on how to use the library.\n\n### Create a store\n\nFirst, let's create our store. A store is a class that implements the following:\n\n- Store value(s) by instantiating `AppsDigestValue` with an initial value (required).\n- Value setters that publish (updates) the store values (optional).\n\nNote: It is a good pattern to keep your stores separate from your UI.\n\n```javascript\nimport { AppsDigestValue } from 'apps-digest';\n\nclass CounterStore {\n  count = new AppsDigestValue(0);\n\n  increment() {\n    const currentCount = this.count.value;\n    this.count.value = currentCount + 1;\n  }\n}\n\nexport default CounterStore;\n```\n\n### Use the store anywhere\n\nNow that we have our store, we can use it anywhere within a JavaScript application by getting its instance via the container.\n\n```javascript\nimport { AppsDigestContainer } from 'apps-digest';\nimport CounterStore from './CounterStore';\n\n// get the container and store instances\nconst storeContainer = AppsDigestContainer.getInstance();\nconst counterStore = storeContainer.get(CounterStore);\n\n// subscribe to the value\nconst subscriberId = counterStore.count.subscribe((count) =\u003e {\n  console.log(`Current Count: ${count}`);\n});\n\n// publish to the value\ncounterStore.increment();\ncounterStore.increment();\ncounterStore.increment();\n\n// unsubscribe from the value\ncounterStore.count.unsubscribe(subscriberId);\n\n// dispose the store\nstoreContainer.remove(CounterStore);\n```\n\n### Use the store in a React Component\n\nAll right, let's use our store in a UI using React (we'll support frameworks in the future).\n\nFirst we need to get our store instance by using the `useStore`. Then we can use the hook `useValue` to subscribe to the store value and trigger the side effects (render).\n\nBy using these hooks, we get automatic value un-subscription and store disposal for free when the component is unmounted.\n\n```javascript\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport { useStore, useValue } from 'apps-digest';\nimport CounterStore from './CounterStore';\n\nconst CounterView = () =\u003e {\n  const counterStore = useStore(CounterStore);\n  const count = useValue(counterStore.count);\n\n  return (\n    \u003cbutton onClick={() =\u003e counterStore.increment()}\u003e\n      Current Count: {count}\n    \u003c/button\u003e\n  );\n};\n\nReactDOM.render(\u003cCounterView /\u003e, document.body);\n```\n\n### See this live\n\nPlease visit our [App's Digest Codesandbox](https://codesandbox.io/s/apps-digest-tutorial-0cwlqq?file=/src/App.js) to see a live example of the React usage.\n\n## Dependency (injection)\n\nIt's important for all applications to follow software design principles, specifically separation of concerns and segregation.\n\nWith App's Digest, we can have segregated stores that contain a small meaningful portion of the application's state, and then leverage the container to inject stores into main stores.\n\nLet's say we have a store that needs to read the count value from our `CounterStore`. We can easily inject the store like this:\n\n```javascript\nimport { AppsDigestValue, AppsDigestStore } from 'apps-digest';\nimport CounterStore from './CounterStore';\n\nclass ApplicationStore extends AppsDigestStore {\n  counterStore = this.inject(CounterStore);\n  isMax = new AppsDigestValue(false);\n\n  constructor() {\n    super();\n\n    this.subscribeToStoreValue(this.counterStore.count, (count) =\u003e {\n      if (!this.isMax.value \u0026\u0026 count \u003e= 10) {\n        this.isMax.value = true;\n      }\n    });\n  }\n}\n\nexport default ApplicationStore;\n```\n\nBy extending from `AppsDigestStore`, we get the automatic un-subscription for free when the store is disposed.\n\n## Persistency\n\nIn order to persist a store value, we need to specify the persist key we would like to use (has to be unique) in the second argument of `AppsDigestValue`.\n\nEvery time the value is published, the value will be persisted. And, the next time the store is instantiated, the value will be rehydrated.\n\n```javascript\n// assuming CountValue was persisted as 2, count will be hydrated with 2 instead of 0\ncount = new AppsDigestValue(0, 'CountValue');\n\n// this will persist the new value\nthis.count.value = currentCount + 1;\n```\n\n### Persistency - Custom Storage\n\nYou can configure App's Digest values to use custom storage for persistency. For instance, in React Native, you can use `AsyncStorage`:\n\n```javascript\nimport AsyncStorage from '@react-native-async-storage/async-storage';\n\ncount = new AppsDigestValue(0, 'CountValue', {\n  storage: AsyncStorage,\n});\n```\n\n## Computed Values\n\nSometimes we have complex stores with several values that we then need to use to derive a value from. App's Digest offers computed values feature, which allows us to consume store values, compute them in a callback and produce a single result. To do so, we need our store to extend from `AppsDigestStore`.\n\nLet's say we have a store that manages the user session, and we have a `isAuth` value to determine if the user is authenticated. Now, let's say our user store depends on the API store, which has a value `isConnected` to allow API requests. Given a requirement that we should only allow requests from authenticated users when the API is connected, we can create a computed property called `shouldMakeRequest`, like so:\n\n### ApiStore\n\n```javascript\nimport { AppsDigestValue } from 'apps-digest';\n\nclass ApiStore {\n  isConnected = new AppsDigestValue(false);\n}\n\nexport default ApiStore;\n```\n\n### UserStore\n\n```javascript\nimport { AppsDigestValue, AppsDigestStore } from 'apps-digest';\nimport ApiStore from './ApiStore';\n\nclass UserStore extends AppsDigestStore {\n  apiStore = this.inject(ApiStore);\n  isAuth = new AppsDigestValue(false);\n  shouldMakeRequest = this.computedValue(\n    [this.isAuth, this.apiStore.isConnected],\n    (isAuthValue, isConnectedValue) =\u003e {\n      return isAuthValue \u0026\u0026 isConnectedValue;\n    },\n  );\n}\n\nexport default UserStore;\n```\n\nWith this, `shouldMakeRequest` will track both `isAuth` and `isConnected` values and produce a single `boolean` value as a result. This computed value can be used as a regular store value anywhere in our app.\n\n```javascript\nimport React, { useEffect } from 'react';\nimport ReactDOM from 'react-dom';\nimport { useStore, useValue } from 'apps-digest';\nimport UserStore from './UserStore';\n\nconst App = () =\u003e {\n  const userStore = useStore(UserStore);\n  const shouldMakeRequest = useValue(userStore.shouldMakeRequest);\n\n  useEffect(() =\u003e {\n    if (shouldMakeRequest) {\n      // make a fetch request\n    }\n  }, [shouldMakeRequest]);\n\n  // ...\n};\n\nReactDOM.render(\u003cApp /\u003e, document.body);\n```\n\n## React Native\n\nApp's Digest uses `nanoid` for a secure unique string ID generation to create value subscriptions and store identifiers. React Native does not have built-in random generator. The following polyfill works for plain React Native and Expo starting with 39.x.\n\n```javascript\n// App.jsx\nimport 'react-native-get-random-values';\nimport { View } from 'react-native';\nimport { useStore, useValue } from 'apps-digest';\n\nimport YourStore from './YourStore';\n\nexport default function App() {\n  const store = useStore(YourStore);\n  const value = useValue(store.value);\n\n  return \u003cView\u003e{/* ... */}\u003c/View\u003e;\n}\n```\n\n## Author\n\n- **Marty Roque**\n  - GitHub: [@martyroque](https://github.com/martyroque)\n  - Twitter: [@lmproque](https://twitter.com/lmproque)\n\n## License\n\n[ISC License](LICENSE)\n\nCopyright © 2023 [Marty Roque](https://github.com/martyroque).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartyroque%2Fapps-digest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmartyroque%2Fapps-digest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartyroque%2Fapps-digest/lists"}