{"id":13850252,"url":"https://github.com/saarw/flushout","last_synced_at":"2026-03-02T07:35:42.234Z","repository":{"id":57239089,"uuid":"191352341","full_name":"saarw/flushout","owner":"saarw","description":"Flushout is a distributed data model based on event sourcing written in TypeScript.","archived":false,"fork":false,"pushed_at":"2023-01-24T12:25:25.000Z","size":171,"stargazers_count":52,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-21T22:42:50.586Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/saarw.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":"2019-06-11T10:52:29.000Z","updated_at":"2025-04-02T15:17:56.000Z","dependencies_parsed_at":"2023-02-13T21:17:01.437Z","dependency_job_id":null,"html_url":"https://github.com/saarw/flushout","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/saarw/flushout","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saarw%2Fflushout","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saarw%2Fflushout/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saarw%2Fflushout/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saarw%2Fflushout/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saarw","download_url":"https://codeload.github.com/saarw/flushout/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saarw%2Fflushout/sbom","scorecard":{"id":794176,"data":{"date":"2025-08-11","repo":{"name":"github.com/saarw/flushout","commit":"71e7302f8fff5f9419133c8972435e56b2d37359"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 1/25 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 6 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"48 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-6chw-6frg-f759","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-8r6j-v8pm-fqw3","Warn: Project is vulnerable to: MAL-2023-462","Warn: Project is vulnerable to: GHSA-2cf5-4w76-r9qv","Warn: Project is vulnerable to: GHSA-3cqr-58rm-57f8","Warn: Project is vulnerable to: GHSA-g9r4-xpmj-mj65","Warn: Project is vulnerable to: GHSA-q2c6-c6pm-g3gh","Warn: Project is vulnerable to: GHSA-765h-qjxv-5f44","Warn: Project is vulnerable to: GHSA-f2jv-r9rf-7988","Warn: Project is vulnerable to: GHSA-43f8-2h32-f4cj","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-3jfq-g458-7qm9","Warn: Project is vulnerable to: GHSA-r628-mhmh-qjhw","Warn: Project is vulnerable to: GHSA-9r2w-394v-53qc","Warn: Project is vulnerable to: GHSA-5955-9wpr-37jh","Warn: Project is vulnerable to: GHSA-qq89-hq3f-393p","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-23T08:27:37.833Z","repository_id":57239089,"created_at":"2025-08-23T08:27:37.834Z","updated_at":"2025-08-23T08:27:37.834Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29995112,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T01:47:34.672Z","status":"online","status_checked_at":"2026-03-02T02:00:07.342Z","response_time":60,"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":[],"created_at":"2024-08-04T20:01:02.139Z","updated_at":"2026-03-02T07:35:42.215Z","avatar_url":"https://github.com/saarw.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# flushout\n\nFlushout is a distributed data model based on event sourcing. Collaborative applications use it for clients that need responsive interaction without network delay, or need to function offline. \n\nClients interact with a local proxy of a remote master model without accessing the network. They can then periodically flush changes from the proxy to the master in the background when the network is available.\n\n# Installation\n\n```\nnpm install --save flushout\n```\n\n# Example usage\n*Also see the blog post [Building a collaborative React app with Flushout](https://saarw.github.io/dev/2020/03/02/building-a-collaborative-react-app-with-flushout.html)*\n\nA client initializes a proxy with the latest snapshot of a Todo-list model from\nthe backend and applies commands to create and update a Todo-item. The client\nflushes its changes to the master and ends the flush with the master's\nsynchronization response that brings the proxy model up to the state of the\nmaster, including changes flushed by other clients.\n\n```typescript\nconst proxy = new Proxy(latestSnapshot);\n\nconst result = proxy.apply({\n  action: CommandAction.Create,\n  path: ['todos'],\n  props: {\n    title: 'shopping',\n    details: 'coffee'\n  }\n});\n\nproxy.apply({\n  action: CommandAction.Update,\n  path: ['todos', result.createdId],\n  props: {\n    details: 'coffee and cookies'\n  }\n});\n\nconst flush = proxy.beginFlush();\n\n// ... Send the flush to the backend to apply it to the master, use its response to end the flush\n\nproxy.endFlush(flushResponse.sync);\n\n// The current document is always available to the app using\n\nconst currentTodoList = proxy.getDocument();\n```\n\nThe backend initializes a master with latest snapshot from the database and an\noptional command history provider. The application then applies flushed command\nbatches from clients, adding the commands that were applied to the command\nhistory and returning any sync information to the proxy that sent the flush.\n\n```typescript\nconst latest: Snapshot\u003cTodoList\u003e = {\n  commandCount: 0,\n  document: {\n    title: '',\n    todos: {}\n  }\n};\n\nconst master = new Master(latest, {\n  historyProvider: historyStore.createProvider()\n});\n\n// ...\n\nconst flushResponse = await master.apply(flush);\n\nhistoryStore.store(\n  flushResponse.applied.from,\n  flushResponse.applied.completions\n);\n```\n\nFor complete examples, check out the integration tests\nhttps://github.com/saarw/flushout/blob/master/src/index.test.ts\n\n# How it works\n\nFlushout is written in TypeScript and has no other dependencies.\n\nDesign properties\n\n- Minimizes network traffic by only initializing clients with the latest model\n  snapshot and then only send updates\n- Storing update history is optional and command history is kept separate from\n  the model state\n- Flexible about deployment and agnostic about network transport to fit many\n  sorts of backends and protocols\n- Defines communication between client and server as data-only interfaces to\n  support inspection and validation\n- Optimizes for reducing network traffic and load on the server in favor of\n  performing more work on the client\n\n## Document and snapshot\n\nA document in Flushout is a simple JavaScript object that may contain primitive\nfields or additional object fields to form a tree graph. Applications modify the\nmodel by applying commands. A **snapshot** is simply a document and a count of\nhow many commands have been applied to the document.\n\n## Client proxies\n\nClients initialize a Proxy model with the latest snapshot from the backend.\nClients then apply commands to modify the model and perform flush operations to\nsynchronize their state with the remote master.\n\n## Remote master\n\nThe server initialize a Master model with the latest snapshot and an optional\nhistory provider. When the server applies a command batch from a client proxy on \nthe master, the master returns optional synchronization information that lets the \nproxy update its state to that of the master with updates from other clients. \n\nIf the master has a history provider, synchronization responses can consist \nof incremental command batches, otherwise the update is always the latest full snapshot.\n\n### Commands\n\nAll commands include an action and allow specifying a path to where in the\ndocument graph the command should operate (omitting the path uses the root of\nthe document).  \n**Create** - Creates a new object field inside an object in the document graph,\noptionally initializing the object with the values in the command's props\nobject. The field will receive a random ID and the ID is returned to the\napplication.  \n**Update** - Updates an object field in the document graph by setting the values\nspecified in the command's props object.  \n**Delete** - Deletes the object in the document graph.\n\n### History provider\n\nThe master can be initialized with an optional history provider function. This\nlets master produce partial flush synchronization responses with a batches of\ncommands that bring each proxy up to latest state when multiple proxies flush to\nthe master simultanteously. Without the history provider, or with insufficient\nhistory (the amount of history to store is optional), flush synchronizations\nwill include the full model snapshot.\n\n### Interceptor\n\nBoth client and master can be initialized with interceptor functions that can\nvalidate and modify command properties before they are applied to the model.\nThis provides for security and can help resolve certain conflicts, as\ninterceptors at the master can modify command properties based on the current\nstate of the model.\n\n### Storing history, replays, and implementing undo\n\nWhen flushes are applied to the master, the master's response contains an\n**applied** field that returns the batch of **command completions** that were\nsuccessfully applied to the master, along with the master model's command count\nat the start of the batch. This information can be stored as history and each\ndocument can rebuilt from an earlier version by re-applying all command\ncompletions that occurred after the older snapshot's command count. Applications\ncan implement undo by storing older snapshots of the master model and apply all\ncommands to just before the operation that should be undone (it may be necessary\nto implement and pass in a context to the apply-operation that tells the\ninterceptor to disable itself for replays).\n\n### Collisions\n\n- Updates to the same node will simply overwrite each other, but applications\n  that preserve command history may be able to implement more advanced merge\n  operations.\n- Updates on deleted nodes will fail silently.\n- If two proxies perform create commands that create a node with the same ID\n  before flushing to the master, the flush will remap any queued up commands in\n  the second proxy to the new node's ID and notify the application that IDs may\n  have changed.\n\n### Usage notes and error handling\n\nTo not waste performance in Node's single-threaded event loop, Flushout avoids\nfunctional-style protective object copying so you should be careful not to\nmanually modify objects once they have been passed in to Flushout, or to modify\nobjects received from the proxy's and master's getDocument/getSnapshot methods.\n\nClients can recover from errors to send flushes by cancelling their flush and\ntrying again, but this may result in duplicate updates to the Master if the\nerror happened when a successful flush had already been applied but there was a\nproblem sending the response. Applications can add code to track each client's\nlatest command count in the backend for deduplication. Otherwise, fatal errors\nin the client can be recovered by recreating the proxy with the latest snapshot\nfrom the server.\n\n## Background\n\nFlushout was built for https://plotdash.com to offer a Google Docs-like\nexperience where data model is always immediately responsive to the user while\nremote synchronization happens in the background. Flushout was developed to fit\nsystems that use TypeScript as a full-stack language, exploiting the ease of\nsharing code between clients and server while recognizing the importance of\nserver-side performance due to Node's single-threaded event loop.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaarw%2Fflushout","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaarw%2Fflushout","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaarw%2Fflushout/lists"}