1
- import { openDB , DBSchema } from "idb" ;
1
+ import { openDB , DBSchema , IDBPDatabase } from "idb" ;
2
2
import { ProtocolVersion } from "edgedb/dist/ifaces" ;
3
3
import { StoredSchemaData } from "../state/database" ;
4
4
import { StoredSessionStateData } from "../state/sessionState" ;
@@ -68,53 +68,83 @@ interface IDBStore extends DBSchema {
68
68
} ;
69
69
}
70
70
71
- const db = openDB < IDBStore > ( "EdgeDBStudio" , 4 , {
72
- upgrade ( db , oldVersion ) {
73
- switch ( oldVersion ) {
74
- // @ts -ignore fallthrough
75
- case 0 : {
76
- db . createObjectStore ( "schemaData" ) . createIndex (
77
- "byInstanceId" ,
78
- "instanceId"
79
- ) ;
80
- }
81
- // @ts -ignore fallthrough
82
- case 1 : {
83
- db . createObjectStore ( "queryHistory" , {
84
- keyPath : [ "instanceId" , "dbName" , "timestamp" ] ,
85
- } ) . createIndex ( "byInstanceId" , "instanceId" ) ;
86
- db . createObjectStore ( "replHistory" , {
87
- keyPath : [ "instanceId" , "dbName" , "timestamp" ] ,
88
- } ) . createIndex ( "byInstanceId" , "instanceId" ) ;
71
+ function _initDB ( ) {
72
+ return openDB < IDBStore > ( "EdgeDBStudio" , 4 , {
73
+ upgrade ( db , oldVersion ) {
74
+ switch ( oldVersion ) {
75
+ // @ts -ignore fallthrough
76
+ case 0 : {
77
+ db . createObjectStore ( "schemaData" ) . createIndex (
78
+ "byInstanceId" ,
79
+ "instanceId"
80
+ ) ;
81
+ }
82
+ // @ts -ignore fallthrough
83
+ case 1 : {
84
+ db . createObjectStore ( "queryHistory" , {
85
+ keyPath : [ "instanceId" , "dbName" , "timestamp" ] ,
86
+ } ) . createIndex ( "byInstanceId" , "instanceId" ) ;
87
+ db . createObjectStore ( "replHistory" , {
88
+ keyPath : [ "instanceId" , "dbName" , "timestamp" ] ,
89
+ } ) . createIndex ( "byInstanceId" , "instanceId" ) ;
89
90
90
- db . createObjectStore ( "queryResultData" ) ;
91
- }
92
- // @ts -ignore fallthrough
93
- case 2 : {
94
- db . createObjectStore ( "sessionState" , {
95
- keyPath : [ "instanceId" , "dbName" ] ,
96
- } ) ;
91
+ db . createObjectStore ( "queryResultData" ) ;
92
+ }
93
+ // @ts -ignore fallthrough
94
+ case 2 : {
95
+ db . createObjectStore ( "sessionState" , {
96
+ keyPath : [ "instanceId" , "dbName" ] ,
97
+ } ) ;
98
+ }
99
+ // @ts -ignore fallthrough
100
+ case 3 : {
101
+ db . createObjectStore ( "aiPlaygroundChatHistory" , {
102
+ keyPath : [ "instanceId" , "dbName" , "timestamp" ] ,
103
+ } ) . createIndex ( "byInstanceId" , "instanceId" ) ;
104
+ }
97
105
}
98
- // @ts -ignore fallthrough
99
- case 3 : {
100
- db . createObjectStore ( "aiPlaygroundChatHistory" , {
101
- keyPath : [ "instanceId" , "dbName" , "timestamp" ] ,
102
- } ) . createIndex ( "byInstanceId" , "instanceId" ) ;
106
+ } ,
107
+ } ) ;
108
+ }
109
+
110
+ let _db : IDBPDatabase < IDBStore > | null = null ;
111
+ async function retryingIDBRequest < T > (
112
+ request : ( db : IDBPDatabase < IDBStore > ) => Promise < T >
113
+ ) : Promise < T > {
114
+ let i = 3 ;
115
+ while ( true ) {
116
+ i -- ;
117
+ if ( ! _db ) {
118
+ _db = await _initDB ( ) ;
119
+ }
120
+ try {
121
+ return await request ( _db ) ;
122
+ } catch ( err ) {
123
+ if (
124
+ i === 0 ||
125
+ ! (
126
+ err instanceof DOMException &&
127
+ ( err . message . includes ( "closing" ) || err . message . includes ( "closed" ) )
128
+ )
129
+ ) {
130
+ throw err ;
103
131
}
132
+ _db = null ;
104
133
}
105
- } ,
106
- } ) ;
134
+ }
135
+ }
107
136
108
137
// session state
109
138
110
139
export async function fetchSessionState ( instanceId : string , dbName : string ) {
111
- return (
112
- ( await ( await db ) . get ( "sessionState" , [ instanceId , dbName ] ) ) ?. data ?? null
140
+ return retryingIDBRequest (
141
+ async ( db ) =>
142
+ ( await db . get ( "sessionState" , [ instanceId , dbName ] ) ) ?. data ?? null
113
143
) ;
114
144
}
115
145
116
146
export async function storeSessionState ( data : SessionStateData ) {
117
- await ( await db ) . put ( "sessionState" , data ) ;
147
+ await retryingIDBRequest ( ( db ) => db . put ( "sessionState" , data ) ) ;
118
148
}
119
149
120
150
// query / repl history
@@ -125,15 +155,17 @@ async function _storeHistoryItem(
125
155
item : QueryHistoryItem ,
126
156
resultData ?: QueryResultData
127
157
) {
128
- const tx = ( await db ) . transaction ( [ storeId , "queryResultData" ] , "readwrite" ) ;
158
+ return retryingIDBRequest ( async ( db ) => {
159
+ const tx = db . transaction ( [ storeId , "queryResultData" ] , "readwrite" ) ;
129
160
130
- return Promise . all ( [
131
- tx . objectStore ( storeId ) . add ( item ) ,
132
- resultData
133
- ? tx . objectStore ( "queryResultData" ) . add ( resultData , itemId )
134
- : null ,
135
- tx . done ,
136
- ] ) ;
161
+ return Promise . all ( [
162
+ tx . objectStore ( storeId ) . add ( item ) ,
163
+ resultData
164
+ ? tx . objectStore ( "queryResultData" ) . add ( resultData , itemId )
165
+ : null ,
166
+ tx . done ,
167
+ ] ) ;
168
+ } ) ;
137
169
}
138
170
139
171
export function storeQueryHistoryItem (
@@ -159,24 +191,26 @@ async function _fetchHistory(
159
191
fromTimestamp : number ,
160
192
count = 50
161
193
) {
162
- const tx = ( await db ) . transaction ( storeId , "readonly" ) ;
163
- let cursor = await tx . store . openCursor (
164
- IDBKeyRange . bound (
165
- [ instanceId , dbName , - Infinity ] ,
166
- [ instanceId , dbName , fromTimestamp ] ,
167
- true ,
168
- true
169
- ) ,
170
- "prev"
171
- ) ;
172
- const items : QueryHistoryItem [ ] = [ ] ;
173
- let i = 0 ;
174
- while ( cursor && i < count ) {
175
- items . push ( cursor . value ) ;
176
- i ++ ;
177
- cursor = await cursor . continue ( ) ;
178
- }
179
- return items ;
194
+ return retryingIDBRequest ( async ( db ) => {
195
+ const tx = db . transaction ( storeId , "readonly" ) ;
196
+ let cursor = await tx . store . openCursor (
197
+ IDBKeyRange . bound (
198
+ [ instanceId , dbName , - Infinity ] ,
199
+ [ instanceId , dbName , fromTimestamp ] ,
200
+ true ,
201
+ true
202
+ ) ,
203
+ "prev"
204
+ ) ;
205
+ const items : QueryHistoryItem [ ] = [ ] ;
206
+ let i = 0 ;
207
+ while ( cursor && i < count ) {
208
+ items . push ( cursor . value ) ;
209
+ i ++ ;
210
+ cursor = await cursor . continue ( ) ;
211
+ }
212
+ return items ;
213
+ } ) ;
180
214
}
181
215
182
216
async function _clearHistory (
@@ -185,27 +219,29 @@ async function _clearHistory(
185
219
dbName : string ,
186
220
getResultDataId : ( item : QueryHistoryItem ) => string | null
187
221
) {
188
- const tx = ( await db ) . transaction ( [ storeId , "queryResultData" ] , "readwrite" ) ;
189
- let cursor = await tx
190
- . objectStore ( storeId )
191
- . openCursor (
192
- IDBKeyRange . bound (
193
- [ instanceId , dbName , - Infinity ] ,
194
- [ instanceId , dbName , Infinity ]
195
- )
196
- ) ;
197
- const deletes : Promise < any > [ ] = [ ] ;
198
- while ( cursor ) {
199
- const currentItem = cursor ;
200
- const resultDataId = getResultDataId ( currentItem . value ) ;
201
- if ( resultDataId ) {
202
- deletes . push ( tx . objectStore ( "queryResultData" ) . delete ( resultDataId ) ) ;
222
+ return retryingIDBRequest ( async ( db ) => {
223
+ const tx = db . transaction ( [ storeId , "queryResultData" ] , "readwrite" ) ;
224
+ let cursor = await tx
225
+ . objectStore ( storeId )
226
+ . openCursor (
227
+ IDBKeyRange . bound (
228
+ [ instanceId , dbName , - Infinity ] ,
229
+ [ instanceId , dbName , Infinity ]
230
+ )
231
+ ) ;
232
+ const deletes : Promise < any > [ ] = [ ] ;
233
+ while ( cursor ) {
234
+ const currentItem = cursor ;
235
+ const resultDataId = getResultDataId ( currentItem . value ) ;
236
+ if ( resultDataId ) {
237
+ deletes . push ( tx . objectStore ( "queryResultData" ) . delete ( resultDataId ) ) ;
238
+ }
239
+ deletes . push ( currentItem . delete ( ) ) ;
240
+ cursor = await cursor . continue ( ) ;
203
241
}
204
- deletes . push ( currentItem . delete ( ) ) ;
205
- cursor = await cursor . continue ( ) ;
206
- }
207
- deletes . push ( tx . done ) ;
208
- return await Promise . all ( deletes ) ;
242
+ deletes . push ( tx . done ) ;
243
+ return await Promise . all ( deletes ) ;
244
+ } ) ;
209
245
}
210
246
211
247
export function fetchQueryHistory (
@@ -247,7 +283,7 @@ export function clearReplHistory(
247
283
}
248
284
249
285
export async function fetchResultData ( itemId : string ) {
250
- return ( await db ) . get ( "queryResultData" , itemId ) ;
286
+ return retryingIDBRequest ( ( db ) => db . get ( "queryResultData" , itemId ) ) ;
251
287
}
252
288
253
289
// schema data
@@ -257,14 +293,16 @@ export async function storeSchemaData(
257
293
instanceId : string ,
258
294
data : StoredSchemaData
259
295
) {
260
- await (
261
- await db
262
- ) . put ( "schemaData" , { instanceId , data } , ` ${ instanceId } / ${ dbName } ` ) ;
296
+ await retryingIDBRequest ( ( db ) =>
297
+ db . put ( "schemaData" , { instanceId , data } , ` ${ instanceId } / ${ dbName } ` )
298
+ ) ;
263
299
}
264
300
265
301
export async function fetchSchemaData ( dbName : string , instanceId : string ) {
266
- const result = await ( await db ) . get ( "schemaData" , `${ instanceId } /${ dbName } ` ) ;
267
- return result ?. data ;
302
+ return retryingIDBRequest ( async ( db ) => {
303
+ const result = await db . get ( "schemaData" , `${ instanceId } /${ dbName } ` ) ;
304
+ return result ?. data ;
305
+ } ) ;
268
306
}
269
307
270
308
export async function cleanupOldSchemaDataForInstance (
@@ -274,20 +312,22 @@ export async function cleanupOldSchemaDataForInstance(
274
312
const currentDbKeys = new Set (
275
313
currentDbNames . map ( ( dbName ) => `${ instanceId } /${ dbName } ` )
276
314
) ;
277
- const tx = ( await db ) . transaction ( "schemaData" , "readwrite" ) ;
278
- const dbKeys = await tx . store . index ( "byInstanceId" ) . getAllKeys ( instanceId ) ;
279
- await Promise . all ( [
280
- ...dbKeys
281
- . filter ( ( dbKey ) => ! currentDbKeys . has ( dbKey ) )
282
- . map ( ( dbKey ) => tx . store . delete ( dbKey ) ) ,
283
- tx . done ,
284
- ] ) ;
315
+ await retryingIDBRequest ( async ( db ) => {
316
+ const tx = db . transaction ( "schemaData" , "readwrite" ) ;
317
+ const dbKeys = await tx . store . index ( "byInstanceId" ) . getAllKeys ( instanceId ) ;
318
+ return Promise . all ( [
319
+ ...dbKeys
320
+ . filter ( ( dbKey ) => ! currentDbKeys . has ( dbKey ) )
321
+ . map ( ( dbKey ) => tx . store . delete ( dbKey ) ) ,
322
+ tx . done ,
323
+ ] ) ;
324
+ } ) ;
285
325
}
286
326
287
327
// ai playground chat
288
328
289
329
export async function storeAIPlaygroundChatItem ( item : AIPlaygroundChatItem ) {
290
- await ( await db ) . add ( "aiPlaygroundChatHistory" , item ) ;
330
+ await retryingIDBRequest ( ( db ) => db . add ( "aiPlaygroundChatHistory" , item ) ) ;
291
331
}
292
332
293
333
export async function fetchAIPlaygroundChatHistory (
@@ -296,22 +336,24 @@ export async function fetchAIPlaygroundChatHistory(
296
336
fromTimestamp : number ,
297
337
count = 50
298
338
) {
299
- const tx = ( await db ) . transaction ( "aiPlaygroundChatHistory" , "readonly" ) ;
300
- let cursor = await tx . store . openCursor (
301
- IDBKeyRange . bound (
302
- [ instanceId , dbName , - Infinity ] ,
303
- [ instanceId , dbName , fromTimestamp ] ,
304
- true ,
305
- true
306
- ) ,
307
- "prev"
308
- ) ;
309
- const items : AIPlaygroundChatItem [ ] = [ ] ;
310
- let i = 0 ;
311
- while ( cursor && i < count ) {
312
- items . push ( cursor . value ) ;
313
- i ++ ;
314
- cursor = await cursor . continue ( ) ;
315
- }
316
- return items ;
339
+ return retryingIDBRequest ( async ( db ) => {
340
+ const tx = db . transaction ( "aiPlaygroundChatHistory" , "readonly" ) ;
341
+ let cursor = await tx . store . openCursor (
342
+ IDBKeyRange . bound (
343
+ [ instanceId , dbName , - Infinity ] ,
344
+ [ instanceId , dbName , fromTimestamp ] ,
345
+ true ,
346
+ true
347
+ ) ,
348
+ "prev"
349
+ ) ;
350
+ const items : AIPlaygroundChatItem [ ] = [ ] ;
351
+ let i = 0 ;
352
+ while ( cursor && i < count ) {
353
+ items . push ( cursor . value ) ;
354
+ i ++ ;
355
+ cursor = await cursor . continue ( ) ;
356
+ }
357
+ return items ;
358
+ } ) ;
317
359
}
0 commit comments