{"id":48708393,"url":"https://github.com/becher/ngStato","last_synced_at":"2026-04-13T11:01:13.017Z","repository":{"id":345826069,"uuid":"1187232858","full_name":"becher/ngStato","owner":"becher","description":"State management Angular — Signals-first, sans RxJS obligatoire","archived":false,"fork":false,"pushed_at":"2026-04-06T18:40:50.000Z","size":1154,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-06T20:21:34.662Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/becher.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-20T13:47:10.000Z","updated_at":"2026-04-06T18:40:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/becher/ngStato","commit_stats":null,"previous_names":["becher/ngstato"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/becher/ngStato","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/becher%2FngStato","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/becher%2FngStato/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/becher%2FngStato/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/becher%2FngStato/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/becher","download_url":"https://codeload.github.com/becher/ngStato/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/becher%2FngStato/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31749763,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-13T09:16:15.125Z","status":"ssl_error","status_checked_at":"2026-04-13T09:16:05.023Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":[],"created_at":"2026-04-11T13:00:21.204Z","updated_at":"2026-04-13T11:01:13.001Z","avatar_url":"https://github.com/becher.png","language":"TypeScript","readme":"\u003cdiv align=\"center\"\u003e\n\n# ngStato\n\n### Stop writing 14 lines of NgRx for a 5-line API call.\n\n**State management for Angular — async/await instead of RxJS, ~3 KB instead of ~50 KB.**\n\n[![npm](https://img.shields.io/badge/npm-v0.4.0-blue)](https://www.npmjs.com/package/@ngstato/core)\n[![gzip](https://img.shields.io/badge/gzip-~3KB-brightgreen)](#benchmarks)\n[![tests](https://img.shields.io/badge/tests-169%2B-green)](#)\n[![Angular](https://img.shields.io/badge/Angular-17%2B-dd0031)](https://angular.dev)\n[![license](https://img.shields.io/badge/license-MIT-lightgrey)](./LICENSE)\n\n[Start in 5 min](https://becher.github.io/ngStato/guide/start-in-5-min) · [Documentation](https://becher.github.io/ngStato/) · [API Reference](https://becher.github.io/ngStato/api/core) · [NgRx Migration](https://becher.github.io/ngStato/migration/ngrx-to-ngstato)\n\n\u003c/div\u003e\n\n---\n\n## NgRx vs ngStato — same result, different experience\n\n**NgRx** — 9 concepts, 14 lines:\n\n```ts\nload: rxMethod\u003cvoid\u003e(pipe(\n  tap(() =\u003e patchState(store, { loading: true })),\n  switchMap(() =\u003e from(service.getAll()).pipe(\n    tapResponse({\n      next:  (users) =\u003e patchState(store, { users, loading: false }),\n      error: (e)     =\u003e patchState(store, { error: e.message, loading: false })\n    })\n  ))\n))\n```\n\n**ngStato** — 1 concept, 5 lines:\n\n```ts\nasync load(state) {\n  state.loading = true\n  state.users   = await http.get('/users')\n  state.loading = false\n}\n```\n\n\u003e Same behavior. Same Signals. Same Angular DI. **87% less code.**\n\n---\n\n## Get started in 60 seconds\n\n```bash\nnpm install @ngstato/core @ngstato/angular\n```\n\n```ts\n// app.config.ts\nimport { provideStato } from '@ngstato/angular'\nimport { isDevMode }    from '@angular/core'\n\nexport const appConfig = {\n  providers: [\n    provideStato({\n      http: { baseUrl: 'https://api.example.com' },\n      devtools: isDevMode()\n    })\n  ]\n}\n```\n\n```ts\n// users.store.ts\nimport { createStore, http, connectDevTools } from '@ngstato/core'\nimport { StatoStore, injectStore }            from '@ngstato/angular'\n\nexport const UsersStore = StatoStore(() =\u003e {\n  const store = createStore({\n    users:   [] as User[],\n    loading: false,\n    error:   null as string | null,\n\n    selectors: {\n      total:      (s) =\u003e s.users.length,\n      activeUsers: (s) =\u003e s.users.filter(u =\u003e u.active)\n    },\n\n    actions: {\n      async loadUsers(state) {\n        state.loading = true\n        state.error   = null\n        try {\n          state.users = await http.get('/users')\n        } catch (e) {\n          state.error = (e as Error).message\n          throw e\n        } finally {\n          state.loading = false\n        }\n      },\n\n      async createUser(state, payload: Omit\u003cUser, 'id'\u003e) {\n        const user = await http.post\u003cUser\u003e('/users', payload)\n        state.users = [...state.users, user]\n      },\n\n      async deleteUser(state, id: string) {\n        await http.delete(`/users/${id}`)\n        state.users = state.users.filter(u =\u003e u.id !== id)\n      }\n    },\n\n    hooks: {\n      onInit:  (store) =\u003e store.loadUsers(),\n      onError: (err, action) =\u003e console.error(`[UsersStore] ${action}:`, err.message)\n    }\n  })\n\n  connectDevTools(store, 'UsersStore')\n  return store\n})\n```\n\n```ts\n// users.component.ts — all state properties are Angular Signals\n@Component({\n  template: `\n    @if (store.loading()) {\n      \u003cdiv class=\"spinner\"\u003eLoading...\u003c/div\u003e\n    }\n\n    \u003ch2\u003eUsers ({{ store.total() }})\u003c/h2\u003e\n\n    @for (user of store.users(); track user.id) {\n      \u003cdiv class=\"user-card\"\u003e\n        \u003cspan\u003e{{ user.name }}\u003c/span\u003e\n        \u003cbutton (click)=\"store.deleteUser(user.id)\"\u003eDelete\u003c/button\u003e\n      \u003c/div\u003e\n    }\n\n    \u003cbutton (click)=\"store.loadUsers()\"\u003eRefresh\u003c/button\u003e\n  `\n})\nexport class UsersComponent {\n  store = injectStore(UsersStore)\n}\n```\n\n**Done.** State is Signals. Actions are functions. No boilerplate.\n\n---\n\n## Why teams switch\n\n| | NgRx v21 | ngStato |\n|:--|:--|:--|\n| **Bundle** | ~50 KB gzip | **~3 KB** gzip |\n| **Concepts for async action** | 9 (rxMethod, pipe, tap, switchMap…) | **1** (async/await) |\n| **Lines for a CRUD store** | ~90 | **~45** |\n| **RxJS required** | Yes | **No** |\n| **DevTools** | Chrome extension only | **Built-in panel, all browsers, mobile** |\n| **Time-travel** | ✅ via extension | ✅ **built-in with fork-on-dispatch** |\n| **Action replay** | ❌ | ✅ **re-execute any action** |\n| **State export/import** | Via extension | ✅ **JSON file for bug reports** |\n| **Prod safety** | Manual `logOnly` | **Auto `isDevMode()`** |\n| **Entity adapter** | ✅ | ✅ `createEntityAdapter` + `withEntities` |\n| **Feature composition** | ✅ `signalStoreFeature` | ✅ `mergeFeatures()` |\n| **Service injection** | ✅ `withProps` | ✅ `withProps()` + closures |\n| **Concurrency control** | Via RxJS operators | ✅ Native helpers |\n| **Testing** | `provideMockStore` | ✅ `createMockStore()` |\n| **Persistence** | Custom meta-reducers | ✅ `withPersist()` built-in |\n| **Schematics CLI** | ✅ `ng generate` | ✅ `ng generate @ngstato/schematics:store` |\n| **ESLint plugin** | ✅ `@ngrx/eslint-plugin` | ✅ `@ngstato/eslint-plugin` |\n\n---\n\n## Built-in helpers — no RxJS needed\n\n```ts\nimport { exclusive, retryable, optimistic, abortable, queued } from '@ngstato/core'\n\nactions: {\n  submit:  exclusive(async (s) =\u003e { ... }),           // ignore while running (exhaustMap)\n  search:  abortable(async (s, q, { signal }) =\u003e {}), // cancel previous (switchMap)\n  load:    retryable(async (s) =\u003e { ... }, { attempts: 3 }), // auto-retry\n  delete:  optimistic((s, id) =\u003e { ... }, async () =\u003e { ... }), // instant + rollback\n  send:    queued(async (s, msg) =\u003e { ... }),          // process in order (concatMap)\n}\n```\n\n**Plus:** `debounced` · `throttled` · `distinctUntilChanged` · `forkJoin` · `race` · `combineLatest` · `fromStream` · `pipeStream` + 12 stream operators · `createEntityAdapter` · `withEntities` · `withPersist` · `mergeFeatures` · `withProps` · `on()` inter-store reactions\n\n→ [Full helpers API](https://becher.github.io/ngStato/api/helpers)\n\n---\n\n## DevTools — zero install, built-in time-travel\n\nBuilt-in panel. Drag, resize, minimize. No Chrome extension.\nAuto-disabled in production via `isDevMode()`.\n\n```ts\nimport { connectDevTools, devTools } from '@ngstato/core'\nconnectDevTools(store, 'UsersStore')\n\n// Time-travel programmatically\ndevTools.undo()                    // step backward\ndevTools.redo()                    // step forward\ndevTools.travelTo(logId)           // jump to any action\ndevTools.replay(logId)             // re-execute an action\ndevTools.resume()                  // resume live mode\n\n// Export/import for bug reports\nconst snapshot = devTools.exportSnapshot()\ndevTools.importSnapshot(snapshot)\n```\n\n```html\n\u003c!-- app.component.html --\u003e\n\u003cstato-devtools /\u003e\n```\n\n---\n\n## Schematics — scaffold in seconds\n\n```bash\n# Generate a full CRUD store with tests\nng generate @ngstato/schematics:store users --crud --entity\n\n# Generate a reusable feature\nng generate @ngstato/schematics:feature loading\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eExample generated store\u003c/summary\u003e\n\n```ts\n// users.store.ts (auto-generated)\nimport { createStore, http, createEntityAdapter, withEntities, connectDevTools } from '@ngstato/core'\nimport { StatoStore } from '@ngstato/angular'\n\nexport interface User { id: string; name: string }\nconst adapter = createEntityAdapter\u003cUser\u003e()\n\nfunction createUserStore() {\n  const store = createStore({\n    ...withEntities\u003cUser\u003e(),\n    loading: false,\n    error: null as string | null,\n\n    selectors: { total: (s) =\u003e s.ids.length },\n\n    actions: {\n      async loadUsers(state) { /* ... */ },\n      async createUser(state, payload) { /* ... */ },\n      async updateUser(state, id, changes) { /* ... */ },\n      async deleteUser(state, id) { /* ... */ }\n    },\n\n    hooks: {\n      onInit: (store) =\u003e store.loadUsers(),\n      onError: (err, action) =\u003e console.error(`[UserStore] ${action}:`, err.message)\n    }\n  })\n  connectDevTools(store, 'UserStore')\n  return store\n}\n\nexport const UserStore = StatoStore(() =\u003e createUserStore())\n```\n\n\u003c/details\u003e\n\n---\n\n## ESLint — catch mistakes at dev time\n\n```bash\nnpm install -D @ngstato/eslint-plugin\n```\n\n```js\n// eslint.config.js\nimport ngstato from '@ngstato/eslint-plugin'\nexport default [ngstato.configs.recommended]\n```\n\n| Rule | Default | Description |\n|------|---------|-------------|\n| `ngstato/no-state-mutation-outside-action` | `error` | Prevent direct state mutation |\n| `ngstato/no-async-without-error-handling` | `warn` | Require try/catch in async actions |\n| `ngstato/require-devtools` | `warn` | Suggest `connectDevTools()` |\n\n---\n\n## Packages\n\n| Package | Description | Size |\n|---------|-------------|------|\n| [`@ngstato/core`](./packages/core) | Framework-agnostic store engine + helpers | ~3 KB |\n| [`@ngstato/angular`](./packages/angular) | Angular Signals + DI + DevTools | ~1 KB |\n| [`@ngstato/testing`](./packages/testing) | `createMockStore()` test utilities | \u003c 1 KB |\n| [`@ngstato/schematics`](./packages/schematics) | `ng generate` — store \u0026 feature scaffolding | CLI |\n| [`@ngstato/eslint-plugin`](./packages/eslint-plugin) | 3 ESLint rules for best practices | CLI |\n\n## Documentation\n\n📖 **[becher.github.io/ngStato](https://becher.github.io/ngStato/)**\n\n| | |\n|:--|:--|\n| [Start in 5 min](https://becher.github.io/ngStato/guide/start-in-5-min) | [Core concepts](https://becher.github.io/ngStato/guide/core-concepts) |\n| [Angular guide](https://becher.github.io/ngStato/guide/angular) | [Architecture](https://becher.github.io/ngStato/guide/architecture) |\n| [Testing guide](https://becher.github.io/ngStato/guide/testing) | [NgRx migration](https://becher.github.io/ngStato/migration/ngrx-to-ngstato) |\n| [CRUD recipe](https://becher.github.io/ngStato/recipes/crud) | [API reference](https://becher.github.io/ngStato/api/core) |\n| [Entities](https://becher.github.io/ngStato/guide/entities) | [Benchmarks](https://becher.github.io/ngStato/benchmarks/overview) |\n\n## Contributing\n\n```bash\ngit clone https://github.com/becher/ngStato\ncd ngStato \u0026\u0026 pnpm install \u0026\u0026 pnpm build \u0026\u0026 pnpm test\n```\n\n## License\n\nMIT — Copyright © 2025-2026 ngStato\n","funding_links":[],"categories":["State Management"],"sub_categories":["Other State Libraries"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbecher%2FngStato","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbecher%2FngStato","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbecher%2FngStato/lists"}