From 279ad0c69b083595148068cac36ad0fb69fb3460 Mon Sep 17 00:00:00 2001 From: YuriiShchyrba Date: Sat, 12 Mar 2022 17:34:53 -0500 Subject: [PATCH 01/16] feat: delete Logic for WriteThrough done --- ObsidianWrapper/ObsidianWrapper.jsx | 27 ++++++++++++++------ src/Browser/CacheClassBrowser.js | 39 +++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/ObsidianWrapper/ObsidianWrapper.jsx b/ObsidianWrapper/ObsidianWrapper.jsx index b424d7b..35aa57c 100644 --- a/ObsidianWrapper/ObsidianWrapper.jsx +++ b/ObsidianWrapper/ObsidianWrapper.jsx @@ -1,12 +1,11 @@ import React from 'https://dev.jspm.io/react'; -import Cache from '../src/Browser/CacheClassBrowser.js'; +import BrowserCache from '../src/Browser/CacheClassBrowser.js'; import { insertTypenames } from '../src/Browser/insertTypenames.js'; const cacheContext = React.createContext(); function ObsidianWrapper(props) { - const [cache, setCache] = React.useState(new Cache()); - + const [cache, setCache] = React.useState(new BrowserCache()); async function query(query, options = {}) { // set the options object default properties if not provided const { @@ -84,14 +83,19 @@ function ObsidianWrapper(props) { async function mutate(mutation, options = {}) { // set the options object default properties if not provided mutation = insertTypenames(mutation); - const { + let { endpoint = '/graphql', cacheWrite = true, toDelete = false, update = null, + writeThrough = true, } = options; // for any mutation a request to the server is made try { + // one-time check to add types to cache + if(writeThrough){ + writeThrough = await cache.write(mutation, {}, toDelete, writeThrough); + } const responseObj = await fetch(endpoint, { method: 'POST', headers: { @@ -102,7 +106,9 @@ function ObsidianWrapper(props) { }).then((resp) => resp.json()); if (!cacheWrite) return responseObj; // first behaviour when delete cache is set to true - if (toDelete) { + console.log('writeThrough before toDelete: ', writeThrough) + if (!writeThrough && toDelete) { + console.log('in the toDelete') cache.write(mutation, responseObj, true); return responseObj; } @@ -111,7 +117,14 @@ function ObsidianWrapper(props) { update(cache, responseObj); } // third behaviour just for normal update (no-delete, no update function) - cache.write(mutation, responseObj); + if(!writeThrough){ + console.log('in the !writeThrough') + cache.write(mutation, responseObj); + } + if (writeThrough) { + console.log('Here\'s the response from cache: ', writeThrough); + return writeThrough; + } return responseObj; } catch (e) { console.log(e); @@ -132,4 +145,4 @@ function useObsidian() { } // Exporting of Custom wrapper and hook to access wrapper cache -export { ObsidianWrapper, useObsidian }; +export { ObsidianWrapper, useObsidian }; \ No newline at end of file diff --git a/src/Browser/CacheClassBrowser.js b/src/Browser/CacheClassBrowser.js index f23ff1b..793892f 100644 --- a/src/Browser/CacheClassBrowser.js +++ b/src/Browser/CacheClassBrowser.js @@ -3,11 +3,13 @@ import normalizeResult from "./normalize.js"; import destructureQueries from "./destructure.js"; -export default class Cache { +export default class BrowserCache { constructor( initialCache = { ROOT_QUERY: {}, ROOT_MUTATION: {}, + // match resolvers to types in order to add them in write-through + types: {} } ) { this.storage = initialCache; @@ -49,14 +51,46 @@ export default class Cache { return { data: responseObject }; } - async write(queryStr, respObj, deleteFlag) { + async write(queryStr, respObj, deleteFlag, writeThrough=false) { const queryObj = destructureQueries(queryStr); + // if it's not already included in types, skip write-through and make graphQL call + console.log('WriteThrough before :' , writeThrough) + if(writeThrough && queryObj.mutations){ + if (!this.storage.types.hasOwnProperty(queryObj.mutations[0].name)){ + console.log('Skipping first write through') + return false; + } + console.log('Second time delete') + let mut = queryObj.mutations[0].name; + let idAndVal = queryObj.mutations[0].arguments; + idAndVal = idAndVal.split(':'); + let id = idAndVal[0].substring(1); + let val = idAndVal[1].substring(0,idAndVal[1].length-1); + let __typename = this.storage.types[queryObj.mutations[0].name]; + respObj.data = { + }; + let obj = {}; + obj[id] = val; + obj.__typename = __typename; + respObj.data[mut] = obj; + console.log('respObj: ',respObj) + } const resFromNormalize = normalizeResult(queryObj, respObj, deleteFlag); + console.log('WriteThrough After :' , writeThrough) + console.log('Query :' , queryObj) + if(!writeThrough && queryObj.mutations){ + console.log('First time delete') + console.log('queryObj: ',queryObj); + this.storage.types[queryObj.mutations[0].name] = respObj.data[queryObj.mutations[0].name].__typename; + } // update the original cache with same reference for (const hash in resFromNormalize) { + const resp = await this.cacheRead(hash); + // console.log(resp) if (resFromNormalize[hash] === "DELETED") { await this.cacheWrite(hash, "DELETED"); + return resp; } else if (resp) { const newObj = Object.assign(resp, resFromNormalize[hash]); await this.cacheWrite(hash, newObj); @@ -64,6 +98,7 @@ export default class Cache { await this.cacheWrite(hash, resFromNormalize[hash]); } } + return true; } gc() { From 97f4a324d0ebd37aefdb33edc181dc0acd3a5a32 Mon Sep 17 00:00:00 2001 From: Anthony Guan Date: Sun, 13 Mar 2022 21:58:40 -0700 Subject: [PATCH 02/16] Refactor delete mutation Co-authored-by: YuriiShchyrba --- ObsidianWrapper/ObsidianWrapper.jsx | 24 ++ src/Browser/CacheClassBrowser.js | 555 ++++++++++++++++------------ 2 files changed, 333 insertions(+), 246 deletions(-) diff --git a/ObsidianWrapper/ObsidianWrapper.jsx b/ObsidianWrapper/ObsidianWrapper.jsx index 35aa57c..2b4cea6 100644 --- a/ObsidianWrapper/ObsidianWrapper.jsx +++ b/ObsidianWrapper/ObsidianWrapper.jsx @@ -130,6 +130,30 @@ function ObsidianWrapper(props) { console.log(e); } } + + // breaking out writethrough logic vs. non-writethrough logic + async function mutateRefactored(mutation, options = {}) { + const { + endpoint = '/graphql', + cacheWrite = true, + toDelete = false, + update = null, + writeThrough = true, + } = options; + try { + if (writeThrough) { + // helper function to check if we've stored the type yet + if (toDelete) { + const responseObj = cache.writeThrough(mutation, {}, true); + return responseObj; + } + } else { + mutation = insertTypenames(mutation); + } + } catch(e) { + console.log(e) + } + } // Returning Provider React component that allows consuming components to subscribe to context changes return ( resp.json()); + // normalize the result, invalidate the cache and return the appropriate object + const resFromNormalize = normalizeResult(queryObj, respObj, deleteFlag); + for (const hash in resFromNormalize) { + const resp = await this.cacheRead(hash); + if (resFromNormalize[hash] === 'DELETED') { + await this.cacheWrite(hash, 'DELETED'); + return resp; + } else if (resp) { + const newObj = Object.assign(resp, resFromNormalize[hash]); + await this.cacheWrite(hash, newObj); + } else { + await this.cacheWrite(hash, resFromNormalize[hash]); + } + } + // store the mutation/type in cache + this.storage.types[queryObj.mutations[0].name] = + respObj.data[queryObj.mutations[0].name].__typename; + return responseObj; + } else { + // construct the response object ourselves + let mutation = queryObj.mutations[0].name; + let idAndVal = queryObj.mutations[0].arguments; + idAndVal = idAndVal.split(':'); + let id = idAndVal[0].substring(1); + let val = idAndVal[1].substring(0, idAndVal[1].length - 1); + let __typename = this.storage.types[queryObj.mutations[0].name]; + respObj.data = {}; + let obj = {}; + obj[id] = val; + obj.__typename = __typename; + respObj.data[mutation] = obj; + // same as above + const resFromNormalize = normalizeResult(queryObj, respObj, deleteFlag); + for (const hash in resFromNormalize) { + const resp = await this.cacheRead(hash); + if (resFromNormalize[hash] === 'DELETED') { + await this.cacheWrite(hash, 'DELETED'); + return resp; + } else if (resp) { + const newObj = Object.assign(resp, resFromNormalize[hash]); + await this.cacheWrite(hash, newObj); + } else { + await this.cacheWrite(hash, resFromNormalize[hash]); + } + } + return respObj; + } + } + } - gc() { - // garbageCollection; garbage collection: removes any inaccessible hashes from the cache - const badHashes = getBadHashes(); - const goodHashes = rootQueryCleaner(badHashes); - const goodHashes2 = getGoodHashes(badHashes, goodHashes); - removeInaccessibleHashes(badHashes, goodHashes2); - } + gc() { + // garbageCollection; garbage collection: removes any inaccessible hashes from the cache + const badHashes = getBadHashes(); + const goodHashes = rootQueryCleaner(badHashes); + const goodHashes2 = getGoodHashes(badHashes, goodHashes); + removeInaccessibleHashes(badHashes, goodHashes2); + } - // remove hashes that are flagged for deletion and store records of them in a set badHashes for removal inside root queries - getBadHashes() { - const badHashes = new Set(); - for (let key in this.storage) { - if (key === "ROOT_QUERY" || key === "ROOT_MUTATION") continue; - if (this.storage[key] === "DELETED") { - badHashes.add(key); - delete this.storage[key]; - } - } - return badHashes; - } + // remove hashes that are flagged for deletion and store records of them in a set badHashes for removal inside root queries + getBadHashes() { + const badHashes = new Set(); + for (let key in this.storage) { + if (key === 'ROOT_QUERY' || key === 'ROOT_MUTATION') continue; + if (this.storage[key] === 'DELETED') { + badHashes.add(key); + delete this.storage[key]; + } + } + return badHashes; + } - // go through root queries, remove all instances of bad hashes, add remaining hashes into goodHashes set - rootQueryCleaner(badHashes) { - const goodHashes = new Set(); - const rootQuery = this.storage["ROOT_QUERY"]; - for (let key in rootQuery) { - if (Array.isArray(rootQuery[key])) { - rootQuery[key] = rootQuery[key].filter((x) => !badHashes.has(x)); - if (rootQuery[key].length === 0) delete rootQuery[key]; - for (let el of rootQuery[key]) goodHashes.add(el); - } else - badHashes.has(rootQuery[key]) - ? delete rootQuery[key] - : goodHashes.add(rootQuery[key]); - } - return goodHashes; - } + // go through root queries, remove all instances of bad hashes, add remaining hashes into goodHashes set + rootQueryCleaner(badHashes) { + const goodHashes = new Set(); + const rootQuery = this.storage['ROOT_QUERY']; + for (let key in rootQuery) { + if (Array.isArray(rootQuery[key])) { + rootQuery[key] = rootQuery[key].filter((x) => !badHashes.has(x)); + if (rootQuery[key].length === 0) delete rootQuery[key]; + for (let el of rootQuery[key]) goodHashes.add(el); + } else + badHashes.has(rootQuery[key]) + ? delete rootQuery[key] + : goodHashes.add(rootQuery[key]); + } + return goodHashes; + } - // Go through the cache, check good hashes for any nested hashes and add them to goodHashes set - getGoodHashes(badHashes, goodHashes) { - for (let key in this.storage) { - if (key === "ROOT_QUERY" || key === "ROOT_MUTATION") continue; - for (let i in this.storage[key]) { - if (Array.isArray(this.storage[key][i])) { - for (let el of this.storage[key][i]) { - if (el.includes("~") && !badHashes.has(el)) { - goodHashes.add(el); - } - } - } else if (typeof this.storage[key][i] === "string") { - if ( - this.storage[key][i].includes("~") && - !badHashes.has(this.storage[key][i]) - ) { - goodHashes.add(this.storage[key][i]); - } - } - } - } - return goodHashes; - } + // Go through the cache, check good hashes for any nested hashes and add them to goodHashes set + getGoodHashes(badHashes, goodHashes) { + for (let key in this.storage) { + if (key === 'ROOT_QUERY' || key === 'ROOT_MUTATION') continue; + for (let i in this.storage[key]) { + if (Array.isArray(this.storage[key][i])) { + for (let el of this.storage[key][i]) { + if (el.includes('~') && !badHashes.has(el)) { + goodHashes.add(el); + } + } + } else if (typeof this.storage[key][i] === 'string') { + if ( + this.storage[key][i].includes('~') && + !badHashes.has(this.storage[key][i]) + ) { + goodHashes.add(this.storage[key][i]); + } + } + } + } + return goodHashes; + } - // Remove inaccessible hashes by checking if they are in goodhashes set or not - removeInaccessibleHashes(badHashes, goodHashes) { - for (let key in this.storage) { - if (key === "ROOT_QUERY" || key === "ROOT_MUTATION") continue; - if (!goodHashes.has(key)) delete this.storage[key]; - for (let i in this.storage[key]) { - if (Array.isArray(this.storage[key][i])) { - this.storage[key][i] = this.storage[key][i].filter( - (x) => !badHashes.has(x) - ); - } else if (typeof this.storage[key][i] === "string") { - if ( - this.storage[key][i].includes("~") && - badHashes.has(this.storage[key][i]) - ) { - delete this.storage[key][i]; - } - } - } - } - } + // Remove inaccessible hashes by checking if they are in goodhashes set or not + removeInaccessibleHashes(badHashes, goodHashes) { + for (let key in this.storage) { + if (key === 'ROOT_QUERY' || key === 'ROOT_MUTATION') continue; + if (!goodHashes.has(key)) delete this.storage[key]; + for (let i in this.storage[key]) { + if (Array.isArray(this.storage[key][i])) { + this.storage[key][i] = this.storage[key][i].filter( + (x) => !badHashes.has(x) + ); + } else if (typeof this.storage[key][i] === 'string') { + if ( + this.storage[key][i].includes('~') && + badHashes.has(this.storage[key][i]) + ) { + delete this.storage[key][i]; + } + } + } + } + } - // cache read/write helper methods - async cacheRead(hash) { - return this.storage[hash]; - } + // cache read/write helper methods + async cacheRead(hash) { + return this.storage[hash]; + } - async cacheWrite(hash, value) { - this.storage[hash] = value; - } + async cacheWrite(hash, value) { + this.storage[hash] = value; + } - async cacheDelete(hash) { - delete this.storage[hash]; - } + async cacheDelete(hash) { + delete this.storage[hash]; + } - async cacheClear() { - this.storage = { - ROOT_QUERY: {}, - ROOT_MUTATION: {}, - }; - } + async cacheClear() { + this.storage = { + ROOT_QUERY: {}, + ROOT_MUTATION: {}, + }; + } - // functionality to stop polling - stopPollInterval(interval) { - clearInterval(interval); - } + // functionality to stop polling + stopPollInterval(interval) { + clearInterval(interval); + } - writeWholeQuery(queryStr, respObj) { - const hash = queryStr.replace(/\s/g, ""); - this.cacheWrite(ROOT_QUERY[hash], respObj); - return respObj; - } + writeWholeQuery(queryStr, respObj) { + const hash = queryStr.replace(/\s/g, ''); + this.cacheWrite(ROOT_QUERY[hash], respObj); + return respObj; + } - readWholeQuery(queryStr) { - const hash = queryStr.replace(/\s/g, ""); - const root = this.cacheRead("ROOT_QUERY"); - if (root[hash]) return { data: root[hash] }; - return undefined; - } + readWholeQuery(queryStr) { + const hash = queryStr.replace(/\s/g, ''); + const root = this.cacheRead('ROOT_QUERY'); + if (root[hash]) return { data: root[hash] }; + return undefined; + } - // specialized helper methods - async populateAllHashes(allHashesFromQuery, fields) { - // include the hashname for each hash - if (!allHashesFromQuery.length) return []; - const hyphenIdx = allHashesFromQuery[0].indexOf("~"); - const typeName = allHashesFromQuery[0].slice(0, hyphenIdx); - return allHashesFromQuery.reduce(async (acc, hash) => { - // for each hash from the input query, build the response object - const readVal = await this.cacheRead(hash); - // return undefine if hash has been garbage collected - if (readVal === undefined) return undefined; - if (readVal === "DELETED") return acc; - const dataObj = {}; - for (const field in fields) { - if (readVal[field] === "DELETED") continue; - // for each field in the fields input query, add the corresponding value from the cache if the field is not another array of hashs - if (readVal[field] === undefined && field !== "__typename") { - return undefined; - } else if (typeof fields[field] !== "object") { - // add the typename for the type - if (field === "__typename") { - dataObj[field] = typeName; - } else dataObj[field] = readVal[field]; - } else { - // case where the field from the input query is an array of hashes, recursively invoke populateAllHashes - dataObj[field] = await this.populateAllHashes( - readVal[field], - fields[field] - ); - if (dataObj[field] === undefined) return undefined; - } - } - // acc is an array within a Response object for each hash - try { - const resolvedProm = await Promise.resolve(acc); - resolvedProm.push(dataObj); - return resolvedProm; - } catch (error) { - return undefined; - } - }, []); - } + // specialized helper methods + async populateAllHashes(allHashesFromQuery, fields) { + // include the hashname for each hash + if (!allHashesFromQuery.length) return []; + const hyphenIdx = allHashesFromQuery[0].indexOf('~'); + const typeName = allHashesFromQuery[0].slice(0, hyphenIdx); + return allHashesFromQuery.reduce(async (acc, hash) => { + // for each hash from the input query, build the response object + const readVal = await this.cacheRead(hash); + // return undefine if hash has been garbage collected + if (readVal === undefined) return undefined; + if (readVal === 'DELETED') return acc; + const dataObj = {}; + for (const field in fields) { + if (readVal[field] === 'DELETED') continue; + // for each field in the fields input query, add the corresponding value from the cache if the field is not another array of hashs + if (readVal[field] === undefined && field !== '__typename') { + return undefined; + } else if (typeof fields[field] !== 'object') { + // add the typename for the type + if (field === '__typename') { + dataObj[field] = typeName; + } else dataObj[field] = readVal[field]; + } else { + // case where the field from the input query is an array of hashes, recursively invoke populateAllHashes + dataObj[field] = await this.populateAllHashes( + readVal[field], + fields[field] + ); + if (dataObj[field] === undefined) return undefined; + } + } + // acc is an array within a Response object for each hash + try { + const resolvedProm = await Promise.resolve(acc); + resolvedProm.push(dataObj); + return resolvedProm; + } catch (error) { + return undefined; + } + }, []); + } } From 23c7dd4377fa3886c8c37415017f8c78c83e6362 Mon Sep 17 00:00:00 2001 From: Anthony Guan Date: Sun, 13 Mar 2022 22:00:38 -0700 Subject: [PATCH 03/16] Add comment --- ObsidianWrapper/ObsidianWrapper.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ObsidianWrapper/ObsidianWrapper.jsx b/ObsidianWrapper/ObsidianWrapper.jsx index 2b4cea6..c232128 100644 --- a/ObsidianWrapper/ObsidianWrapper.jsx +++ b/ObsidianWrapper/ObsidianWrapper.jsx @@ -148,7 +148,7 @@ function ObsidianWrapper(props) { return responseObj; } } else { - mutation = insertTypenames(mutation); + // old mutate logic } } catch(e) { console.log(e) From e6381ddde10fd1ceea611511c11fb7145bc7b41f Mon Sep 17 00:00:00 2001 From: Anthony Guan Date: Sun, 13 Mar 2022 22:42:04 -0700 Subject: [PATCH 04/16] Break up logic for add, update, delete mutations --- ObsidianWrapper/ObsidianWrapper.jsx | 10 +++++++++- src/Browser/CacheClassBrowser.js | 4 +++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ObsidianWrapper/ObsidianWrapper.jsx b/ObsidianWrapper/ObsidianWrapper.jsx index c232128..da7b549 100644 --- a/ObsidianWrapper/ObsidianWrapper.jsx +++ b/ObsidianWrapper/ObsidianWrapper.jsx @@ -146,9 +146,17 @@ function ObsidianWrapper(props) { if (toDelete) { const responseObj = cache.writeThrough(mutation, {}, true); return responseObj; + } else { + if (update) { + // run the update function + } + // always write/over-write to cache (add/update) + const responseObj = cache.writeThrough(mutation, {}); + return responseObj; } } else { - // old mutate logic + // copy-paste mutate logic from 4.0 + // use cache.write instead of cache.writeThrough } } catch(e) { console.log(e) diff --git a/src/Browser/CacheClassBrowser.js b/src/Browser/CacheClassBrowser.js index c81ab42..4f17fc0 100644 --- a/src/Browser/CacheClassBrowser.js +++ b/src/Browser/CacheClassBrowser.js @@ -102,6 +102,7 @@ export default class BrowserCache { async writeThrough(queryStr, respObj, deleteFlag) { const queryObj = destructureQueries(queryStr); + // check if it's a mutation if (queryObj.mutations) { // check to see if the mutation/type has been stored in the cache yet // if so, make the graphQL call @@ -132,6 +133,7 @@ export default class BrowserCache { this.storage.types[queryObj.mutations[0].name] = respObj.data[queryObj.mutations[0].name].__typename; return responseObj; + // below is for situations when the type is already stored } else { // construct the response object ourselves let mutation = queryObj.mutations[0].name; @@ -145,7 +147,7 @@ export default class BrowserCache { obj[id] = val; obj.__typename = __typename; respObj.data[mutation] = obj; - // same as above + // complete respObj for ADD mutation (queryStr should contain necessary fields) const resFromNormalize = normalizeResult(queryObj, respObj, deleteFlag); for (const hash in resFromNormalize) { const resp = await this.cacheRead(hash); From 2a310c13796696300ba3beeef8a79d04fe3eec3c Mon Sep 17 00:00:00 2001 From: Anthony Guan Date: Mon, 14 Mar 2022 13:21:58 -0700 Subject: [PATCH 05/16] Finish add and delete mutations Co-authored-by: YuriiShchyrba --- ObsidianWrapper/ObsidianWrapper.jsx | 306 +++++++++++++++------------- src/Browser/CacheClassBrowser.js | 206 ++++++++++--------- 2 files changed, 278 insertions(+), 234 deletions(-) diff --git a/ObsidianWrapper/ObsidianWrapper.jsx b/ObsidianWrapper/ObsidianWrapper.jsx index da7b549..be774e2 100644 --- a/ObsidianWrapper/ObsidianWrapper.jsx +++ b/ObsidianWrapper/ObsidianWrapper.jsx @@ -5,141 +5,142 @@ import { insertTypenames } from '../src/Browser/insertTypenames.js'; const cacheContext = React.createContext(); function ObsidianWrapper(props) { - const [cache, setCache] = React.useState(new BrowserCache()); - async function query(query, options = {}) { - // set the options object default properties if not provided - const { - endpoint = '/graphql', - cacheRead = true, - cacheWrite = true, - pollInterval = null, - wholeQuery = false, - } = options; + const [cache, setCache] = React.useState(new BrowserCache()); + async function query(query, options = {}) { + // set the options object default properties if not provided + const { + endpoint = '/graphql', + cacheRead = true, + cacheWrite = true, + pollInterval = null, + wholeQuery = false, + } = options; - // when pollInterval is not null the query will be sent to the server every inputted number of milliseconds - if (pollInterval) { - const interval = setInterval(() => { - // pass in query() with options instead - new Promise((resolve, reject) => - resolve( - query(query, { pollInterval: null, cacheRead: false, ...options }) - ) - ); - }, pollInterval); - return interval; - } + // when pollInterval is not null the query will be sent to the server every inputted number of milliseconds + if (pollInterval) { + const interval = setInterval(() => { + // pass in query() with options instead + new Promise((resolve, reject) => + resolve( + query(query, { pollInterval: null, cacheRead: false, ...options }) + ) + ); + }, pollInterval); + return interval; + } - // when cacheRead set to true - if (cacheRead) { - let resObj; - // when the developer decides to only utilize whole query for cache - if (wholeQuery) resObj = await cache.readWholeQuery(query); - else resObj = await cache.read(query); - // check if query is stored in cache - if (resObj) { - // returning cached response as a promise - return new Promise((resolve, reject) => resolve(resObj)); - } - // execute graphql fetch request if cache miss - return new Promise((resolve, reject) => resolve(hunt(query))); - // when cacheRead set to false - } - if (!cacheRead) { - return new Promise((resolve, reject) => resolve(hunt(query))); - } + // when cacheRead set to true + if (cacheRead) { + let resObj; + // when the developer decides to only utilize whole query for cache + if (wholeQuery) resObj = await cache.readWholeQuery(query); + else resObj = await cache.read(query); + // check if query is stored in cache + if (resObj) { + // returning cached response as a promise + return new Promise((resolve, reject) => resolve(resObj)); + } + // execute graphql fetch request if cache miss + return new Promise((resolve, reject) => resolve(hunt(query))); + // when cacheRead set to false + } + if (!cacheRead) { + return new Promise((resolve, reject) => resolve(hunt(query))); + } - // when cache miss or on intervals - async function hunt(query) { - if (!wholeQuery) query = insertTypenames(query); - try { - // send fetch request with query - const resJSON = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: JSON.stringify({ query }), - }); - const resObj = await resJSON.json(); - const deepResObj = { ...resObj }; - // update result in cache if cacheWrite is set to true - if (cacheWrite) { - if (wholeQuery) cache.writeWholeQuery(query, deepResObj); - else cache.write(query, deepResObj); - } - return resObj; - } catch (e) { - console.log(e); - } - } - } + // when cache miss or on intervals + async function hunt(query) { + if (!wholeQuery) query = insertTypenames(query); + try { + // send fetch request with query + const resJSON = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify({ query }), + }); + const resObj = await resJSON.json(); + const deepResObj = { ...resObj }; + // update result in cache if cacheWrite is set to true + if (cacheWrite) { + if (wholeQuery) cache.writeWholeQuery(query, deepResObj); + else cache.write(query, deepResObj); + } + return resObj; + } catch (e) { + console.log(e); + } + } + } - // Function to clear cache and session storage - function clearCache() { - cache.cacheClear(); - } - // mutate method, refer to mutate.js for more info - async function mutate(mutation, options = {}) { - // set the options object default properties if not provided - mutation = insertTypenames(mutation); - let { - endpoint = '/graphql', - cacheWrite = true, - toDelete = false, - update = null, - writeThrough = true, - } = options; - // for any mutation a request to the server is made - try { - // one-time check to add types to cache - if(writeThrough){ - writeThrough = await cache.write(mutation, {}, toDelete, writeThrough); - } - const responseObj = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: JSON.stringify({ query: mutation }), - }).then((resp) => resp.json()); - if (!cacheWrite) return responseObj; - // first behaviour when delete cache is set to true - console.log('writeThrough before toDelete: ', writeThrough) - if (!writeThrough && toDelete) { - console.log('in the toDelete') - cache.write(mutation, responseObj, true); - return responseObj; - } - // second behaviour if update function provided - if (update) { - update(cache, responseObj); - } - // third behaviour just for normal update (no-delete, no update function) - if(!writeThrough){ - console.log('in the !writeThrough') - cache.write(mutation, responseObj); - } - if (writeThrough) { - console.log('Here\'s the response from cache: ', writeThrough); - return writeThrough; - } - return responseObj; - } catch (e) { - console.log(e); - } - } + // Function to clear cache and session storage + function clearCache() { + cache.cacheClear(); + } + // mutate method, refer to mutate.js for more info + // async function mutateOld(mutation, options = {}) { + // // set the options object default properties if not provided + // mutation = insertTypenames(mutation); + // let { + // endpoint = '/graphql', + // cacheWrite = true, + // toDelete = false, + // update = null, + // writeThrough = true, + // } = options; + // // for any mutation a request to the server is made + // try { + // // one-time check to add types to cache + // if(writeThrough){ + // writeThrough = await cache.write(mutation, {}, toDelete, writeThrough); + // } + // const responseObj = await fetch(endpoint, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // Accept: 'application/json', + // }, + // body: JSON.stringify({ query: mutation }), + // }).then((resp) => resp.json()); + // if (!cacheWrite) return responseObj; + // // first behaviour when delete cache is set to true + // console.log('writeThrough before toDelete: ', writeThrough) + // if (!writeThrough && toDelete) { + // console.log('in the toDelete') + // cache.write(mutation, responseObj, true); + // return responseObj; + // } + // // second behaviour if update function provided + // if (update) { + // update(cache, responseObj); + // } + // // third behaviour just for normal update (no-delete, no update function) + // if(!writeThrough){ + // console.log('in the !writeThrough') + // cache.write(mutation, responseObj); + // } + // if (writeThrough) { + // console.log('Here\'s the response from cache: ', writeThrough); + // return writeThrough; + // } + // return responseObj; + // } catch (e) { + // console.log(e); + // } + // } // breaking out writethrough logic vs. non-writethrough logic - async function mutateRefactored(mutation, options = {}) { - const { - endpoint = '/graphql', - cacheWrite = true, - toDelete = false, - update = null, - writeThrough = true, - } = options; + async function mutate(mutation, options = {}) { + mutation = insertTypenames(mutation); + const { + endpoint = '/graphql', + cacheWrite = true, + toDelete = false, + update = null, + writeThrough = true, + } = options; try { if (writeThrough) { // helper function to check if we've stored the type yet @@ -152,29 +153,52 @@ function ObsidianWrapper(props) { } // always write/over-write to cache (add/update) const responseObj = cache.writeThrough(mutation, {}); + // GQL call to make changes and synchronize database + return responseObj; } } else { // copy-paste mutate logic from 4.0 // use cache.write instead of cache.writeThrough + const responseObj = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify({ query: mutation }), + }).then((resp) => resp.json()); + if (!cacheWrite) return responseObj; + // first behaviour when delete cache is set to true + if (toDelete) { + cache.write(mutation, responseObj, true); + return responseObj; + } + // second behaviour if update function provided + if (update) { + update(cache, responseObj); + } + // third behaviour just for normal update (no-delete, no update function) + cache.write(mutation, responseObj); + return responseObj; } - } catch(e) { - console.log(e) + } catch (e) { + console.log(e); } } - // Returning Provider React component that allows consuming components to subscribe to context changes - return ( - - ); + // Returning Provider React component that allows consuming components to subscribe to context changes + return ( + + ); } // Declaration of custom hook to allow access to provider function useObsidian() { - // React useContext hook to access the global provider by any of the consumed components - return React.useContext(cacheContext); + // React useContext hook to access the global provider by any of the consumed components + return React.useContext(cacheContext); } // Exporting of Custom wrapper and hook to access wrapper cache -export { ObsidianWrapper, useObsidian }; \ No newline at end of file +export { ObsidianWrapper, useObsidian }; diff --git a/src/Browser/CacheClassBrowser.js b/src/Browser/CacheClassBrowser.js index 4f17fc0..4266360 100644 --- a/src/Browser/CacheClassBrowser.js +++ b/src/Browser/CacheClassBrowser.js @@ -9,7 +9,7 @@ export default class BrowserCache { ROOT_QUERY: {}, ROOT_MUTATION: {}, // match resolvers to types in order to add them in write-through - types: {}, + writeThroughInfo: {}, } ) { this.storage = initialCache; @@ -51,45 +51,62 @@ export default class BrowserCache { return { data: responseObject }; } - async write(queryStr, respObj, deleteFlag, writeThrough = false) { - const queryObj = destructureQueries(queryStr); - // if it's not already included in types, skip write-through and make graphQL call - console.log('WriteThrough before :', writeThrough); - if (writeThrough && queryObj.mutations) { - if (!this.storage.types.hasOwnProperty(queryObj.mutations[0].name)) { - console.log('Skipping first write through'); - return false; + async writeThrough(queryStr, respObj, deleteFlag) { + try { + const queryObj = destructureQueries(queryStr); + console.log("Here's the query object: ", queryObj); + const mutationName = queryObj.mutations[0].name; + // check if it's a mutation + if (queryObj.mutations) { + // check to see if the mutation/type has been stored in the cache yet + // if so, make the graphQL call + if (!this.storage.writeThroughInfo.hasOwnProperty(mutationName)) { + respObj = await fetch('/graphql', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify({ query: queryStr }), + }).then((resp) => resp.json()); + // store the mutation/type in cache + this.storage.writeThroughInfo[mutationName] = {}; + this.storage.writeThroughInfo[mutationName].type = + respObj.data[mutationName].__typename; + this.storage.writeThroughInfo[mutationName].lastId = + respObj.data[mutationName].id; + // below is for situations when the type is already stored + } else { + // construct the response object ourselves + this.constructResponseObject(queryObj, respObj, deleteFlag); + await fetch('/graphql', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify({ query: queryStr }), + }); + } + console.log('Here is response from Data base ', respObj); + // same logic for both situations + // normalize the result, invalidate the cache and return the appropriate object + this.write(queryStr, respObj, deleteFlag); + return respObj; } - console.log('Second time delete'); - let mut = queryObj.mutations[0].name; - let idAndVal = queryObj.mutations[0].arguments; - idAndVal = idAndVal.split(':'); - let id = idAndVal[0].substring(1); - let val = idAndVal[1].substring(0, idAndVal[1].length - 1); - let __typename = this.storage.types[queryObj.mutations[0].name]; - respObj.data = {}; - let obj = {}; - obj[id] = val; - obj.__typename = __typename; - respObj.data[mut] = obj; - console.log('respObj: ', respObj); + } catch (e) { + console.log(e); } + } + + async write(queryStr, respObj, deleteFlag) { + const queryObj = destructureQueries(queryStr); const resFromNormalize = normalizeResult(queryObj, respObj, deleteFlag); - console.log('WriteThrough After :', writeThrough); - console.log('Query :', queryObj); - if (!writeThrough && queryObj.mutations) { - console.log('First time delete'); - console.log('queryObj: ', queryObj); - this.storage.types[queryObj.mutations[0].name] = - respObj.data[queryObj.mutations[0].name].__typename; - } // update the original cache with same reference for (const hash in resFromNormalize) { const resp = await this.cacheRead(hash); - // console.log(resp) if (resFromNormalize[hash] === 'DELETED') { await this.cacheWrite(hash, 'DELETED'); - return resp; } else if (resp) { const newObj = Object.assign(resp, resFromNormalize[hash]); await this.cacheWrite(hash, newObj); @@ -97,72 +114,75 @@ export default class BrowserCache { await this.cacheWrite(hash, resFromNormalize[hash]); } } - return true; } - async writeThrough(queryStr, respObj, deleteFlag) { - const queryObj = destructureQueries(queryStr); - // check if it's a mutation - if (queryObj.mutations) { - // check to see if the mutation/type has been stored in the cache yet - // if so, make the graphQL call - if (!this.storage.types.hasOwnProperty(queryObj.mutations[0].name)) { - const responseObj = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: JSON.stringify({ query: mutation }), - }).then((resp) => resp.json()); - // normalize the result, invalidate the cache and return the appropriate object - const resFromNormalize = normalizeResult(queryObj, respObj, deleteFlag); - for (const hash in resFromNormalize) { - const resp = await this.cacheRead(hash); - if (resFromNormalize[hash] === 'DELETED') { - await this.cacheWrite(hash, 'DELETED'); - return resp; - } else if (resp) { - const newObj = Object.assign(resp, resFromNormalize[hash]); - await this.cacheWrite(hash, newObj); - } else { - await this.cacheWrite(hash, resFromNormalize[hash]); - } + constructResponseObject(queryObj, respObj, deleteFlag) { + const mutationData = queryObj.mutations[0]; + const mutationName = mutationData.name; + const __typename = (this.storage.writeThroughInfo[mutationName] = {}); + writeThroughInfo[mutationName].type; + respObj.data = {}; + let obj = {}; + respObj.data[mutationName] = obj; + obj.__typename = __typename; + // delete logic + if (deleteFlag) { + // add id and value from the queryObj + let idAndVal = mutationData.arguments; + idAndVal = idAndVal.split(':'); + const id = idAndVal[0].substring(1); + const val = idAndVal[1].substring(0, idAndVal[1].length - 1); + obj[id] = val; + // return out of this function so we don't continue + // onto add/update logic + return respObj; + } + // increment ID for ADD mutations only + obj.id = (++this.storage.writeThroughInfo[mutationName].lastId).toString(); + + // ADD mutation logic + // grab arguments (which is a string) + const argumentsStr = mutationData.arguments; + addNonScalarFields(argumentsStr); + separateArguments(argumentsStr); + console.log('Final obj', respObj); + + function separateArguments(str) { + const startIndex = str.indexOf('{'); + const slicedStr = str.slice(startIndex + 1, str.length - 2); + console.log('slicedStr: ', slicedStr); + const argumentPairs = slicedStr.split(','); + console.log('argumentPairs', argumentPairs); + for (const argumentPair of argumentPairs) { + const argumentKeyAndValue = argumentPair.split(':'); + const argumentKey = argumentKeyAndValue[0]; + let argumentValue = Number(argumentKeyAndValue[1]) + ? Number(argumentKeyAndValue[1]) + : argumentKeyAndValue[1]; + if (typeof argumentValue === 'string') { + console.log("It's a string"); + argumentValue = argumentValue.replace(/\"/g, ''); } - // store the mutation/type in cache - this.storage.types[queryObj.mutations[0].name] = - respObj.data[queryObj.mutations[0].name].__typename; - return responseObj; - // below is for situations when the type is already stored - } else { - // construct the response object ourselves - let mutation = queryObj.mutations[0].name; - let idAndVal = queryObj.mutations[0].arguments; - idAndVal = idAndVal.split(':'); - let id = idAndVal[0].substring(1); - let val = idAndVal[1].substring(0, idAndVal[1].length - 1); - let __typename = this.storage.types[queryObj.mutations[0].name]; - respObj.data = {}; - let obj = {}; - obj[id] = val; - obj.__typename = __typename; - respObj.data[mutation] = obj; - // complete respObj for ADD mutation (queryStr should contain necessary fields) - const resFromNormalize = normalizeResult(queryObj, respObj, deleteFlag); - for (const hash in resFromNormalize) { - const resp = await this.cacheRead(hash); - if (resFromNormalize[hash] === 'DELETED') { - await this.cacheWrite(hash, 'DELETED'); - return resp; - } else if (resp) { - const newObj = Object.assign(resp, resFromNormalize[hash]); - await this.cacheWrite(hash, newObj); - } else { - await this.cacheWrite(hash, resFromNormalize[hash]); - } + respObj.data[mutationName][argumentKey] = argumentValue; + } + console.log("Here's after adding arguments: ", respObj); + } + + function addNonScalarFields(str) { + for (const field in mutationData.fields) { + console.log( + 'in addNonScalarFields function: ', + field, + mutationData.fields[field] + ); + if ( + mutationData.fields[field] !== 'scalar' && + mutationData.fields[field] !== 'meta' + ) { + respObj.data[mutationName][field] = []; } - return respObj; } + console.log("Here's after adding array fields: ", respObj); } } From a33a2692b861d229164cb73877c80a756d4f2307 Mon Sep 17 00:00:00 2001 From: Anthony Guan Date: Mon, 14 Mar 2022 17:38:16 -0700 Subject: [PATCH 06/16] A couple more changes Co-authored-by: YuriiShchyrba --- ObsidianWrapper/ObsidianWrapper.jsx | 10 ++++++---- src/Browser/CacheClassBrowser.js | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/ObsidianWrapper/ObsidianWrapper.jsx b/ObsidianWrapper/ObsidianWrapper.jsx index be774e2..297e54b 100644 --- a/ObsidianWrapper/ObsidianWrapper.jsx +++ b/ObsidianWrapper/ObsidianWrapper.jsx @@ -139,22 +139,23 @@ function ObsidianWrapper(props) { cacheWrite = true, toDelete = false, update = null, - writeThrough = true, + writeThrough = false, } = options; try { + console.log('Lets see endpoint ' , endpoint); if (writeThrough) { // helper function to check if we've stored the type yet if (toDelete) { - const responseObj = cache.writeThrough(mutation, {}, true); + const responseObj = cache.writeThrough(mutation, {}, true, endpoint); return responseObj; } else { if (update) { // run the update function } // always write/over-write to cache (add/update) - const responseObj = cache.writeThrough(mutation, {}); + const responseObj = cache.writeThrough(mutation, {},false,endpoint); // GQL call to make changes and synchronize database - + console.log('WriteThrough - true ', responseObj); return responseObj; } } else { @@ -180,6 +181,7 @@ function ObsidianWrapper(props) { } // third behaviour just for normal update (no-delete, no update function) cache.write(mutation, responseObj); + console.log('WriteThrough - false ', responseObj); return responseObj; } } catch (e) { diff --git a/src/Browser/CacheClassBrowser.js b/src/Browser/CacheClassBrowser.js index 4266360..297951f 100644 --- a/src/Browser/CacheClassBrowser.js +++ b/src/Browser/CacheClassBrowser.js @@ -51,7 +51,8 @@ export default class BrowserCache { return { data: responseObject }; } - async writeThrough(queryStr, respObj, deleteFlag) { + async writeThrough(queryStr, respObj, deleteFlag, endpoint) { + console.log('Endpoint in writeThrough ', endpoint); try { const queryObj = destructureQueries(queryStr); console.log("Here's the query object: ", queryObj); @@ -61,7 +62,7 @@ export default class BrowserCache { // check to see if the mutation/type has been stored in the cache yet // if so, make the graphQL call if (!this.storage.writeThroughInfo.hasOwnProperty(mutationName)) { - respObj = await fetch('/graphql', { + respObj = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -79,7 +80,7 @@ export default class BrowserCache { } else { // construct the response object ourselves this.constructResponseObject(queryObj, respObj, deleteFlag); - await fetch('/graphql', { + await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -87,8 +88,9 @@ export default class BrowserCache { }, body: JSON.stringify({ query: queryStr }), }); + console.log('Successful fetch request'); } - console.log('Here is response from Data base ', respObj); + console.log('Here is response from Data base ', respObj); // same logic for both situations // normalize the result, invalidate the cache and return the appropriate object this.write(queryStr, respObj, deleteFlag); @@ -119,8 +121,8 @@ export default class BrowserCache { constructResponseObject(queryObj, respObj, deleteFlag) { const mutationData = queryObj.mutations[0]; const mutationName = mutationData.name; - const __typename = (this.storage.writeThroughInfo[mutationName] = {}); - writeThroughInfo[mutationName].type; + const __typename = this.storage.writeThroughInfo[mutationName].type; + // this.storage.writeThroughInfo[mutationName].type; respObj.data = {}; let obj = {}; respObj.data[mutationName] = obj; @@ -138,6 +140,10 @@ export default class BrowserCache { return respObj; } // increment ID for ADD mutations only + console.log( + 'Line 141, F constructResponseObject, id:', + this.storage.writeThroughInfo[mutationName] + ); obj.id = (++this.storage.writeThroughInfo[mutationName].lastId).toString(); // ADD mutation logic From c462ddc9c17e84a4acedeab8daf0704a93641993 Mon Sep 17 00:00:00 2001 From: Anthony Guan Date: Tue, 15 Mar 2022 08:47:31 -0700 Subject: [PATCH 07/16] Finish add and delete mutations Co-authored-by: YuriiShchyrba --- ObsidianWrapper/ObsidianWrapper.jsx | 16 ++++++++++------ src/Browser/CacheClassBrowser.js | 16 +++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/ObsidianWrapper/ObsidianWrapper.jsx b/ObsidianWrapper/ObsidianWrapper.jsx index 297e54b..32ad89b 100644 --- a/ObsidianWrapper/ObsidianWrapper.jsx +++ b/ObsidianWrapper/ObsidianWrapper.jsx @@ -35,6 +35,7 @@ function ObsidianWrapper(props) { // when the developer decides to only utilize whole query for cache if (wholeQuery) resObj = await cache.readWholeQuery(query); else resObj = await cache.read(query); + console.log('query function resObj: ', resObj) // check if query is stored in cache if (resObj) { // returning cached response as a promise @@ -139,27 +140,30 @@ function ObsidianWrapper(props) { cacheWrite = true, toDelete = false, update = null, - writeThrough = false, + writeThrough = true, } = options; try { - console.log('Lets see endpoint ' , endpoint); if (writeThrough) { - // helper function to check if we've stored the type yet + // if it's a deletion, then delete from cache and return the object if (toDelete) { - const responseObj = cache.writeThrough(mutation, {}, true, endpoint); + const responseObj = await cache.writeThrough(mutation, {}, true, endpoint); return responseObj; } else { + // for add mutation + const responseObj = await cache.writeThrough(mutation, {},false,endpoint); + // for update mutation if (update) { // run the update function + update(cache, responseObj) } // always write/over-write to cache (add/update) - const responseObj = cache.writeThrough(mutation, {},false,endpoint); // GQL call to make changes and synchronize database console.log('WriteThrough - true ', responseObj); return responseObj; } } else { - // copy-paste mutate logic from 4.0 + // copy-paste mutate logic from 4. + // use cache.write instead of cache.writeThrough const responseObj = await fetch(endpoint, { method: 'POST', diff --git a/src/Browser/CacheClassBrowser.js b/src/Browser/CacheClassBrowser.js index 297951f..0992806 100644 --- a/src/Browser/CacheClassBrowser.js +++ b/src/Browser/CacheClassBrowser.js @@ -52,7 +52,6 @@ export default class BrowserCache { } async writeThrough(queryStr, respObj, deleteFlag, endpoint) { - console.log('Endpoint in writeThrough ', endpoint); try { const queryObj = destructureQueries(queryStr); console.log("Here's the query object: ", queryObj); @@ -79,21 +78,22 @@ export default class BrowserCache { // below is for situations when the type is already stored } else { // construct the response object ourselves - this.constructResponseObject(queryObj, respObj, deleteFlag); - await fetch(endpoint, { + const dummyResponse = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, body: JSON.stringify({ query: queryStr }), - }); - console.log('Successful fetch request'); + }).then((resp) => resp.json()); + console.log("Here's the dummy response :", dummyResponse); + this.constructResponseObject(queryObj, respObj, deleteFlag); } - console.log('Here is response from Data base ', respObj); // same logic for both situations // normalize the result, invalidate the cache and return the appropriate object - this.write(queryStr, respObj, deleteFlag); + console.log('Before write query :', queryStr) + console.log('Before write respObh :', respObj) + await this.write(queryStr, respObj, deleteFlag); return respObj; } } catch (e) { @@ -102,6 +102,8 @@ export default class BrowserCache { } async write(queryStr, respObj, deleteFlag) { + console.log('In write query :', queryStr) + console.log('In write respObj :', respObj) const queryObj = destructureQueries(queryStr); const resFromNormalize = normalizeResult(queryObj, respObj, deleteFlag); // update the original cache with same reference From 71c229a3c863bdca32a3799fb244d05b82e88b82 Mon Sep 17 00:00:00 2001 From: Anthony Guan Date: Tue, 15 Mar 2022 17:04:41 -0700 Subject: [PATCH 08/16] Send messages to chrome extension --- ObsidianWrapper/ObsidianWrapper.jsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ObsidianWrapper/ObsidianWrapper.jsx b/ObsidianWrapper/ObsidianWrapper.jsx index 32ad89b..8685665 100644 --- a/ObsidianWrapper/ObsidianWrapper.jsx +++ b/ObsidianWrapper/ObsidianWrapper.jsx @@ -7,6 +7,9 @@ const cacheContext = React.createContext(); function ObsidianWrapper(props) { const [cache, setCache] = React.useState(new BrowserCache()); async function query(query, options = {}) { + // dev tool messages + const startTime = Date.now(); + chrome.runtime.sendMessage('mjlkdebdclaakhcdbaapleegkoehnboj', {'query': query}); // set the options object default properties if not provided const { endpoint = '/graphql', @@ -39,6 +42,8 @@ function ObsidianWrapper(props) { // check if query is stored in cache if (resObj) { // returning cached response as a promise + const cacheHitResponseTime = Date.now() - startTime; + chrome.runtime.sendMessage('mjlkdebdclaakhcdbaapleegkoehnboj', {'cacheHitResponseTime': cacheHitResponseTime}); return new Promise((resolve, reject) => resolve(resObj)); } // execute graphql fetch request if cache miss @@ -69,6 +74,9 @@ function ObsidianWrapper(props) { if (wholeQuery) cache.writeWholeQuery(query, deepResObj); else cache.write(query, deepResObj); } + const cacheMissResponseTime = Date.now() - startTime; + chrome.runtime.sendMessage('mjlkdebdclaakhcdbaapleegkoehnboj', {'cacheMissResponseTime': cacheMissResponseTime}); + console.log('Here\'s the response time on the front end: ', cacheMissResponseTime); return resObj; } catch (e) { console.log(e); @@ -134,6 +142,9 @@ function ObsidianWrapper(props) { // breaking out writethrough logic vs. non-writethrough logic async function mutate(mutation, options = {}) { + // dev tool messages + chrome.runtime.sendMessage('mjlkdebdclaakhcdbaapleegkoehnboj', {'mutation': mutation}); + const startTime = Date.now(); mutation = insertTypenames(mutation); const { endpoint = '/graphql', @@ -147,6 +158,8 @@ function ObsidianWrapper(props) { // if it's a deletion, then delete from cache and return the object if (toDelete) { const responseObj = await cache.writeThrough(mutation, {}, true, endpoint); + const deleteMutationResponseTime = Date.now() - startTime; + chrome.runtime.sendMessage('mjlkdebdclaakhcdbaapleegkoehnboj', {'deleteMutationResponseTime': deleteMutationResponseTime}); return responseObj; } else { // for add mutation @@ -159,6 +172,8 @@ function ObsidianWrapper(props) { // always write/over-write to cache (add/update) // GQL call to make changes and synchronize database console.log('WriteThrough - true ', responseObj); + const addOrUpdateMutationResponseTime = Date.now() - startTime; + chrome.runtime.sendMessage('mjlkdebdclaakhcdbaapleegkoehnboj', {'addOrUpdateMutationResponseTime': addOrUpdateMutationResponseTime}); return responseObj; } } else { From e74ee3b7ece31dd5dbc54f78bcddd56ec9b3dc3c Mon Sep 17 00:00:00 2001 From: YuriiShchyrba Date: Wed, 16 Mar 2022 16:59:26 -0400 Subject: [PATCH 09/16] feat: refactor --- src/Browser/CacheClassBrowser.js | 71 +++++++++++--------------------- 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/src/Browser/CacheClassBrowser.js b/src/Browser/CacheClassBrowser.js index 0992806..265f78a 100644 --- a/src/Browser/CacheClassBrowser.js +++ b/src/Browser/CacheClassBrowser.js @@ -54,7 +54,6 @@ export default class BrowserCache { async writeThrough(queryStr, respObj, deleteFlag, endpoint) { try { const queryObj = destructureQueries(queryStr); - console.log("Here's the query object: ", queryObj); const mutationName = queryObj.mutations[0].name; // check if it's a mutation if (queryObj.mutations) { @@ -86,13 +85,10 @@ export default class BrowserCache { }, body: JSON.stringify({ query: queryStr }), }).then((resp) => resp.json()); - console.log("Here's the dummy response :", dummyResponse); this.constructResponseObject(queryObj, respObj, deleteFlag); } // same logic for both situations // normalize the result, invalidate the cache and return the appropriate object - console.log('Before write query :', queryStr) - console.log('Before write respObh :', respObj) await this.write(queryStr, respObj, deleteFlag); return respObj; } @@ -102,8 +98,6 @@ export default class BrowserCache { } async write(queryStr, respObj, deleteFlag) { - console.log('In write query :', queryStr) - console.log('In write respObj :', respObj) const queryObj = destructureQueries(queryStr); const resFromNormalize = normalizeResult(queryObj, respObj, deleteFlag); // update the original cache with same reference @@ -126,7 +120,7 @@ export default class BrowserCache { const __typename = this.storage.writeThroughInfo[mutationName].type; // this.storage.writeThroughInfo[mutationName].type; respObj.data = {}; - let obj = {}; + const obj = {}; respObj.data[mutationName] = obj; obj.__typename = __typename; // delete logic @@ -142,55 +136,40 @@ export default class BrowserCache { return respObj; } // increment ID for ADD mutations only - console.log( - 'Line 141, F constructResponseObject, id:', - this.storage.writeThroughInfo[mutationName] - ); obj.id = (++this.storage.writeThroughInfo[mutationName].lastId).toString(); // ADD mutation logic // grab arguments (which is a string) const argumentsStr = mutationData.arguments; - addNonScalarFields(argumentsStr); - separateArguments(argumentsStr); - console.log('Final obj', respObj); + this.addNonScalarFields(argumentsStr, respObj, mutationData); + this.separateArguments(argumentsStr, respObj, mutationName ); + } - function separateArguments(str) { - const startIndex = str.indexOf('{'); - const slicedStr = str.slice(startIndex + 1, str.length - 2); - console.log('slicedStr: ', slicedStr); - const argumentPairs = slicedStr.split(','); - console.log('argumentPairs', argumentPairs); - for (const argumentPair of argumentPairs) { - const argumentKeyAndValue = argumentPair.split(':'); - const argumentKey = argumentKeyAndValue[0]; - let argumentValue = Number(argumentKeyAndValue[1]) - ? Number(argumentKeyAndValue[1]) - : argumentKeyAndValue[1]; - if (typeof argumentValue === 'string') { - console.log("It's a string"); - argumentValue = argumentValue.replace(/\"/g, ''); - } - respObj.data[mutationName][argumentKey] = argumentValue; + separateArguments(str,respObj,mutationName) { + const startIndex = str.indexOf('{'); + const slicedStr = str.slice(startIndex + 1, str.length - 2); + const argumentPairs = slicedStr.split(','); + for (const argumentPair of argumentPairs) { + const argumentKeyAndValue = argumentPair.split(':'); + const argumentKey = argumentKeyAndValue[0]; + let argumentValue = Number(argumentKeyAndValue[1]) + ? Number(argumentKeyAndValue[1]) + : argumentKeyAndValue[1]; + if (typeof argumentValue === 'string') { + argumentValue = argumentValue.replace(/\"/g, ''); } - console.log("Here's after adding arguments: ", respObj); + respObj.data[mutationName][argumentKey] = argumentValue; } + } - function addNonScalarFields(str) { - for (const field in mutationData.fields) { - console.log( - 'in addNonScalarFields function: ', - field, - mutationData.fields[field] - ); - if ( - mutationData.fields[field] !== 'scalar' && - mutationData.fields[field] !== 'meta' - ) { - respObj.data[mutationName][field] = []; - } + addNonScalarFields(str, respObj , mutationData) { + for (const field in mutationData.fields) { + if ( + mutationData.fields[field] !== 'scalar' && + mutationData.fields[field] !== 'meta' + ) { + respObj.data[mutationData.name][field] = []; } - console.log("Here's after adding array fields: ", respObj); } } From 7d0e4b23de6de3bac53c6b9378c909fb32d2dc28 Mon Sep 17 00:00:00 2001 From: Anthony Guan Date: Wed, 16 Mar 2022 17:52:17 -0700 Subject: [PATCH 10/16] Add another sendMessage Co-authored-by: Yasir Choudhury Co-authored-by: YuriiShchyrba --- ObsidianWrapper/ObsidianWrapper.jsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ObsidianWrapper/ObsidianWrapper.jsx b/ObsidianWrapper/ObsidianWrapper.jsx index 8685665..28090cb 100644 --- a/ObsidianWrapper/ObsidianWrapper.jsx +++ b/ObsidianWrapper/ObsidianWrapper.jsx @@ -6,10 +6,23 @@ const cacheContext = React.createContext(); function ObsidianWrapper(props) { const [cache, setCache] = React.useState(new BrowserCache()); + + const chromeExtensionId = 'mjlkdebdclaakhcdbaapleegkoehnboj'; + + window.localStorage.setItem('cache', JSON.stringify(cache)); + + // for (const field in cache.storage) { + // window.localStorage.setItem(field, JSON.stringify(cache.storage[field])); + // } + // window.localStorage.setItem('yasir','anthony'); + // const testResponse = window.localStorage.getItem('yasir'); + // console.log('Here\'s the test response from localStorage: ', testResponse) async function query(query, options = {}) { // dev tool messages const startTime = Date.now(); - chrome.runtime.sendMessage('mjlkdebdclaakhcdbaapleegkoehnboj', {'query': query}); + chrome.runtime.sendMessage(chromeExtensionId, {'query': query}); + chrome.runtime.sendMessage(chromeExtensionId, {'cache': window.localStorage.getItem('cache')}) + console.log('Here\'s the message content: ', window.localStorage.getItem('cache')) // set the options object default properties if not provided const { endpoint = '/graphql', From 8fadbd8e79344e6c330dc84db9630e7a645046f0 Mon Sep 17 00:00:00 2001 From: Alison Fay Date: Wed, 30 Mar 2022 11:36:26 -0400 Subject: [PATCH 11/16] consolidated ID fields to variable --- ObsidianWrapper/ObsidianWrapper.jsx | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/ObsidianWrapper/ObsidianWrapper.jsx b/ObsidianWrapper/ObsidianWrapper.jsx index 28090cb..d633783 100644 --- a/ObsidianWrapper/ObsidianWrapper.jsx +++ b/ObsidianWrapper/ObsidianWrapper.jsx @@ -7,7 +7,7 @@ const cacheContext = React.createContext(); function ObsidianWrapper(props) { const [cache, setCache] = React.useState(new BrowserCache()); - const chromeExtensionId = 'mjlkdebdclaakhcdbaapleegkoehnboj'; + const chromeExtensionId = 'dkbfipkapkljpdbhdihnlnbieffhjdmh'; window.localStorage.setItem('cache', JSON.stringify(cache)); @@ -56,7 +56,9 @@ function ObsidianWrapper(props) { if (resObj) { // returning cached response as a promise const cacheHitResponseTime = Date.now() - startTime; - chrome.runtime.sendMessage('mjlkdebdclaakhcdbaapleegkoehnboj', {'cacheHitResponseTime': cacheHitResponseTime}); + chrome.runtime.sendMessage(chromeExtensionId, { + cacheHitResponseTime: cacheHitResponseTime, + }); return new Promise((resolve, reject) => resolve(resObj)); } // execute graphql fetch request if cache miss @@ -88,7 +90,9 @@ function ObsidianWrapper(props) { else cache.write(query, deepResObj); } const cacheMissResponseTime = Date.now() - startTime; - chrome.runtime.sendMessage('mjlkdebdclaakhcdbaapleegkoehnboj', {'cacheMissResponseTime': cacheMissResponseTime}); + chrome.runtime.sendMessage(chromeExtensionId, { + cacheMissResponseTime: cacheMissResponseTime, + }); console.log('Here\'s the response time on the front end: ', cacheMissResponseTime); return resObj; } catch (e) { @@ -156,7 +160,9 @@ function ObsidianWrapper(props) { // breaking out writethrough logic vs. non-writethrough logic async function mutate(mutation, options = {}) { // dev tool messages - chrome.runtime.sendMessage('mjlkdebdclaakhcdbaapleegkoehnboj', {'mutation': mutation}); + chrome.runtime.sendMessage(chromeExtensionId, { + mutation: mutation, + }); const startTime = Date.now(); mutation = insertTypenames(mutation); const { @@ -172,7 +178,9 @@ function ObsidianWrapper(props) { if (toDelete) { const responseObj = await cache.writeThrough(mutation, {}, true, endpoint); const deleteMutationResponseTime = Date.now() - startTime; - chrome.runtime.sendMessage('mjlkdebdclaakhcdbaapleegkoehnboj', {'deleteMutationResponseTime': deleteMutationResponseTime}); + chrome.runtime.sendMessage(chromeExtensionId, { + deleteMutationResponseTime: deleteMutationResponseTime, + }); return responseObj; } else { // for add mutation @@ -186,7 +194,9 @@ function ObsidianWrapper(props) { // GQL call to make changes and synchronize database console.log('WriteThrough - true ', responseObj); const addOrUpdateMutationResponseTime = Date.now() - startTime; - chrome.runtime.sendMessage('mjlkdebdclaakhcdbaapleegkoehnboj', {'addOrUpdateMutationResponseTime': addOrUpdateMutationResponseTime}); + chrome.runtime.sendMessage(chromeExtensionId, { + addOrUpdateMutationResponseTime: addOrUpdateMutationResponseTime, + }); return responseObj; } } else { From fd5e402361dc6b9a9c54500095f3a74ff670e59d Mon Sep 17 00:00:00 2001 From: Yasir Choudhury <44238394+Yasir-Choudhury@users.noreply.github.com> Date: Wed, 30 Mar 2022 15:39:33 -0400 Subject: [PATCH 12/16] Update README.md --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fddb734..feb2336 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ - Configurable caching options, giving you complete control over your cache - Fullstack integration, leveraging client-side and server-side caching to streamline your caching strategy - Support for the full GraphQL convention -- Support for server-side cache invalidation +- Support for client-side and server-side cache invalidation - Optional GraphQL DoS attack mitigation security module ## Overview @@ -151,9 +151,12 @@ const MovieApp = () => { ``` ## Documentation - [obsidian.land](http://obsidian.land) +## Developer Tool +information and instructions on how to use our developer tool can be found here: +https://github.com/oslabs-beta/obsidian-developer-tool + ## Dockerized Demo working demo to install locally in docker: [oslabs-beta/obsidian-demo-docker](https://github.com/oslabs-beta/obsidian-demo-docker) @@ -164,7 +167,11 @@ github for a demo with some example code to play with: ## Authors - +[Yurii Shchyrba](https://github.com/YuriiShchyrba) +[Linda Zhao](https://github.com/lzhao15) +[Ali Fay](https://github.com/ali-fay) +[Anthony Guan](https://github.com/guananthony) +[Yasir Choudhury](https://github.com/Yasir-Choudhury) [Yogi Paturu](https://github.com/YogiPaturu) [Michael Chin](https://github.com/mikechin37) [Dana Flury](https://github.com/dmflury) From eda7ddc70a3a43770cc2309f10edcd0ba777b047 Mon Sep 17 00:00:00 2001 From: Yasir Choudhury <44238394+Yasir-Choudhury@users.noreply.github.com> Date: Wed, 30 Mar 2022 15:41:13 -0400 Subject: [PATCH 13/16] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index feb2336..437ccef 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ const MovieApp = () => { ## Developer Tool information and instructions on how to use our developer tool can be found here: -https://github.com/oslabs-beta/obsidian-developer-tool +[oslabs-beta/obsidian-developer-tool](https://github.com/oslabs-beta/obsidian-developer-tool) ## Dockerized Demo working demo to install locally in docker: From 2ec4141ca867ccb65e665627b1dc22c3adf2a4fe Mon Sep 17 00:00:00 2001 From: Ali <88858204+ali-fay@users.noreply.github.com> Date: Wed, 30 Mar 2022 16:52:58 -0400 Subject: [PATCH 14/16] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 437ccef..8bd88df 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ const MovieApp = () => { [obsidian.land](http://obsidian.land) ## Developer Tool -information and instructions on how to use our developer tool can be found here: +information and instructions on how to use our developer tool can be found here:
[oslabs-beta/obsidian-developer-tool](https://github.com/oslabs-beta/obsidian-developer-tool) ## Dockerized Demo From 2c9af64fb63f2ecd823a4d05089e65402c9539be Mon Sep 17 00:00:00 2001 From: YuriiShchyrba Date: Tue, 12 Apr 2022 19:19:28 -0400 Subject: [PATCH 15/16] feat: cleanup --- ObsidianWrapper/ObsidianWrapper.jsx | 404 ++++++++---------- src/Browser/CacheClassBrowser.js | 630 ++++++++++++++-------------- 2 files changed, 499 insertions(+), 535 deletions(-) diff --git a/ObsidianWrapper/ObsidianWrapper.jsx b/ObsidianWrapper/ObsidianWrapper.jsx index d633783..73c162a 100644 --- a/ObsidianWrapper/ObsidianWrapper.jsx +++ b/ObsidianWrapper/ObsidianWrapper.jsx @@ -1,247 +1,209 @@ -import React from 'https://dev.jspm.io/react'; -import BrowserCache from '../src/Browser/CacheClassBrowser.js'; -import { insertTypenames } from '../src/Browser/insertTypenames.js'; +import React from "https://dev.jspm.io/react"; +import BrowserCache from "../src/Browser/CacheClassBrowser.js"; +import { insertTypenames } from "../src/Browser/insertTypenames.js"; const cacheContext = React.createContext(); function ObsidianWrapper(props) { - const [cache, setCache] = React.useState(new BrowserCache()); - - const chromeExtensionId = 'dkbfipkapkljpdbhdihnlnbieffhjdmh'; - - window.localStorage.setItem('cache', JSON.stringify(cache)); + const [cache, setCache] = React.useState(new BrowserCache()); - // for (const field in cache.storage) { - // window.localStorage.setItem(field, JSON.stringify(cache.storage[field])); - // } - // window.localStorage.setItem('yasir','anthony'); - // const testResponse = window.localStorage.getItem('yasir'); - // console.log('Here\'s the test response from localStorage: ', testResponse) - async function query(query, options = {}) { - // dev tool messages - const startTime = Date.now(); - chrome.runtime.sendMessage(chromeExtensionId, {'query': query}); - chrome.runtime.sendMessage(chromeExtensionId, {'cache': window.localStorage.getItem('cache')}) - console.log('Here\'s the message content: ', window.localStorage.getItem('cache')) - // set the options object default properties if not provided - const { - endpoint = '/graphql', - cacheRead = true, - cacheWrite = true, - pollInterval = null, - wholeQuery = false, - } = options; + // You have to put your Google Chrome Obsidian developer tool extension id to connect Obsidian Wrapper with dev tool + const chromeExtensionId = "dkbfipkapkljpdbhdihnlnbieffhjdmh"; - // when pollInterval is not null the query will be sent to the server every inputted number of milliseconds - if (pollInterval) { - const interval = setInterval(() => { - // pass in query() with options instead - new Promise((resolve, reject) => - resolve( - query(query, { pollInterval: null, cacheRead: false, ...options }) - ) - ); - }, pollInterval); - return interval; - } + window.localStorage.setItem("cache", JSON.stringify(cache)); - // when cacheRead set to true - if (cacheRead) { - let resObj; - // when the developer decides to only utilize whole query for cache - if (wholeQuery) resObj = await cache.readWholeQuery(query); - else resObj = await cache.read(query); - console.log('query function resObj: ', resObj) - // check if query is stored in cache - if (resObj) { - // returning cached response as a promise - const cacheHitResponseTime = Date.now() - startTime; - chrome.runtime.sendMessage(chromeExtensionId, { + async function query(query, options = {}) { + // dev tool messages + const startTime = Date.now(); + chrome.runtime.sendMessage(chromeExtensionId, { query: query }); + chrome.runtime.sendMessage(chromeExtensionId, { + cache: window.localStorage.getItem("cache"), + }); + console.log( + "Here's the message content: ", + window.localStorage.getItem("cache") + ); + // set the options object default properties if not provided + const { + endpoint = "/graphql", + cacheRead = true, + cacheWrite = true, + pollInterval = null, + wholeQuery = false, + } = options; + + // when pollInterval is not null the query will be sent to the server every inputted number of milliseconds + if (pollInterval) { + const interval = setInterval(() => { + // pass in query() with options instead + new Promise((resolve, reject) => + resolve( + query(query, { pollInterval: null, cacheRead: false, ...options }) + ) + ); + }, pollInterval); + return interval; + } + + // when cacheRead set to true + if (cacheRead) { + let resObj; + // when the developer decides to only utilize whole query for cache + if (wholeQuery) resObj = await cache.readWholeQuery(query); + else resObj = await cache.read(query); + console.log("query function resObj: ", resObj); + // check if query is stored in cache + if (resObj) { + // returning cached response as a promise + const cacheHitResponseTime = Date.now() - startTime; + chrome.runtime.sendMessage(chromeExtensionId, { cacheHitResponseTime: cacheHitResponseTime, }); - return new Promise((resolve, reject) => resolve(resObj)); - } - // execute graphql fetch request if cache miss - return new Promise((resolve, reject) => resolve(hunt(query))); - // when cacheRead set to false - } - if (!cacheRead) { - return new Promise((resolve, reject) => resolve(hunt(query))); - } + return new Promise((resolve, reject) => resolve(resObj)); + } + // execute graphql fetch request if cache miss + return new Promise((resolve, reject) => resolve(hunt(query))); + // when cacheRead set to false + } + if (!cacheRead) { + return new Promise((resolve, reject) => resolve(hunt(query))); + } - // when cache miss or on intervals - async function hunt(query) { - if (!wholeQuery) query = insertTypenames(query); - try { - // send fetch request with query - const resJSON = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: JSON.stringify({ query }), - }); - const resObj = await resJSON.json(); - const deepResObj = { ...resObj }; - // update result in cache if cacheWrite is set to true - if (cacheWrite) { - if (wholeQuery) cache.writeWholeQuery(query, deepResObj); - else cache.write(query, deepResObj); - } - const cacheMissResponseTime = Date.now() - startTime; - chrome.runtime.sendMessage(chromeExtensionId, { + // when cache miss or on intervals + async function hunt(query) { + if (!wholeQuery) query = insertTypenames(query); + try { + // send fetch request with query + const resJSON = await fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ query }), + }); + const resObj = await resJSON.json(); + const deepResObj = { ...resObj }; + // update result in cache if cacheWrite is set to true + if (cacheWrite) { + if (wholeQuery) cache.writeWholeQuery(query, deepResObj); + else cache.write(query, deepResObj); + } + const cacheMissResponseTime = Date.now() - startTime; + chrome.runtime.sendMessage(chromeExtensionId, { cacheMissResponseTime: cacheMissResponseTime, }); - console.log('Here\'s the response time on the front end: ', cacheMissResponseTime); - return resObj; - } catch (e) { - console.log(e); - } - } - } + console.log( + "Here's the response time on the front end: ", + cacheMissResponseTime + ); + return resObj; + } catch (e) { + console.log(e); + } + } + } - // Function to clear cache and session storage - function clearCache() { - cache.cacheClear(); - } - // mutate method, refer to mutate.js for more info - // async function mutateOld(mutation, options = {}) { - // // set the options object default properties if not provided - // mutation = insertTypenames(mutation); - // let { - // endpoint = '/graphql', - // cacheWrite = true, - // toDelete = false, - // update = null, - // writeThrough = true, - // } = options; - // // for any mutation a request to the server is made - // try { - // // one-time check to add types to cache - // if(writeThrough){ - // writeThrough = await cache.write(mutation, {}, toDelete, writeThrough); - // } - // const responseObj = await fetch(endpoint, { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json', - // Accept: 'application/json', - // }, - // body: JSON.stringify({ query: mutation }), - // }).then((resp) => resp.json()); - // if (!cacheWrite) return responseObj; - // // first behaviour when delete cache is set to true - // console.log('writeThrough before toDelete: ', writeThrough) - // if (!writeThrough && toDelete) { - // console.log('in the toDelete') - // cache.write(mutation, responseObj, true); - // return responseObj; - // } - // // second behaviour if update function provided - // if (update) { - // update(cache, responseObj); - // } - // // third behaviour just for normal update (no-delete, no update function) - // if(!writeThrough){ - // console.log('in the !writeThrough') - // cache.write(mutation, responseObj); - // } - // if (writeThrough) { - // console.log('Here\'s the response from cache: ', writeThrough); - // return writeThrough; - // } - // return responseObj; - // } catch (e) { - // console.log(e); - // } - // } + // Function to clear cache and session storage + function clearCache() { + cache.cacheClear(); + } - // breaking out writethrough logic vs. non-writethrough logic - async function mutate(mutation, options = {}) { - // dev tool messages - chrome.runtime.sendMessage(chromeExtensionId, { + // breaking out writethrough logic vs. non-writethrough logic + async function mutate(mutation, options = {}) { + // dev tool messages + chrome.runtime.sendMessage(chromeExtensionId, { mutation: mutation, }); - const startTime = Date.now(); - mutation = insertTypenames(mutation); - const { - endpoint = '/graphql', - cacheWrite = true, - toDelete = false, - update = null, - writeThrough = true, - } = options; - try { - if (writeThrough) { - // if it's a deletion, then delete from cache and return the object - if (toDelete) { - const responseObj = await cache.writeThrough(mutation, {}, true, endpoint); - const deleteMutationResponseTime = Date.now() - startTime; - chrome.runtime.sendMessage(chromeExtensionId, { + const startTime = Date.now(); + mutation = insertTypenames(mutation); + const { + endpoint = "/graphql", + cacheWrite = true, + toDelete = false, + update = null, + writeThrough = false, + } = options; + try { + if (writeThrough) { + // if it's a deletion, then delete from cache and return the object + if (toDelete) { + const responseObj = await cache.writeThrough( + mutation, + {}, + true, + endpoint + ); + const deleteMutationResponseTime = Date.now() - startTime; + chrome.runtime.sendMessage(chromeExtensionId, { deleteMutationResponseTime: deleteMutationResponseTime, }); - return responseObj; - } else { - // for add mutation - const responseObj = await cache.writeThrough(mutation, {},false,endpoint); - // for update mutation - if (update) { - // run the update function - update(cache, responseObj) - } - // always write/over-write to cache (add/update) - // GQL call to make changes and synchronize database - console.log('WriteThrough - true ', responseObj); - const addOrUpdateMutationResponseTime = Date.now() - startTime; - chrome.runtime.sendMessage(chromeExtensionId, { + return responseObj; + } else { + // for add mutation + const responseObj = await cache.writeThrough( + mutation, + {}, + false, + endpoint + ); + // for update mutation + if (update) { + // run the update function + update(cache, responseObj); + } + // always write/over-write to cache (add/update) + // GQL call to make changes and synchronize database + console.log("WriteThrough - true ", responseObj); + const addOrUpdateMutationResponseTime = Date.now() - startTime; + chrome.runtime.sendMessage(chromeExtensionId, { addOrUpdateMutationResponseTime: addOrUpdateMutationResponseTime, }); - return responseObj; - } - } else { - // copy-paste mutate logic from 4. - - // use cache.write instead of cache.writeThrough - const responseObj = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: JSON.stringify({ query: mutation }), - }).then((resp) => resp.json()); - if (!cacheWrite) return responseObj; - // first behaviour when delete cache is set to true - if (toDelete) { - cache.write(mutation, responseObj, true); - return responseObj; - } - // second behaviour if update function provided - if (update) { - update(cache, responseObj); - } - // third behaviour just for normal update (no-delete, no update function) - cache.write(mutation, responseObj); - console.log('WriteThrough - false ', responseObj); - return responseObj; - } - } catch (e) { - console.log(e); - } - } - // Returning Provider React component that allows consuming components to subscribe to context changes - return ( - - ); + return responseObj; + } + } else { + // copy-paste mutate logic from 4. + + // use cache.write instead of cache.writeThrough + const responseObj = await fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ query: mutation }), + }).then((resp) => resp.json()); + if (!cacheWrite) return responseObj; + // first behaviour when delete cache is set to true + if (toDelete) { + cache.write(mutation, responseObj, true); + return responseObj; + } + // second behaviour if update function provided + if (update) { + update(cache, responseObj); + } + // third behaviour just for normal update (no-delete, no update function) + cache.write(mutation, responseObj); + console.log("WriteThrough - false ", responseObj); + return responseObj; + } + } catch (e) { + console.log(e); + } + } + // Returning Provider React component that allows consuming components to subscribe to context changes + return ( + + ); } // Declaration of custom hook to allow access to provider function useObsidian() { - // React useContext hook to access the global provider by any of the consumed components - return React.useContext(cacheContext); + // React useContext hook to access the global provider by any of the consumed components + return React.useContext(cacheContext); } // Exporting of Custom wrapper and hook to access wrapper cache diff --git a/src/Browser/CacheClassBrowser.js b/src/Browser/CacheClassBrowser.js index 265f78a..fb58f3f 100644 --- a/src/Browser/CacheClassBrowser.js +++ b/src/Browser/CacheClassBrowser.js @@ -1,340 +1,342 @@ /** @format */ -import normalizeResult from './normalize.js'; -import destructureQueries from './destructure.js'; +import normalizeResult from "./normalize.js"; +import destructureQueries from "./destructure.js"; export default class BrowserCache { - constructor( - initialCache = { - ROOT_QUERY: {}, - ROOT_MUTATION: {}, - // match resolvers to types in order to add them in write-through - writeThroughInfo: {}, - } - ) { - this.storage = initialCache; - this.context = 'client'; - } + constructor( + initialCache = { + ROOT_QUERY: {}, + ROOT_MUTATION: {}, + // match resolvers to types in order to add them in write-through + writeThroughInfo: {}, + }, + ) { + this.storage = initialCache; + this.context = "client"; + } - // Main functionality methods - async read(queryStr) { - if (typeof queryStr !== 'string') - throw TypeError('input should be a string'); - // destructure the query string into an object - const queries = destructureQueries(queryStr).queries; - // breaks out of function if queryStr is a mutation - if (!queries) return undefined; - const responseObject = {}; - // iterate through each query in the input queries object - for (const query in queries) { - // get the entire str query from the name input query and arguments - const queryHash = queries[query].name.concat(queries[query].arguments); - const rootQuery = await this.cacheRead('ROOT_QUERY'); - // match in ROOT_QUERY - if (rootQuery[queryHash]) { - // get the hashs to populate from the existent query in the cache - const arrayHashes = rootQuery[queryHash]; - // Determines responseObject property labels - use alias if applicable, otherwise use name - const respObjProp = queries[query].alias ?? queries[query].name; - // invoke populateAllHashes and add data objects to the response object for each input query - responseObject[respObjProp] = await this.populateAllHashes( - arrayHashes, - queries[query].fields - ); - if (!responseObject[respObjProp]) return undefined; + // Main functionality methods + async read(queryStr) { + if (typeof queryStr !== "string") { + throw TypeError("input should be a string"); + } + // destructure the query string into an object + const queries = destructureQueries(queryStr).queries; + // breaks out of function if queryStr is a mutation + if (!queries) return undefined; + const responseObject = {}; + // iterate through each query in the input queries object + for (const query in queries) { + // get the entire str query from the name input query and arguments + const queryHash = queries[query].name.concat(queries[query].arguments); + const rootQuery = await this.cacheRead("ROOT_QUERY"); + // match in ROOT_QUERY + if (rootQuery[queryHash]) { + // get the hashs to populate from the existent query in the cache + const arrayHashes = rootQuery[queryHash]; + // Determines responseObject property labels - use alias if applicable, otherwise use name + const respObjProp = queries[query].alias ?? queries[query].name; + // invoke populateAllHashes and add data objects to the response object for each input query + responseObject[respObjProp] = await this.populateAllHashes( + arrayHashes, + queries[query].fields, + ); + if (!responseObject[respObjProp]) return undefined; - // no match with ROOT_QUERY return null or ... - } else { - return undefined; - } - } - return { data: responseObject }; - } + // no match with ROOT_QUERY return null or ... + } else { + return undefined; + } + } + return { data: responseObject }; + } - async writeThrough(queryStr, respObj, deleteFlag, endpoint) { - try { - const queryObj = destructureQueries(queryStr); - const mutationName = queryObj.mutations[0].name; - // check if it's a mutation - if (queryObj.mutations) { - // check to see if the mutation/type has been stored in the cache yet - // if so, make the graphQL call - if (!this.storage.writeThroughInfo.hasOwnProperty(mutationName)) { - respObj = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: JSON.stringify({ query: queryStr }), - }).then((resp) => resp.json()); - // store the mutation/type in cache - this.storage.writeThroughInfo[mutationName] = {}; - this.storage.writeThroughInfo[mutationName].type = - respObj.data[mutationName].__typename; - this.storage.writeThroughInfo[mutationName].lastId = - respObj.data[mutationName].id; - // below is for situations when the type is already stored - } else { - // construct the response object ourselves - const dummyResponse = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: JSON.stringify({ query: queryStr }), - }).then((resp) => resp.json()); - this.constructResponseObject(queryObj, respObj, deleteFlag); - } - // same logic for both situations - // normalize the result, invalidate the cache and return the appropriate object - await this.write(queryStr, respObj, deleteFlag); - return respObj; - } - } catch (e) { - console.log(e); - } - } + async writeThrough(queryStr, respObj, deleteFlag, endpoint) { + try { + const queryObj = destructureQueries(queryStr); + const mutationName = queryObj.mutations[0].name; + // check if it's a mutation + if (queryObj.mutations) { + // check to see if the mutation/type has been stored in the cache yet + // if so, make the graphQL call + if (!this.storage.writeThroughInfo.hasOwnProperty(mutationName)) { + respObj = await fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ query: queryStr }), + }).then((resp) => resp.json()); + // store the mutation/type in cache + this.storage.writeThroughInfo[mutationName] = {}; + this.storage.writeThroughInfo[mutationName].type = + respObj.data[mutationName].__typename; + this.storage.writeThroughInfo[mutationName].lastId = + respObj.data[mutationName].id; + // below is for situations when the type is already stored + } else { + // construct the response object ourselves + const dummyResponse = await fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ query: queryStr }), + }).then((resp) => resp.json()); + this.constructResponseObject(queryObj, respObj, deleteFlag); + } + // same logic for both situations + // normalize the result, invalidate the cache and return the appropriate object + await this.write(queryStr, respObj, deleteFlag); + return respObj; + } + } catch (e) { + console.log(e); + } + } - async write(queryStr, respObj, deleteFlag) { - const queryObj = destructureQueries(queryStr); - const resFromNormalize = normalizeResult(queryObj, respObj, deleteFlag); - // update the original cache with same reference - for (const hash in resFromNormalize) { - const resp = await this.cacheRead(hash); - if (resFromNormalize[hash] === 'DELETED') { - await this.cacheWrite(hash, 'DELETED'); - } else if (resp) { - const newObj = Object.assign(resp, resFromNormalize[hash]); - await this.cacheWrite(hash, newObj); - } else { - await this.cacheWrite(hash, resFromNormalize[hash]); - } - } - } + async write(queryStr, respObj, deleteFlag) { + const queryObj = destructureQueries(queryStr); + const resFromNormalize = normalizeResult(queryObj, respObj, deleteFlag); + // update the original cache with same reference + for (const hash in resFromNormalize) { + const resp = await this.cacheRead(hash); + if (resFromNormalize[hash] === "DELETED") { + await this.cacheWrite(hash, "DELETED"); + } else if (resp) { + const newObj = Object.assign(resp, resFromNormalize[hash]); + await this.cacheWrite(hash, newObj); + } else { + await this.cacheWrite(hash, resFromNormalize[hash]); + } + } + } - constructResponseObject(queryObj, respObj, deleteFlag) { - const mutationData = queryObj.mutations[0]; - const mutationName = mutationData.name; - const __typename = this.storage.writeThroughInfo[mutationName].type; - // this.storage.writeThroughInfo[mutationName].type; - respObj.data = {}; - const obj = {}; - respObj.data[mutationName] = obj; - obj.__typename = __typename; - // delete logic - if (deleteFlag) { - // add id and value from the queryObj - let idAndVal = mutationData.arguments; - idAndVal = idAndVal.split(':'); - const id = idAndVal[0].substring(1); - const val = idAndVal[1].substring(0, idAndVal[1].length - 1); - obj[id] = val; - // return out of this function so we don't continue - // onto add/update logic - return respObj; - } - // increment ID for ADD mutations only - obj.id = (++this.storage.writeThroughInfo[mutationName].lastId).toString(); + constructResponseObject(queryObj, respObj, deleteFlag) { + const mutationData = queryObj.mutations[0]; + const mutationName = mutationData.name; + const __typename = this.storage.writeThroughInfo[mutationName].type; + // this.storage.writeThroughInfo[mutationName].type; + respObj.data = {}; + const obj = {}; + respObj.data[mutationName] = obj; + obj.__typename = __typename; + // delete logic + if (deleteFlag) { + // add id and value from the queryObj + let idAndVal = mutationData.arguments; + idAndVal = idAndVal.split(":"); + const id = idAndVal[0].substring(1); + const val = idAndVal[1].substring(0, idAndVal[1].length - 1); + obj[id] = val; + // return out of this function so we don't continue + // onto add/update logic + return respObj; + } + // increment ID for ADD mutations only + obj.id = (++this.storage.writeThroughInfo[mutationName].lastId).toString(); - // ADD mutation logic - // grab arguments (which is a string) - const argumentsStr = mutationData.arguments; - this.addNonScalarFields(argumentsStr, respObj, mutationData); - this.separateArguments(argumentsStr, respObj, mutationName ); - } + // ADD mutation logic + // grab arguments (which is a string) + const argumentsStr = mutationData.arguments; + this.addNonScalarFields(argumentsStr, respObj, mutationData); + this.separateArguments(argumentsStr, respObj, mutationName); + } - separateArguments(str,respObj,mutationName) { - const startIndex = str.indexOf('{'); - const slicedStr = str.slice(startIndex + 1, str.length - 2); - const argumentPairs = slicedStr.split(','); - for (const argumentPair of argumentPairs) { - const argumentKeyAndValue = argumentPair.split(':'); - const argumentKey = argumentKeyAndValue[0]; - let argumentValue = Number(argumentKeyAndValue[1]) - ? Number(argumentKeyAndValue[1]) - : argumentKeyAndValue[1]; - if (typeof argumentValue === 'string') { - argumentValue = argumentValue.replace(/\"/g, ''); - } - respObj.data[mutationName][argumentKey] = argumentValue; - } - } + separateArguments(str, respObj, mutationName) { + const startIndex = str.indexOf("{"); + const slicedStr = str.slice(startIndex + 1, str.length - 2); + const argumentPairs = slicedStr.split(","); + for (const argumentPair of argumentPairs) { + const argumentKeyAndValue = argumentPair.split(":"); + const argumentKey = argumentKeyAndValue[0]; + let argumentValue = Number(argumentKeyAndValue[1]) + ? Number(argumentKeyAndValue[1]) + : argumentKeyAndValue[1]; + if (typeof argumentValue === "string") { + argumentValue = argumentValue.replace(/\"/g, ""); + } + respObj.data[mutationName][argumentKey] = argumentValue; + } + } - addNonScalarFields(str, respObj , mutationData) { - for (const field in mutationData.fields) { - if ( - mutationData.fields[field] !== 'scalar' && - mutationData.fields[field] !== 'meta' - ) { - respObj.data[mutationData.name][field] = []; - } - } - } + addNonScalarFields(respObj, mutationData) { + for (const field in mutationData.fields) { + if ( + mutationData.fields[field] !== "scalar" && + mutationData.fields[field] !== "meta" + ) { + respObj.data[mutationData.name][field] = []; + } + } + } - gc() { - // garbageCollection; garbage collection: removes any inaccessible hashes from the cache - const badHashes = getBadHashes(); - const goodHashes = rootQueryCleaner(badHashes); - const goodHashes2 = getGoodHashes(badHashes, goodHashes); - removeInaccessibleHashes(badHashes, goodHashes2); - } + gc() { + // garbageCollection; garbage collection: removes any inaccessible hashes from the cache + const badHashes = getBadHashes(); + const goodHashes = rootQueryCleaner(badHashes); + const goodHashes2 = getGoodHashes(badHashes, goodHashes); + removeInaccessibleHashes(badHashes, goodHashes2); + } - // remove hashes that are flagged for deletion and store records of them in a set badHashes for removal inside root queries - getBadHashes() { - const badHashes = new Set(); - for (let key in this.storage) { - if (key === 'ROOT_QUERY' || key === 'ROOT_MUTATION') continue; - if (this.storage[key] === 'DELETED') { - badHashes.add(key); - delete this.storage[key]; - } - } - return badHashes; - } + // remove hashes that are flagged for deletion and store records of them in a set badHashes for removal inside root queries + getBadHashes() { + const badHashes = new Set(); + for (let key in this.storage) { + if (key === "ROOT_QUERY" || key === "ROOT_MUTATION") continue; + if (this.storage[key] === "DELETED") { + badHashes.add(key); + delete this.storage[key]; + } + } + return badHashes; + } - // go through root queries, remove all instances of bad hashes, add remaining hashes into goodHashes set - rootQueryCleaner(badHashes) { - const goodHashes = new Set(); - const rootQuery = this.storage['ROOT_QUERY']; - for (let key in rootQuery) { - if (Array.isArray(rootQuery[key])) { - rootQuery[key] = rootQuery[key].filter((x) => !badHashes.has(x)); - if (rootQuery[key].length === 0) delete rootQuery[key]; - for (let el of rootQuery[key]) goodHashes.add(el); - } else - badHashes.has(rootQuery[key]) - ? delete rootQuery[key] - : goodHashes.add(rootQuery[key]); - } - return goodHashes; - } + // go through root queries, remove all instances of bad hashes, add remaining hashes into goodHashes set + rootQueryCleaner(badHashes) { + const goodHashes = new Set(); + const rootQuery = this.storage["ROOT_QUERY"]; + for (let key in rootQuery) { + if (Array.isArray(rootQuery[key])) { + rootQuery[key] = rootQuery[key].filter((x) => !badHashes.has(x)); + if (rootQuery[key].length === 0) delete rootQuery[key]; + for (let el of rootQuery[key]) goodHashes.add(el); + } else { + badHashes.has(rootQuery[key]) + ? delete rootQuery[key] + : goodHashes.add(rootQuery[key]); + } + } + return goodHashes; + } - // Go through the cache, check good hashes for any nested hashes and add them to goodHashes set - getGoodHashes(badHashes, goodHashes) { - for (let key in this.storage) { - if (key === 'ROOT_QUERY' || key === 'ROOT_MUTATION') continue; - for (let i in this.storage[key]) { - if (Array.isArray(this.storage[key][i])) { - for (let el of this.storage[key][i]) { - if (el.includes('~') && !badHashes.has(el)) { - goodHashes.add(el); - } - } - } else if (typeof this.storage[key][i] === 'string') { - if ( - this.storage[key][i].includes('~') && - !badHashes.has(this.storage[key][i]) - ) { - goodHashes.add(this.storage[key][i]); - } - } - } - } - return goodHashes; - } + // Go through the cache, check good hashes for any nested hashes and add them to goodHashes set + getGoodHashes(badHashes, goodHashes) { + for (let key in this.storage) { + if (key === "ROOT_QUERY" || key === "ROOT_MUTATION") continue; + for (let i in this.storage[key]) { + if (Array.isArray(this.storage[key][i])) { + for (let el of this.storage[key][i]) { + if (el.includes("~") && !badHashes.has(el)) { + goodHashes.add(el); + } + } + } else if (typeof this.storage[key][i] === "string") { + if ( + this.storage[key][i].includes("~") && + !badHashes.has(this.storage[key][i]) + ) { + goodHashes.add(this.storage[key][i]); + } + } + } + } + return goodHashes; + } - // Remove inaccessible hashes by checking if they are in goodhashes set or not - removeInaccessibleHashes(badHashes, goodHashes) { - for (let key in this.storage) { - if (key === 'ROOT_QUERY' || key === 'ROOT_MUTATION') continue; - if (!goodHashes.has(key)) delete this.storage[key]; - for (let i in this.storage[key]) { - if (Array.isArray(this.storage[key][i])) { - this.storage[key][i] = this.storage[key][i].filter( - (x) => !badHashes.has(x) - ); - } else if (typeof this.storage[key][i] === 'string') { - if ( - this.storage[key][i].includes('~') && - badHashes.has(this.storage[key][i]) - ) { - delete this.storage[key][i]; - } - } - } - } - } + // Remove inaccessible hashes by checking if they are in goodhashes set or not + removeInaccessibleHashes(badHashes, goodHashes) { + for (let key in this.storage) { + if (key === "ROOT_QUERY" || key === "ROOT_MUTATION") continue; + if (!goodHashes.has(key)) delete this.storage[key]; + for (let i in this.storage[key]) { + if (Array.isArray(this.storage[key][i])) { + this.storage[key][i] = this.storage[key][i].filter( + (x) => !badHashes.has(x), + ); + } else if (typeof this.storage[key][i] === "string") { + if ( + this.storage[key][i].includes("~") && + badHashes.has(this.storage[key][i]) + ) { + delete this.storage[key][i]; + } + } + } + } + } - // cache read/write helper methods - async cacheRead(hash) { - return this.storage[hash]; - } + // cache read/write helper methods + async cacheRead(hash) { + return this.storage[hash]; + } - async cacheWrite(hash, value) { - this.storage[hash] = value; - } + async cacheWrite(hash, value) { + this.storage[hash] = value; + } - async cacheDelete(hash) { - delete this.storage[hash]; - } + async cacheDelete(hash) { + delete this.storage[hash]; + } - async cacheClear() { - this.storage = { - ROOT_QUERY: {}, - ROOT_MUTATION: {}, - }; - } + async cacheClear() { + this.storage = { + ROOT_QUERY: {}, + ROOT_MUTATION: {}, + }; + } - // functionality to stop polling - stopPollInterval(interval) { - clearInterval(interval); - } + // functionality to stop polling + stopPollInterval(interval) { + clearInterval(interval); + } - writeWholeQuery(queryStr, respObj) { - const hash = queryStr.replace(/\s/g, ''); - this.cacheWrite(ROOT_QUERY[hash], respObj); - return respObj; - } + writeWholeQuery(queryStr, respObj) { + const hash = queryStr.replace(/\s/g, ""); + this.cacheWrite(ROOT_QUERY[hash], respObj); + return respObj; + } - readWholeQuery(queryStr) { - const hash = queryStr.replace(/\s/g, ''); - const root = this.cacheRead('ROOT_QUERY'); - if (root[hash]) return { data: root[hash] }; - return undefined; - } + readWholeQuery(queryStr) { + const hash = queryStr.replace(/\s/g, ""); + const root = this.cacheRead("ROOT_QUERY"); + if (root[hash]) return { data: root[hash] }; + return undefined; + } - // specialized helper methods - async populateAllHashes(allHashesFromQuery, fields) { - // include the hashname for each hash - if (!allHashesFromQuery.length) return []; - const hyphenIdx = allHashesFromQuery[0].indexOf('~'); - const typeName = allHashesFromQuery[0].slice(0, hyphenIdx); - return allHashesFromQuery.reduce(async (acc, hash) => { - // for each hash from the input query, build the response object - const readVal = await this.cacheRead(hash); - // return undefine if hash has been garbage collected - if (readVal === undefined) return undefined; - if (readVal === 'DELETED') return acc; - const dataObj = {}; - for (const field in fields) { - if (readVal[field] === 'DELETED') continue; - // for each field in the fields input query, add the corresponding value from the cache if the field is not another array of hashs - if (readVal[field] === undefined && field !== '__typename') { - return undefined; - } else if (typeof fields[field] !== 'object') { - // add the typename for the type - if (field === '__typename') { - dataObj[field] = typeName; - } else dataObj[field] = readVal[field]; - } else { - // case where the field from the input query is an array of hashes, recursively invoke populateAllHashes - dataObj[field] = await this.populateAllHashes( - readVal[field], - fields[field] - ); - if (dataObj[field] === undefined) return undefined; - } - } - // acc is an array within a Response object for each hash - try { - const resolvedProm = await Promise.resolve(acc); - resolvedProm.push(dataObj); - return resolvedProm; - } catch (error) { - return undefined; - } - }, []); - } + // specialized helper methods + async populateAllHashes(allHashesFromQuery, fields) { + // include the hashname for each hash + if (!allHashesFromQuery.length) return []; + const hyphenIdx = allHashesFromQuery[0].indexOf("~"); + const typeName = allHashesFromQuery[0].slice(0, hyphenIdx); + return allHashesFromQuery.reduce(async (acc, hash) => { + // for each hash from the input query, build the response object + const readVal = await this.cacheRead(hash); + // return undefine if hash has been garbage collected + if (readVal === undefined) return undefined; + if (readVal === "DELETED") return acc; + const dataObj = {}; + for (const field in fields) { + if (readVal[field] === "DELETED") continue; + // for each field in the fields input query, add the corresponding value from the cache if the field is not another array of hashs + if (readVal[field] === undefined && field !== "__typename") { + return undefined; + } else if (typeof fields[field] !== "object") { + // add the typename for the type + if (field === "__typename") { + dataObj[field] = typeName; + } else dataObj[field] = readVal[field]; + } else { + // case where the field from the input query is an array of hashes, recursively invoke populateAllHashes + dataObj[field] = await this.populateAllHashes( + readVal[field], + fields[field], + ); + if (dataObj[field] === undefined) return undefined; + } + } + // acc is an array within a Response object for each hash + try { + const resolvedProm = await Promise.resolve(acc); + resolvedProm.push(dataObj); + return resolvedProm; + } catch (error) { + return undefined; + } + }, []); + } } From 01fe334931d4e5349f02b606dd5f71062c19b2df Mon Sep 17 00:00:00 2001 From: YuriiShchyrba Date: Tue, 12 Apr 2022 19:24:54 -0400 Subject: [PATCH 16/16] final tag --- ObsidianWrapper/ObsidianWrapper.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ObsidianWrapper/ObsidianWrapper.jsx b/ObsidianWrapper/ObsidianWrapper.jsx index 73c162a..57933d7 100644 --- a/ObsidianWrapper/ObsidianWrapper.jsx +++ b/ObsidianWrapper/ObsidianWrapper.jsx @@ -9,7 +9,6 @@ function ObsidianWrapper(props) { // You have to put your Google Chrome Obsidian developer tool extension id to connect Obsidian Wrapper with dev tool const chromeExtensionId = "dkbfipkapkljpdbhdihnlnbieffhjdmh"; - window.localStorage.setItem("cache", JSON.stringify(cache)); async function query(query, options = {}) {