1
1
import * as React from "https://esm.sh/react@18" ;
2
2
import LFUCache from '../src/Browser/lfuBrowserCache.js' ;
3
3
import LRUCache from '../src/Browser/lruBrowserCache.js' ;
4
+ import WTinyLFUCache from "../src/Browser/wTinyLFUBrowserCache.js" ;
4
5
import { insertTypenames } from '../src/Browser/insertTypenames.js' ;
6
+ import { sha256 } from 'https://denopkg.com/chiefbiiko/sha256@v1.0.0/mod.ts' ;
5
7
6
8
const cacheContext = React . createContext ( ) ;
7
9
8
10
function ObsidianWrapper ( props ) {
9
- const { algo, capacity } = props
10
- const [ cache , setCache ] = React . useState ( new LFUCache ( Number ( capacity || 2000 ) ) ) ;
11
- if ( algo === 'LRU' ) setCache ( new LRUCache ( Number ( capacity || 2000 ) ) ) ; // You have to put your Google Chrome Obsidian developer tool extension id to connect Obsidian Wrapper with dev tool
12
- const chromeExtensionId = 'apcpdmmbhhephobnmnllbklplpaoiemo' ;
13
- // initialice cache in local storage
14
- //window.localStorage.setItem('cache', JSON.stringify(cache));
11
+ // props to be inputted by user when using the Obsdian Wrapper
12
+ const { algo, capacity, searchTerms, useCache, persistQueries } = props ;
13
+ // if useCache hasn't been set, default caching to true
14
+ let caching = true ;
15
+ // if it has been set to false, turn client-side caching off
16
+ if ( useCache === false ) caching = false ;
17
+
18
+ // algo defaults to LFU, capacity defaults to 2000
19
+ const setAlgoCap = ( algo , capacity ) => {
20
+ let cache ;
21
+ if ( caching && algo === 'LRU' ) {
22
+ cache = new LRUCache ( Number ( capacity || 2000 ) )
23
+ } else if ( caching && algo === 'W-TinyLFU' ) {
24
+ cache = new WTinyLFUCache ( Number ( capacity || 2000 ) )
25
+ } else if ( caching ) {
26
+ cache = new LFUCache ( Number ( capacity || 2000 ) )
27
+ }
28
+ return cache ;
29
+ }
30
+
31
+ // once cache is initialized, cannot setCache
32
+ // state for cache is initialized based on developer settings in wrapper
33
+ // to successfully change between algo types for testing, kill the server, change the algo type in wrapper, then restart server
34
+ const [ cache , setCache ] = React . useState ( setAlgoCap ( algo , capacity ) ) ;
35
+
36
+ // FOR DEVTOOL - listening for message from content.js to be able to send algo type and capacity to devtool
37
+ window . addEventListener ( 'message' , msg => {
38
+ if ( msg . data . type === 'algocap' ) {
39
+ window . postMessage ( {
40
+ algo : algo ? algo : 'LFU' ,
41
+ capacity : capacity ? capacity : 2000
42
+ } )
43
+ }
44
+ } ) ;
15
45
16
46
async function query ( query , options = { } ) {
17
- // dev tool messages
47
+ // FOR DEVTOOL - startTime is used to calculate the performance of the cache
48
+ // startDate is to find out when query was made, this data is passed to devtools
18
49
const startTime = Date . now ( ) ;
19
- /*
20
- chrome.runtime.sendMessage(chromeExtensionId, { query: query });
21
- chrome.runtime.sendMessage(chromeExtensionId, {
22
- cache: window.localStorage.getItem('cache'),
23
- });
24
- */
50
+ const startDate = new Date ( Date . now ( ) ) ;
25
51
26
52
// set the options object default properties if not provided
27
53
const {
28
54
endpoint = '/graphql' ,
29
- cacheRead = true ,
30
- cacheWrite = true ,
55
+ cacheRead = ! caching ? false : true ,
56
+ cacheWrite = ! caching ? false : true ,
31
57
pollInterval = null ,
32
- wholeQuery = true ,
58
+ wholeQuery = false , //Note: logic for true is currently nonfunctional
33
59
} = options ;
34
60
35
61
// when pollInterval is not null the query will be sent to the server every inputted number of milliseconds
@@ -45,70 +71,101 @@ function ObsidianWrapper(props) {
45
71
return interval ;
46
72
}
47
73
48
- // when cacheRead set to true
49
- if ( cacheRead ) {
74
+ // when cacheRead set to true & we are utilizing client side caching
75
+ if ( cacheRead && caching ) {
50
76
let resObj ;
51
77
// when the developer decides to only utilize whole query for cache
52
- if ( ! wholeQuery ) resObj = await cache . readWholeQuery ( query ) ;
78
+ if ( wholeQuery ) resObj = await cache . readWholeQuery ( query ) ;
79
+ // attempt to read from the cache
53
80
else resObj = await cache . read ( query ) ;
54
81
// check if query is stored in cache
55
82
if ( resObj ) {
56
83
// returning cached response as a promise
57
84
const cacheHitResponseTime = Date . now ( ) - startTime ;
58
85
59
- // Allow for access of the response time
60
- // const cacheCopy = {...cache};
61
- // cacheCopy.callTime = cacheHitResponseTime;
62
- // setCache(cacheCopy);
63
- resObj [ 'time' ] = cacheHitResponseTime
86
+ // FOR DEVTOOL - sends message to content.js with query metrics when query is a hit
87
+ window . postMessage ( {
88
+ type : 'query' ,
89
+ time : cacheHitResponseTime ,
90
+ date : startDate . toDateString ( ) . slice ( 0 , 24 ) ,
91
+ query : query ,
92
+ hit : true
93
+ } ) ;
64
94
65
- console . log (
66
- "From cacheRead: Here's the response time on the front end: " ,
67
- cacheHitResponseTime
68
- ) ;
69
- /*chrome.runtime.sendMessage(chromeExtensionId, {
70
- cacheHitResponseTime: cacheHitResponseTime,
71
- });*/
72
95
return new Promise ( ( resolve , reject ) => resolve ( resObj ) ) ;
73
96
}
74
97
// execute graphql fetch request if cache miss
75
98
return new Promise ( ( resolve , reject ) => resolve ( hunt ( query ) ) ) ;
76
- // when cacheRead set to false
77
99
}
78
- if ( ! cacheRead ) {
100
+ // when cacheRead set to false & not using client-side cache
101
+ if ( ! cacheRead || ! caching ) {
79
102
return new Promise ( ( resolve , reject ) => resolve ( hunt ( query ) ) ) ;
80
103
}
81
104
82
- // when cache miss or on intervals
105
+ // function to be called on cache miss or on intervals or not looking in the cache
83
106
async function hunt ( query ) {
84
- if ( wholeQuery ) query = insertTypenames ( query ) ;
107
+ if ( ! wholeQuery ) query = insertTypenames ( query ) ;
85
108
try {
86
- // send fetch request with query
87
- const resJSON = await fetch ( endpoint , {
88
- method : 'POST' ,
89
- headers : {
90
- 'Content-Type' : 'application/json' ,
91
- Accept : 'application/json' ,
92
- } ,
93
- body : JSON . stringify ( { query } ) ,
94
- } ) ;
109
+ let resJSON ;
110
+ // IF WE ARE USING PERSIST QUERIES
111
+ if ( persistQueries ) {
112
+ // SEND THE HASH
113
+ const hash = sha256 ( query , 'utf8' , 'hex' ) ;
114
+ resJSON = await fetch ( endpoint , {
115
+ method : 'POST' ,
116
+ headers : {
117
+ 'Content-Type' : 'application/json' ,
118
+ Accept : 'application/json' ,
119
+ } ,
120
+ body : JSON . stringify ( { hash } ) ,
121
+ } ) ;
122
+
123
+ // IF HASH WAS NOT FOUND IN HASH TABLE
124
+ if ( resJSON . status === 204 ) {
125
+ // SEND NEW REQUEST WITH HASH AND QUERY
126
+ resJSON = await fetch ( endpoint , {
127
+ method : 'POST' ,
128
+ headers : {
129
+ 'Content-Type' : 'application/json' ,
130
+ Accept : 'application/json' ,
131
+ } ,
132
+ body : JSON . stringify ( { hash, query } ) ,
133
+ } ) ;
134
+
135
+ }
136
+
137
+ // IF WE ARE NOT USING PERSIST QUERIES
138
+ } else {
139
+ // JUST SEND THE QUERY ONLY
140
+ resJSON = await fetch ( endpoint , {
141
+ method : 'POST' ,
142
+ headers : {
143
+ 'Content-Type' : 'application/json' ,
144
+ Accept : 'application/json' ,
145
+ } ,
146
+ body : JSON . stringify ( { query } ) ,
147
+ } ) ;
148
+ }
149
+
95
150
const resObj = await resJSON . json ( ) ;
96
151
const deepResObj = { ...resObj } ;
97
152
// update result in cache if cacheWrite is set to true
98
- if ( cacheWrite && resObj . data [ Object . keys ( resObj . data ) [ 0 ] ] !== null ) {
99
- if ( ! wholeQuery ) cache . writeWholeQuery ( query , deepResObj ) ;
153
+ if ( cacheWrite && caching && resObj . data [ Object . keys ( resObj . data ) [ 0 ] ] !== null ) {
154
+ if ( wholeQuery ) cache . writeWholeQuery ( query , deepResObj ) ;
100
155
else if ( resObj . data [ Object . keys ( resObj . data ) [ 0 ] ] . length > cache . capacity ) console . log ( 'Please increase cache capacity' ) ;
101
- else cache . write ( query , deepResObj ) ;
156
+ else cache . write ( query , deepResObj , searchTerms ) ;
102
157
}
103
158
const cacheMissResponseTime = Date . now ( ) - startTime ;
104
- /*chrome.runtime.sendMessage(chromeExtensionId, {
105
- cacheMissResponseTime: cacheMissResponseTime,
106
- });*/
107
- resObj [ 'time' ] = cacheMissResponseTime
108
- console . log (
109
- "After the hunt: Here's the response time on the front end: " ,
110
- cacheMissResponseTime
111
- ) ;
159
+
160
+ // FOR DEVTOOL - sends message to content.js when query is a miss
161
+ window . postMessage ( {
162
+ type : 'query' ,
163
+ time : cacheMissResponseTime ,
164
+ date : startDate . toDateString ( ) . slice ( 0 , 24 ) ,
165
+ query : query ,
166
+ hit : false
167
+ } ) ;
168
+
112
169
return resObj ;
113
170
} catch ( e ) {
114
171
console . log ( e ) ;
@@ -121,20 +178,19 @@ function ObsidianWrapper(props) {
121
178
cache . cacheClear ( ) ;
122
179
}
123
180
181
+ // NOTE - FOR DEVTOOL - no messages are currently being passed for mutations
182
+ // so some logic in content.js and background.js may be missing to handle mutations
183
+
124
184
// breaking out writethrough logic vs. non-writethrough logic
125
185
async function mutate ( mutation , options = { } ) {
126
- // dev tool messages
127
- // chrome.runtime.sendMessage(chromeExtensionId, {
128
- // mutation: mutation,
129
- // });
130
186
const startTime = Date . now ( ) ;
131
187
mutation = insertTypenames ( mutation ) ;
132
188
const {
133
189
endpoint = '/graphql' ,
134
- cacheWrite = true ,
190
+ cacheWrite = ! caching ? false : true ,
135
191
toDelete = false ,
136
192
update = null ,
137
- writeThrough = true , // not true
193
+ writeThrough = true , // unsure if boolean is symantically backwards or not
138
194
} = options ;
139
195
try {
140
196
if ( ! writeThrough ) {
@@ -147,9 +203,6 @@ function ObsidianWrapper(props) {
147
203
endpoint
148
204
) ;
149
205
const deleteMutationResponseTime = Date . now ( ) - startTime ;
150
- chrome . runtime . sendMessage ( chromeExtensionId , {
151
- deleteMutationResponseTime : deleteMutationResponseTime ,
152
- } ) ;
153
206
return responseObj ;
154
207
} else {
155
208
// for add mutation
@@ -168,15 +221,9 @@ function ObsidianWrapper(props) {
168
221
// GQL call to make changes and synchronize database
169
222
console . log ( 'WriteThrough - false ' , responseObj ) ;
170
223
const addOrUpdateMutationResponseTime = Date . now ( ) - startTime ;
171
- chrome . runtime . sendMessage ( chromeExtensionId , {
172
- addOrUpdateMutationResponseTime : addOrUpdateMutationResponseTime ,
173
- } ) ;
174
224
return responseObj ;
175
225
}
176
226
} else {
177
- // copy-paste mutate logic from 4.
178
-
179
- // use cache.write instead of cache.writeThrough
180
227
const responseObj = await fetch ( endpoint , {
181
228
method : 'POST' ,
182
229
headers : {
@@ -185,18 +232,18 @@ function ObsidianWrapper(props) {
185
232
} ,
186
233
body : JSON . stringify ( { query : mutation } ) ,
187
234
} ) . then ( ( resp ) => resp . json ( ) ) ;
188
- if ( ! cacheWrite ) return responseObj ;
235
+ if ( ! cacheWrite || ! caching ) return responseObj ;
189
236
// first behaviour when delete cache is set to true
190
237
if ( toDelete ) {
191
- cache . write ( mutation , responseObj , true ) ;
238
+ cache . write ( mutation , responseObj , searchTerms , true ) ;
192
239
return responseObj ;
193
240
}
194
241
// second behaviour if update function provided
195
242
if ( update ) {
196
243
update ( cache , responseObj ) ;
197
244
}
198
245
199
- if ( ! responseObj . errors ) cache . write ( mutation , responseObj ) ;
246
+ if ( ! responseObj . errors ) cache . write ( mutation , responseObj , searchTerms ) ;
200
247
// third behaviour just for normal update (no-delete, no update function)
201
248
console . log ( 'WriteThrough - true ' , responseObj ) ;
202
249
return responseObj ;
0 commit comments