Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"plugins": [
"transform-flow-strip-types"
],
"presets": [
"es2015",
"stage-0"
]
],
"retainLines": true
}
9 changes: 9 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@
"mocha": true,
},
"extends": "airbnb-base",
"parser": "babel-eslint",
"plugins": [
"flowtype"
],
"rules": {
"import/no-extraneous-dependencies": ["error", {
"devDependencies": ["**/*Spec.js"]
}],
"indent": [2, 4],
},
"settings": {
"flowtype": {
"onlyFilesWithFlowAnnotation": true
}
}
}
2 changes: 2 additions & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[options]
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,16 @@
"devDependencies": {
"babel-cli": "6.22.2",
"babel-core": "6.22.1",
"babel-eslint": "7.1.1",
"babel-plugin-transform-flow-strip-types": "6.22.0",
"babel-preset-es2015": "6.22.0",
"babel-preset-stage-0": "6.22.0",
"eslint": "3.14.1",
"eslint-config-airbnb-base": "11.0.1",
"eslint-plugin-flowtype": "2.30.3",
"eslint-plugin-import": "2.2.0",
"expect": "1.20.2",
"flow-bin": "0.41.0",
"mocha": "3.2.0"
}
}
9 changes: 7 additions & 2 deletions src/applyPatch.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
// @flow
import cloneDeep from 'lodash.clonedeep';
import get from 'lodash.get';
import has from 'lodash.has';
import set from 'lodash.set';
import unset from 'lodash.unset';
import createRejecter from './createRejecter';

export default function applyPatch(patch, data, resolver) {
import type { Patch } from './createPatch';

export type Resolver = (payload: Object, value: mixed, reject: () => void) => void;

export default function applyPatch(patch: Patch, data: Object, resolver: Resolver) {
const output = cloneDeep(data);

patch.forEach((payload) => {
patch.forEach((payload: Object) : void => {
const path = payload.path.slice(1).split('/');

switch (payload.op) {
Expand Down
22 changes: 14 additions & 8 deletions src/buildMergePatch.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
// @flow
import createRejecter from './createRejecter';

export default function buildMergePatch(currentBranchPatch, targetBranchPatch, resolver) {
const currentBranchPatchByPaths = currentBranchPatch.reduce((indexed, payload) => ({
...indexed,
[payload.path]: payload,
}), {});
export default function buildMergePatch(
currentBranchPatch: Object,
targetBranchPatch: Object,
resolver: ?Function,
) {
const currentBranchPatchByPaths: Object = currentBranchPatch.reduce(
(indexed: Object, payload: Object) => ({
...indexed,
[payload.path]: payload,
}), {});

return targetBranchPatch.filter((payload) => {
const conflictPayload = currentBranchPatchByPaths[payload.path];
return targetBranchPatch.filter((payload: Object) => {
const conflictPayload: Object = currentBranchPatchByPaths[payload.path];

if (!resolver || !conflictPayload) {
return true;
}

const rejecter = createRejecter();
const rejecter: Object = createRejecter();
resolver(payload, conflictPayload, rejecter.reject);

return !rejecter.rejected;
Expand Down
6 changes: 4 additions & 2 deletions src/computeHash.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// @flow
/* eslint-disable no-bitwise */
import Rusha from 'rusha';

const rusha = new Rusha();

export const EMPTY_HASH = '0000000000000000000000000000000000000000';
export type Hash = string;
export const EMPTY_HASH: Hash = '0000000000000000000000000000000000000000';

export default function computeHash(input) {
export default function computeHash(input: mixed): Hash {
const hash = rusha.digest(typeof input === 'object' ? JSON.stringify(input) : input);
return hash;
}
42 changes: 42 additions & 0 deletions src/createCompressedHashStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// @flow
import memoize from 'lodash.memoize';
import type { HashStore } from './createHashStore';
import type { Hash } from './computeHash';
import compressObject from './compressObject';
import expandObject from './expandObject';

export type CompressedHashStore = HashStore;

export default function createCompressedHashStore(store: HashStore): CompressedHashStore {
const {
write,
...baseStore
}: {
write: (data: Object) => Hash,
} = store;

const compressedHashStore = {
...baseStore,
write(data: Object, refHash: Hash): string {
if (!refHash) {
return write(data);
}

const parentData = compressedHashStore.read(refHash);

if (typeof data !== typeof parentData) {
return write(data);
}

return write(compressObject(parentData, data, `$$ref:${refHash}`));
},

read(hash) {
return expandObject(store.read(hash), compressedHashStore);
},
};

compressedHashStore.read = memoize(compressedHashStore.read);

return compressedHashStore;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import expect from 'expect';
import createCompressedStore from './createCompressedStore';
import createCompressedHashStore from './createCompressedHashStore';

describe('createCompressedStore()', () => {
describe('createCompressedHashStore()', () => {
let store;
let compressedStore;
let compressedHashStore;

beforeEach(() => {
store = {
Expand All @@ -12,7 +12,7 @@ describe('createCompressedStore()', () => {
toJSON: expect.createSpy(),
write: expect.createSpy(),
};
compressedStore = createCompressedStore(store);
compressedHashStore = createCompressedHashStore(store);
});

it('should first compress data and then give it to its store when write() is called', () => {
Expand All @@ -21,7 +21,7 @@ describe('createCompressedStore()', () => {
});
store.write.andReturn('b4a');

const hash = compressedStore.write({
const hash = compressedHashStore.write({
foo: 'bar',
foo2: 'bar2',
}, 'ae3');
Expand All @@ -48,7 +48,7 @@ describe('createCompressedStore()', () => {
};
}).andCallThrough();

const data = compressedStore.read('b4a');
const data = compressedHashStore.read('b4a');

expect(store.read).toHaveBeenCalledWith('b4a');
expect(store.read).toHaveBeenCalledWith('ae3');
Expand All @@ -61,12 +61,12 @@ describe('createCompressedStore()', () => {
it('should call store.keys() when keys() is called', () => {
store.keys.andReturn(['foo']);

expect(compressedStore.keys()).toEqual(['foo']);
expect(compressedHashStore.keys()).toEqual(['foo']);
});

it('should call store.toJSON() when toJSON() is called', () => {
store.toJSON.andReturn(['foo']);

expect(compressedStore.toJSON()).toEqual(['foo']);
expect(compressedHashStore.toJSON()).toEqual(['foo']);
});
});
45 changes: 0 additions & 45 deletions src/createCompressedStore.js

This file was deleted.

24 changes: 24 additions & 0 deletions src/createHashStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @flow
import computeHash from './computeHash';
import type { Hash } from './computeHash';
import type { Store } from './createStore';

export type HashStore = Store;

export default function createHashStore(store: Store): Store {
const {
write,
...baseStore
} = store;

return {
...baseStore,
write(data: Object): Hash {
const hash: Hash = computeHash(data);
write(hash, data);

return hash;
},

};
}
21 changes: 15 additions & 6 deletions src/createPatch.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
// @flow
import get from 'lodash.get';
import has from 'lodash.has';
import forEachDeep from './forEachDeep';

export default function createPatch(left, right) {
type Operation =
{ op: 'add', path: string, value: any } |
{ op: 'replace', path: string, value: any } |
{ op: 'remove', path: string }
;

export type Patch = Array<Operation>;

export default function createPatch(left: Object, right: Object) {
const paths = [];
const patch = [];

forEachDeep(right, (value, key, path) => {
const absolutePath = `/${path.join('/')}`;
forEachDeep(right, (value: any, key: string, path: Array<string>) => {
const absolutePath: string = `/${path.join('/')}`;
paths.push(absolutePath);

if (typeof value === 'object') {
Expand All @@ -34,8 +43,8 @@ export default function createPatch(left, right) {
});
});

forEachDeep(left, (value, key, path) => {
const absolutePath = `/${path.join('/')}`;
forEachDeep(left, (value: any, key: string, path: Array<string>) => {
const absolutePath: string = `/${path.join('/')}`;

if (paths.includes(absolutePath)) {
return;
Expand All @@ -47,7 +56,7 @@ export default function createPatch(left, right) {
});
});

return patch.sort((a, b) => {
return patch.sort((a: Operation, b: Operation) => {
if (a.op < b.op) {
return -1;
}
Expand Down
6 changes: 4 additions & 2 deletions src/createRejecter.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// @flow
export default function createRejecter() {
let rejected = false;
let rejected: boolean = false;

return {
// $FlowIssue - get/set properties not yet supported
get rejected() {
return rejected;
},

reject() {
reject(): void {
rejected = true;
},
};
Expand Down
Loading