Skip to content
This repository has been archived by the owner on Jul 30, 2018. It is now read-only.

JSON merge patch #80

Closed
wants to merge 5 commits into from
Closed
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
24 changes: 24 additions & 0 deletions src/patch/createOperation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export interface Add extends Operation {
value: any;
}

export function isAdd(operation: Operation): operation is Add {
return operation.op === 'add';
}

function navigatePath(target: any, path: JsonPointer) {
let currentPath = '';
let lastSegment = '';
Expand Down Expand Up @@ -101,23 +105,43 @@ function test(this: Test, target: any) {

export interface Remove extends Operation {}

export function isRemove(operation: Operation): operation is Remove {
return operation.op === 'remove';
}

export interface Replace extends Operation {
value: any;
oldValue: any;
}

export function isReplace(operation: Operation): operation is Replace {
return operation.op === 'replace';
}

export interface Move extends Operation {
from: JsonPointer;
}

export function isMove(operation: Operation): operation is Move {
return operation.op === 'move';
}

export interface Copy extends Operation {
from: JsonPointer;
}

export function isCopy(operation: Operation): operation is Copy {
return operation.op === 'copy';
}

export interface Test extends Operation {
value: any;
}

export function isTest(operation: Operation): operation is Test {
return operation.op === 'test';
}

function getPath(path: JsonPointer | string[]) {
if (Array.isArray(path)) {
return createJsonPointer(...path);
Expand Down
33 changes: 31 additions & 2 deletions src/patch/createPatch.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { shouldRecurseInto, isEqual } from '../utils';
import createOperation, { Operation, OperationType } from './createOperation';
import createOperation, {Operation, OperationType, isAdd, isReplace, isRemove} from './createOperation';
import createJsonPointer, { JsonPointer } from './createJsonPointer';
export interface Patch<T, U> {
operations: Operation[];
apply(target: T): U;
toString(): String;
toString(): string;
toMergeString(): string;
}

export type PatchMapEntry<T, U> = { id: string; patch: Patch<T, U> };
Expand Down Expand Up @@ -62,6 +63,34 @@ function createPatch(operations: Operation[]) {
return next.toString();
}
}, '') + ']';
},
toMergeString(this: Patch<any, any>) {
const mergeObject: any = {};

this.operations.forEach((operation) => {
if (!isAdd(operation) && !isReplace(operation) && !isRemove(operation)) {
console.warn('Patch contains unsupported operation for JSON Merge serialization');
}
else {
const segments = operation.path.segments();
let currentObject: any = mergeObject;
let nextProperty = segments.shift();
while (segments.length && nextProperty) {
currentObject = currentObject[nextProperty] = currentObject[nextProperty] || {};
nextProperty = segments.shift();
}
if (nextProperty) {
if (isAdd(operation) || isReplace(operation)) {
currentObject[nextProperty] = operation.value;
}
else {
currentObject[nextProperty] = null;
}
}
}
});

return JSON.stringify(mergeObject);
}
};
}
Expand Down
68 changes: 68 additions & 0 deletions src/storage/createBaseStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import WeakMap from 'dojo-shim/WeakMap';
import Promise from 'dojo-shim/Promise';
import { Patch, PatchMapEntry } from '../patch/createPatch';
import { Query } from '../query/interfaces';
import { StoreOperation, CrudOptions, StoreOptions } from '../store/createStore';
import compose, { ComposeFactory } from 'dojo-compose/compose';

export type IdObject = { [ index: string ]: string; id: string };

export type CrudArgument<T> = T | string | PatchMapEntry<T, T>;

export interface UpdateResults<T> {
currentItems?: T[];
failedData?: CrudArgument<T>[];
successfulData: T[] | string[];
type: StoreOperation;
}

export interface BaseStorage<T> {
identify(items: T[]|T): string[];
}

export interface Storage<T, O extends CrudOptions> extends BaseStorage<T> {
createId(): Promise<string>;
fetch(query?: Query<T>): Promise<T[]>;
get(ids: string[]): Promise<T[]>;
put(items: T[], options?: O): Promise<UpdateResults<T>>;
add(items: T[], options?: O): Promise<UpdateResults<T>>;
delete(ids: string[]): Promise<UpdateResults<T>>;
patch(updates: { id: string; patch: Patch<T, T> }[], options?: O): Promise<UpdateResults<T>>;
isUpdate(item: T): Promise<{ isUpdate: boolean; item: T, id: string }>;
}

export interface BaseStorageState<T> {
idProperty?: string;
idFunction?: (item: T) => string;
}

export interface StorageFactory extends ComposeFactory<Storage<{}, {}>, StoreOptions<{}, CrudOptions>> {
<T extends {}, O extends CrudOptions>(options?: O): Storage<T, O>;
}

const instanceStateMap = new WeakMap<Storage<{}, {}>, BaseStorageState<{}>>();

const createBaseStorage = compose<BaseStorage<IdObject>, StoreOptions<{}, CrudOptions>>({
identify(this: Storage<{}, {}>, items: IdObject[]| IdObject): string[] {
const state = instanceStateMap.get(this);
const itemArray = Array.isArray(items) ? <IdObject []> items : [ <IdObject> items ];
if (state.idProperty) {
return itemArray.map((item) => item[state.idProperty!]);
}
else if (state.idFunction) {
return itemArray.map(state.idFunction);
}
else {
return itemArray.map((item) => item.id);
}
}

}, <T, O>(instance: Storage<T, O>, options?: StoreOptions<T, CrudOptions>) => {
options = options || {};
instanceStateMap.set(instance, {
idProperty: options.idProperty,
idFunction: options.idFunction
});
});

export default createBaseStorage;
Loading