{"id":15485762,"url":"https://github.com/codediodeio/lightstate","last_synced_at":"2025-08-30T20:07:21.045Z","repository":{"id":90246313,"uuid":"129679763","full_name":"codediodeio/lightstate","owner":"codediodeio","description":"Experimental StatefulObject","archived":false,"fork":false,"pushed_at":"2018-04-21T21:48:53.000Z","size":102,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-22T17:14:35.555Z","etag":null,"topics":[],"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/codediodeio.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}},"created_at":"2018-04-16T03:30:59.000Z","updated_at":"2023-01-15T19:15:05.000Z","dependencies_parsed_at":null,"dependency_job_id":"4850bf5c-c6a3-4c6a-91bf-a2dda6ada9a3","html_url":"https://github.com/codediodeio/lightstate","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/codediodeio/lightstate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codediodeio%2Flightstate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codediodeio%2Flightstate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codediodeio%2Flightstate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codediodeio%2Flightstate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codediodeio","download_url":"https://codeload.github.com/codediodeio/lightstate/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codediodeio%2Flightstate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272900157,"owners_count":25012033,"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","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":"2024-10-02T06:03:09.972Z","updated_at":"2025-08-30T20:07:21.031Z","avatar_url":"https://github.com/codediodeio.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![CircleCI](https://circleci.com/gh/codediodeio/lightstate.svg?style=svg)](https://circleci.com/gh/codediodeio/lightstate)\n\n[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)\n\n# Notes\n\nStatus: Experimental\n\nPart of a lesson on Continuous Integration for AngularFirebase.com\n\n# LightState\n\n_Why?_ Because the best code is the code you don't write. Convention over configuration.\n\n_What?_ LightState is a magic state container designed for optimal developer happiness.\n\n* Not a library, just a drop-in tool\n* Simple data selection and mutation\n* Automatic actions\n* Automatic Observable/Promise management\n* Intercept state changes with middleware\n* Redux Dev Tools support out of the box\n* Only dependency is RxJS\n\nLightState is not opinionated. You can have one global state or multiple smaller states.\n\n## Let there be State...\n\n```\nnpm i lightstate -s\n```\n\n## Quick Start\n\nLightState provides you with one thing - `StatefulObject`.\n\n```js\n// Define it\nconst state = new StatefulObject({ favorites: null });\n\n// Slice it\nconst slice = state.at('favorites');\n\n// Read it\nslice.get$(); // as Rx Observable\nslice.value; // as JS object\n\n// Mutate it\nslice.update({ beer: 'La Fin du Monde' });\n```\n\nMake sure to install [Redux Dev Tools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en) to watch your state magically manage itself.\n\n### Conventional Actions\n\nLightstate removes the mental boilerplate of redux by generating actions based on predictible [conventions](https://en.wikipedia.org/wiki/Convention_over_configuration).\n\nWhen you mutate the state, it creates an action based on the following convention (you can override this behavior).\n\nname - optional name of the stateful object\naction - name of the action\npath - where the action occured in dot notation\n\n```text\n[name] action@path\n```\n\nSynchronous mutations will look this this.\n\n```text\n[userState] UPDATE@profile.displayName\n[userState] CLEAR\n```\n\nAsync operations have their own convention. (1) start (2) mutate (3) complete/cancel/err.\n\n```text\n[userState] OBSERVABLE_START@reviews.phoenix\n[userState] OBSERVABLE_UPDATE@reviews.phoenix\n[userState] OBSERVABLE_COMPLETE@reviews.phoenix\n```\n\n### Options\n\nYou can can pass options as a second argument.\n\n* `name` give your container a unique name for action creation (a random name is assigned by default).\n* `devTools` devtools config or false (enabled by default)\n* `logger` console log state changes (enabled by default)\n* `middleware` a function that can intercept state changes\n* `[key:string]` add your own options, then intercept them with middleware\n\n```js\nconst opts = {\n    name: 'user',\n    devTools: false,\n    middleware: myMiddlewareFunction\n}\nconst state = new StatefulObject( default, opts )\n```\n\n## Select\n\nAll selectors can return a plain object, or `Observable` with a `$` convention.\n\n```js\n// From Root state\nstate.get('hello.world'); // JS Object\nstate.get$('hello.world'); // Rx Observable\n\n// From Slice\nconst slice = state.at('hello.world');\nslice.value; // JS Object\nslice.get$(); // Rx Observable\n```\n\nSee if a property exists\n\n```js\nstate.has('some.deeply.nested.array[23]');\n// returns boolean\n```\n\nPerform advanced collection filtering when you have an array of objects.\n\n```js\nstate.where$('users', {\n  age: v =\u003e v \u003e 21,\n  city: v =\u003e v === 'NYC'\n});\n// returns filtered Observable array\n```\n\n## Mutate\n\nSet will override the entire object with new data.\n\n```js\nstate.set({ username: 'nash' });\n```\n\nUpdate will only mutate the specified values.\n\n```js\nstate.update({ age: 30 });\n```\n\nModify deeply nested properties\n\n```js\nstate.updateAt('users.userABC.profile', { name: 'doug' });\nstate.setAt(...);\n```\n\nAlso\n\n```js\nstate.clear(); // next state {}\nstate.reset(); // reset to initial state\n```\n\n## Slice\n\nIn most cases, you will need a reference to a specific slice of the stateful object.\n\n```ts\nconst beer = state.at('beers.budlight.platinum');\n```\n\nA slice is just a scoped reference to the parent object, allowing you to to make references to deeply nested paths in the state.\n\n```js\nbeer.update({ abv: 6.0 });\n```\n\n## Dispatch Custom Actions\n\nYou should use the mutation API above most of the time, but sometimes you might want to dispatch and handle an action manually.\n\nDispatch allows you to pass an action name, payload, and handler function (similar to a reducer function), then you return the next state.\n\n```js\nconst handler = (state, payload) =\u003e {\n  return { ...state, username: payload };\n};\nstate.dispatch('CHANGE_USERNAME', payload, handler);\n```\n\n## Async Data\n\nAsync state management is a challenge on the web. The tool can automatically subscribe to Observables and resolve Promises, then make updates with the resolved values. It also keeps track of all running subscriptions in the state object, so you can manage, cancel, and analyze streaming subscriptions\n\n### Method 1 - Automatic\n\n```ts\nconst observable = http.get(...);\n\nthis.state.async('tasks.items', observable);\n// note that it also works with promises\n```\n\nThe `feed` method will subscribe to the Observable (or Promise), patch the emitted values to the state, and dispatch two actions:\n\n1.  `OBSERVABLE_START@path` on subscribe\n2.  `OBSERVABLE_SET@path` update with emitted data\n\nBonus features: If you feed multiple Observables to a path, it will automatically cancel previous requests. It will also let you know when your observable sends the complete signal.\n\n3.  `OBSERVABLE_COMPLETE@path` on complete\n4.  `OBSERVABLE_CANCEL@path` if unsubscribed automatically\n\nYou can access all active subscriptions the stateful object with `state.subs`\n\nWorks great with realtime data feeds like Firebase.\n\n### Method 2 - Manually\n\nYou can dispatch arbitrary actions to signal the start of something async. This can be useful debugging or reacting to the action stream (see method 3).\n\n```js\nthis.state.signal('MAKE_GET_REQUEST');\nhttp.get(url).pipe(tap(data =\u003e {\n    state.update(data)\n})\n.subscribe()\n```\n\n### Method 3 - React to an Action Stream\n\nInspired by NgRx Effects, you can listen to actions and react.\n\n```ts\nthis.state.actions$\n  .ofType('[mystate] MY_ACTION')\n  .pipe(\n    tap(action =\u003e {\n      // do stuff\n    })\n  )\n  .subscribe();\n```\n\n## Middleware\n\nYou can intercept state changes with middleware. For example, this package uses middleware to implement a logger and Redux Devtools. Just return the next state.\n\n```ts\nconst yourMiddleware = (current, next, action, opts) =\u003e {\n  console.log(current, next, action, opts);\n  next = { ...next, hello: 'middleware' };\n  return next;\n};\n```\n\nYou can pass middleware through options or by calling `use`. Note: Only one middleware function is allowed per StatefulObject.\n\n```ts\nconst state = new StatefulObject({ name: 'bubba' }, opts);\nstate.use(yourMiddleware);\n```\n\n## Usage in Angular\n\nIf you have highly complex requirements, look into a fully integrated solution like NGXS or NgRX.\n\n**Global Store, Redux Pattern**. If you want global store, create a service that instantiates a single `StatefulObject` then pass it around with Angular's DI.\n\n**Smart parent, dumb children**. It's common to make a parent component stateful, then pass the state context down via `@Input()`. Don't be afraid to use multiple state containers. Each container gets a unique name and it's own instance in devtools.\n\n**Get Creative**. This is not an opinionated library. Architect your own state management solution with this swiss army knife.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodediodeio%2Flightstate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodediodeio%2Flightstate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodediodeio%2Flightstate/lists"}