{"id":37224924,"url":"https://github.com/mnasyrov/jetstate","last_synced_at":"2026-01-15T01:44:32.582Z","repository":{"id":48236049,"uuid":"159900093","full_name":"mnasyrov/jetstate","owner":"mnasyrov","description":"JetState - Reactive state management with RxJS 🚀","archived":true,"fork":false,"pushed_at":"2021-08-07T16:20:30.000Z","size":2127,"stargazers_count":2,"open_issues_count":9,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-13T03:30:49.273Z","etag":null,"topics":["angular","react","reactive","rxjs","state-management"],"latest_commit_sha":null,"homepage":"","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/mnasyrov.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":"2018-12-01T02:13:22.000Z","updated_at":"2023-01-28T07:22:02.000Z","dependencies_parsed_at":"2022-08-24T00:10:59.860Z","dependency_job_id":null,"html_url":"https://github.com/mnasyrov/jetstate","commit_stats":null,"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"purl":"pkg:github/mnasyrov/jetstate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnasyrov%2Fjetstate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnasyrov%2Fjetstate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnasyrov%2Fjetstate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnasyrov%2Fjetstate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mnasyrov","download_url":"https://codeload.github.com/mnasyrov/jetstate/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnasyrov%2Fjetstate/sbom","scorecard":{"id":654798,"data":{"date":"2025-08-18","repo":{"name":"github.com/mnasyrov/jetstate","commit":"eadd9dae321c8b8108827600d3ec75306c7e341f"},"scorecard":{"version":"v5.2.1-41-g40576783","commit":"40576783fda6698350fcbbeaea760ff827433034"},"score":2.7,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/nodeci.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"project is archived","details":["Warn: Repository is archived."],"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#binary-artifacts"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#sast"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#cii-best-practices"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":3,"reason":"dependency not pinned by hash detected -- score normalized to 3","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodeci.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/mnasyrov/jetstate/nodeci.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodeci.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/mnasyrov/jetstate/nodeci.yml/master?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"82 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-rmvr-2pp2-xj38","Warn: Project is vulnerable to: GHSA-xx4v-prfh-6cgc","Warn: Project is vulnerable to: GHSA-6chw-6frg-f759","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-257v-vj4p-3w2h","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-ff7x-qrg7-qggm","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-8r6j-v8pm-fqw3","Warn: Project is vulnerable to: MAL-2023-462","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-2cf5-4w76-r9qv","Warn: Project is vulnerable to: GHSA-3cqr-58rm-57f8","Warn: Project is vulnerable to: GHSA-g9r4-xpmj-mj65","Warn: Project is vulnerable to: GHSA-q2c6-c6pm-g3gh","Warn: Project is vulnerable to: GHSA-765h-qjxv-5f44","Warn: Project is vulnerable to: GHSA-f2jv-r9rf-7988","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-rc47-6667-2j5j","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-78xj-cgh5-2h22","Warn: Project is vulnerable to: GHSA-2p57-rm9w-gvfp","Warn: Project is vulnerable to: GHSA-7r28-3m3f-r2pr","Warn: Project is vulnerable to: GHSA-r8j5-h5cx-65gg","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp","Warn: Project is vulnerable to: GHSA-76p3-8jx3-jpfq","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-w7rc-rwvf-8q5r","Warn: Project is vulnerable to: GHSA-r683-j2x4-v87g","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-rp65-9cf3-cjxr","Warn: Project is vulnerable to: GHSA-3j8f-xvm3-ffx4","Warn: Project is vulnerable to: GHSA-4p35-cfcx-8653","Warn: Project is vulnerable to: GHSA-7f3x-x4pr-wqhj","Warn: Project is vulnerable to: GHSA-jpp7-7chh-cf67","Warn: Project is vulnerable to: GHSA-q6wq-5p59-983w","Warn: Project is vulnerable to: GHSA-j9fq-vwqv-2fm2","Warn: Project is vulnerable to: GHSA-pqw5-jmp5-px4v","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-566m-qj78-rww5","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-hwj9-h5mp-3pm3","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-h9rv-jmmf-4pgx","Warn: Project is vulnerable to: GHSA-hxcc-f52p-wc94","Warn: Project is vulnerable to: GHSA-vx3p-948g-6vhq","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-29xr-v42j-r956","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-7p7h-4mm5-852v","Warn: Project is vulnerable to: GHSA-38fc-wpqx-33j7","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T14:22:39.143Z","repository_id":48236049,"created_at":"2025-08-21T14:22:39.143Z","updated_at":"2025-08-21T14:22:39.143Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28441031,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-15T00:55:22.719Z","status":"ssl_error","status_checked_at":"2026-01-15T00:55:20.945Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","react","reactive","rxjs","state-management"],"created_at":"2026-01-15T01:44:31.958Z","updated_at":"2026-01-15T01:44:32.569Z","avatar_url":"https://github.com/mnasyrov.png","language":"TypeScript","readme":"# JetState 🚀\n\nReactive state management with RxJS.\n\n\u003e **The project is archived. Please visit the successor – [RxEffects](https://github.com/mnasyrov/rx-effects).**\n\n[![NPM Version](https://badge.fury.io/js/%40jetstate%2Fcore.svg)](https://www.npmjs.com/@jetstate/core)\n[![Build Status](https://github.com/mnasyrov/jetstate/workflows/Node%20CI/badge.svg)](https://github.com/mnasyrov/jetstate/actions)\n[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)\n\n## Introduction\n\nJetState is a library for reactive state management and built on top of RxJS. It takes the idea of multiple data stores from Flux, immutable updates from Redux and leverage data streaming by RxJS. In result, it provides observable data store model.\n\nJetState has influenced by [Akita](https://github.com/datorama/akita/). This library is a kind of reimplementation (not fork) of Akita's core API and its pattern, albeit some docs are reused. JetState provides only core features for observable data store pattern and tends to be clean and simple tool.\n\nJetState is framework agnostic, it is more like \"M\" in your MVVM, MVP and other M?? architecture. Its opinionated structure provides a pattern for managing app's state which can be used in many cases.\n\n```\n\n                          Updates            Data streams\n                      +-------------\u003e Store --------------+\n                      |                                   |\n                      |                                   |\n                      |                                   v\n Backend API \u003c---\u003e Service                              Query \u003c--- Other queries\n                      ^                                   |\n                      |                                   |\n                      |                                   |\n                      +---------- UI Component \u003c----------+\n                        Actions                 Rendering\n                       (methods)\n\n```\n\n## Installation\n\nInstall from the NPM repository using npm or yarn:\n\n- `@jetstate/core`\n\n  Core functionality, framework agnostic. Can be used with Angular as is.\n\n  ```bash\n  npm install @jetstate/core\n  ```\n\n  ```bash\n  yarn add @jetstate/core\n  ```\n\n- `@jetstate/react`\n\n  Helpers for React.js to use query's observables in components with hooks.\n\n  ```bash\n  npm install @jetstate/react\n  ```\n\n  ```bash\n  yarn add @jetstate/react\n  ```\n\n## Concepts and API\n\n### Store\n\nStore is a single object which contains the store state and serves as the \"single source of truth.\"\n\nTo create a store, you need to extend `Store` class, passing the type as well as its initial state.\n\n```typescript\nimport {Store} from '@jetstate/core';\n\nexport interface SessionState {\n  token: string;\n  name: string;\n}\n\nexport function createInitialState(): SessionState {\n  return {\n    token: '',\n    name: '',\n  };\n}\n\nexport class SessionStore extends Store\u003cSessionState\u003e {\n  constructor() {\n    super(createInitialState());\n  }\n}\n```\n\nWith this setup you get a `Store` object with the following interface:\n\n```typescript\nimport {Observable} from 'rxjs';\n\ninterface Store\u003cState extends object\u003e {\n  /** Returns a current value of the state */\n  readonly state: Readonly\u003cState\u003e;\n\n  /** Returns an observable of state value which pushes a current value first */\n  readonly state$: Observable\u003cReadonly\u003cState\u003e\u003e;\n\n  /** Returns an observable of state changes */\n  readonly changes$: Observable\u003cReadonly\u003cState\u003e\u003e;\n\n  /** Updates the store by a specified patch object */\n  update(patch: Partial\u003cReadonly\u003cState\u003e\u003e): void;\n\n  /** Updates the store by a patch which is produced by calling the updater with a current state */\n  update(updater: (state: State) =\u003e Partial\u003cReadonly\u003cState\u003e\u003e): void;\n}\n```\n\n### Query\n\nQuery is a class offering functionality responsible for querying the store.\n\nYou can think of the query as being similar to database queries. Its constructor function receives as parameters its own store and possibly other query classes.\n\nQueries can talk to other queries, join entities from different stores, etc.\n\nTo create a Query, you need to extend the `Query` class from JetState.\n\n```typescript\nimport {Query} from '@jetstate/core';\n\nexport class SessionQuery extends Query\u003cSessionState\u003e {\n  name$ = this.select(state =\u003e state.name);\n\n  constructor(store: SessionStore) {\n    super(store);\n  }\n}\n```\n\nWith this setup you get a `Query` object with the following interface:\n\n```typescript\nimport {Observable} from 'rxjs';\nimport {Selector, Projection} from '@jetstate/core';\n\nexport interface Query\u003cState extends object\u003e {\n  /** Returns a current value of the state */\n  readonly state: Readonly\u003cReadonly\u003cState\u003e\u003e;\n\n  /** Returns an observable which pushes the current value first. */\n  select\u003cV\u003e(selector: Selector\u003cState, V\u003e): Observable\u003cV\u003e;\n\n  /** Returns a subset of a state. */\n  project\u003cV\u003e(selector: Selector\u003cState, V\u003e): Projection\u003cV\u003e;\n}\n```\n\nWhere `Selector` is a function which returns a value from a state. Its type is the following:\n\n```typescript\ntype Selector\u003cState extends object, V\u003e = (state: Readonly\u003cState\u003e) =\u003e V;\n```\n\n`Projection` allows to slice a streaming subset of the state:\n\n```typescript\nimport {Observable} from 'rxjs';\n\nexport interface Projection\u003cV\u003e {\n  /** A current value */\n  readonly value: V;\n\n  /** An observable which pushes the current value first. */\n  readonly value$: Observable\u003cV\u003e;\n\n  /** An observable for value changes. */\n  readonly changes$: Observable\u003cV\u003e;\n}\n```\n\n### Service\n\nIt is recommended to use a service rather than call the store update methods directly by a component.\n\n```typescript\nimport {SessionStore} from './sessionStore';\nimport {tap} from 'rxjs/operators';\n\nexport class SessionService {\n  constructor(private sessionStore: SessionStore, private http: HttpClient) {}\n\n  login(credentials) {\n    return this.http.login(credentials).pipe(\n      tap(({name, token}) =\u003e {\n        this.sessionStore.update({name, token});\n      }),\n    );\n  }\n}\n```\n\n### Functional utilities\n\nThere are a few functions which help to create and use stores and queries in functional way.\n\n```typescript\nimport {createStore, createQuery, select, project} from '@jetstate/core';\n\nexport interface SessionState {\n  token: string;\n  name: string;\n}\n\nconst sessionStore = createStore\u003cSessionState\u003e({\n  token: '',\n  name: '',\n});\n\nconst sessionQuery = createQuery\u003cSessionState\u003e(sessionStore);\nconst name$ = sessionQuery.select(state =\u003e state.name);\n\nconst token$ = select(sessionStore, state =\u003e state.token);\n\nconst tokenChanges$ = project(sessionStore, state =\u003e state.token).changes$;\n```\n\n### Best Practices\n\nJetState does not restrict how you structure your code. Instead, it enforces a set of high-level principles:\n\n1. The Store is a single object that contains the store state and serves as the \"single source of truth.\"\n2. The only way to change the state is by calling its `update()` method.\n3. A UI component should NOT get data from the store directly but instead use a Query.\n4. Asynchronous logic and update calls should be encapsulated in services and data services.\n\nWhen possible, try to avoid injecting the Query in the service. Instead, use the fact that it's already injected in the component and pass the required data into the service's method by arguments.\n\n## Examples\n\n### Angular project\n\nInstall dependencies:\n\n```bash\nnpm install --save @jetstate/core\n```\n\nUsage example:\n\n```typescript\nimport {Component, Injectable, NgModule} from '@angular/core';\nimport {Query, Store} from '@jetstate/core';\nimport {map} from 'rxjs/operators';\n\n// Declare a state:\nexport interface UserState {\n  userName: string;\n}\n\n// Define the store\nexport class UserStore extends Store\u003cUserState\u003e {\n  constructor() {\n    super({\n      userName: 'World',\n    });\n  }\n}\n\n// Use Query to read data from the store.\n@Injectable()\nexport class UserQuery extends Query\u003cUserState\u003e {\n  username$ = this.select(state =\u003e state.username);\n}\n\n// Separate store updating from components.\n@Injectable()\nexport class UserService {\n  constructor(private store: UserStore) {}\n\n  setUserName(username: string) {\n    this.store.update({username});\n  }\n}\n\n// Provide the store, query and service to the app:\n@NgModule({\n  providers: [UserStore, UserQuery, UserService],\n})\nexport class AppModule {}\n\n// Use the state:\n@Component({\n  selector: 'User',\n  template: `\n    {{ message$ | async }}\n  `,\n})\nexport class UserComponent {\n  constructor(private query: UserQuery, private service: UserService) {}\n\n  message$ = this.query.username$.pipe(map(username =\u003e `Hello ${username}!`));\n\n  changeUserName(value: string) {\n    this.service.setUserName(value);\n  }\n}\n```\n\n### React project\n\nInstall dependencies:\n\n```bash\nnpm install --save @jetstate/core @jetstate/react\n```\n\nUsage example:\n\n```typescript jsx\nimport {Query, Store} from '@jetstate/core';\nimport {useObservable, useProjection} from '@jetstate/react';\n\n// Declare a state:\nexport interface UserState {\n  userName: string;\n}\n\n// Define the store\nexport class UserStore extends Store\u003cUserState\u003e {\n  constructor() {\n    super({\n      userName: 'World',\n    });\n  }\n}\n\n// Use Query to read data from the store.\nexport class UserQuery extends Query\u003cUserState\u003e {\n  username$ = this.select(state =\u003e state.username);\n  usernameUpperCased = this.project(state =\u003e state.username.toUpperCase());\n}\n\n// Separate store updating from components.\nexport class UserService {\n  constructor(private store: UserStore) {}\n\n  setUserName(username: string) {\n    this.store.update({username});\n  }\n}\n\n// Use the state:\nexport function UserComponent(props: {query: UserQuery; service: UserService}) {\n  const {query, service} = props;\n\n  const username = useObservable(query.username$);\n  const usernameUpperCased = useProjection(query.usernameUpperCased);\n\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003e\n        Hello {username}! {usernameUpperCased}!!\n      \u003c/h1\u003e\n      \u003cinput\n        type=\"text\"\n        value={username}\n        onChange={event =\u003e service.setUserName(event.target.value)}\n      /\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n## License\n\n[MIT](LICENSE)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmnasyrov%2Fjetstate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmnasyrov%2Fjetstate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmnasyrov%2Fjetstate/lists"}