{"id":23822395,"url":"https://github.com/ericvera/whatnow","last_synced_at":"2026-02-17T08:05:24.427Z","repository":{"id":270628733,"uuid":"910952558","full_name":"ericvera/whatnow","owner":"ericvera","description":null,"archived":false,"fork":false,"pushed_at":"2025-02-24T02:50:31.000Z","size":1431,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-27T08:22:24.257Z","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":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ericvera.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2025-01-01T22:14:52.000Z","updated_at":"2025-02-24T02:50:28.000Z","dependencies_parsed_at":"2025-01-20T03:22:54.329Z","dependency_job_id":"7427eb3c-dec7-49cb-9f40-a66cae253f0d","html_url":"https://github.com/ericvera/whatnow","commit_stats":null,"previous_names":["ericvera/whatnow"],"tags_count":10,"template":false,"template_full_name":"ericvera/ts-lib-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fwhatnow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fwhatnow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fwhatnow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericvera%2Fwhatnow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ericvera","download_url":"https://codeload.github.com/ericvera/whatnow/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248637774,"owners_count":21137538,"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":"2025-01-02T09:18:03.621Z","updated_at":"2026-02-17T08:05:24.410Z","avatar_url":"https://github.com/ericvera.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WhatNow\n\n**A lightweight, type-safe state machine for managing complex async workflows.**\n\n[![github license](https://img.shields.io/github/license/ericvera/whatnow.svg?style=flat-square)](https://github.com/ericvera/whatnow/blob/master/LICENSE)\n[![npm version](https://img.shields.io/npm/v/whatnow.svg?style=flat-square)](https://npmjs.org/package/whatnow)\n\n## Features\n\n- 🪶 **Lightweight**: Zero dependencies, minimal overhead\n- 🔒 **Immutable**: Predictable state updates that are always immutable\n- 🎯 **Type-safe**: Built-in type safety for your state machines\n- 📋 **Queue-based**: Keeps async operations ordered and sequential\n- 🛑 **Abortable**: Reset or abort operations mid-flight\n\n## AI Disclosure\n\nThis library was co-created with AI, which means it was thoughtfully designed and reviewed by humans.\n\n## Installation\n\nUsing npm:\n\n```bash\nnpm install whatnow\n```\n\nUsing yarn:\n\n```bash\nyarn add whatnow\n```\n\n## Quick Start\n\n### Simple Counter Example\n\n```typescript\nimport { WhatNow } from 'whatnow'\n\n// Define your steps\ntype Step = 'START' | 'INCREMENTING' | 'DONE'\n\n// Define your state\ninterface State {\n  count: number\n}\n\n// Create a simple counter machine\nconst counter = new WhatNow\u003cStep, State\u003e({\n  initialState: { count: 0 },\n  steps: {\n    // Initial step\n    START: async (_, { act }) =\u003e {\n      return { step: 'INCREMENTING' }\n    },\n    // Increment the counter\n    INCREMENTING: async ({ state }) =\u003e {\n      return {\n        step: 'DONE',\n        state: { count: state.count + 1 },\n      }\n    },\n    // Terminal state\n    DONE: null,\n  },\n  onChange: () =\u003e {\n    console.log('Count:', counter.state.count)\n  },\n  onError: (error) =\u003e {\n    console.error('Error:', error)\n  },\n})\n\n// Start counting\ncounter.act('START')\n```\n\n### Advanced Example: Data Processing\n\n```typescript\nimport { WhatNow } from 'whatnow'\n\n// Define your steps\ntype Step =\n  | 'UNLOADED'\n  | 'INITIALIZING'\n  | 'LOADED'\n  | 'PROCESSINGDATA'\n  | 'UNLOADING'\n\n// Define your state shape (external state)\ninterface State {\n  data: string[]\n  processedCount: number\n  loaded: boolean\n}\n\n// Define internal context (machine-only state)\ninterface Context {\n  unsubscribe?: () =\u003e void // Cleanup function for subscription\n}\n\n// Define any payload types\ninterface Payload {\n  newData?: string[]\n}\n\n// Simulate a data source\nconst dataSource = {\n  subscribe: (callback: (data: string[]) =\u003e void) =\u003e {\n    // Simulate async data arrival\n    setTimeout(() =\u003e {\n      callback(['item1', 'item2', 'item3'])\n    }, 100)\n\n    return () =\u003e {\n      // Cleanup subscription\n      console.log('Unsubscribed from data source')\n    }\n  },\n}\n\n// Create your state machine\nconst machine = new WhatNow\u003cStep, State, Payload, Context\u003e({\n  initialState: {\n    data: [],\n    processedCount: 0,\n    loaded: false,\n  },\n  initialContext: {\n    unsubscribe: undefined,\n  },\n  steps: {\n    // Terminal state - initial state before initialization\n    UNLOADED: null,\n\n    // Initialize the system by subscribing to data source\n    INITIALIZING: async ({ context }, { act }) =\u003e {\n      const unsubscribe = dataSource.subscribe((newData) =\u003e {\n        act('PROCESSINGDATA', { newData })\n      })\n\n      return {\n        step: 'LOADED',\n        state: {\n          loaded: true,\n          data: [],\n          processedCount: 0,\n        },\n        context: {\n          unsubscribe,\n        },\n      }\n    },\n\n    // Process items in batch\n    PROCESSINGDATA: async ({ state, payload }, { act }) =\u003e {\n      // If the system is not loaded, we can skip the processing\n      if (!state.loaded) {\n        return { step: 'UNLOADED' }\n      }\n\n      if (payload.newData) {\n        // Add new data to existing data and update processed count\n        return {\n          step: 'LOADED',\n          state: {\n            data: [...state.data, ...payload.newData],\n            processedCount: state.processedCount + payload.newData.length,\n            loaded: true,\n          },\n        }\n      }\n\n      return { step: 'PROCESSINGDATA' }\n    },\n\n    // Terminal state - system is ready for new data\n    LOADED: null,\n\n    // Cleanup state - unsubscribe and reset\n    UNLOADING: async ({ state, context }, { act }) =\u003e {\n      // If the system is not loaded, we can skip the cleanup\n      if (!state.loaded) {\n        return { step: 'UNLOADED' }\n      }\n\n      // Cleanup subscription using stored cleanup function\n      context.unsubscribe?.()\n\n      // Reset to initial state and transition to UNLOADED\n      return {\n        step: 'UNLOADED',\n        state: {\n          data: [],\n          processedCount: 0,\n          loaded: false,\n        },\n        context: {\n          unsubscribe: undefined,\n        },\n      }\n    },\n  },\n  onChange: () =\u003e {\n    console.log('State updated:', machine.state)\n  },\n  onError: (error) =\u003e {\n    console.error('Error occurred:', error)\n  },\n})\n\n// Example usage:\n// Will subscribe and wait for data\nmachine.act('INITIALIZING')\n\n// Data will arrive via subscription, triggering PROCESSINGDATA and moving to LOADED\n\n// Cleanup and return to UNLOADED state\nmachine.act('UNLOADING')\n```\n\n## API Reference\n\n### `WhatNow\u003cTStep, TState, TPayload, TContext\u003e`\n\nThe main class for creating a state machine.\n\n#### Constructor Options\n\n```typescript\ninterface WhatNowConfig\u003cTStep, TState, TPayload, TContext\u003e {\n  steps: StepHandlers\u003cTStep, TState, TPayload, TContext\u003e\n  initialState: TState\n  initialContext?: TContext\n  onChange: () =\u003e void\n  onError: (error: Error) =\u003e void\n}\n```\n\n- `steps`: Map of step names to their handler functions\n- `initialState`: Initial state object (external state)\n- `initialContext`: Optional initial context object (internal state)\n- `onChange`: Callback triggered when state changes\n- `onError`: Error handler for async operations\n\n#### Methods\n\n- `act(step: TStep, payload?: Partial\u003cTPayload\u003e)`: Enqueue a new step transition\n- `reset(resetStep: TStep)`: Clear the queue and schedule a new step. The currently executing step will complete, but any steps it triggers or that were previously queued will be discarded. The specified reset step will run after the current step completes.\n- `state`: Getter that returns the current state (readonly)\n\n### Step Handlers\n\nStep handlers are async functions that process the current state and return the next step:\n\n```typescript\ntype StepHandler\u003cTStep, TState, TPayload, TContext\u003e = (\n  params: Readonly\u003c{\n    state: TState\n    context: TContext\n    step: TStep\n    payload: Partial\u003cTPayload\u003e\n  }\u003e,\n  actions: {\n    act: (step: TStep, payload?: Partial\u003cTPayload\u003e) =\u003e void\n    reset: (step: TStep) =\u003e void\n  },\n) =\u003e Promise\u003cReadonly\u003cStepHandlerReturn\u003cTStep, TState, TContext\u003e\u003e\u003e\n```\n\nReturn value:\n\n```typescript\ninterface StepHandlerReturn\u003cTStep, TState, TContext\u003e {\n  step: TStep // The next step to transition to\n  state?: TState // Optional state updates (must provide complete state object)\n  context?: TContext // Optional context updates (must provide complete context object)\n}\n```\n\n## Best Practices\n\n1. **Step Names**\n\n   - Define step types as string literals for type safety\n   - Use -ing suffix for active steps (e.g., 'LOADING')\n   - Use -ed suffix for terminal states (e.g., 'LOADED')\n\n2. **State Management**\n\n   - Keep state updates immutable\n   - Use context for internal machine state\n   - Provide complete objects when updating state/context\n   - Avoid optional properties in state/context objects\n\n3. **Step Handlers**\n   - Define terminal states as `null`\n   - Use payloads to pass data between steps\n   - Handle errors in the `onError` callback\n\n# API Reference\n\nSee [docs](docs/README.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericvera%2Fwhatnow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fericvera%2Fwhatnow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericvera%2Fwhatnow/lists"}