{"id":44437143,"url":"https://github.com/levelfourab/otter-js","last_synced_at":"2026-02-12T14:01:05.982Z","repository":{"id":55905792,"uuid":"64754974","full_name":"LevelFourAB/otter-js","owner":"LevelFourAB","description":"Collaborative Realtime Editing via Operational Transformation","archived":false,"fork":false,"pushed_at":"2020-12-08T07:14:54.000Z","size":145,"stargazers_count":4,"open_issues_count":7,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-05T21:56:39.155Z","etag":null,"topics":["collaboration","collaborative-editing","operational-transformation","realtime-editing"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LevelFourAB.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":"2016-08-02T12:29:38.000Z","updated_at":"2024-12-07T21:20:36.000Z","dependencies_parsed_at":"2022-08-15T09:10:15.936Z","dependency_job_id":null,"html_url":"https://github.com/LevelFourAB/otter-js","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/LevelFourAB/otter-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LevelFourAB%2Fotter-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LevelFourAB%2Fotter-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LevelFourAB%2Fotter-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LevelFourAB%2Fotter-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LevelFourAB","download_url":"https://codeload.github.com/LevelFourAB/otter-js/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LevelFourAB%2Fotter-js/sbom","scorecard":{"id":83798,"data":{"date":"2025-08-11","repo":{"name":"github.com/LevelFourAB/otter-js","commit":"7537fda83b2a104a3e8b1d7e2a26157d33221bb9"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"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":"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":"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 0/30 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":"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":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"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":"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":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"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: Apache License 2.0: 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"}}]},"last_synced_at":"2025-08-15T06:29:02.698Z","repository_id":55905792,"created_at":"2025-08-15T06:29:02.698Z","updated_at":"2025-08-15T06:29:02.698Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29367813,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-12T08:51:36.827Z","status":"ssl_error","status_checked_at":"2026-02-12T08:51:26.849Z","response_time":55,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["collaboration","collaborative-editing","operational-transformation","realtime-editing"],"created_at":"2026-02-12T14:00:53.426Z","updated_at":"2026-02-12T14:01:05.975Z","avatar_url":"https://github.com/LevelFourAB.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Otter\n\nOtter is a library to support collaborative realtime editing using\n[Operational Transformation](https://en.wikipedia.org/wiki/Operational_transformation).\nThis repository contains the JavaScript-implementation for use both in a browser\nand in a Node-based server.\n\nA [Java-implementation](https://github.com/LevelFourAB/otter-java) is also available.\n\n## Using Otter\n\nInstall via NPM:\n\n`npm install --save otter-js`\n\nOtter consists of three parts, the operations library, the editing engine and\na high level model. The high level model is what you usually want to use\nunless you are implementing something special.\n\n### Operations\n\nThe lowest level of Otter is the operational transformation algorithms. Otter\nsupports transformations on maps, lists and strings. There is also a combined\ntype that can be used to combine several other types based on unique\nidentifiers. All of these transformations are used together to create the\nhigher level model.\n\n### Engine\n\nThe engine contains editing control. It provides support for creating\neditors on top of any supported operational transformation.\n\n\n```javascript\nconst string = require('otter-js/operations/string');\nconst Editor = require('otter-js/engine/editor');\n\nconst sync = new YourOperationSync(string.newType(), ...);\nconst editor = new Editor(sync);\n\n// Connect and do something with the current version\neditor.connect()\n\t.then(() =\u003e {\n\t\teditor.current.apply(...);\n\t\teditor.on('change', function(e) {\n\t\t\t// This will receive all operations that occur on the editor\n\t\t});\n\t});\n\n\n// Perform an operation\neditor.apply(string.delta()\n\t.retain(currentStringLength)\n\t.insert('abc')\n\t.done()\n);\n```\n\nEditors require a synchronization helper for sending and receiving operations\nfrom a server. There is intentionally no default implementation of such a sync\nas different applications will have different requirements here.\n\nIn the end all operations performed by an editor will end up being handled by\nan instance of `EditorControl`.\n\n```javascript\nconst EditorControl = require('otter-js/engine/editor-control');\n\nconst control = new EditorControl(historyStorage);\n\ncontrol.latest()\n\t.then(latestVersion =\u003e {\n\t\t// Do something with the latest version\n\t});\n\n\n// When an operation is received from a client it needs to be stored and\n// the result needs to be sent back to all clients\ncontrol.store(taggedOperation)\n\t.then(op =\u003e {\n\t\t// Op should be sent back to all clients\n\t});\n```\n\n### Model\n\nThis is the high level API that makes it easier to work with shared editing.\nThe model provides shared objects of different types that are synchronized\nbetween all editors of the model.\n\nHere is a tiny example of working with the model:\n\n```javascript\nconst combined = require('otter-js/operations/combined');\nconst Editor = require('otter-js/engine/editor');\nconst Model = require('otter-js/model');\n\nconst sync = new YourOperationSync(combined.newType(), ...);\nconst editor = new Editor(sync);\nconst model = new Model(editor);\n\nmodel.open()\n\t.then(function() {\n\t\t// Create a new string and store it in the root map\n\t\tconst title = model.newString();\n\t\ttitle.set('Cookies are tasty');\n\t\tmodel.set('title', title);\n\n\t\t// Set a primitive value in the map\n\t\tmodel.set('priority', 10);\n\t});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flevelfourab%2Fotter-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flevelfourab%2Fotter-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flevelfourab%2Fotter-js/lists"}