{"id":30770447,"url":"https://github.com/feliperdamaceno/syncrate","last_synced_at":"2025-10-07T12:12:16.527Z","repository":{"id":301805248,"uuid":"1009896149","full_name":"feliperdamaceno/syncrate","owner":"feliperdamaceno","description":"A simple and flexible state management solution for Vanilla JavaScript, TypeScript, and Web Components.","archived":false,"fork":false,"pushed_at":"2025-07-05T17:38:59.000Z","size":83,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-25T21:28:39.869Z","etag":null,"topics":["featured","management","state","store","web-components"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/syncrate","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/feliperdamaceno.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,"zenodo":null}},"created_at":"2025-06-27T23:05:45.000Z","updated_at":"2025-07-24T19:16:15.000Z","dependencies_parsed_at":"2025-06-28T23:44:47.439Z","dependency_job_id":"ec8e002c-4c04-4a7a-8e28-0bbf6611030c","html_url":"https://github.com/feliperdamaceno/syncrate","commit_stats":null,"previous_names":["feliperdamaceno/syncrate"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/feliperdamaceno/syncrate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feliperdamaceno%2Fsyncrate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feliperdamaceno%2Fsyncrate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feliperdamaceno%2Fsyncrate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feliperdamaceno%2Fsyncrate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/feliperdamaceno","download_url":"https://codeload.github.com/feliperdamaceno/syncrate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feliperdamaceno%2Fsyncrate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278772070,"owners_count":26043120,"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-10-07T02:00:06.786Z","response_time":59,"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":["featured","management","state","store","web-components"],"created_at":"2025-09-04T23:05:25.325Z","updated_at":"2025-10-07T12:12:16.512Z","avatar_url":"https://github.com/feliperdamaceno.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Syncrate\n\nA simple and flexible state management solution for Vanilla JavaScript,\nTypeScript, and Web Components.\n\n## Table of contents\n\n- [Installation](#installation)\n- [How to Use](#how-to-use)\n- [Implementation Example](#implementation-example)\n- [License](#license)\n- [Contact](#contact-me)\n\n## Installation\n\n### Using via the CDN (Recommended):\n\nThe easiest way to consume the library is to import it via the CDN by taking\nadvantage of ECMAScript Modules:\n\n```html\n\u003cscript type=\"module\"\u003e\n  import { defineStore } from 'https://cdn.jsdelivr.net/npm/syncrate/+esm'\n\u003c/script\u003e\n```\n\nThe library was designed to be used mostly with Vanilla JavaScript and Vanilla\nWeb Components, so importing via the CDN is the most straightforward approach as\nit does not require any build process.\n\n### Installing as a Package:\n\nAlternatively, you can install the library with your package manager of choice:\n\n```bash\nbun install syncrate\n```\n\nAnd then consume as usual:\n\n```typescript\nimport { defineStore } from 'syncrate'\n```\n\nThis approach is recommended if you already have an existing TypeScript project\nor would like to keep everything type-safe.\n\n## How to Use\n\n**Creating a New Store**\n\nFirst step is to use the `defineStore` function to instantiate a new store:\n\n```typescript\nconst store = defineStore({\n  name: 'todos',\n  state: {\n    todos: [\n      {\n        id: 1,\n        title: 'Learn about syncrate!'\n      },\n      {\n        id: 2,\n        title: 'Share syncrate with my friends :P'\n      }\n    ]\n  }\n})\n```\n\nThe `defineStore` function requires a unique store `name` and an `object` as the\ninitial state.\n\n**Getting values from the Store**\n\nOnce the store is defined, you can call the `store.get` method by passing a\n`callback` function that receives the `state` object as the first parameter:\n\n```typescript\n/* ...code from previous example */\n\nlet todos = []\n\nstore.get((state) =\u003e {\n  todos = state.todos\n})\n\nconsole.log(todos) /* returns an array of todos */\n```\n\nThis will also subscribe the reader `callback` to an internal listeners list,\nwhich will be notified on every store update.\n\nIf you want to unsubscribe from those changes, the `store.get` method returns an\nunsubscribe `callback` function:\n\n```typescript\n/* ...code from previous example */\n\nconst unsubscribe = store.get((state) =\u003e {\n  todos = state.todos\n})\n\nunsubscribe() /* unsubscribes the reader callback from store changes */\n```\n\n**Updating a value from the Store**\n\nSimilarly to `store.get`, the store also provides a `store.set` method that can\nbe used to update the state:\n\n```typescript\n/* ...code from previous example */\n\nstore.set((state) =\u003e ({\n  todos: [\n    ...state.todos,\n    {\n      id: 3,\n      title: 'Give a GitHub star to the syncrate project :P'\n    }\n  ]\n}))\n\nconsole.log(todos) /* will now include all 3 todos */\n```\n\n**Listening for store changes when getting a value**\n\nAs mentioned earlier, a `store.get` callback will be subscribed to store\nchanges. Any logic inside that callback will be executed on every update.\n\n```typescript\n/* ...code from previous example */\n\nstore.get((state) =\u003e {\n  test = state.todos\n\n  /* perform a rerender for example or any operation you need */\n  console.log('changed')\n})\n```\n\n**Listening for store changes from anywhere**\n\nWhen you call `store.set`, a `CustomEvent` is dispatched on the `document`\nannouncing the changes. The event name is a combination of the `syncrate:`\nprefix and the store `name`.\n\nThe event includes the changed `state` in the `event.detail` property.\n\n```typescript\n/* listen from anywhere in the application */\ndocument.addEventListener('syncrate:todos', (event) =\u003e {\n  if (!event.detail) return\n\n  const todos = event.detail\n\n  /* perform any operation you need */\n  alert(todos.at(-1).title)\n})\n```\n\nAll store update events are dispatched on the `document`, and they bubble up by\ndefault. This means any component within the `document` can listen for updates.\n\nAlso, if you are mutating `store` values within Web Components, the\n`CustomEvent` has `composed: true` by default, ensuring the event propagates\nacross the shadow DOM boundary.\n\nListening to `CustomEvent` keeps business logic decoupled from the component,\nespecially when using Web Components.\n\n**CustomEvent options**\n\nIf you want to customize the emitted event behavior, `defineStore` also accepts\nan optional configuration object:\n\n```typescript\nconst store = defineStore({\n  name: 'todos',\n  state: {\n    todos: []\n  },\n  options: {\n    event: {\n      bubbles: true,\n      cancelable: true,\n      composed: true\n    }\n  }\n})\n```\n\n**Persisting state on local or session storage**\n\nThe store also supports persisting state in `sessionStorage` or `localStorage`.\nEnable it via the `options`:\n\n```typescript\nconst store = defineStore({\n  name: 'todos',\n  state: {\n    todos: []\n  },\n  options: {\n    storage: {\n      persist: true,\n      type: 'session' /* default, optional */\n    }\n  }\n})\n```\n\nSetting `persist: true` uses `sessionStorage` by default. To use `localStorage`\ninstead:\n\n```typescript\nconst store = defineStore({\n  name: 'todos',\n  state: {\n    todos: []\n  },\n  options: {\n    storage: {\n      persist: true,\n      type: 'local'\n    }\n  }\n})\n```\n\n## Implementation Example\n\nHere we’ll create a minimal todo app that consumes the store we previously\ncreated within Web Components.\n\n**Defining our Store**\n\n```typescript\nimport { defineStore } from 'https://cdn.jsdelivr.net/npm/syncrate/+esm'\n\nconst store = defineStore({\n  name: 'todos',\n  state: {\n    todos: []\n  },\n  options: {\n    storage: {\n      persist: true,\n      type: 'local'\n    }\n  }\n})\n```\n\n**Creating a Base Class**\n\nThis class includes helper methods to handle shadow DOM and rendering.\n\n```typescript\nclass BaseElement extends HTMLElement {\n  constructor() {\n    super()\n    this.shadow = this.attachShadow({ mode: 'open' })\n    this.css = new CSSStyleSheet()\n    this.html = document.createElement('template')\n  }\n\n  /* helper method used render or rerender the elements */\n  render() {\n    while (this.shadow.firstChild) {\n      this.shadow.lastChild.remove()\n    }\n\n    const host = this.html.content.cloneNode(true)\n    this.shadow.appendChild(host)\n  }\n\n  connectedCallback() {\n    this.shadow.adoptedStyleSheets = [this.css]\n    this.render()\n  }\n}\n```\n\n**The TodoList Component**\n\n```typescript\nclass TodoList extends BaseElement {\n  constructor() {\n    super()\n    this.todos = []\n\n    store.get((state) =\u003e {\n      this.todos = state.todos\n\n      /* call the render method in the reader, and it will be re-called every time the state changes */\n      this.render()\n    })\n  }\n\n  render() {\n    this.css.replace`\n      ul {\n        list-style: none;\n        padding-inline-start: 0;\n      }\n    `\n\n    const items = this.todos?.map((todo) =\u003e `\u003cli\u003e${todo.title}\u003c/li\u003e`).join('')\n\n    /* to prevent security concerns, avoid using innerHTML in a real-world project; this is simply an example */\n    this.html.innerHTML = `\n      \u003cul\u003e\n        ${items}\n      \u003c/ul\u003e\n    `\n\n    super.render()\n  }\n}\n\ncustomElements.define('todo-list', TodoList)\n```\n\n**The TodoUpdater Component**\n\n```typescript\nclass TodoUpdater extends BaseElement {\n  connectedCallback() {\n    super.connectedCallback()\n\n    const button = this.shadow.querySelector('button')\n    const input = this.shadow.querySelector('input')\n\n    button?.addEventListener('click', () =\u003e {\n      this.handleClick(input)\n    })\n  }\n\n  handleClick(input) {\n    if (!input?.value) return\n\n    store.set((state) =\u003e ({\n      todos: [\n        ...state.todos,\n        {\n          id: state.todos.length + 1,\n          title: input.value\n        }\n      ]\n    }))\n\n    input.value = ''\n  }\n\n  render() {\n    this.html.innerHTML = `\n      \u003ch3\u003eAdd Todo:\u003c/h3\u003e\n\n      \u003cdiv\u003e\n        \u003cinput type=\"text\" /\u003e\n        \u003cbutton\u003eAdd\u003c/button\u003e\n      \u003c/div\u003e\n    `\n\n    super.render()\n  }\n}\n\ncustomElements.define('todo-updater', TodoUpdater)\n```\n\n**Using the Components**\n\n```html\n\u003cbody\u003e\n  \u003ctodo-list\u003e\u003c/todo-list\u003e\n  \u003ctodo-updater\u003e\u003c/todo-updater\u003e\n\u003c/body\u003e\n```\n\nAfter mounting all elements, name your todo, press the `Add` button, and watch\nthe magic happen! 🎆\n\n## License\n\nThis is an open-source library and is available under the\n[**MIT License**](LICENSE). You are free to use, modify, and distribute the code\nin accordance with the terms of the license.\n\n## Contact me\n\nLinkedin: [feliperdamaceno](https://www.linkedin.com/in/feliperdamaceno)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeliperdamaceno%2Fsyncrate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffeliperdamaceno%2Fsyncrate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeliperdamaceno%2Fsyncrate/lists"}