Skip to content

Commit 7537414

Browse files
authored
Various small bugfixes (#400)
* Catch error and redirect if example database creation is tried twice * Attempt to retry IDB requests if the db was closed by the browser It seems chrome and maybe safari might unexpectedly close the db sometimes: jakearchibald/idb#229 * Refresh the branch graph if it's found to be outdated when fetching migrations info * Fixes for various small issues caught by sentry
1 parent 42b4841 commit 7537414

File tree

7 files changed

+198
-135
lines changed

7 files changed

+198
-135
lines changed

shared/common/branchGraph/index.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ export const BranchGraphContext = createContext<{
9292
) => Promise<{script: string; sdl: string | null}[]>;
9393
}>(null!);
9494

95+
class MissingMigrationsError extends Error {}
96+
9597
export const BranchGraph = observer(function BranchGraph({
9698
instanceId,
9799
instanceState,
@@ -176,7 +178,8 @@ export const BranchGraph = observer(function BranchGraph({
176178
.filter((name) => !migrations.has(name));
177179

178180
if (missingMigrations.length) {
179-
throw new Error(
181+
setRefreshing(true);
182+
throw new MissingMigrationsError(
180183
`Migrations not found for ${missingMigrations.join(", ")}`
181184
);
182185
}
@@ -864,6 +867,13 @@ const MigrationsPanel = observer(function MigrationsPanel({
864867
});
865868
}
866869
})
870+
.catch((err) => {
871+
if (err instanceof MissingMigrationsError) {
872+
closePanel();
873+
} else {
874+
throw err;
875+
}
876+
})
867877
.finally(() => setFetching(false));
868878
}
869879
}, [history, fetching]);

shared/studio/idbStore/index.ts

Lines changed: 157 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {openDB, DBSchema} from "idb";
1+
import {openDB, DBSchema, IDBPDatabase} from "idb";
22
import {ProtocolVersion} from "edgedb/dist/ifaces";
33
import {StoredSchemaData} from "../state/database";
44
import {StoredSessionStateData} from "../state/sessionState";
@@ -68,53 +68,83 @@ interface IDBStore extends DBSchema {
6868
};
6969
}
7070

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");
8990

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+
}
97105
}
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;
103131
}
132+
_db = null;
104133
}
105-
},
106-
});
134+
}
135+
}
107136

108137
// session state
109138

110139
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
113143
);
114144
}
115145

116146
export async function storeSessionState(data: SessionStateData) {
117-
await (await db).put("sessionState", data);
147+
await retryingIDBRequest((db) => db.put("sessionState", data));
118148
}
119149

120150
// query / repl history
@@ -125,15 +155,17 @@ async function _storeHistoryItem(
125155
item: QueryHistoryItem,
126156
resultData?: QueryResultData
127157
) {
128-
const tx = (await db).transaction([storeId, "queryResultData"], "readwrite");
158+
return retryingIDBRequest(async (db) => {
159+
const tx = db.transaction([storeId, "queryResultData"], "readwrite");
129160

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+
});
137169
}
138170

139171
export function storeQueryHistoryItem(
@@ -159,24 +191,26 @@ async function _fetchHistory(
159191
fromTimestamp: number,
160192
count = 50
161193
) {
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+
});
180214
}
181215

182216
async function _clearHistory(
@@ -185,27 +219,29 @@ async function _clearHistory(
185219
dbName: string,
186220
getResultDataId: (item: QueryHistoryItem) => string | null
187221
) {
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();
203241
}
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+
});
209245
}
210246

211247
export function fetchQueryHistory(
@@ -247,7 +283,7 @@ export function clearReplHistory(
247283
}
248284

249285
export async function fetchResultData(itemId: string) {
250-
return (await db).get("queryResultData", itemId);
286+
return retryingIDBRequest((db) => db.get("queryResultData", itemId));
251287
}
252288

253289
// schema data
@@ -257,14 +293,16 @@ export async function storeSchemaData(
257293
instanceId: string,
258294
data: StoredSchemaData
259295
) {
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+
);
263299
}
264300

265301
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+
});
268306
}
269307

270308
export async function cleanupOldSchemaDataForInstance(
@@ -274,20 +312,22 @@ export async function cleanupOldSchemaDataForInstance(
274312
const currentDbKeys = new Set(
275313
currentDbNames.map((dbName) => `${instanceId}/${dbName}`)
276314
);
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+
});
285325
}
286326

287327
// ai playground chat
288328

289329
export async function storeAIPlaygroundChatItem(item: AIPlaygroundChatItem) {
290-
await (await db).add("aiPlaygroundChatHistory", item);
330+
await retryingIDBRequest((db) => db.add("aiPlaygroundChatHistory", item));
291331
}
292332

293333
export async function fetchAIPlaygroundChatHistory(
@@ -296,22 +336,24 @@ export async function fetchAIPlaygroundChatHistory(
296336
fromTimestamp: number,
297337
count = 50
298338
) {
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+
});
317359
}

0 commit comments

Comments
 (0)