npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

canop

v0.4.1

Published

Convergent algorithm for collaborative text.

Downloads

33

Readme

canop

Transport-agnostic operation-rich intention-preserving client-server JSON synchronization protocol.

Work in progress. Status: alpha. Currently only supports string values.

A simple client-server example is available, using adapters for WebSocket and CodeMirror.

Exploratory API description:

var canop = require('canop');
var server = new canop.Server({ data: {some: 'data'} });
// send must throw if it cannot send the message.
var client = new canop.Client({ send: function(message) {} });
server.addClient({
  send: function(message) { client.receive(message); },
  onReceive: function(receive) { client.send = receive; },
});

// You must emit syncing when the connection opens,
// and unsyncable when it closes.
client.emit('syncing');
client.once('ready', function() {…});

client.get(['some']);  // 'data'
client.add(['some'], 0, 'modified ');  // 'modified data'
client.move([], 'some', ['final']);  // {final: 'modified data'}
client.on('signal', function(event) { event.clientId, event.data });
// Typically, sel is a list of selection ranges, where the first offset is the
// cursor (which moves the selection with shift+arrow).
// Also, connected is signaled when a node joins or leaves.
client.signal({connectred: true, name: 'Grace', focus: ['some'], sel: [[9,9]]});
client.clientCount  // Number of clients currently connected.
client.signalFromClient[clientId]  // aggregate of a client’s signals

// This event has the following keys:
// - changes: Array of [path, action type, parameters…]
// - posChanges: Array of canop.PosChange
var changeListener = function(event) {};
// Changes from other computers.
client.on('change', changeListener);
server.on('change', changeListener);
// Changes from this computer.
client.on('localChange', changeListener);
// An immutable copy of the data at a path. TODO
client.on('update', function(updated) {}, {path: ['some']});
client.on('localUpdate', function(updated) {}, {path: ['some']});
client.removeListener('change', changeListener);
// Returns an array of [path, action type, parameters…]
client.undo()
client.redo()

// When all local operations are acknowledged by the server.
client.on('synced', function() {});
// When some local operations are not acknowledged by the server.
client.on('syncing', function() {});
// When we cannot send operations to the server.
client.on('unsyncable', function() {});

// Return a position (eg. the index of a cursor in a string) corresponding to an
// initial position, mapped through a sequence of changes. You can get an
// Array of PosChanges from the 'change' event.
// By default, returns undefined if it cannot give an intention-preserving
// result. If bestGuess is true, returns a guess instead.
canop.changePosition(position, posChanges, bestGuess);

// Create custom operations. TODO
var actionType = client.registerAction(
  canop.type.list | canop.type.string,  // Types this applies to.
  // Put whatever parameters here, modify object.
  function action(object, params) {},
  // Return the reverse operation for a change with this action.
  // It must be so reverse(reverse(change)) == change.
  function reverse(change) {});
client.act([actionType, path, …params]);
// For instance, this works:
client.act([client.action.set, ['final'], 'the end.']);
// You can buffer operations locally to make them into an atomic transaction.
client.actAtomically([
  [client.action.stringAdd, ['some'], 0, 'modified'],
  [client.action.objectMove, [], 'some', ['final']],
]);

Pros

  • Operational Transformations minimizes the number of UI operations at the cost of tremendous implementation complexity that rises exponentially with the number of operations it supports.
  • CRDTs tend to use a lot of memory, and require tricky garbage collection to avoid bloat. Canop does not suffer from memory bloat. Canonical operations are immutable, and so, can be substituted for the equivalent string. Furthermore, CRDT reads require more computation for complex structures. CRDTs are, however, great for peer-to-peer synchronization.
  • Rebase-sync requires local changes to be rebased by changes that the server has accepted, similar to our design. However, it denies changes that are not rebased, causing the potential for long-term divergence if changes happen faster than a client-server round-trip, and preventing the "every individual key press appears instantly" experience that has become concurrent live editing's signature. Canop operations are immediately rebased and accepted by the server.

Limitations

Out of the box, Canop does not support peer-to-peer editing. If the probability of a central server crashing is at odds with your availability requirements, that is not a worry, as you can easily add a server fallback mechanism. On the other hand, if the frequency of edits goes beyond what a single server can handle, the algorithm can be tweaked to be peer-to-peer (at the expense of intention preservation). A description of that algorithm will be available in the paper. You may contribute a patch that implements this algorithm.

Atomic transactions will usually keep their meaning thanks to Canop's intention preservation system. However, it is not guaranteed. For instance, assuming we store the money of two bank accounts in a list. We encode a transaction between them with two operations, add and remove: [20, 50] ① → [15, 50] ② → [15, 55]. We encode a swap between then with two operations that set their values: [20, 50] ③ → [20, 20] ④ → [50, 20]. If those compound operations happen concurrently, they can converge to the following sequence: [20, 50] ① → [15, 50] ③ → [15, 20] ② → [15, 25] ④ → [50, 25]. Semantically, it should converge to [55, 15], which is clearly not the case; one account did not receive its money, and the other received money that was not even in the system.

Use client.actAtomically() to send operations that are part of an atomic transaction in bulk, ensuring that no operation will be executed in the middle, and that the data will never be read within operations. Alternatively, you may add a custom atomic operation (once the primitives for that are implemented).

Contributing

git clone https://github.com/espadrine/canop.git
cd canop
make

TODO

  • Readonly clients
  • Textarea adapter
  • Customizable UI sync debouncing
  • JSON-compatible protocol
  • Array index rebasing
  • Autosave of operations to disk
  • Allow creating user-defined operations