• Stars
    star
    222
  • Rank 174,974 (Top 4 %)
  • Language
    JavaScript
  • Created over 9 years ago
  • Updated over 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Like Immutable, but actually Mutable with diffs and versions.

Remutable

Ultra simple key-value map with constant-time identity check, replayable, undo-able and serializable diffing.

Remutable is specifically designed to be nearly as performant as a plain Object, but with patching over the wire in mind.

Implementation is backed by the awesome Immutable-JS lib.

The problem

Immutable-JS is neat, but what happens when you want to share Immutable objects over the wire, across a transport which doesn't understand Javascript reference identity? Remutable solves this by leveraging a simple fact: if you apply the same set of mutations, in the same order, to the same initial state, then the final state will also be the same. Internally, Remutable uses patch hashing to preserve the identity check constant-time, as the hash of the current state is computed by recursively hashing the initial state and each subsequent mutation (think git commit hash).

We can illustrate this pseudocode diagram:

single source of truth (server)       consumer (client)
v1 = Immutable.Map()
send(serialize(v1))       ----------> v1 = unserialize(receive())
(v2, diff1) = v1.set(...)
send(diff1)               ----------> v2 = v1.patch(unserialize(receive()))
(v3, diff2) = 2.set(...)
send(diff2)               ----------> v3 = v2.patch(unserialize(receive()))

Example

const Remutable = require('remutable');
const { Patch } = Remutable;

const robert = 'Robert Heinlein';
const isaac = 'Isaac Asimov';
const dan = 'Dan Simmons';
const bard = 'William Shakespeare';
const manu = 'Emmanuel Kant';

// Let's create an empty Remutable object
const userList = new Remutable();
userList.hash.should.be.exactly(366298937);
userList.dirty.should.not.be.ok;

// And set two values
userList.set('1', robert);
userList.dirty.should.be.ok;
userList.set('2', isaac);

// Head is the latest committed state and is an empty object right now
(userList.head.get('1') === void 0).should.be.ok;
// Working is the most up to date version
userList.working.get('1').should.be.exactly(robert);

// After we commit, head now reflects the changes
userList.commit();
userList.head.get('1').should.be.exactly(robert);
userList.head.get('2').should.be.exactly(isaac);

// We can rollback changes that have no been committed yet
userList.set('3', dan);
userList.working.get('3').should.be.exactly(dan);
userList.rollback();
(userList.working.get('3') === void 0).should.be.ok;

// Now we can serialize it to send it to the server via toJSON
const json = userList.toJSON();
json.should.be.exactly('{"h":2045445329,"v":1,"d":{"1":"Robert Heinlein","2":"Isaac Asimov"}}');

// and read it back from the server via fromJSON
const userListCopy = Remutable.fromJSON(json);
userListCopy.toJSON().should.be.exactly(json);
userListCopy.head.size.should.be.exactly(2);

// In order to communicate changes between the client and the server,
// we get a patch when doing a commit and apply it
userList.set('3', dan);
const patch = userList.commit();
// We can transfer the patch in JSON form
const jsonPatch = patch.toJSON();
jsonPatch.should.be.exactly('{"m":{"3":{"t":"Dan Simmons"}},"f":{"h":2045445329,"v":1},"t":{"h":-195302221,"v":2}}');
const patchCopy = Patch.fromJSON(jsonPatch);
userListCopy.apply(patchCopy);
userListCopy.head.get('3').should.be.exactly(dan);

// It's possible to implement an undo stack by reverting patches
userListCopy.set('4', bard);
const patch1 = userListCopy.commit();
userListCopy.set('5', manu);
const patch2 = userListCopy.commit();
userListCopy.head.has('5').should.be.exactly(true);
userListCopy.head.contains(manu).should.be.exactly(true);
const revert2 = Patch.revert(patch2);
userListCopy.apply(revert2);
userListCopy.head.has('4').should.be.exactly(true);
userListCopy.head.has('5').should.be.exactly(false);
userListCopy.head.contains(bard).should.be.exactly(true);
userListCopy.head.contains(manu).should.be.exactly(false);
const revert1 = Patch.revert(patch1);
userListCopy.apply(revert1);
userListCopy.head.has('4').should.be.exactly(false);
userListCopy.head.contains(bard).should.be.exactly(false);

// Several small patches can be combined into a bigger one
const userListCopy2 = Remutable.fromJSON(userList.toJSON());
userList.set('4', bard);
const patchA = userList.commit();
userList.set('5', manu);
const patchB = userList.commit();
const patchC = Patch.combine(patchA, patchB);
patchC.source.should.be.exactly(patchA.source);
patchC.target.should.be.exactly(patchC.target);
userListCopy2.apply(patchC);
userListCopy2.head.contains(bard).should.be.exactly(true);
userListCopy2.head.contains(manu).should.be.exactly(true);

// We make some changes without recording the patch objects
userList.delete('5');
userList.commit();
userList.delete('4');
userList.commit();
// We can deep-diff and regenerate a new patch object
// It is relatively slow and should be used with care.
const diffPatch = Patch.fromDiff(userListCopy2, userList);
userListCopy2.apply(diffPatch);
userListCopy2.head.has('5').should.be.exactly(false);

// We can also restrict to Consumer and Producer facades.
const userListProducer = userList.createProducer();
const userListConsummer = userList.createConsumer();
userListProducer.should.not.have.property('get');
userListConsummer.should.not.have.property('set');
userListProducer.set('5', manu).commit();
userListConsummer.head.get('5').should.be.exactly(manu);

Usage

This module is written in ES6/7. You will need babel to use it.

API

new Remutable(): new Remutable

Creates a new Remutable object instance.

r.set(key: string, value: any): Remutable

r.get(key): any

r.delete(key): Remutable

Get/set/delete value in the underlying map. Only string keys are allowed. value should be JSON-stringifyiable. After a .set/.delete, .get will return the cached, modified value. .set with value === undefined is equivalent to .delete.

get r.head: Immutable.Map

Returns an Immutable.Map which represents the state after the last commit. You can use all the methods of Immutable.Map, such as r.head.map, r.head.contains, etc.

get r.working: Immutable.Map

Returns an Immutable.Map which represents the cached, up-to-date state, including any mutations since the last commit. You can use all the methods of Immutable.Map, such as r.working.map, r.working.contains, etc.

get r.hash: String

Returns a string hash of the remutable object, so that r1.hash === r2.hash implies that r1 and r2 are identical.

r.commit(): new Patch

Flush the current mutations and returns a patch object. After a commit, memory from the previous commit is lost and you can not rollback unless you explicitly store and revert the patch object.

r.rollback(): Remutable

Cancel all the non-commited mutations.

r.match(patch: Patch)

Checks whether the given patch can be applied to the current remutable.

r.apply(patch: Patch)

Checks that the patch is a fast-forward from the current object version (or throws) and applies the patch efficiently.

r.toJSON(): string/r.toJS(): Object

Returns a compact JSON string representing (resp. a serializable Object) the remutable instance. Can then be passed to Remutable.fromJSON() (resp. Remutable.fromJS()).

This methods is efficently cached so that each subsequent call to toJSON() (resp. toJS()) is nearly instant. The result from toJS() should be considered read-only.

r.createConsumer(): new Remutable.Consumer

Create a new Consumer object, with read-only semantics interface, namely mirrors head, hash and version of r.

r.createProducer(): new Remutable.Producer

Creates a new Producer object, with write-only semantics interface, eg. set, delete, rollback, commit, match and apply. set and apply return the producer instance for chainability and non-leaking.

Remutable.fromJSON(json: String): new Remutable

Reconstructs a fresh Remutable instance from a JSON string representation. It is guaranteed that Remutable.fromJSON(r.toString()).head.is(r.head) === true.

patch.toJSON(): string/patch.toJS(): Object

Returns a compact JSON string (resp. a serializable Object) representing the patch instance. Can then be passed to Remutable.Patch.fromJSON() (resp. Remutable.Patch.fromJS().

This method is efficiently cached so that each subsequent call to toJSON() (resp. toJS()) is nearly instant. The result from toJS() should be considered read-only.

Remutable.Patch.fromJSON(json): new Patch/Remutable.Patch.fromJS(js): Object

Reconstructs a fresh Patch instance from a JSON string representation (resp. from a serializable Object).

get patch.source: string

get patch.target: string

Returns the underlying hash of the patch source/target, so that p1.target === p2.target implies that p1 and p2 are identical and r.match(p) is equivalent to r.hash === p.source.

Remutable.Patch.revert(patch: Patch): new Patch

Creates a new Patch instance which does the exact reverse mutations that patch does. Useful to implement undo/redo mechanisms.

Remutable.Patch.combine(patchA: Patch, patchB: Patch): new Patch

Assuming that patchA's target is exactly patchB's source, creates a new Patch instance which maps patchA's source to patchB's target. Internal representation is optimized, so that there is no redundant information.

Remutable.Patch.fromDiff(prev: Remutable, next: Remutable): new Patch

In some rare cases, you known that next is a more recent version of prev, but don't have the underlying transition patches (for example, after a server full resync). Patch#fromDiff creates a new Patch object that reflects this transition: its source match prev and its target match next. Note however that this construction is relatively slow, as it requires to scan all the key/value pairs of both prev and next. Whenever possible, avoid deep diffing and maintain patches.

Configuration

By default, Remutable uses CRC-32 as its hash function and JSON.stringify as its object signature function.

You may override this by simply setting Remutable.hashFn and/or Remutable.signFn before instanciating any Remutable or Remutable.Patch object.

If you want to use, say, sha1 and sigmund, you may do the following:

Remutable.hashFn = require('sha1');
Remutable.signFn = require('sigmund');

More Repositories

1

react-armor

Protect your DOM from third-party tampering.
JavaScript
636
star
2

coding-styles

My coding styles.
398
star
3

react-animate

React animation mixin.
JavaScript
271
star
4

nexus-flux

Streamlined Flux abstract interface suitable for a variety of backends.
JavaScript
241
star
5

fastify-zod

Zod integration with Fastify
TypeScript
188
star
6

react-prepare

Prepare you app state for async server-side rendering and more!
JavaScript
100
star
7

directus-typescript-gen

JavaScript
84
star
8

react-traverse

React Components Magic
JavaScript
70
star
9

nexus-flux-socket.io

socket.io adapter for Nexus Flux, implementing Flux over the Wire.
JavaScript
53
star
10

react-rails-starterkit

Starter repository for React on Rails. Fork it!
JavaScript
47
star
11

typed-assert

A typesafe TS assertion library
TypeScript
47
star
12

react-css

Converts plain CSS into (optionally auto-prefixed) React-style properties map.
JavaScript
35
star
13

es6-starterkit

The future is today!
JavaScript
33
star
14

useless

Useless React hooks
TypeScript
32
star
15

react-query

React Virtual DOM querying made easy.
JavaScript
30
star
16

react-statics-styles

Declarative styles in React components with support for pre- and post-processing.
JavaScript
26
star
17

react-nexus-chat

Demo chat app using React Nexus.
CSS
18
star
18

react-styling-demo

Demos for react-animate, react-css and other React styling demos.
JavaScript
17
star
19

react-defer

React Mixin for dealing with defers/timeouts/intervals/requestAnimationFrame with less boilerplate.
JavaScript
16
star
20

react-nexus-starterkit

React Nexus Starterkit Project. Clone/fork, hack, deploy!
JavaScript
12
star
21

node-async-context

Node Async Context Monitoring & Isolation
JavaScript
8
star
22

immutable-request

Isomorphic cacheable and cancellable HTTP request than return Promise for Immutable.Map.
JavaScript
8
star
23

lifespan

Unifying callbacks removals.
JavaScript
7
star
24

typed-jest-expect

Elegant jest.expect typings for a more civilized age
TypeScript
7
star
25

isomorphic-router

Tiny, lightweight isomorphic router.
JavaScript
6
star
26

gulp-react-statics-styles

Gulp task for react-statics-styles.
JavaScript
5
star
27

tween-interpolate

Extra lightweight generic and CSS values interpolators and tweens.
JavaScript
5
star
28

typed-react-openapi

Idiomatic, strongly typed React OpenAPI integration
TypeScript
5
star
29

hypernode

Ideas and prototypes for Hypernode, a scalable, distributed JS node environment.
JavaScript
4
star
30

react-transform-props

React Component decorator for transforming props
JavaScript
4
star
31

nexus-uplink-simple-server

Nexus Uplink Simple Server (for Node).
JavaScript
4
star
32

react-nexus-app

React Nexus App skeleton.
JavaScript
4
star
33

nexus-uplink-client

Nexus Uplink Client (isomorphic).
JavaScript
4
star
34

algebraic-effects-experiments

Experiments with algebraic effects
JavaScript
3
star
35

react-esi-poc

JavaScript
3
star
36

typecheck-decorator

Runtime arguments validation as ES7 decorators.
JavaScript
3
star
37

virtual

Userland virtual methods and abstract classes for ES6.
JavaScript
3
star
38

typed-result

A liberal reinterpretation of the Result type for TypeScript
TypeScript
3
star
39

lisa-db

TypeScript
2
star
40

react-maybe-state

React Mixin for maybe getting state (defaulting to null).
JavaScript
2
star
41

otter-den

TypeScript
2
star
42

snippets

Snippets of code to copy/paste.
TypeScript
2
star
43

react-ml

JavaScript
2
star
44

http-exceptions

HTTP Exceptions with err codes.
JavaScript
2
star
45

lisa-prototype

TypeScript
2
star
46

react-nexus-todomvc

React Nexus obligatory TodoMVC. (WORK IN PROGRESS)
JavaScript
2
star
47

react-lambda

Higher order and functional utilies for manipulating React components
JavaScript
2
star
48

react-http

Universal HTTP client for use in React applications.
JavaScript
2
star
49

rotenberg.io

My homepage
Lua
1
star
50

RktTools

Wow RktTools addon
Lua
1
star
51

backend-starterkit

Node Koa/postgresql backend starterkit
JavaScript
1
star
52

typed-utilities

Strongly typed general purpose utilities
TypeScript
1
star
53

wow-scripts

Lua
1
star
54

typed-ref

TypeScript
1
star
55

wow-workbench

TypeScript
1
star
56

react-identicon

React Component for Gravatar identicons.
JavaScript
1
star
57

mindstorms-utilities

Utilities for Lego Mindstorms
JavaScript
1
star
58

netlify-cms-toolkit

Tools and utilities for Netlify CMS
TypeScript
1
star
59

nexus-events

Yet Another EventEmitter. Sane callbacks removals.
JavaScript
1
star
60

lodash-next

An extension of lodash for next level js.
JavaScript
1
star
61

remembrall-pi

Python
1
star
62

mindlogger-node

TypeScript
1
star
63

nexus-flux-rest

Nexus Flux backend using plain HTTP requests (REST-like).
JavaScript
1
star
64

workbench

JavaScript
1
star
65

where-did-i-put-it

One button to find them all
TypeScript
1
star
66

node-pg-container

Postgres containers for Node
TypeScript
1
star
67

react-ml-editor

ReactML editor with live preview and suggest-completion.
JavaScript
1
star