{"id":28043052,"url":"https://github.com/sftsrv/synk","last_synced_at":"2026-05-09T05:32:59.028Z","repository":{"id":214111372,"uuid":"735615068","full_name":"sftsrv/synk","owner":"sftsrv","description":"An offline-first data synchronization library","archived":false,"fork":false,"pushed_at":"2024-03-26T20:59:15.000Z","size":292,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-09T19:11:52.528Z","etag":null,"topics":["database","nodejs","offline-first","web","websocket"],"latest_commit_sha":null,"homepage":"https://jsr.io/@sftsrv/synk","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sftsrv.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":"2023-12-25T14:55:09.000Z","updated_at":"2024-03-26T17:22:01.000Z","dependencies_parsed_at":"2023-12-25T23:32:22.230Z","dependency_job_id":"9915dd20-5e44-4179-866c-099634e61b6c","html_url":"https://github.com/sftsrv/synk","commit_stats":null,"previous_names":["nabeelvalley/synk","sftsrv/synk"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sftsrv/synk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sftsrv%2Fsynk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sftsrv%2Fsynk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sftsrv%2Fsynk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sftsrv%2Fsynk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sftsrv","download_url":"https://codeload.github.com/sftsrv/synk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sftsrv%2Fsynk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32808445,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"online","status_checked_at":"2026-05-09T02:00:06.633Z","response_time":123,"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":["database","nodejs","offline-first","web","websocket"],"created_at":"2025-05-11T15:36:50.257Z","updated_at":"2026-05-09T05:32:58.977Z","avatar_url":"https://github.com/sftsrv.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Synk\n\nA library for developing offline-first web applications based on async data replication and synchronization between clients and the server\n\n## Status\n\n- [x] In memory database implementation\n- [x] In memory replica implementation\n- [x] Connector for synchronizing an in memory database with an in memory replica\n- [x] Connector for synchronizing over a websocket\n- [x] Method for broadcasting deletes\n- [x] IndexedDB replication on browser\n- [ ] Offline staging of changes - allow for different strategies\n- [ ] Better websocket connection and reconnection management, should be able to use an existing websocket library for this, we don't need to be dependant on the implementation since it would be outside of the scope of our implementation\n- [ ] Full-database synchronization example\n- [ ] Testing methodology for websocket implementation\n- [ ] More sophisticated merge handling (CRDT?)\n- [ ] Method for initial sync and cleanup of bad entries\n- [ ] Can we make it possible to sync to a file system, check: https://github.com/streetwriters/notesnook/tree/master/packages/streamable-fs\n\n# Examples\n\n\u003cdetails\u003e\n\nFor usage take a look at the `src/examples` directory which has examples for:\n\n1. `pnpm run example:server` - Example can be found in `src/example/websocket-server` - A Node.js erver using an in-memory db and the websocket interfaces\n\n```ts\nimport { InMemoryOwnedStore } from \"@sftsrv/synk/in-memory\"\nimport { WebSocket, WebSocketServer } from \"ws\"\nimport { Changes } from \"../types\"\nimport { Notify } from \"../async/types\"\nimport { Data } from \"./types\"\n\nlet connections: WebSocket[] = []\n\nconst Command = Changes(Data)\n\nconst db = new InMemoryOwnedStore\u003cData\u003e()\ndb.put({\n  version: 0,\n  type: \"user\",\n  id: \"initial\",\n  name: \"initial user\",\n  age: 5,\n})\n\nconst wss = new WebSocketServer({ port: 8080 }, () =\u003e\n  console.log(\"Server Listening\")\n)\n\nwss.on(\"connection\", (ws) =\u003e {\n  connections.push(ws)\n\n  ws.on(\"message\", (data) =\u003e {\n    const message = Command.safeParse(JSON.parse(data.toString()))\n    if (!message.success) {\n      console.error(message.error)\n      return\n    }\n\n    console.log(message)\n\n    const command = message.data\n\n    db.applyChanges(command)\n    const changes = db.getChanges(command.version)\n    const newVersion = db.getVersion()\n\n    console.log(\"changes to client\", changes)\n\n    // send latest data to the client that submitted the change\n    ws.send(JSON.stringify(changes))\n\n    // send a notification to all other clients that there is new data available\n    const notify: Notify = {\n      type: \"notify\",\n      version: newVersion,\n    }\n\n    connections.forEach((conn) =\u003e conn.send(JSON.stringify(notify)))\n  })\n\n  ws.on(\"open\", () =\u003e {\n    console.log(\"open\")\n    connections.push(ws)\n  })\n\n  ws.on(\"close\", () =\u003e {\n    console.log(\"closed\")\n    connections = connections.filter((conn) =\u003e conn !== ws)\n  })\n\n  ws.on(\"error\", (err) =\u003e {\n    console.log(err)\n    connections = connections.filter((conn) =\u003e conn !== ws)\n  })\n})\n```\n\n2. `pnpm run example:client-produce` - Example can be found in `src/example/websocket-client-produce` - A Node.js client using the `WebsocketNodeJSConnector` that produces and replicates data from the server\n\n```ts\nimport { WebsocketNodeJSClientConnector } from \"@sftsrv/synk/websocket\"\nimport { InMemoryReplicatedStore } from \"@sftsrv/synk/in-memory\"\nimport WebSocket from \"ws\"\nimport { Data } from \"./types\"\n\nconst ws = new WebSocket(\"ws://localhost:8080\")\nconst db = new InMemoryReplicatedStore\u003cData\u003e()\n\nconst connector = new WebsocketNodeJSClientConnector(db, ws, console.log, Data)\n\nsetInterval(() =\u003e {\n  connector.putOne({\n    type: \"post\",\n    id: Date.now().toString(),\n    version: db.getVersion(),\n    userId: \"1\",\n    content: \"some content\",\n  })\n}, 5000)\n```\n\n3. `pnpm run example:client-watch` - Example can be found in `src/example/websocket-client-watch` - A Node.js client using the `WebsocketNodeJSConnector` that replicates data from the server\n\n```ts\nimport { InMemoryReplicatedStore } from \"@sftsrv/synk/in-memory\"\nimport { WebsocketNodeJSClientConnector } from \"@sftsrv/synk/websocket\"\nimport WebSocket from \"ws\"\nimport { Data } from \"./types\"\n\nconst ws = new WebSocket(\"ws://localhost:8080\")\nconst db = new InMemoryReplicatedStore\u003cData\u003e()\n\nconst connector = new WebsocketNodeJSClientConnector(db, ws, console.log, Data)\n```\n\n4. `pnpm run example:client-browser` - Example can be found in `src/example/browser` - Browser app using the `IndexedDBStore` and `WebsocketClientConnector`\n\n```ts\nimport { IndexedDBStore } from \"@sftsrv/synk/indexed-db\"\nimport { WebsocketClientConnector } from \"@sftsrv/synk/websocket\"\nimport { Data } from \"../types\"\n\nconst changes = document.getElementById(\"changes\") as HTMLDivElement\nconst database = document.getElementById(\"database\") as HTMLDivElement\nconst add = document.getElementById(\"add\") as HTMLButtonElement\nconst dlt = document.getElementById(\"delete\") as HTMLButtonElement\nconst input = document.getElementById(\"input\") as HTMLInputElement\n\nconst main = async () =\u003e {\n  console.log(\"Starting\")\n  const db = new IndexedDBStore\u003cData\u003e(\"my-store\")\n\n  const ws = new WebSocket(\"ws://localhost:8080\")\n\n  const connector = new WebsocketClientConnector\u003cData\u003e(db, ws, async (data) =\u003e {\n    const version = await db.getVersion()\n    const store = await db.getAll()\n    changes.innerHTML = JSON.stringify(data, null, 2)\n    database.innerHTML = JSON.stringify({ version, store }, null, 2)\n  })\n\n  add.addEventListener(\"click\", async () =\u003e {\n    connector.putOne({\n      version: await db.getVersion(),\n      type: \"user\",\n      id: new Date().toString(),\n      name: input.value || \"\",\n      age: Date.now(),\n    })\n\n    input.value = \"\"\n  })\n\n  dlt.addEventListener(\"click\", async () =\u003e {\n    const data = await db.getAll()\n    const first = await data[0]\n\n    if (!first) {\n      return\n    }\n\n    await connector.delete(first)\n  })\n}\n\nmain()\n```\n\n\u003c/details\u003e\n\n\u003e Running any client example requires the server to also be running, the relevant commands for running the examples can be found in the `package.json` file\n\n## References\n\n- https://ics.uci.edu/~cs230/lectures20/distrsyslectureset2-win20.pdf\n- https://doc.replicache.dev/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsftsrv%2Fsynk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsftsrv%2Fsynk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsftsrv%2Fsynk/lists"}