Using fast-json-patched for document versioning and timelining - javascript
I'm attempting to create a revisable document, with the edits stored as fast-json-patched objects.
The rough outline is :
const jsonpatch = require('fast-json-patch');
/**
* Generate a doc, with all the state changes recorded as
* diffed objects.
* #param {object} docParams The parameters to generate the doc from
* #returns {object} the doc
*/
function generateDoc(docParams) {
const defaults = {
docnumber: 'TS99999',
edits: 1,
};
// create a complete set of properties to work from
const { docnumber, edits } = {
...defaults,
...docParams,
};
// basic docs setup
const doc = {
parameters: { docnumber, edits },
history: [],
state: { docnumber, notes: [] },
};
// update the doc 'edits' times
for (let edit = 0; edit < edits; edit++) {
// clone to preserve the current state
const currentState = JSON.parse(JSON.stringify(doc.state));
// add at least one note, up to every edit
const notesToAdd = Math.ceil(Math.random() * 5);
for (let i = 0; i < notesToAdd; i++) {
doc.state.notes.push('Note: ' + Math.ceil(Math.random() * 500));
}
doc.history.push(jsonpatch.compare(currentState, doc.state));
}
return doc;
}
/**
* Set the current doc state to the state at the spercifier point in time
* #param {object} doc The doc to update
* #param {integer} edit The point in time to use
* #returns {boolean} Was the doc set updated?
*/
function timetravel(doc, editPoint) {
if (
doc.parameters.currentedit === editPoint ||
editPoint > doc.parameters.edits ||
editPoint < 1
) {
return false; // don't travel too far into the future or past!
}
const patchesToApply = doc.history.slice(0, editPoint);
const patches = [].concat.apply([], patchesToApply);
let newDoc = {};
newDoc = jsonpatch.applyPatch(newDoc, patches).newDocument;
doc.state = newDoc;
doc.parameters.currentedit = editPoint;
return true;
}
// Testing....
const doc = generateDoc({
docnumber: 'TS99999',
edits: 5,
});
console.log(doc);
timetravel(doc, 2);
console.log(doc);
Clearly my understanding of what should be happening is wrong, as I get the following error...
/Users/dd/Code/patchtesting/node_modules/fast-json-patch/commonjs/core.js:14
obj[key] = this.value;
just at the jsonpatch.applyPatch line.
I've tried alternative approaches:
// seems to be one popular suggestion...
const patchesToApply = doc.history.slice(0, editPoint);
const patches = [].concat.apply([], patchesToApply);
doc.state = patches.reduce(jsonpatch.applyReducer, {});
doc.parameters.currentedit = editPoint;
or...
// Trying to see the effect of applying a single patch at a time...
patches.forEach(patch => {
console.log(patch);
newDoc = jsonpatch.applyPatch(newDoc, [patch]).newDocument;
console.log(newDoc);
});
The patches that are generated make sense, I just can't seem to apply them :-(
Sorry, it was a basic coding / fencepost-ish error:
...
// basic docs setup
const doc = {
parameters: { docnumber, edits },
history: [],
state: { docnumber, notes: [] },
};
// ADDED: need to record the initial state
doc.history.push(jsonpatch.compare({}, doc.state))
// update the doc 'edits' times
...
;
Related
Compare an array with itself for duplicate and store in new array
I have a question, how do I compare values in an array with themselves without the value to be compared being shown as true? As an example, if the value EUW1_6011808396 occurs again, it should be stored in the array matchingMatches. The Values of the Array are: [EUW1_6011808396, EUW1_6011824351, EUW1_6011720277, EUW1_6010413995, EUW1_6010218048, EUW1_6010184913, EUW1_6010131700, EUW1_6009739853, EUW1_6008825456, EUW1_6008833322, EUW1_6008409245, EUW1_6008369887, EUW1_6008355242, EUW1_6007567238, EUW1_6007269146, EUW1_6007226284, EUW1_6007192332, EUW1_6005571988, EUW1_6005438941, EUW1_6005495312, EUW1_6013263286, EUW1_6013193252, EUW1_6012475324, EUW1_6012411610, EUW1_6012315128, EUW1_6012011561, EUW1_6011110477, EUW1_6011026046, EUW1_6009739853, EUW1_6006439870, EUW1_6006434580, EUW1_6005238786, EUW1_6005191249, EUW1_6005026992, EUW1_6005015187, EUW1_6004958241, EUW1_6003811368, EUW1_6002847479, EUW1_6002164371, EUW1_6002148723, EUW1_6015524685, EUW1_6015387328, EUW1_6015402003, EUW1_6014779337, EUW1_6014724668, EUW1_6014701498, EUW1_6014655368, EUW1_6014580839, EUW1_6014429620, EUW1_6014475971, EUW1_6014473252, EUW1_6013334881, EUW1_6013322375, EUW1_6012669749, EUW1_6012635347, EUW1_6012583396, EUW1_6010971941, EUW1_6006896961, EUW1_6006881165, EUW1_6006518887, EUW1_6015745842, EUW1_6015589872, EUW1_6014068520, EUW1_6014044304, EUW1_6007955310, EUW1_6003705297, EUW1_6003569783, EUW1_6002003834, EUW1_6000787500, EUW1_5994465297, EUW1_5993391050, EUW1_5992233473, EUW1_5992169601, EUW1_5984062877, EUW1_5984034743, EUW1_5983855739, EUW1_5983880569, EUW1_5983766086, EUW1_5982931745, EUW1_5982372929, EUW1_6005238786, EUW1_6005191249, EUW1_6005026992, EUW1_6005015187, EUW1_6004958241, EUW1_6002164371, EUW1_6002148723, EUW1_6002057259, EUW1_6002053660, EUW1_6002009239, EUW1_6002003834, EUW1_6001939719, EUW1_6001867883, EUW1_6001022392, EUW1_6000887143, EUW1_6000892356, EUW1_6000787500, EUW1_6000820954, EUW1_5996485374, EUW1_5994559073, EUW1_6010083174, EUW1_6010017420, EUW1_6006898776, EUW1_6006838293, EUW1_6005225782, EUW1_6005135031, EUW1_6003899867, EUW1_6003883079, EUW1_6003786523, EUW1_6002164371, EUW1_6002148723, EUW1_6002057259, EUW1_6002053660, EUW1_6002009239, EUW1_6001022392, EUW1_6000887143, EUW1_6000892356, EUW1_6000787500, EUW1_5999368247, EUW1_5999295110, EUW1_5989231240, EUW1_5989055249, EUW1_5987149834, EUW1_5978125118, EUW1_5969701977, EUW1_5969187233, EUW1_5956294382, EUW1_5955846040, EUW1_5949708234, EUW1_5934525960, EUW1_5916275391, EUW1_5916168691, EUW1_5916132470, EUW1_5907690529, EUW1_5897979620, EUW1_5897921186, EUW1_5896786548, EUW1_5880625543, EUW1_5880541891, EUW1_5878457213, EUW1_6015589872, EUW1_5969852994, EUW1_5969788713, EUW1_5940681289, EUW1_5940556247, EUW1_5937866203, EUW1_5937892773, EUW1_5937768916, EUW1_5937765393, EUW1_5934600651, EUW1_5934512860, EUW1_5934339533, EUW1_5932238516, EUW1_5932166008, EUW1_5932211862, EUW1_5929934534, EUW1_5930153889, EUW1_5931242804, EUW1_5919621815, EUW1_5918236611] The function which is used for getting the Id for the API call. /** * Getting the Team Members and their IDs * * #param {info_id|String} * #return Team Members IDs * #customfunction */ function getTeamMembersIDs(info_id) { try { startup(); var teamMembers = getTeamMembersSheet(); var riotIds = []; for (var counter = 0; counter != teamMembers.length; counter = counter + 1) { switch (info_id){ case "puuid": var data = buildURL("https://euw1.api.riotgames.com/lol/summoner/v4/summoners/by-name/"+teamMembers[counter]+"?api_key="+apiKey); var puuid = data["puuid"]; riotIds.push(puuid); break; case "id": var data = buildURL("https://euw1.api.riotgames.com/lol/summoner/v4/summoners/by-name/"+teamMembers[counter]+"?api_key="+apiKey); var id = data["id"]; riotIDs.push(id); break; case "accountId": var data = buildURL("https://euw1.api.riotgames.com/lol/summoner/v4/summoners/by-name/"+teamMembers[counter]+"?api_key="+apiKey); var accountId = data["accountId"]; riotIds.push(accountId); break; } } return riotIds } catch (err) { Logger.log('Failed with error %s', err.message); } } This Function is Used for Getting the Matches that got played per Team Member. /** * Getting the Data from the Api which matches got played * * #return the matches played * #customfunction */ function getMatchesPlayed() { try{ startup(); var puuids = getTeamMembersIDs("puuid"); var games = []; for (var counter = 0; counter != puuids.length; counter = counter + 1) { var data = buildURL("https://europe.api.riotgames.com/lol/match/v5/matches/by-puuid/"+puuids[counter]+"/ids?start=0&count=20&api_key="+apiKey) for(var counter2 = 0; counter2 != data.length; counter2 = counter2 + 1){ games.push(data[counter2]) } } return games }catch (err) { Logger.log('Failed with error %s', err.message); } } The function which should return the Matching Matches in the Array gets from the getMatchesPlayed function. /** * Checking the games for matches * * #return the matches which match * #customfunction */ function getMatchingMatches() { try{ var games = getMatchesPlayed(); var matchingMatches = []; Logger.log(games) const gettingGames = games.map(element => element) for (var counter = 0; counter != games.length; counter = counter + 1){ Logger.log(gettingGames[counter]) } }catch (err) { Logger.log('Failed with error %s', err.message); } }
As I understand, you need to get duplicate ids. Use the Set data structure for comparing the array values. // assume this should return an array [EUW1_6011808396, EUW1_6011824351] var games = getMatchesPlayed(); // var matched_games = [] // Initialise set data structure. var set = = new Set(); //Iterate over the games. Runtime complexity O(n) games.forEach((element) => { //the case when set does not contain the id we should add it to `set` if(!set.has(element)){ // O(1) set.add(element); // O(1) continue; } // if the set already contains the id, we should add it to the matching games array. matched_games.push(element); }) I hope this will help.
welcome to Stackoverflow! You have a lot of ways to store duplicate values In a new array, and this is one. // get duplicartes and store them in a array function getDuplicates(array) { let sorted = array.sort(); for (let i = 0; i < sorted.length - 1; i++) { if (sorted[i + 1] == sorted[i]) { duplicates.push(sorted[i]); } } }
Another way to get non-unique values const euws = ['EUW1_6011808396','EUW1_6011824351','EUW1_6011720277','EUW1_6010413995','EUW1_6010218048','EUW1_6010184913','EUW1_6010131700','EUW1_6009739853','EUW1_6008825456','EUW1_6008833322','EUW1_6008409245','EUW1_6008369887','EUW1_6008355242','EUW1_6007567238','EUW1_6007269146','EUW1_6007226284','EUW1_6007192332','EUW1_6005571988','EUW1_6005438941','EUW1_6005495312','EUW1_6013263286','EUW1_6013193252','EUW1_6012475324','EUW1_6012411610','EUW1_6012315128','EUW1_6012011561','EUW1_6011110477','EUW1_6011026046','EUW1_6009739853','EUW1_6006439870','EUW1_6006434580','EUW1_6005238786','EUW1_6005191249','EUW1_6005026992','EUW1_6005015187','EUW1_6004958241','EUW1_6003811368','EUW1_6002847479','EUW1_6002164371','EUW1_6002148723','EUW1_6015524685','EUW1_6015387328','EUW1_6015402003','EUW1_6014779337','EUW1_6014724668','EUW1_6014701498','EUW1_6014655368','EUW1_6014580839','EUW1_6014429620','EUW1_6014475971','EUW1_6014473252','EUW1_6013334881','EUW1_6013322375','EUW1_6012669749','EUW1_6012635347','EUW1_6012583396','EUW1_6010971941','EUW1_6006896961','EUW1_6006881165','EUW1_6006518887','EUW1_6015745842','EUW1_6015589872','EUW1_6014068520','EUW1_6014044304','EUW1_6007955310','EUW1_6003705297','EUW1_6003569783','EUW1_6002003834','EUW1_6000787500','EUW1_5994465297','EUW1_5993391050','EUW1_5992233473','EUW1_5992169601','EUW1_5984062877','EUW1_5984034743','EUW1_5983855739','EUW1_5983880569','EUW1_5983766086','EUW1_5982931745','EUW1_5982372929','EUW1_6005238786','EUW1_6005191249','EUW1_6005026992','EUW1_6005015187','EUW1_6004958241','EUW1_6002164371','EUW1_6002148723','EUW1_6002057259','EUW1_6002053660','EUW1_6002009239','EUW1_6002003834','EUW1_6001939719','EUW1_6001867883','EUW1_6001022392','EUW1_6000887143','EUW1_6000892356','EUW1_6000787500','EUW1_6000820954','EUW1_5996485374','EUW1_5994559073','EUW1_6010083174','EUW1_6010017420','EUW1_6006898776','EUW1_6006838293','EUW1_6005225782','EUW1_6005135031','EUW1_6003899867','EUW1_6003883079','EUW1_6003786523','EUW1_6002164371','EUW1_6002148723','EUW1_6002057259','EUW1_6002053660','EUW1_6002009239','EUW1_6001022392','EUW1_6000887143','EUW1_6000892356','EUW1_6000787500','EUW1_5999368247','EUW1_5999295110','EUW1_5989231240','EUW1_5989055249','EUW1_5987149834','EUW1_5978125118','EUW1_5969701977','EUW1_5969187233','EUW1_5956294382','EUW1_5955846040','EUW1_5949708234','EUW1_5934525960','EUW1_5916275391','EUW1_5916168691','EUW1_5916132470','EUW1_5907690529','EUW1_5897979620','EUW1_5897921186','EUW1_5896786548','EUW1_5880625543','EUW1_5880541891','EUW1_5878457213','EUW1_6015589872','EUW1_5969852994','EUW1_5969788713','EUW1_5940681289','EUW1_5940556247','EUW1_5937866203','EUW1_5937892773','EUW1_5937768916','EUW1_5937765393','EUW1_5934600651','EUW1_5934512860','EUW1_5934339533','EUW1_5932238516','EUW1_5932166008','EUW1_5932211862','EUW1_5929934534','EUW1_5930153889','EUW1_5931242804','EUW1_5919621815','EUW1_5918236611']; const nonUnique = euws.reduce((r, v, i, a) => { if (!r.includes(v) && a.indexOf(v) !== i) r.push(v); return r; }, []); console.log(nonUnique) .as-console-wrapper { max-height: 100% !important; top: 0 }
Implementing the randomized set class with O(1) operation
This is related to the leetcode - question here, which asks: Implement the RandomizedSet class: RandomizedSet() Initializes the RandomizedSet object. bool insert(int val) Inserts an item val into the set if not present. Returns true if the item was not present, false otherwise. bool remove(int val) Removes an item val from the set if present. Returns true if the item was present, false otherwise. int getRandom() Returns a random element from the current set of elements (it's guaranteed that at least one element exists when this method is called). Each element must have the same probability of being returned. You must implement the functions of the class such that each function works in average O(1) time complexity. I am able to pass some of the test cases but it fails one of the test cases (where in some places, my program returns undefined). What is it that I am doing wrong here? I am unable to find my mistake. var RandomizedSet = function() { this.map = new Map(); this.vector = []; }; /** * #param {number} val * #return {boolean} */ RandomizedSet.prototype.insert = function(val) { if(this.map.has(val)) return false; else { let position = 0; if(this.vector.length > 0) position = this.vector.length-1; this.map.set(val, position); this.vector.push(val); return true; } }; /** * #param {number} val * #return {boolean} */ RandomizedSet.prototype.remove = function(val) { if(this.map.has(val)) { const index = this.map.get(val); const lastVal = this.vector[this.vector.length-1]; this.vector[index] = lastVal; this.vector.pop(); this.map.delete(val); return true; }else { return false; } }; /** * #return {number} */ RandomizedSet.prototype.getRandom = function() { const randIndex = Math.floor(Math.random() * this.vector.length); return this.vector[randIndex]; }; /** * Your RandomizedSet object will be instantiated and called as such: * var obj = new RandomizedSet() * var param_1 = obj.insert(val) * var param_2 = obj.remove(val) * var param_3 = obj.getRandom() */
When moving the last element into the place of the removed element, you forgot to update the moved element's index in the map: RandomizedSet.prototype.remove = function(val) { if(this.map.has(val)) { const index = this.map.get(val); const lastVal = this.vector[this.vector.length-1]; this.vector[index] = lastVal; this.vector.pop(); this.map.set(lastVal, index); // ADDED this.map.delete(val); return true; }else { return false; } }; Be aware of the case index == this.vector.length-1; the order of operations shown above will handle this case correctly without additional code.
Options for passing and describing arguments to js function
What other options are there for passing and using arguments in a function using an object besides these two? Option 1: let timerClosure = function ({ period, // number funStart, // function funEnd, // function funStartArguments = [], funEndArguments = [], warming = 0 }) {// something } Option 2: let timerClosure = function (timerConfigObj) { let period = timerConfigObj.period; // number let funStart = timerConfigObj.funStart; let funEnd = timerConfigObj.funEnd; let funStartArguments = timerConfigObj.funStartArguments || []; let funEndArguments = timerConfigObj.funStartArguments || []; let warming = timerConfigObj.warming || 0; }
Those, or other ways of spinning them, are basically it. Well, those and using an array, but if you use an array you may as well use discrete parameters, you'll have the same issue with order being significant and the problems with that as you get to more than three parameters. Another way to spin what you have: let timerClosure = function (timerConfigObj) { const { period, // number funStart, // function funEnd, // function funStartArguments = [], funEndArguments = [], warming = 0 } = timerConfigObj; // ... }; You've said "...and describing" in the title but not the text. If that part is important to you, you can describe these more completely by using JDDoc annotations, which many IDEs can read and present to you (even if you never actually run JSDoc) when you're using the function: /** * Does something nifty with timers and closures. * * #param {Object} options - Options for the nifty thing. * #param {number} options.period - `period` description... * #param {function} options.funStart - `funStart` description... * #param {function} options.funEnd - `funEnd` description... * #param {array} options.funStartArguments - `funStartArguments` description... * #param {array} options.funEndArguments - `funEndArguments` description... * #param {number} options.warning - `warning` description... */ let timerClosure = function ({ period, // number funStart, // function funEnd, // function funStartArguments = [], funEndArguments = [], warming = 0 }) { // ... }; Similarly, if you create a TypeScript type/interface and document its properties, IDEs will show that to you as well. /** * Options for `timerClosure` */ interface TimerClosureOptions { /** * Period description... */ period: number; funStart: function; funEnd: function; funStartArguments?: any[]; funEndArguments?: any[]; warming?: number; } /** * Does something nifty with timers and closures. * * #param {TimerClosureOptions} options - Options for the nifty thing. */ let timerClosure = function ({ period, funStart, funEnd, funStartArguments = [], funEndArguments = [], warming = 0 }: TimerClosureOptions) { // ... };
JSDoc: Is conditional ignore possible?
Is there a way to ignore some documented symbols conditionally? I'd like to do something like this (pseudo-code): /** * #ignore if dev */ var a = 42; /** * #ignore if prod */ var b = 24; In this example I would like to have my JSDoc generator to only document var a if I configured my generator to dev and vice versa for var b. Is this possible?
You can implement your own jsdoc plugin to test an ignore condition and set its value in doclet.ignore property. Setting it to true will prevent the doclet being processed - not adding it to the final documentation. exports.defineTags = function(dictionary) { var env = require('jsdoc/env'); /* * Usage: #customIgnore prod, test */ dictionary.defineTag('customIgnore', { mustHaveValue: true, onTagged: function(doclet, tag) { var i; // "envParams" is a property of your jsdoc.json var environments = env.conf.envParams; // Will hold "prod, test" var tagValue = tag.value; var conditionValues = tag.value.split(","); for (i = 0; i < conditionValues.length && !doclet.ignore; i++) { if (environments.indexOf(conditionValues[i].trim()) !== -1) { doclet.ignore = true; } } } }); };
Illegal return statement in JavaScript
A 'days' node with more than 1 child isn't getting removed. How can I fix this issue? I need to ensure that my promise bubbles up to the last then() on the top-level. So I need a return before collectionRef.once. But that return statement now prevents the collectionRef.once from happening. I'm stuck! Here's my code const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(functions.config().firebase); const defaultDatabase = admin.database(); exports.deleteOldItems = functions.database.ref('/path/to/items/{pushId}') .onWrite(event => { var ref = event.data.ref.parent; // reference to the items var now = Date.now(); var cutoff = now - 2 * 60 * 60 * 1000; var oldItemsQuery = ref.orderByChild('timestamp').endAt(cutoff); return oldItemsQuery.once('value', function(snapshot) { // create a map with all children that need to be removed var updates = {}; snapshot.forEach(function(child) { updates[child.key] = null }); // execute all updates in one go and return the result to end the function return ref.update(updates); }).then(function() {; const theRef = event.data.ref; const collectionRef = theRef.parent.child('days'); return collectionRef; // ILEGAL RETURN STATEMENT collectionRef.once('value').then(messagesData => { if(messagesData.numChildren() > 1) { let updates = {}; updates['/days'] = null; return defaultDatabase.ref().update(updates); // 'days' doesn't get removed even if it has more than 1 child (as in the image)! } }) }); }); Data structure: https://i.stack.imgur.com/gVn8S.jpg
exports.deleteOldItems = functions.database.ref('/path/to/items/{pushId}') .onWrite(event => { var ref = event.data.ref.parent // reference to the items var now = Date.now() var cutoff = now - 2 * 60 * 60 * 1000 var oldItemsQuery = ref.orderByChild('timestamp').endAt(cutoff) return oldItemsQuery.once('value', function(snapshot) { // create a map with all children that need to be removed var updates = {} snapshot.forEach(function(child) { updates[child.key] = null }) // execute all updates in one go and return the result to end the function return ref.update(updates) }).then(function() { // const theRef = event.data.ref const collectionRef = defaultDatabase.ref().child('/days') // return collectionRef // ILEGAL RETURN STATEMENT collectionRef.once('value').then(messagesData => { console.log(`Hello messageData : ${messagesData.numChildren()}`) if(messagesData.numChildren() > 1) { const updates = {} updates['/days'] = null return defaultDatabase.ref().update(updates); // 'days' doesn't get removed even if it has more than 1 child (as in the image)! } }) }) use defaultDatabase.ref().child('/days') instead of using event.data.ref.parent also please go through the documentation and learn how promises works it will help you future. For now these changes will work.Tested at my end. videos you must watch What's the difference between event.data.ref and event.data.adminRef? - #AskFirebase Asynchronous Programming (I Promise!) with Cloud Functions for Firebase - Firecasts you can subscribe their Firebase YouTube Channel to get latest updates and learn More.