{"id":23507368,"url":"https://github.com/simplyhexagonal/mono-context","last_synced_at":"2025-10-19T15:33:06.963Z","repository":{"id":57160386,"uuid":"410707968","full_name":"simplyhexagonal/mono-context","owner":"simplyhexagonal","description":"Global context to easily share data between monorepo packages","archived":false,"fork":false,"pushed_at":"2022-03-09T18:40:55.000Z","size":147,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-05-06T20:33:57.525Z","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/simplyhexagonal.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-09-27T01:42:38.000Z","updated_at":"2021-10-23T22:22:57.000Z","dependencies_parsed_at":"2022-09-09T03:20:52.092Z","dependency_job_id":null,"html_url":"https://github.com/simplyhexagonal/mono-context","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplyhexagonal%2Fmono-context","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplyhexagonal%2Fmono-context/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplyhexagonal%2Fmono-context/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplyhexagonal%2Fmono-context/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simplyhexagonal","download_url":"https://codeload.github.com/simplyhexagonal/mono-context/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253525723,"owners_count":21922150,"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":"2024-12-25T10:18:16.737Z","updated_at":"2025-10-19T15:33:06.883Z","avatar_url":"https://github.com/simplyhexagonal.png","language":"TypeScript","funding_links":["https://www.buymeacoffee.com/jeanlescure","https://www.patreon.com/jeanlescure"],"categories":[],"sub_categories":[],"readme":"# Mono Context\n![Tests](https://github.com/simplyhexagonal/mono-context/workflows/tests/badge.svg)\n\nThis library does two things across multiple packages on a [monorepo](https://en.wikipedia.org/wiki/Monorepo):\n\n- incrementally count arbitrary events of your chosing\n- store a (type-safe) global state of any values you want\n\nand gosh darn it, is it [REALLY GOOD](https://github.com/simplyhexagonal/mono-context/blob/main/src/test.ts) at doing so!\n\n## Open source notice\n\nThis project is open to updates by its users, I ensure that PRs are relevant to the community.\nIn other words, if you find a bug or want a new feature, please help us by becoming one of the\n[contributors](#contributors-) ✌️ ! See the [contributing section](#contributing).\n\n## Like this module? ❤\n\nPlease consider:\n\n- [Buying me a coffee](https://www.buymeacoffee.com/jeanlescure) ☕\n- Supporting me on [Patreon](https://www.patreon.com/jeanlescure) 🏆\n- Starring this repo on [Github](https://github.com/simplyhexagonal/mono-context) 🌟\n\n## Example use case\n\nLet's say you have a monorepo with package `a` and package `b`.\n\nPackage `a` has a set of commonly used functions, and `b` is an app that consumes said functions:\n\n```\nmy-awesome-monorepo/\n├─ packages/\n│  ├─ a/\n│  │  ├─ src/\n│  │  │  ├─ ...\n│  │  ├─ package.json\n├─ apps/\n│  ├─ b/\n│  │  ├─ src/\n│  │  │  ├─ ...\n│  │  ├─ package.json\n```\n\nPackage `a` has a function that should only ever run once in an app's lifecycle:\n\n```ts\nexport const initDataModel = (/* ... */) =\u003e {\n  // if this runs more than once your app will crash\n  // and somewhere in the world a puppy will die\n  // ...\n}\n```\n\nApp `b` calls the function:\n\n```ts\nimport { initDataModel } from 'a';\n\nconst startServer = () =\u003e {\n  initDataModel(/* ... */);\n\n  // ...\n}\n```\n\nYou see this, and see that it is good.\n\nBut, oh no... your teammate, Steve, comes by a week later and implements in app `b` a controller\nthat he only ever tests via unit testing, and within the controller:\n\n```ts\nimport { initDataModel } from 'a';\n\ninitDataModel(/* ... */); // Steve, just, why?\n\nexport default class ControllerOfDoom {\n  // ...\n}\n```\n\nNobody catches the double call of `initDataModel` until it's too late.\n\nNow you have been tasked with solving this dilemma and Steve's on vacation.\n\nYou are certain the error is caused by a double call of `initDataModel`, but how can you fix this\nproperly and in a timely fashion?\n\nHow about a singleton? No, that won't do. Only classes can be singleton and your coding standards\nspecify that package `a` should only ever export functions.\n\nFear not, `MonoContext` can easily help you!\n\nSimply add it as a dependency of package `a` and count:\n\n```ts\nimport MonoContext from '@simplyhexagonal/mono-context';\n\nexport const initDataModel = (/* ... */) =\u003e {\n  const callCount = MonoContext.count('initDataModel');\n\n  if (callCount \u003e 1) {\n    console.log('WARNING: initDataModel has been called more than once');\n  } else {\n    // if this runs more than once your app will crash\n    // and somewhere in the world a puppy will die\n    // ...\n  }\n}\n```\n\nWhat's more, on app `b` you can get the count as well:\n\n```ts\nimport MonoContext from '@simplyhexagonal/mono-context';\n\nimport ControllerOfDoom from '../controllers/controller-of-doom';\n\nconsole.log(MonoContext.getCount('initDataModel'));\n// and this is how you found the culprit of Buddy's untimely demise\n```\n\nGreat!\n\nNow, let's say you have been tasked with replacing all `console.logs` in the monorepo with a `debug`\nfunction from package `a`:\n\n```ts\nexport const debug = (...args) =\u003e {\n  if (/* ... */) {\n    console.log(...args);\n  }\n}\n```\n\nYou dilligently replace all `console.log` calls on all apps in the monorepo, it's hundreds of files,\nbut then, they tell you that they want all logs to include the app's unique ID.\n\nWell, f...\n\nYou feel tempted to simply search and replace all instances of `debug(` in the monorepo with\n`debug(appId, `, somehow. But that wouldn't be very [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself),\nnow would it? Also you'd have to somehow define or import `appId` on hundreds of files.\n\nBut hey, you've already got **MonoContext**!\n\nYou might as well just refactor the `debug` function as so:\n\n```ts\nimport MonoContext from '@simplyhexagonal/mono-context';\n\nexport const debug = (...args) =\u003e {\n  if (/* ... */) {\n    const { appId } = MonoContext.getState(); // *sigh of relief\n\n    console.log(`(${appId}) -`, ...args);\n  }\n}\n```\n\nThen, simply make sure each app stores the `appId` on MonoContext's state:\n\n```ts\n// on app `b`\nconst startServer = () =\u003e {\n  const appId = /* */;\n  MonoContext.setState({ appId });\n\n  // ...\n}\n\n// on app `c`\nconst initApp = () =\u003e {\n  const appId = /* */;\n  MonoContext.setState({ appId });\n\n  // ...\n}\n\n// etc\n```\n\n## Usage\n\nImport `MonoContext`:\n\n```tsx\n// Node\nconst MonoContext = require('@simplyhexagonal/mono-context');\n\n// ES6/Typescript\nimport MonoContext from '@simplyhexagonal/mono-context';\n\n// Browser (i.e. useful to debug components within React)\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/@simplyhexagonal/mono-context@latest/dist/mono-context.min.js\"\u003e\u003c/script\u003e\n```\n\nThen, simply call `MonoContext`'s static functions:\n\n```ts\nMonoContext.count('myCount');\nMonoContext.count('myOtherCount');\n\nMonoContext.getCount('myCount'); // 1\n\nMonoContext.setState({\n  some: 'data',\n  that: {\n    is: 'useful'\n  }\n});\n\nMonoContext.getStateValue('some'); // data\n\nconst {\n  that,\n} = MonoContext.getState(); // that == { is: 'useful' }\n\nMonoContext.resetState();\nMonoContext.resetCount('myOtherCount');\nMonoContext.resetAllCounts();\n```\n\n## Type-safe states\n\nIt's easy to work with typed states, simply extend the `MonoContextState` as follows:\n\n```ts\nimport { MonoContextState } from '@simplyhexagonal/mono-context';\n\ninterface MyAwesomeState extends MonoContextState {\n  hello: string;\n}\n```\n\nThen, pass your state as the generic type for `setState` and `getState`:\n\n```ts\nMonoContext.setState\u003cMyAwesomeState\u003e({\n  hello: 'world',\n});\n```\n\n![Image depicting VSCode catching a type error when storing a number on the state's \"hello\" string property](https://raw.githubusercontent.com/simplyhexagonal/mono-context/main/assets/type-safe-set-state-example.png)\n\n```ts\nconst state = MonoContext.getState\u003cMyAwesomeState\u003e({\n  hello: 'world',\n});\n```\n\n![Image depicting VSCode intellisense displaying the \"hello\" property as part of the retreived state](https://raw.githubusercontent.com/simplyhexagonal/mono-context/main/assets/type-safe-get-state-example.png)\n\n## State update tracking\n\n`MonoContext` tracks the time at which the state is created and the time of the last update\nperformed over the state:\n\n```ts\nconst { stateCreatedAt } =  MonoContext.getState();\n\nsetTimeout(() =\u003e {\n  MonoContext.setState({some: 'thing'}); // only `setState()` updates `stateUpdatedAt`\n  const { stateUpdatedAt } =  MonoContext.getState();\n\n  const timeElapsed = stateUpdatedAt.valueOf() - stateCreatedAt.valueOf();\n\n  console.log(timeElapsed);\n  // 1003\n}, 1000);\n```\n\n**NOTE:** the `count` method does **NOT** update the `stateUpdatedAt` value.\n\n**NOTE:** the `stateUpdatedAt` value will **only** be updated if a value is **actually stored** in\nthe state (i.e. `setState({})` does **NOT** update the `stateUpdatedAt` value).\n\n## Reserved state keys\n\nThe following keys will print a warning on console and be ignored by `MonoContext` when using\n`setState` to try and store them:\n\n- counts\n- stateCreatedAt\n- stateUpdatedAt\n\n## Static vs instance\n\nThere is **no** reason to instantiate `MonoContext`.\n\nIf you do so by mistake, `MonoContext` will log to console an inoffensive warning message advicing\nagainst this practice.\n\nWith this said, we understand that there will be edge-cases (there always are) in which you might\nneed to instantiate `MonoContext` in order to avoid some linting, standards, or whatnot that go\nagainst the usage of statically defined functionality, and thus, will want to turn off the\naforementioned warning.\n\nIn such cases just instantiate `MonoContext` with a `true` argument as follows:\n\n```ts\nconst monoContext = new MonoContext(true);\n```\n\nThis instance is a singleton, so you never have to worry what might happen if you instantiate\n`MonoContext` more than once.\n\nAnd all static functions are available as instance functions, which are [thoroughly tested](https://github.com/simplyhexagonal/mono-context/blob/main/src/test.ts) to make\nsure will perform exactly the same.\n\n## Contributing\n\nYes, thank you! This plugin is community-driven, most of its features are from different authors.\nPlease update the tests and don't forget to add your name to the `package.json` file.\n\n## Contributors ✨\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://jeanlescure.cr\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/3330339?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eJean Lescure\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"#maintenance-jeanlescure\" title=\"Maintenance\"\u003e🚧\u003c/a\u003e \u003ca href=\"https://github.com/simplyhexagonal/mono-context/commits?author=jeanlescure\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"#userTesting-jeanlescure\" title=\"User Testing\"\u003e📓\u003c/a\u003e \u003ca href=\"https://github.com/simplyhexagonal/mono-context/commits?author=jeanlescure\" title=\"Tests\"\u003e⚠️\u003c/a\u003e \u003ca href=\"#example-jeanlescure\" title=\"Examples\"\u003e💡\u003c/a\u003e \u003ca href=\"https://github.com/simplyhexagonal/mono-context/commits?author=jeanlescure\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-enable --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\n## License\n\nCopyright (c) 2021-Present [MonoContext Contributors](https://github.com/simplyhexagonal/mono-context/#contributors-).\u003cbr/\u003e\nLicensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimplyhexagonal%2Fmono-context","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimplyhexagonal%2Fmono-context","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimplyhexagonal%2Fmono-context/lists"}