{"id":13517031,"url":"https://github.com/logux/docs","last_synced_at":"2025-04-08T09:11:47.579Z","repository":{"id":14081304,"uuid":"75781495","full_name":"logux/docs","owner":"logux","description":"Guide, recipes, and protocol specifications for Logux","archived":false,"fork":false,"pushed_at":"2024-11-25T17:30:00.000Z","size":975,"stargazers_count":359,"open_issues_count":0,"forks_count":39,"subscribers_count":13,"default_branch":"main","last_synced_at":"2025-04-01T08:38:10.622Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://logux.org/","language":"JavaScript","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/logux.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":"2016-12-06T23:44:24.000Z","updated_at":"2025-03-18T02:07:20.000Z","dependencies_parsed_at":"2024-01-06T01:56:18.723Z","dependency_job_id":"1aed6a29-e7bb-4f90-aab9-84d11498f595","html_url":"https://github.com/logux/docs","commit_stats":{"total_commits":585,"total_committers":36,"mean_commits":16.25,"dds":"0.11794871794871797","last_synced_commit":"3a53dde4dc1dc60fda6b2dc721e90ab26a1eb8cd"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/logux%2Fdocs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/logux%2Fdocs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/logux%2Fdocs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/logux%2Fdocs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/logux","download_url":"https://codeload.github.com/logux/docs/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247809964,"owners_count":20999816,"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-08-01T05:01:28.806Z","updated_at":"2025-04-08T09:11:47.545Z","avatar_url":"https://github.com/logux.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# Logux\n\n\u003cimg align=\"right\" width=\"95\" height=\"148\" title=\"Logux logotype\"\n     src=\"https://logux.org/branding/logotype.svg\"\u003e\n\nLogux is a flexible JS framework to make [local-first](https://www.inkandswitch.com/local-first/) **sync engine** with **real-time updates**, **offline-first**, **CRDT**, an **optimistic UI**.\n\n- Instead of other local-first solutions, it is **not a database, but a framework** to build sync engines with specific needs of your project.\n- **No vendor lock-in**. It works with any database and in any cloud.\n- Great **TypeScript** support with end-to-end type checking from client to server.\n- We thought about many production-ready problems like **monitoring**, **scaling**, **outdated clients**, authentication, rich test API.\n- Optional **end-to-end encryption**.\n- Just extra [**7 KB**](https://github.com/logux/client/blob/main/package.json#L141-L148) in client-side JS bundle.\n\nAsk your questions at [community](https://github.com/orgs/logux/discussions) or [commercial support](mailto:logux@evilmartians.com).\n\n[Next chapter](./guide/starting/project.md)\n\n\u003ca href=\"https://evilmartians.com/?utm_source=logux-docs\"\u003e\n  \u003cimg src=\"https://evilmartians.com/badges/sponsored-by-evil-martians.svg\"\n       alt=\"Sponsored by Evil Martians\" width=\"236\" height=\"54\"\u003e\n\u003c/a\u003e\n\n\n## Client Example\n\nUsing [Logux Client](https://github.com/logux/client/):\n\n\u003cdetails open\u003e\u003csummary\u003eReact client\u003c/summary\u003e\n\n```ts\nimport { syncMapTemplate } from '@logux/client'\n\nexport type TaskValue = {\n  finished: boolean\n  text: string\n  authorId: string\n}\n\nexport const Task = syncMapTemplate\u003cTaskValue\u003e('tasks')\n```\n\n```ts\nexport const ToDo = ({ userId }) =\u003e {\n  const tasks = useFilter(Task, { authorId: userId })\n  if (tasks.isLoading) {\n    return \u003cLoader /\u003e\n  } else {\n    return \u003cul\u003e\n      {tasks.map(task =\u003e \u003cli\u003e{task.text}\u003c/li\u003e)}\n    \u003c/ul\u003e\n  }\n}\n```\n\n```ts\nexport const TaskPage = ({ id }) =\u003e {\n  const client = useClient()\n  const task = useSync(Task, id)\n  if (task.isLoading) {\n    return \u003cLoader /\u003e\n  } else {\n    return \u003cform\u003e\n      \u003cinput type=\"checkbox\" checked={task.finished} onChange={e =\u003e {\n        changeSyncMapById(client, Task, id, { finished: e.target.checked })\n      }}\u003e\n      \u003cinput type=\"text\" value={task.text} onChange={e =\u003e {\n        changeSyncMapById(client, Task, id, { text: e.target.value })\n      }} /\u003e\n    \u003c/form\u003e\n  }\n}\n```\n\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003eVue client\u003c/summary\u003e\n\nUsing [Logux Vuex](https://github.com/logux/vuex/):\n\n```html\n\u003ctemplate\u003e\n  \u003ch1 v-if=\"isSubscribing\"\u003eLoading\u003c/h1\u003e\n  \u003cdiv v-else\u003e\n    \u003ch1\u003e{{ counter }}\u003c/h1\u003e\n    \u003cbutton @click=\"increment\"\u003e\u003c/button\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\nimport { computed } from 'vue'\nimport { useStore, useSubscription } from '@logux/vuex'\n\nexport default {\n  setup () {\n    // Inject store into the component\n    let store = useStore()\n    // Retrieve counter state from store\n    let counter = computed(() =\u003e store.state.counter)\n    // Load current counter from server and subscribe to counter changes\n    let isSubscribing = useSubscription(['counter'])\n\n    function increment () {\n      // Send action to the server and all tabs in this browser\n      store.commit.sync({ type: 'INC' })\n    }\n\n    return {\n      counter,\n      increment,\n      isSubscribing\n    }\n  }\n}\n\u003c/script\u003e\n```\n\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003ePure JS client\u003c/summary\u003e\n\nYou can use [Logux Client](https://github.com/logux/client/) API with any framework:\n\n```js\nclient.type('INC', (action, meta) =\u003e {\n  counter.innerHTML = parseInt(counter.innerHTML) + 1\n})\n\nincrease.addEventListener('click', () =\u003e {\n  client.sync({ type: 'INC' })\n})\n\nloading.classList.add('is-show')\nawait client.sync({ type: 'logux/subscribe' channel: 'counter' })\nloading.classList.remove('is-show')\n```\n\n\u003c/details\u003e\n\n\n## Server Example\n\nUsing [Logux Server](https://github.com/logux/server/):\n\n```js\naddSyncMap\u003cTaskValue\u003e(server, 'tasks', {\n  async access (ctx, id) {\n    const task = await Task.find(id)\n    return ctx.userId === task.authorId\n  },\n  async load (ctx, id, since) {\n    const task = await Task.find(id)\n    if (!task) throw new LoguxNotFoundError()\n    return {\n      id: task.id,\n      text: ChangedAt(task.text, task.textChanged),\n      finished: ChangedAt(task.finished, task.finishedChanged),\n    }\n  },\n  async create (ctx, id, fields, time) {\n    await Task.create({\n      id,\n      authorId: ctx.userId,\n      text: fields.text,\n      textChanged: time,\n      finished: fields.finished,\n      finishedChanged: time\n    })\n  },\n  async change (ctx, id, fields, time) {\n    const task = await Task.find(id)\n    if ('text' in fields) {\n      if (task.textChanged \u003c time) {\n        await task.update({\n          text: fields.text,\n          textChanged: time\n        })\n      }\n    }\n    if ('finished' in fields) {\n      if (task.finishedChanged \u003c time) {\n        await task.update({\n          finished: fields.finished,\n          finishedChanged: time\n        })\n      }\n    }\n  }\n  async delete (ctx, id) {\n    await Task.delete(id)\n  }\n})\n\naddSyncMapFilter\u003cTaskValue\u003e(server, 'tasks', {\n  access (ctx, filter) {\n    return true\n  },\n  initial (ctx, filter, since) {\n    let tasks = await Tasks.where({ ...filter, authorId: ctx.userId })\n    return tasks.map(task =\u003e ({\n      id: task.id,\n      text: ChangedAt(task.text, task.textChanged),\n      finished: ChangedAt(task.finished, task.finishedChanged),\n    }))\n  },\n  actions (filterCtx, filter) {\n    return (actionCtx, action, meta) =\u003e {\n      return actionCtx.userId === filterCtx.userId\n    }\n  }\n})\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flogux%2Fdocs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flogux%2Fdocs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flogux%2Fdocs/lists"}