{"id":20147264,"url":"https://github.com/voodooattack/when-ts","last_synced_at":"2025-04-09T19:35:58.809Z","repository":{"id":57397710,"uuid":"149202620","full_name":"voodooattack/when-ts","owner":"voodooattack","description":"When: recombinant design pattern for state machines based on gene expression with a temporal model","archived":false,"fork":false,"pushed_at":"2018-11-07T16:47:14.000Z","size":301,"stargazers_count":115,"open_issues_count":0,"forks_count":3,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-06T15:55:38.819Z","etag":null,"topics":["design-pattern","design-patterns","event-based","nodejs","software-engineering","state-machine","state-management","temporal-models","typescript","typescript-decorators","typescript-library"],"latest_commit_sha":null,"homepage":"https://voodooattack.github.io/when-ts/","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/voodooattack.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}},"created_at":"2018-09-17T23:51:52.000Z","updated_at":"2024-06-23T04:54:14.000Z","dependencies_parsed_at":"2022-08-31T14:20:53.195Z","dependency_job_id":null,"html_url":"https://github.com/voodooattack/when-ts","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voodooattack%2Fwhen-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voodooattack%2Fwhen-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voodooattack%2Fwhen-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voodooattack%2Fwhen-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/voodooattack","download_url":"https://codeload.github.com/voodooattack/when-ts/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248098394,"owners_count":21047432,"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":["design-pattern","design-patterns","event-based","nodejs","software-engineering","state-machine","state-management","temporal-models","typescript","typescript-decorators","typescript-library"],"created_at":"2024-11-13T22:28:15.346Z","updated_at":"2025-04-09T19:35:58.787Z","avatar_url":"https://github.com/voodooattack.png","language":"TypeScript","readme":"# When: TypeScript Implementation\n##### A software design pattern for building event-based recombinant state machines \n\n[![npm](https://img.shields.io/npm/v/when-ts.svg)](https://www.npmjs.com/package/when-ts) \n [![GitHub license](https://img.shields.io/github/license/voodooattack/when-ts.svg)](https://github.com/voodooattack/when-ts/blob/master/LICENSE)\n [![GitHub issues](https://img.shields.io/github/issues/voodooattack/when-ts.svg)](https://github.com/voodooattack/when-ts/issues) \n [![Build Status](https://travis-ci.org/voodooattack/when-ts.svg?branch=master)](https://travis-ci.org/voodooattack/when-ts) [![Coverage Status](https://coveralls.io/repos/github/voodooattack/when-ts/badge.svg)](https://coveralls.io/github/voodooattack/when-ts)\n ![npm type definitions](https://img.shields.io/npm/types/when-ts.svg)\n\n### Introduction\n\n**The latest version of this README can be found in the [`devel` branch](https://github.com/voodooattack/when-ts/blob/devel/README.md), please read the spec there if that's what you're after.** \n\nThe spec for the abstract syntax and the design pattern itself can be found in [the spec subdirectory](spec/when.md). Please read the specs before delving into the implementation itself to get a good understanding of how things work.\n\nThis is a reference implementation for a new software design pattern that allows for composable event-based state machines with complete (including temporal) control over the state.\n\n\n#### Features:\n\n- Discrete: if your actions only deal with the state object, then every state transition is 100% predictable.\n- Temporal: time can be rewound at any given moment (tick) by default, and the state machine will transition to a previously known state in time, along with any future information in the form of an optional state mutation to apply.\n- Recombinant: the pattern is based on [gene expression](https://en.wikipedia.org/wiki/Gene_expression), and since state machines are composed of events (`condition -\u003e action` pairs) that are quite similar to how real genes are theorised to work (`activation region -\u003e coding region`), this means that genetic recombination can be applied to `when` state machines by transferring new events from one machine to another. Mutating the machine (DNA) by transferring condition/action pairs (genes) from one machine to the other to introduce new behaviour.\n\n#### Possible Proposals\n\nHere are some possible expansions on the idea. These require further discussion before they're mature enough to include:\n\n- Sexual reproduction of state machines: possible use of a similar mechanic to the one used in organic cells to combine two different programs (DNA) by randomly selecting an equal half of each.  \n- Mutation: Possible, but difficult since we can't swap code like basepairs. The simplest possible mutation would be a random swap of conditions between two randomly selected actions. \n\nThis would all lead to more emergent behaviour in agents produced by recombination.\n\n## When: TypeScript Implementation\n\n### Installation\n\nYou need to install `reflect-metadata` in your project.\n\n`npm install when-ts reflect-metadata`\n\nAdditionally, you must add the following to your project's `tsconfig.json` for the TypeScript decorator to work:\n\n```json\n{\n  \"experimentalDecorators\": true,\n  \"emitDecoratorMetadata\": true\n}\n```\n\n### API\n\nSee the [API documentation](https://voodooattack.github.io/when-ts/) for more information.\n\n### Usage\n\nSome examples are located in in [examples/](examples).\n\n#### Simple example:\n\n```typescript\nimport { StateMachine, MachineState, when } from 'when-ts';\n\ninterface State extends MachineState { // the state of our program\n  value: number; // a counter that will be incremented once per tick\n}\n\nclass TestMachine extends StateMachine\u003cState\u003e {\n  constructor() {\n    super({ value: 0 }); // pass the initial state to the event machine\n  }\n\n  @when\u003cState\u003e(true) // define a condition for this block to execute, in this case always\n  reportOncePerTick(s: State, m: TestMachine) {\n    console.log(`beginning tick #${m.history.tick} with state`, s);\n  }\n\n  @when\u003cState\u003e(state =\u003e state.value \u003c 5)\n  incrementOncePerTick(s: State) {\n    return { value: s.value + 1 };\n  }\n\n  @when\u003cState\u003e(state =\u003e state.value \u003e= 5) \n  exitWhenDone(s: State, m: TestMachine) {\n    console.log(`finished on tick #${m.history.tick}, exiting`, s);\n    m.exit(); // exit the state machine\n  }\n}\n\nconst test = new TestMachine();\n\nconst result = test.run(); // this will block until the machine exits, unlike `.step()`\n\nconsole.log('state machine exits with:', result);\n```\n\n#### Brute-forcing primes \n\nThe same prime machine from the spec, implemented in TypeScript. This one uses the `input` feature.\n\nA better implementation exists in [examples/prime.ts](examples/prime.ts)!\n\n```typescript\nimport { StateMachine, when, input, MachineState, MachineInputSource, StateObject } from 'when-ts';\n\ninterface PrimeState extends MachineState {\n  counter: number;\n  current: number;\n  primes: number[];\n}\n\ninterface IPrimeInputSource extends MachineInputSource {\n  readonly maxPrimes: number; \n}\n\nclass PrimeInputSource implements IPrimeInputSource {\n  @input('once') // mark as an input that's only read during startup.\n  public readonly maxPrimes: number;\n  constructor(primes = 10) {\n    this.maxPrimes = primes;\n  }\n}\n\nclass PrimeMachine extends StateMachine\u003cPrimeState, IPrimeInputSource\u003e {\n  constructor(inputSource: IPrimeInputSource) {\n    // pass the initial state\n    super({ counter: 2, current: 3, primes: [2] }, inputSource);\n  }\n\n  // increment the counter with every tick\n  @when\u003cPrimeState, IPrimeInputSource\u003e(state =\u003e state.counter \u003c state.current)\n    .unless(state =\u003e state.primes.length \u003e= state.maxPrimes)\n  incrementCounterOncePerTick({ counter }: StateObject\u003cPrimeState, IPrimeInputSource\u003e) {\n    return { counter: counter + 1 };\n  }\n\n  @when\u003cPrimeState, IPrimeInputSource\u003e(state =\u003e state.counter \u003c state.current \u0026\u0026 state.current % state.counter === 0)\n    .unless(state =\u003e state.primes.length \u003e= state.maxPrimes)\n  resetNotPrime({ counter, primes, current }: StateObject\u003cPrimeState, IPrimeInputSource\u003e) {\n    return { counter: 2, current: current + 1 };\n  }\n\n  @when\u003cPrimeState, IPrimeInputSource\u003e(state =\u003e state.counter \u003e= state.current)\n    .unless(state =\u003e state.primes.length \u003e= state.maxPrimes)\n  capturePrime({ counter, primes, current }: StateObject\u003cPrimeState, IPrimeInputSource\u003e) {\n    return { counter: 2, current: current + 1, primes: [...primes, current] };\n  }\n\n  // this explicit exit clause is not required because `unless` will cause the machine to implicitly exit above\n  //  @when\u003cPrimeState, IPrimeInputSource\u003e(state =\u003e state.primes.length \u003e= state.maxPrimes)\n  //  exitMachine(_, m: StateMachine\u003cPrimeState\u003e) {\n  //    m.exit();\n  //  }\n}\n\nconst inputSource = new PrimeInputSource(10);\nconst primeMachine = new PrimeMachine(inputSource);\n\nconst result = primeMachine.run();\n\nif (result)\n  console.log(result!.primes);\n\n```\n\nOutput: \n\n```json\n{ \n  \"counter\": 2,\n  \"current\": 30,\n  \"primes\": [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ]\n}\n```\n\n### Contributions\n\nAll contributions and pull requests are welcome. \n\nIf you have something to suggest or an idea you'd like to discuss, then please submit an issue or a pull request. \n\nNote: All active development happens in the `devel` branch. Please commit your changes using `npm run commit` to trigger `conventional-changelog`. \n\n### License (MIT)\n\nCopyright (c) 2018 Abdullah A. Hassan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoodooattack%2Fwhen-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvoodooattack%2Fwhen-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoodooattack%2Fwhen-ts/lists"}