{"id":13632328,"url":"https://github.com/ar-nelson/osmosis-js","last_synced_at":"2025-04-24T17:31:05.282Z","repository":{"id":46924174,"uuid":"323232235","full_name":"ar-nelson/osmosis-js","owner":"ar-nelson","description":"JS reference implementation of Osmosis, a JSON data store with peer-to-peer background sync","archived":false,"fork":false,"pushed_at":"2021-03-22T22:38:57.000Z","size":540,"stargazers_count":53,"open_issues_count":1,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-03T08:37:12.716Z","etag":null,"topics":["crdt","json","network","p2p","reactive-programming"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ar-nelson.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":"2020-12-21T04:42:21.000Z","updated_at":"2024-06-20T17:05:05.000Z","dependencies_parsed_at":"2022-08-31T21:14:03.190Z","dependency_job_id":null,"html_url":"https://github.com/ar-nelson/osmosis-js","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ar-nelson%2Fosmosis-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ar-nelson%2Fosmosis-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ar-nelson%2Fosmosis-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ar-nelson%2Fosmosis-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ar-nelson","download_url":"https://codeload.github.com/ar-nelson/osmosis-js/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250674258,"owners_count":21469185,"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":["crdt","json","network","p2p","reactive-programming"],"created_at":"2024-08-01T22:02:59.946Z","updated_at":"2025-04-24T17:31:04.730Z","avatar_url":"https://github.com/ar-nelson.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# Osmosis\n\nAn in-process JSON database with automatic peer-to-peer background\nsynchronization between devices on a local network. Keep your apps in sync\nwithout a cloud!\n\n\u003e **🚧 WORK-IN-PROGRESS 🚧: Osmosis is not yet usable. The npm modules mentioned\n\u003e in this README are not available yet. Watch this space for updates.**\n\nThe library itself is `@nels.onl/osmosis-js`. Separate modules exist for the\nJSON CRDT datastore (`@nels.onl/osmosis-store-js`) and the network stack\n(`@nels.onl/osmosis-net-js`).\n\n\u003e The library is called `osmosis-js` instead of `osmosis` because this Node\n\u003e module is a non-portable reference implementation. Osmosis is really meant for\n\u003e mobile apps, but, unfortunately, this Node module with native dependencies is\n\u003e not easily usable on mobile.\n\u003e\n\u003e My plan is to develop a portable native implementation in C, and to provide\n\u003e bindings for several languages, including a Node wrapper at\n\u003e `@nels.onl/osmosis`. See the [Roadmap](#roadmap) for more information.\n\n## Rationale\n\nConsider an app with both mobile and desktop versions, like a notetaking app,\na password manager, or a podcast client. The user would like to keep its state\nsynced between a laptop and a phone.\n\nNormally, this requires a cloud service to store the synced data. But, with\nOsmosis, after pairing the mobile and desktop apps once, they will detect each\nother every time they connect to the same Wi-Fi network, and will create\na direct p2p connection to sync data.\n\nOsmosis synchronization is automatic, encrypted, and relatively safe from\nconflicts. Updates are modeled with a JSON [CRDT][crdt]. It is possible for\nupdates to fail, but this can only happen if a path is updated after its parent\nis deleted, or if non-typesafe changes are made (e.g., replacing an object with\nan array).\n\n## Usage\n\n### Database\n\nAn Osmosis database is a single JSON object. Osmosis has the API of a reactive\ndata store like Redux:\n\n- Updates are [Actions](#actions), which are JSON messages.\n- Queries are listener callbacks, called every time the data at a given path is\n  updated. Paths are expressed as [JsonPath][jsonpath].\n\nActions are sent with `dispatch`, and queries are created with `subscribe`.\n\n```javascript\nimport Osmosis from '@nels.onl/osmosis-js';\n\nconst store = new Osmosis({\n  appId: '82feacaf-3ae0-41c2-9aff-dd7dcb0a2a80'\n});\n\n// Create an array at the top-level key \"numbers\"\nstore.dispatch({\n  action: 'InitArray',\n  path: '$.numbers'\n});\n\n// Print the list of numbers every time it changes\nstore.subscribe('$.numbers', (numbers) =\u003e {\n  console.log(`Numbers: ${numbers.join(', ')}`);\n});\n\n// Add some entries to the numbers array\nstore.dispatch({ action: 'InsertUnique', path: '$.numbers', payload: 1 });\nstore.dispatch({ action: 'InsertUnique', path: '$.numbers', payload: 2 });\nstore.dispatch({ action: 'InsertUnique', path: '$.numbers', payload: 3 });\n```\n\n### Pairing\n\nThe pairing process looks like this:\n\n1. One device (the _Requester_) dispatches a `RequestPair` action, with\n   a payload containing the UUID of another device (the _Responder_) and\n   a secret string (usually a randomly-generated PIN).\n2. The Requester displays the secret to the user.\n3. The Responder receives the request and prompts the user for the secret.\n4. The user enters the secret.\n5. The Responder dispatches an `AcceptPair` action, with a payload containing\n   the Requester's UUID and the secret.\n6. The Requester receives the response. The devices are now paired, and will\n   begin syncing in the background.\n\nA basic pairing setup for Osmosis, which can send and receive pairing requests,\nlooks like this:\n\n```javascript\nimport Osmosis from '@nels.onl/osmosis-js';\n\nconst store = new Osmosis({\n  appId: 'f8e8eec8-d219-4e6e-b8fc-3363504860ff',\n  peerName: 'Peer 1'\n});\n\nlet peers = [];\n\nstore.subscribeMeta('$.peers', (peerList) =\u003e {\n  peers = peerList;\n});\n\nstore.on('pairRequest', (evt) =\u003e {\n  const secret = prompt(\n    `Received pair request from ${evt.peerName} (${evt.peerId}). ` +\n    'Please enter the PIN displayed on this device.'\n  );\n  if (secret) {\n    store.dispatch({\n      action: 'AcceptPair',\n      payload: {\n        uuid: evt.uuid,\n        secret\n      }\n    });\n  } else {\n    store.dispatch({\n      action: 'RejectPair',\n      payload: evt.uuid\n    });\n  }\n});\n\nstore.on('pairResponse', (evt) =\u003e {\n  if (evt.accepted) {\n    console.log(`Pair request to ${evt.peerName} accepted`);\n  } else {\n    console.log(`Pair request to ${evt.peerName} rejected`);\n  }\n});\n\nfunction pairWithFirstPeer() {\n  if (!peers.length) return;\n\n  // Generate a 4-digit PIN\n  const secret = `${Math.floor(Math.random() * 10000)}`.padStart(4, '0');\n\n  store.dispatch({\n    action: 'RequestPair',\n    payload: {\n      id: peers[0].uuid;\n      secret\n    }\n  });\n\n  alert(`Pairing PIN: ${secret}`);\n}\n```\n\nThere's a lot going on in this example, so let's take it one step at a time.\n\n`subscribeMeta` is like `subscribe`, except that it queries the Osmosis store's\n[Metadata](#metadata) instead of its data. Metadata includes things like network\nstate, action history, and failures. All of this data is available as one\nqueryable JSON object. `peers` is the list of visible peers, which updates every\ntime a peer enters or leaves the network.\n\nAn Osmosis store also emits [Events](#events), which can be listened to with the\n`on` method. Events are mostly network-related.\n\nPairing Osmosis instances requires a few event listeners:\n\n- `pairRequest` - fires when a pair request is received.\n- `pairResponse` - fires when a pair request is accepted or rejected.\n\nFinally, the pairing process uses some actions that operate on network state\ninstead of data. `RequestPair`, `AcceptPair`, and `RejectPair`, among others,\ncontrol the pairing process but are not part of the Osmosis store's history.\nThese actions are documented in [Network Actions](#network-actions).\n\n## How it Works\n\n### Discovery\n\nThe first time an Osmosis instance is created, it generates a Peer ID and\na public/private keypair. This data should be saved to disk, and loaded every\ntime this Osmosis instance is started.\n\nOsmosis uses two kinds of sockets: broadcast UDP sockets that send heartbeat\npackets, and unicast TCP sockets that send [Monocypher][monocypher]-encrypted,\n[zstandard][zstd]-compressed [JSON-RPC][jsonrpc] messages.\n\nWhen a TCP socket is opened, each side sends its 16-byte Peer ID to the other.\nAll subsequent TCP messages are formatted like this:\n\n```txt\n[..][......................][.................... . . .\n|   |                       |\n|   Nonce (24 bytes)        Message (abs(Length) bytes)\n|\nLength (4 bytes, big-endian 32-bit int)\n```\n\nIf `Length` is negative, it signifies that the decrypted contents of `Message`\nare compressed. The length of `Message` is the absolute value of `Length`.\n`Message` is encrypted using Monocypher's AEAD and a shared key computed from\none peer's private key and the other's public key. Inside is a JSON-RPC message,\nwhich may be compressed with zstandard.\n\nEach Osmosis instance starts a TCP service, called the Gateway Service, on\na random ephemeral port. This service receives pair requests and connection\nrequests.\n\nOsmosis sends out a UDP heartbeat packet every minute, on the broadcast address\nof every broadcast-compatible IPv4 network interface, on a port computed from\nthe first 2 bytes of the App ID:\n\n```javascript\nheartbeatPort = appId[0] | appId[1] \u003c\u003c 8 | 0x8000; // always \u003e= 32768\n```\n\nThe heartbeat is a binary message, with this layout:\n\n```txt\n[..][..............][..............][..............................][][... . . .\n|   |               |               |                               | |\n|   App ID (16B)    Peer ID (16B)   Public key (32 bytes)           | |\n|                                                                   | |\nMagic number (4 bytes: 0x054d0515)                                  | |\n                                                                    | |\n                               Port (2 bytes, big-endian 16-bit uint) |\n                                                                      |\n               Peer name (UTF-8 string prefixed with 8-bit uint length)\n```\n\nWhen peer A receives peer B's heartbeat, it is only considered valid if the\nfollowing conditions apply:\n\n- A and B have the same App ID\n- A and B have different Peer IDs\n- B's Gateway port is \u003e 1024\n- A has not recently seen a heartbeat with B's Peer ID from a different IP\n  address, or with a different port or public key\n- If A is already paired with B, B's public key has not changed since pairing\n\nWhen A receives a valid heartbeat from B, B is added to A's list of visible\npeers; it is removed if A does not receive a new heartbeat for 3 minutes.\n\nPairing is done using the Gateway Service. Once two peers are paired, they will\nconnect as soon as one receives a valid heartbeat from the other. Connecting\ninvolves one peer creating a new TCP Connection Service on a random ephemeral\nport, with a random keypair, then sending this port and public key to the other\npeer, which responds with its own newly-generated public key. Once one peer is\nconnected to the other's Connection Service, they synchronize their lists of\npaired peers and begin exchanging state updates.\n\n### Store\n\nThe Osmosis store is a [Causal Tree][causal-tree] JSON [CRDT][crdt] made up of\ntwo parts:\n\n- A list of Actions, each of which is tagged with an ID (peer UUID + Lamport number).\n- A list of Save Points, which are copies of the full JSON tree at specific\n  points in time, with exponentially increasing gaps (first few are ~4 actions\n  apart, then 8, then 16, and so on).\n\nThe Save Points are a performance optimization to avoid reconstructing the full\nJSON tree from scratch every time a merge happens.\n\nWhen a new Action is dispatched, its `path` is compiled to a JSON\nrepresentation. This may split the Action into multiple Actions. Each `path` is\nthen further compiled to a Causal Tree representation by replacing the longest\nprefix referring to an existing location with that location's ID. Actions are\nonly stored or synced in this fully-compiled form.\n\n```javascript\n// Given this Osmosis store state:\n{ foo: { bar: { baz: 1 } } }\n\n// Composed of these (uncompiled) actions:\n[{\n  action: \"InitObject\",\n  path: \"$.foo\",\n  id: { author: \"ef6d1530-5ed7-4330-abb9-4d4accd1ead5\", index: 1 }\n}, {\n  action: \"InitObject\",\n  path: \"$.foo.bar\",\n  id: { author: \"ef6d1530-5ed7-4330-abb9-4d4accd1ead5\", index: 2 }\n}, {\n  action: \"Set\",\n  path: \"$.foo.bar.baz\",\n  payload: 1,\n  id: { author: \"ef6d1530-5ed7-4330-abb9-4d4accd1ead5\", index: 3 }\n}]\n\n// Then, this new action:\n{ action: \"Set\", path: \"$.foo.bar.qux\", payload: 2 }\n\n// Has a path that compiles to this:\n[{ type: \"Key\", query: \"foo\" },\n { type: \"Key\", query: \"bar\" },\n { type: \"Key\", query: \"qux\" }]\n\n// Which can be further compiled to this Causal Tree form:\n[{\n  type: \"Id\",\n  query: { author: \"ef6d1530-5ed7-4330-abb9-4d4accd1ead5\", index: 2 }\n}, {\n  type: \"Key\",\n  query: \"qux\"\n}]\n```\n\n### Synchronization\n\nWhen two Osmosis instances establish a connection, they send each other a State\nSummary. The State Summary consists of a State Hash and a mapping from each\nknown peer's Peer ID to its latest Lamport number.\n\nThe State Hash is a cumulative hash of the IDs of an instance's entire Action\nhistory, in order. It is computed recursively:\n\n```txt\nStateHash(0) = 00000000000000000000000000000000...\n\nStateHash(i + 1) = Blake2B([........ . . . ........][......][..............])\n                                                   |       |               |\n                             StateHash(i) (64 bytes)       |               |\n                                                           |               |\n  Lamport number of Actions[i] (8 bytes, big-endian 64-bit uint)           |\n                                                                           |\n                                            Peer ID of Actions[i] (16 bytes)\n```\n\nIf two peers have different State Hashes, they are out of sync and must be\nsynchronized.\n\nAction synchronization uses a [Causal Tree][causal-tree] algorithm. Actions are\nsorted by ID, first by Lamport timestamp and then by peer UUID. Synchronization\ninvolves rolling back to the latest Save Point that is earlier than all new\nActions, then applying all Actions after that point, while inserting new Actions\nin sorted order.\n\nIf a merged action fails, the failure is added to the `failures` metadata key\nand the action is skipped, but the remaining actions are applied as usual.\n\nOsmosis supports two kinds of sync operations:\n\n1. **Live Sync:** While an Osmosis instance is connected to one or more\n   peers, newly dispatched Actions are sent to all connected peers as soon as\n   they are dispatched.\n\n   Peers return their new State Hashes after a Live Sync update is applied. If,\n   after a Live Sync, a peer's State Hash differs from the local State Hash,\n   then a Full Sync is performed.\n\n2. **Full Sync:** When a connection between two peers is opened, or whenever\n   a Live Sync results in an inconsistent state, peers will negotiate a sync\n   session to ensure that their State Hashes match.\n\n   An Osmosis instance may only perform one Full Sync at a time. While a Full\n   Sync is in progress, all other sync requests and dispatched actions will be\n   enqueued until the sync has completed.\n\n   A Full Sync consists of two steps, performed by both peers:\n\n   1. Peer A looks at every Lamport number in Peer B's State Summary, and sends\n      Peer B a list of every Action whose Lamport number is greater than Peer\n      B's latest Lamport number for that Action's Peer ID.\n\n   2. If Peers A and B still do not have the same State Hash, Peer A sends\n      a list of its Save Points to Peer B, and Peer B returns the latest Action\n      ID in its history that matches a Save Point's ID and State Hash.\n\n      If a shared ID is found, Peer A sends Peer B all of its Actions with an ID\n      later than the shared ID. Peer B rewinds its state to the timestamp of the\n      shared ID, then applies all Actions from both A and B that are later than\n      the shared ID.\n\n      If no shared ID is found, the peers have no history in common. This can\n      happen if one peer has garbage-collected too much of its history. This is\n      an unrecoverable error, and the peers will unpair automatically.\n\n## API\n\n### Methods\n\n#### `new Osmosis(config)`\n\nCreates a new Osmosis store from the given configuration object.\n\n`config` is an object with the following properties, all of which (except\n`appId`) are optional:\n\n- `appId`: A UUID that uniquely identifies this Osmosis app. Osmosis will only\n  detect peers with the same `appId`.\n- `peerName`: A human-readable string to identify this device when pairing. Will\n  appear in the UI of other devices when listing peers. Defaults to something\n  based on the device's hostname.\n- `saveState`: An instance of the `SaveState` class, which controls how the\n  store's data is persisted to disk. `InMemorySaveState` (the default) and\n  `JsonFileSaveState` are aways available. Additional modules are planned for\n  `SqliteSaveState` and `LevelDbSaveState`.\n- `minHistory`: An integer, defaults to `0`. The minimum number of past actions\n  that Osmosis will preserve, even if it knows it doesn't need them (i.e., all\n  known peers are synced). If you intend to do anything with Osmosis's history\n  besides syncing, this should be set to something other than `0`. If it is\n  `-1`, history will never be deleted.\n- `maxHistory`: An integer, defaults to `32768`. The maximum number of actions\n  that Osmosis will remember while preserving history for a pending sync that\n  may need that history. If Osmosis accumulates more than this amount of history\n  without syncing, it will start deleting history, which could make future\n  synchronization impossible. If it is `-1`, there is no limit.\n- `visibleToPeers`: Whether this Osmosis instance is initially visible to other,\n  non-paired peers on the network. Defaults to `true`.\n- `syncEnabled`: Whether this Osmosis instance is initially able to connect to\n  paired peers and sync. Defaults to `true`.\n\n#### `Osmosis.dispatch(action, returnFailures = false)`\n\nSubmits an [Action](#actions) to the store. Throws an `OsmosisFailureError` if\nthe action reports any failures. If `returnFailures` is true, it does not throw,\nand instead returns an array of failures, which will be empty if the action did\nnot cause any failures.\n\n#### `Osmosis.subscribe(path, variables = [], callback)`\n\nSubscribes to update events on the data queried by the JsonPath string `path`.\n`callback` is called with an array of JSON values, which may be empty if there\nis nothing at the subscribed path. Whenever the subscribed data is updated,\n`callback` is called again with a new array of query results.\n\n`variables`, if present, is an array or object containing variables to\ninterpolate into `path`.\n\nReturns an object with one method, `cancel`. Calling `cancel()` on this object\nwill cancel the subscription, and `callback` will not be called again.\n\n#### `Osmosis.subscribeMeta(path, variables = [], callback)`\n\nSubscribes to update events on the [Metadata](#metadata) queried by the JsonPath\nstring `path`. `callback` is called with an array of JSON values, which may be\nempty if there is nothing at the subscribed path. Whenever the subscribed data\nis updated, `callback` is called again with a new array of query results.\n\n`variables`, if present, is an array or object containing variables to\ninterpolate into `path`.\n\nReturns an object with one method, `cancel`. Calling `cancel()` on this object\nwill cancel the subscription, and `callback` will not be called again.\n\n#### `Osmosis.queryOnce(path, variables = [])`\n\nSynchronously queries the store using the JsonPath string `path`, and returns\nthe result as an array of JSON values.\n\n`variables`, if present, is an array or object containing variables to\ninterpolate into `path`.\n\n#### `Osmosis.queryOnceMeta(path, variables = [])`\n\nSynchronously queries the store's [Metadata](#metadata) using the JsonPath\nstring `path`, and returns the result as an array of JSON values.\n\n`variables`, if present, is an array or object containing variables to\ninterpolate into `path`.\n\n#### `Osmosis.on(eventName, callback)`\n\nRegisters an event listener for the [Event](#events) `eventName`. `callback` is\ncalled with one argument (the event) whenever an event of this type occurs.\nThrows an exception if `eventName` is not a known event type.\n\nReturns an object with one method, `cancel`. Calling `cancel()` on this object\nwill cancel the event listener, and `callback` will not be called again.\n\n### Actions\n\n#### Data Actions\n\n##### `Set`\n\n    { action: \"Set\", path: JsonPath string, payload: JSON value }\n\n*To be written.*\n\n##### `Delete`\n\n    { action: \"Delete\", path: JsonPath string }\n\n*To be written.*\n\n##### `InitObject`\n\n    { action: \"InitObject\", path: JsonPath string }\n\n*To be written.*\n\n##### `InitArray`\n\n    { action: \"InitArray\", path: JsonPath string }\n\n*To be written.*\n\n##### `InsertBefore`\n\n    { action: \"InsertBefore\", path: JsonPath string, payload: JSON value }\n\n*To be written.*\n\n##### `InsertAfter`\n\n    { action: \"InsertAfter\", path: JsonPath string, payload: JSON value }\n\n*To be written.*\n\n##### `InsertUnique`\n\n    { action: \"InsertUnique\", path: JsonPath string, payload: JSON value }\n\n*To be written.*\n\n##### `Add`\n\n    { action: \"Add\", path: JsonPath string, payload: number }\n\n*To be written.*\n\n##### `Multiply`\n\n    { action: \"Multiply\", path: JsonPath string, payload: number }\n\n*To be written.*\n\n##### `Move`\n\n    { action: \"Move\", path: JsonPath string, payload: JsonPath string }\n\n*To be written.*\n\n##### `Copy`\n\n    { action: \"Copy\", path: JsonPath string, payload: JsonPath string }\n\n*To be written.*\n\n##### `Transaction`\n\n    { action: \"Transaction\", payload: array of data actions }\n\n*To be written.*\n\n#### Network Actions\n\n##### `RequestPair`\n\n    { action: \"RequestPair\", payload: { id: UUID string, secret: string } }\n\n*To be written.*\n\n##### `AcceptPair`\n\n    { action: \"AcceptPair\", payload: { id: UUID string, secret: string } }\n\n*To be written.*\n\n##### `RejectPair`\n\n    { action: \"RejectPair\", payload: UUID string }\n\n*To be written.*\n\n##### `Unpair`\n\n    { action: \"Unpair\", payload: UUID string }\n\n*To be written.*\n\n##### `SetVisibleToPeers`\n\n    { action: \"SetVisibleToPeers\", payload: boolean }\n\n*To be written.*\n\n##### `SetSyncEnabled`\n\n    { action: \"SetSyncEnabled\", payload: boolean }\n\n*To be written.*\n\n### Events\n\n#### `pairRequest`\n\n*To be written.*\n\n#### `pairResponse`\n\n*To be written.*\n\n### Metadata\n\n#### `actions`\n\nAn array containing the data actions that make up this Osmosis store's history,\nexcept for actions that have been removed by garbage collection.\n\nActions are stored in a different format than the format used by `dispatch`.\nEach action has a `timestamp` and an `id`, JsonPath strings are compiled into\nJSON objects, and actions with complex paths may be split up into multiple\nactions.\n\nNote that, by default, Osmosis's garbage collection is extremely aggressive.\nHistory is only preserved if it is absolutely necessary for a pending sync, and\nis deleted as soon as all known paired peers are synced. If you want more\nhistory (for example, as an Undo feature), set the `minHistory` and `maxHistory`\nconfig parameters when constructing a `new Osmosis` object.\n\n#### `config`\n\nThe configuration options for this Osmosis instance, which are a combination of\nsome options set in the `new Osmosis` constructor and these additional options\nthat are saved to the `SaveState`'s file:\n\n- `peerId`\n- `publicKey`\n- `privateKey`\n\n#### `failures`\n\nAn array of all failures caused by all actions in this store's history. If\nsynced actions from other peers cause failures, this is the only place they will\nbe reported. Each failure has an `id` linking it to the action that caused it.\n\nWhen an action is deleted by garbage collection, its failures will also be\nremoved from this array.\n\n#### `pairings`\n\nAn array of all paired peers, whether they are currently visible or not.\n\n#### `peers`\n\nAn array of all peers currently visible on the network. Peers are visible only\nif they share the same `appId`.\n\n## Roadmap\n\n- [ ] MVP reference implementation in Node\n- [ ] RxJS wrapper library\n- [ ] Sample app\n- [ ] [Chronofold][chronofold]-like string actions\n- [ ] LevelDB support\n- [ ] SQLite support\n- [ ] Blobs and blob actions\n- [ ] Standalone database app\n- [ ] Port to C\n- [ ] More language bindings:\n  - [ ] Node\n  - [ ] Cordova plugin\n  - [ ] Java (+ Android)\n  - [ ] Python\n  - [ ] Go\n- [ ] At-rest encryption support\n- [ ] More sample apps:\n  - [ ] Notes app\n  - [ ] Password manager\n  - [ ] Podcast client\n  - [ ] Dropbox clone\n\n## License\n\nCopyright \u0026copy; 2020-2021 Adam Nelson\n\nOsmosis is distributed under the [Blue Oak Model License][blue-oak]. It is\na MIT/BSD-style license, but with [some clarifying improvements][why-blue-oak]\naround patents, attribution, and multiple contributors.\n\n[jsonpath]: https://goessner.net/articles/JsonPath/\n[causal-tree]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.627.5286\n[crdt]: https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type\n[chronofold]: https://arxiv.org/abs/2002.09511\n[monocypher]: https://monocypher.org\n[zstd]: https://facebook.github.io/zstd/\n[jsonrpc]: https://www.jsonrpc.org/\n[blue-oak]: https://blueoakcouncil.org/license/1.0.0\n[why-blue-oak]: https://writing.kemitchell.com/2019/03/09/Deprecation-Notice.html\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Far-nelson%2Fosmosis-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Far-nelson%2Fosmosis-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Far-nelson%2Fosmosis-js/lists"}