{"id":13808790,"url":"https://github.com/zuriscript/signalstory","last_synced_at":"2025-05-14T03:31:41.073Z","repository":{"id":182163762,"uuid":"668052852","full_name":"zuriscript/signalstory","owner":"zuriscript","description":"Signal-based state management for Angular applications ","archived":false,"fork":false,"pushed_at":"2024-06-16T21:40:17.000Z","size":17830,"stargazers_count":41,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"master","last_synced_at":"2024-11-08T11:13:59.466Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://zuriscript.github.io/signalstory/","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/zuriscript.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-18T23:13:56.000Z","updated_at":"2024-10-17T05:46:52.000Z","dependencies_parsed_at":"2023-11-21T00:26:10.063Z","dependency_job_id":"d659062f-2877-401e-a360-8c8d0a380a85","html_url":"https://github.com/zuriscript/signalstory","commit_stats":null,"previous_names":["zuriscript/signalstory"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zuriscript%2Fsignalstory","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zuriscript%2Fsignalstory/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zuriscript%2Fsignalstory/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zuriscript%2Fsignalstory/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zuriscript","download_url":"https://codeload.github.com/zuriscript/signalstory/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225273276,"owners_count":17448076,"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":[],"created_at":"2024-08-04T01:01:52.012Z","updated_at":"2024-11-19T00:31:05.669Z","avatar_url":"https://github.com/zuriscript.png","language":"TypeScript","funding_links":[],"categories":["State Management"],"sub_categories":["Other State Libraries"],"readme":"\u003ch1 align=\"center\"\u003e\n  \u003ca\n    target=\"_blank\"\n    href=\"https://zuriscript.github.io/signalstory/\"\n  \u003e\n    \u003cimg\n      align=\"center\"\n      alt=\"signalstory\"\n      src=\"signalstory_banner.png\"\n      style=\"width:100%;\"\n    /\u003e\n  \u003c/a\u003e\n\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\n  \u003cb\u003eSignalstory - Angular state management with signals\u003c/b\u003e\u003cbr\u003e\n  Baked into classical angular services!\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca\n    href=\"https://zuriscript.github.io/signalstory/docs/prolog\"\n    target=\"_blank\"\n  \u003e\u003cb\u003eDocumentation\u003c/b\u003e\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;📚\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca\n    href=\"https://stackblitz.com/edit/stackblitz-starters-bjnmnr?file=src%2Fapp%2Fstate%2Fbooks.store.ts\"\n    target=\"_blank\"\n  \u003e\u003cb\u003eSample\u003c/b\u003e\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;🚀\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca\n    href=\"https://zuriscript.github.io/signalstory/\"\n    target=\"_blank\"\n  \u003e\u003cb\u003eWebsite\u003c/b\u003e\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;🔥\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca\n    href=\"https://github.com/zuriscript/signalstory/releases\"\n    target=\"_blank\"\n  \u003e\u003cb\u003eRelease notes\u003c/b\u003e\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;✨\u0026nbsp;\u0026nbsp;\u0026nbsp;\n\u003c/p\u003e\n\n\u003cdiv  align=\"center\"\u003e\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![npm version](https://badge.fury.io/js/signalstory.svg)](https://badge.fury.io/js/signalstory)\n[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)\n[![PRs](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)]()\n[![coc-badge](https://img.shields.io/badge/codeof-conduct-ff69b4.svg?style=flat-square)]()\n[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)\n\n\u003c/div\u003e\n\nsignalstory is a state management library based on angular signals. It offers a range of architectural options, from simple repository-based state management (`signal-in-a-service`) to orchestrating decoupled commands, handling side effects through encapsulated objects, and facilitating inter-store communication using an event-driven approach. The ultimate goal is to provide a great user experience for all developers, whether junior or senior, while incorporating all the features you need to master your frontend state requirements.\n\n\u003e [!TIP]  \n\u003e Starting out? You can keep it nice and simple if you prefer to avoid exploring all the advanced features that a state management library can offer! Begin by checking out the [store](https://zuriscript.github.io/signalstory/docs/store), and only dive into the rest if you're curious later on.\n\nHere's a snapshot of some notable highlights:\n\n✅ \u0026nbsp;Signal-in-a-service approach  \n✅ \u0026nbsp;Simple, non-intrusive and lightweight  \n✅ \u0026nbsp;Optimized for Scalability  \n✅ \u0026nbsp;Imperative-first with Declaritive capabilities  \n✅ \u0026nbsp;Immutability on demand  \n✅ \u0026nbsp;Rich plugin ecosystem  \n✅ \u0026nbsp;Native IndexedDB support  \n✅ \u0026nbsp;Transactional Undo/Redo  \n✅ \u0026nbsp;Global State Snaphots and Rollbacks  \n✅ \u0026nbsp;Devtools support  \n✅ \u0026nbsp;Effect and Store status tracking  \n✅ \u0026nbsp;Realtime store performance statistics  \n✅ \u0026nbsp;Custom plugin support  \n✅ \u0026nbsp;Built-in testing utilities  \n✅ \u0026nbsp;SSR friendly  \n✅ \u0026nbsp;Tree-shakeable\n\n## Let the store grow with your project\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/static/img/code_evolve_landscape_dark.png\"\u003e\n  \u003cimg src=\"docs/static/img/code_evolve_landscape_light.png\"\u003e\n\u003c/picture\u003e\n\n## Guiding Principles\n\n- 🚀 Use class methods to provide controlled access and mutations to shared state.\n- 🌌 If your store becomes too complex and bloated, slice it into multiple stores.\n- ✨ Join and aggregate your state at the component level using signal mechanics.\n- 🌐 Need to sync states between stores synchronously? - Use events.\n- 🔮 Need to decouple actors and consumers as you do in `redux`? - Use events.\n- 🔄 Craving `Immutability`? - Just activate it.\n- 🏎️ Don't want full immutability because your store has to be super fast? - Don't activate it.\n- 🧙‍♂️ Seeking a way to encapsulate side effects in a reusable, maintainable, and testable way? - Use effect objects.\n- 🔍 Want a way to reuse and test queries spanning over multiple stores? - Use query objects.\n- 📦 Don't want to use a class for stores? - You don't have to.\n- 🛠️ Tired of debugging state changes in the console? - Enable redux devtools.\n- 🪄 Still want some good old logging magic? - Enable Store logger plugin\n- ⏳ Need to keep track of store history and perform undo/redo operations? - track the history.\n- 💾 Want to sync your state with local storage? - Enable the persistence plugin.\n- 🗄️ Need a more sophisticated store storage or building an offline app? - Use IndexedDB adapter\n- 📈 Need to get notified of whether your store is modified or currently loading? - Enable the Store Status plugin.\n- 📊 Wondering where your bottlenecks are? - Enable the performance counter plugin\n- 🎨 Something's missing? - Write a custom plugin.\n- 📖 Read the [docs](https://zuriscript.github.io/signalstory/) for more features and concepts.\n\n## Installation\n\nInstall the library using npm:\n\n```shell\nnpm install signalstory\n```\n\n## Sneak peek\n\n```typescript\nimport { produce } from 'immer';\n\n// Immutable store class using immer.js for boosting immutable mutations\n@Injectable({ providedIn: 'root' })\nclass BookStore extends ImmutableStore\u003cBook[]\u003e {\n  constructor() {\n    super({\n        initialState: { ... },\n        name: 'Books Store',\n        mutationProducerFn: produce,\n        plugins: [\n          useDevtools(),\n          usePerformanceCounter(),\n          useLogger(),\n          useStoreStatus(),\n          useStorePersistence(\n            configureIndexedDb({\n              dbName: 'SharedDatabase',\n          })),\n        ],\n    });\n    \n    // Handle store reset request events. Note, the storeResetRequestEvent would \n    // be created or imported, see the events documentation for more details\n    this.registerHandler(storeResetRequestEvent, store =\u003e {\n      store.set([], 'Reset');\n    });\n  }\n\n  // Query\n  public get getBooksInCollection() {\n    return computed(() =\u003e this.state().filter(x =\u003e x.isInCollection));\n  }\n\n  // Command\n  public addToCollection(bookId: string) {\n    this.mutate(state =\u003e {\n      const book = state.find(x =\u003e x.id === bookId);\n      if (book) {\n        book.isInCollection = true;\n      }\n    }, 'Add Book To Collection');\n  }\n}\n```\n\n```typescript\n// Encapsulated multi store query object\nexport const BooksAndPublishersByAuthorInSwitzerlandQuery = createQuery(\n  [BookStore, PublisherStore],\n  (books, publishers, authorId: string) =\u003e {\n    const booksFromAuthor = books.state().filter(x =\u003e x.author === authorId);\n    const publishersInSwitzerland = publishers\n      .state()\n      .filter(x =\u003e x.country === 'CH');\n\n    return booksFromAuthor.map(book =\u003e ({\n      book,\n      publisher: publishersInSwitzerland.find(\n        p =\u003e p.id === book.mainPublisherId\n      ),\n    }));\n  }\n);\n// And then run it\nconst query = myBookStore.runQuery(\n  BooksAndPublishersByAuthorInSwitzerlandQuery,\n  'sapowski'\n);\n```\n\n```typescript\n// Encapsulated effect object\nexport const fetchBooksEffect = createEffect(\n  'Fetch Books',\n  (store: BookStore) =\u003e {\n    const service = inject(BooksService);\n    const notification = inject(NotificationService);\n\n    return service.fetchBooks().pipe(\n      catchError(err =\u003e {\n        notification.alertError(err);\n        return of([]);\n      }),\n      tap(result =\u003e store.setBooks(result))\n    );\n  },\n  {\n    setLoadingStatus: true, // indicates that the store is loading while the effect runs\n    setInitializedStatus: true, // it should mark the store as initialized upon completion\n  }\n);\n// And then run it\nmyBookStore.runEffect(fetchBooksEffect).subscribe();\nconst loadingSignal = isLoading(myBookStore); // true while effect is running\nconst initializedSignal = initialized(myBookStore); // true after initializing effect completion\nconst modifiedSignal = modified(myBookStore); // true after store update\n```\n\n```typescript\n// Track history spanning multiple stores\nconst tracker = trackHistory(50, store1, store2);\n\n// Undo single commands\nstore1.set({ value: 10 }, 'ChangeCommand');\ntracker.undo();\n\ntracker.beginTransaction('Transaction Label');\nstore1.set({ value: 42 }, 'ChangeCommand');\nstore2.set({ value: 23 }, 'AnotherCommand');\ntracker.endTransaction();\n\n// Undo both commands on store1 and store2 at once\ntracker.undo();\n\n// Redo the whole transaction\ntracker.redo();\n```\n\n## Sample Application\n\nTo set up and run the sample app locally, follow the steps below:\n\n1. **Clone the repository:** Clone the repository containing the signalstory library and the sample app.\n\n2. **Install dependencies:** Navigate to the root directory of the repository and run the following command to install the necessary dependencies:\n\n   ```bash\n   npm install\n   ```\n\n3. **Build the library:** Run the following command to build the signalstory library:\n\n   ```bash\n   ng build signalstory\n   ```\n\n4. **Serve the sample app:** Run the following command to serve the sample app locally:\n\n   ```bash\n   ng serve sample --open\n   ```\n\n---\n\n   \u003cp align=\"center\"\u003e  \n     \u003cimg\n         align=\"center\"\n         alt=\"signalstory\"\n         src=\"signalstory.png\"\n         style=\"width:60px;\"\n       /\u003e\n   \u003c/p\u003e\n\n   \u003cp align=\"center\"\u003e\n   made with ❤️ by zuriscript\n   \u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzuriscript%2Fsignalstory","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzuriscript%2Fsignalstory","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzuriscript%2Fsignalstory/lists"}