UNPKG

jsoniq

Version:

JSONiq implementation for JavaScript

323 lines (287 loc) 13.3 kB
/// <reference path="../../typings/lodash/lodash.d.ts" /> import * as _ from "lodash"; import * as jerr from "../errors"; import UpdatePrimitive from "./primitives/UpdatePrimitive"; import InsertIntoObject from "./primitives/InsertIntoObject"; import InsertIntoArray from "./primitives/InsertIntoArray"; import DeleteFromObject from "./primitives/DeleteFromObject"; import DeleteFromArray from "./primitives/DeleteFromArray"; import ReplaceInObject from "./primitives/ReplaceInObject"; import ReplaceInArray from "./primitives/ReplaceInArray"; import RenameInObject from "./primitives/RenameInObject"; import Insert from "./primitives/Insert"; import Remove from "./primitives/Remove"; import UpdatePrimitives from "./UpdatePrimitives"; import { IPUL } from "./IPUL"; import { ITransaction } from "../stores/ITransaction"; export default class PUL implements IPUL { udps = new UpdatePrimitives(); parse(pul: string): PUL { var newPul:PUL = JSON.parse(pul); this.udps.parse(newPul.udps); return this; } serialize(): string { return JSON.stringify(this); } invert(transaction: ITransaction): Promise<PUL> { var promises = []; var pul = new PUL(); this.normalize(); this.udps.getAll().forEach((udp) => { var lPUL = new PUL(); var target; if(udp instanceof Remove) { target = transaction.get(udp.id).then((item) => { return item; }); } else if(!(udp instanceof Insert)) { target = udp.lockTarget(transaction).then(() => { return udp.getTarget(); }); } else { //TODO: remove setTimeout target = new Promise((resolve, reject) => { setTimeout(resolve, 1); }); } target.then((target) => { udp.invert(target, lPUL); lPUL.udps.insert.forEach((udp) => { pul.insert(udp.id, udp.item); }); lPUL.udps.remove.forEach((udp) => { pul.remove(udp.id); }); lPUL.udps.deleteFromArray.forEach((udp) => { pul.deleteFromArray(udp.id, udp.ordPath, udp.position); }); lPUL.udps.deleteFromObject.forEach((udp) => { pul.deleteFromObject(udp.id, udp.ordPath, udp.keys); }); lPUL.udps.insertIntoArray.forEach((udp) => { pul.insertIntoArray(udp.id, udp.ordPath, udp.position, udp.items); }); lPUL.udps.insertIntoObject.forEach((udp) => { var idx = _.findIndex(pul.udps.insertIntoObject, { id: udp.id, ordPath: udp.ordPath }); if(idx > -1) { _.merge(pul.udps.insertIntoObject[idx].pairs, udp.pairs); } else { pul.udps.insertIntoObject.push(udp); } }); }); promises.push(target); }); return Promise.all(promises).then(() => { return pul.normalize(); }); } apply(transaction: ITransaction): Promise<any> { //Normalize PUL this.normalize(); //Lock targets var promises = []; this.udps.getAll().forEach((udp) => { if(!(udp instanceof Insert) && !(udp instanceof Remove)) { var p = udp.lockTarget(transaction); promises.push(p); } }); return Promise.all(promises).then(() => { var apply = (udp: UpdatePrimitive) => { var id = udp.id; udp.apply(); transaction.put(id, udp.getDocument()); }; //Apply updates _.forEach(this.udps.insert, (udp) => { transaction.put(udp.id, udp.item); }); _.forEach(this.udps.remove, (udp) => { transaction.remove(udp.id); }); //1. jupd:replace-in-object _.forEach(this.udps.replaceInObject, apply); //2. jupd:delete-from-object _.forEach(this.udps.deleteFromObject, apply); //3. jupd:rename-in-object _.forEach(this.udps.renameInObject, apply); //4. jupd:insert-into-object _.forEach(this.udps.insertIntoObject, apply); //The array update primitives, furthermore, are applied right-to-left with re- gard to their index. //This obviates the problem of indexes being shifted and/or becoming invalid due to deletions or insertions. //TODO: test //5. jupd:replace-in-array _.sortBy(this.udps.replaceInArray, "position"); _.forEach(this.udps.replaceInArray, apply); //6. jupd:delete-from-array _.sortBy(this.udps.deleteFromArray, "position"); _.forEach(this.udps.deleteFromArray, apply); //7. jupd:insert-into-array _.sortBy(this.udps.insertIntoArray, "position"); _.forEach(this.udps.insertIntoArray, apply); return transaction.done(); }); } normalize(): PUL { //If there is a delete on the same (array,index) target, the replace is omitted. _.forEach(this.udps.deleteFromArray, (udp: DeleteFromArray) => { <ReplaceInArray[]>_.remove(this.udps.replaceInArray, { id: udp.id, ordPath: udp.ordPath, position: udp.position }); }); //If there is a delete on the same (object,name) target, the replace is omitted. //If there is a delete on the same (object,name) target, the rename is omitted. _.forEach(this.udps.deleteFromObject, (udp: DeleteFromObject) => { _.forEach(udp.keys, (key: string) => { <ReplaceInObject[]>_.remove(this.udps.replaceInObject, { id: udp.id, ordPath: udp.ordPath, key: key }); <RenameInObject[]>_.remove(this.udps.renameInObject, { id: udp.id, ordPath: udp.ordPath, key: key }); }); }); //If there is a remove primitive, all primitives with the same target are removed var handler = (udps, id) => { var idx; while((idx = _.findIndex(udps, { id: id })) > -1) { udps.splice(idx, 1); } }; this.udps.remove.forEach(udp => { handler(this.udps.deleteFromArray, udp.id); handler(this.udps.deleteFromObject, udp.id); handler(this.udps.insertIntoArray, udp.id); handler(this.udps.insertIntoObject, udp.id); handler(this.udps.replaceInArray, udp.id); handler(this.udps.replaceInObject, udp.id); handler(this.udps.renameInObject, udp.id); handler(this.udps.insert, udp.id); }); //We remove InsertIntoObject primitives with no pairs (if they have been removed because of composition) var idx = _.findIndex(this.udps.insertIntoObject, udp => { return Object.keys(udp.pairs).length === 0; }); if(idx > -1) { this.udps.insertIntoObject.splice(idx, 1); } return this; } remove(id: string): PUL { var newUdp = new Remove(id); var udp = _.find(this.udps.remove, { id: id }); if(udp === undefined) { this.udps.remove.push(newUdp); } return this; } insert(id: string, item: any): PUL { var newUdp = new Insert(id, item); var udp = _.find(this.udps.insert, { id: id }); if(udp) { throw new jerr.JNUP0005(); } else { this.udps.insert.push(newUdp); } return this; } /* * jupd:insert-into-object($o as object(), $p as object()) * Inserts all pairs of the object $p into the object $o. */ insertIntoObject(id: string, ordPath: string[], pairs: {}): PUL { var newUdp = new InsertIntoObject(id, ordPath, pairs); //Multiple UPs of this type with the same object target are merged into one UP with this target, //where the sources containing the pairs to insert are merged into one object. //An error jerr:JNUP0005 is raised if a collision occurs. var udp = _.find(this.udps.insertIntoObject, { id: id, ordPath: ordPath }); if(udp) { udp.merge(newUdp); } else { this.udps.insertIntoObject.push(newUdp); } return this; } /* * jupd:insert-into-array($a as array(), $i as xs:integer, $c as item()*) * Inserts all items in the sequence $c before position $i into the array $a. */ insertIntoArray(id: string, ordPath: string[], position: number, items: any[]): PUL { var newUdp = new InsertIntoArray(id, ordPath, position, items); //Multiple UPs of this type with the same (array,index) target are merged into one UP with this target, //where the items are merged in an implementation-dependent order. //Several inserts on the same array and selector (position) are equivalent to a unique insert on that array and selector with the content of those original inserts appended in an implementation-dependent order. var udp = _.find(this.udps.insertIntoArray, { id: id, ordPath: ordPath, position: position }); if(udp) { udp.merge(newUdp); } else { this.udps.insertIntoArray.push(newUdp); } return this; } /* * jupd:delete-from-object($o as object(), $s as xs:string*) * Removes the pairs the names of which appear in $s from the object $o. */ deleteFromObject(id: string, ordPath: string[], keys: Array<string>): PUL { var newUdp = new DeleteFromObject(id, ordPath, keys); //Multiple UPs of this type with the same object target are merged into one UP with this target, //where the selectors (names lists) are merged. Duplicate names are removed. var udp = _.find(this.udps.deleteFromObject, { id: id, ordPath: ordPath }); if(udp) { udp.merge(newUdp); } else { this.udps.deleteFromObject.push(newUdp); } return this; } /* * jupd:delete-from-array($a as array(), $i as xs:integer) * Removes the item at position $i from the array $a (causes all following items in the array to move one position to the left). */ deleteFromArray(id: string, ordPath: string[], position: number): PUL { var newUdp = new DeleteFromArray(id, ordPath, position); //Multiple UPs of this type with the same (array,index) target are merged into one UP with this target. var udp = _.find(this.udps.deleteFromArray, { id: id, ordPath: ordPath, position: position }); if(!udp) { this.udps.deleteFromArray.push(newUdp); } return this; } /* * jupd:replace-in-array($a as array(), $i as xs:integer, $v as item()) * Replaces the item at position $i in the array $a with the item $v (do nothing if $i is not comprised between 1 and jdm:size($a)). */ replaceInArray(id: string, ordPath: string[], position: number, item: any): PUL { var newUdp = new ReplaceInArray(id, ordPath, position, item); //The presence of multiple UPs of this type with the same (array,index) target raises an error. var udp = _.find(this.udps.replaceInArray, { id: id, ordPath: ordPath, position: position }); if(udp) { throw new jerr.JNUP0009(); } else { this.udps.replaceInArray.push(newUdp); } return this; } /* * jupd:replace-in-object($o as object(), $n as xs:string, $v as item()) * Replaces the value of the pair named $n in the object $o with the item $v (do nothing if there is no such pair). */ replaceInObject(id: string, ordPath: string[], key: string, item: any): PUL { var newUdp = new ReplaceInObject(id, ordPath, key, item); //The presence of multiple UPs of this type with the same (array,index) target raises an error. var udp = _.find(this.udps.replaceInObject, { id: id, ordPath: ordPath, key: key }); if(udp) { throw new jerr.JNUP0009(); } else { this.udps.replaceInObject.push(newUdp); } return this; } /* * jupd:rename-in-object($o as object(), $n as xs:string, $p as xs:string) * Renames the pair originally named $n in the object $o as $p (do nothing if there is no such pair). */ renameInObject(id: string, ordPath: string[], key: string, newKey: string): PUL { var newUdp = new RenameInObject(id, ordPath, key, newKey); //The presence of multiple UPs of this type with the same (object,name) target raises an error. //If there is a delete on the same (object,name) target, the rename is omitted. var udp = _.find(this.udps.renameInObject, { id: id, ordPath: ordPath, key: key }); if(udp) { throw new jerr.JNUP0009(); } else { this.udps.renameInObject.push(newUdp); } return this; } }