Skip to content

Commit 73ae14d

Browse files
authored
Merge pull request supabase-community#16 from supabase-community/fix/db-deletes
Fix DBs properly deleting from IndexedDB
2 parents 7da1973 + e1d7760 commit 73ae14d

File tree

3 files changed

+56
-85
lines changed

3 files changed

+56
-85
lines changed

apps/postgres-new/app/page.tsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,11 @@
22

33
import { customAlphabet } from 'nanoid'
44
import { useRouter } from 'next/navigation'
5-
import { useCallback, useEffect } from 'react'
5+
import { useCallback, useEffect, useMemo } from 'react'
66
import Workspace from '~/components/workspace'
77
import { useDatabaseCreateMutation } from '~/data/databases/database-create-mutation'
88
import { useDatabaseUpdateMutation } from '~/data/databases/database-update-mutation'
9-
import { getDb } from '~/lib/db'
10-
import { useLocalStorage } from '~/lib/hooks'
11-
12-
export const dynamic = 'force-static'
9+
import { dbExists, getDb } from '~/lib/db'
1310

1411
// Use a DNS safe alphabet
1512
const nanoid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 16)
@@ -29,14 +26,33 @@ export default function Page() {
2926
*/
3027
const preloadDb = useCallback(
3128
async (id: string) => {
32-
await createDatabase({ id, isHidden: true })
33-
await getDb(id)
29+
const exists = await dbExists(id)
30+
if (!exists) {
31+
await createDatabase({ id, isHidden: true })
32+
await getDb(id)
33+
}
3434
},
3535
[createDatabase]
3636
)
3737

3838
// Track the next database ID in local storage
39-
const [nextDatabaseId] = useLocalStorage('next-db-id', uniqueId())
39+
const nextDatabaseId = useMemo(() => {
40+
const id = uniqueId()
41+
42+
// To prevent Next.js from failing SSR
43+
if (typeof window === 'undefined') {
44+
return id
45+
}
46+
47+
// For historical reasons this value exists as JSON
48+
let idJson = localStorage.getItem('next-db-id')
49+
if (idJson) {
50+
return JSON.parse(idJson)
51+
}
52+
53+
localStorage.setItem('next-db-id', JSON.stringify(id))
54+
return id
55+
}, [])
4056

4157
// The very first DB needs to be loaded on mount
4258
useEffect(() => {
@@ -53,7 +69,7 @@ export default function Page() {
5369
// Navigate to this DB's path
5470
router.push(`/db/${nextDatabaseId}`)
5571

56-
// Pre-load the next DB (but without causing a re-render)
72+
// Pre-load the next DB
5773
const nextId = uniqueId()
5874
localStorage.setItem('next-db-id', JSON.stringify(nextId))
5975
preloadDb(nextId)

apps/postgres-new/lib/db/index.ts

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ export async function getMetaDb() {
5050
await handleUnsupportedPGVersion('meta')
5151

5252
const metaDb = await createPGlite('idb://meta')
53-
await metaDb.waitReady
5453
await runMigrations(metaDb, metaMigrations)
5554
return metaDb
5655
}
@@ -63,6 +62,16 @@ export async function getMetaDb() {
6362
return await metaDbPromise
6463
}
6564

65+
export async function dbExists(id: string) {
66+
const metaDb = await getMetaDb()
67+
68+
const {
69+
rows: [database],
70+
} = await metaDb.query<Database>('select * from databases where id = $1', [id])
71+
72+
return database !== undefined
73+
}
74+
6675
export async function getDb(id: string) {
6776
const openDatabasePromise = databaseConnections.get(id)
6877

@@ -71,13 +80,9 @@ export async function getDb(id: string) {
7180
}
7281

7382
async function run() {
74-
const metaDb = await getMetaDb()
83+
const exists = await dbExists(id)
7584

76-
const {
77-
rows: [database],
78-
} = await metaDb.query<Database>('select * from databases where id = $1', [id])
79-
80-
if (!database) {
85+
if (!exists) {
8186
throw new Error(`Database with ID '${id}' doesn't exist`)
8287
}
8388

@@ -86,7 +91,6 @@ export async function getDb(id: string) {
8691
await handleUnsupportedPGVersion(dbPath)
8792

8893
const db = await createPGlite(`idb://${dbPath}`)
89-
await db.waitReady
9094
await runMigrations(db, migrations)
9195

9296
return db
@@ -113,10 +117,7 @@ export async function closeDb(id: string) {
113117

114118
export async function deleteDb(id: string) {
115119
await closeDb(id)
116-
117-
// TODO: fix issue where PGlite holds on to the IndexedDB preventing delete
118-
// Once fixed, turn this into an `await` so we can forward legitimate errors
119-
deleteIndexedDb(`/pglite/${prefix}-${id}`)
120+
await deleteIndexedDb(`/pglite/${prefix}-${id}`)
120121
}
121122

122123
/**
@@ -219,23 +220,24 @@ export async function handleUnsupportedPGVersion(dbPath: string) {
219220
}
220221

221222
export async function deleteIndexedDb(name: string) {
222-
await new Promise<void>((resolve, reject) => {
223-
const req = indexedDB.deleteDatabase(name)
224-
225-
req.onsuccess = () => {
226-
resolve()
227-
}
228-
req.onerror = () => {
229-
reject(
230-
req.error
231-
? `An error occurred when deleting IndexedDB database: ${req.error.message}`
232-
: 'An unknown error occurred when deleting IndexedDB database'
233-
)
234-
}
235-
req.onblocked = () => {
236-
reject('IndexedDB database was blocked when deleting')
237-
}
238-
})
223+
// Sometimes IndexedDB is still finishing a transaction even after PGlite closes
224+
// causing the delete to be blocked, so loop until the delete is successful
225+
while (true) {
226+
const closed = await new Promise((resolve, reject) => {
227+
const req = indexedDB.deleteDatabase(name)
228+
229+
req.onsuccess = () => {
230+
resolve(true)
231+
}
232+
req.onerror = () => {
233+
reject(req.error ?? 'An unknown error occurred when deleting IndexedDB database')
234+
}
235+
req.onblocked = () => {
236+
resolve(false)
237+
}
238+
})
239+
if (closed) break
240+
}
239241
}
240242

241243
/**

apps/postgres-new/lib/hooks.ts

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
'use client'
22

3-
import { useQuery, useQueryClient } from '@tanstack/react-query'
43
import { FeatureExtractionPipelineOptions, pipeline } from '@xenova/transformers'
54
import { generateId } from 'ai'
65
import { Chart } from 'chart.js'
76
import { codeBlock } from 'common-tags'
87
import {
98
cloneElement,
10-
Dispatch,
119
isValidElement,
1210
ReactNode,
13-
SetStateAction,
1411
useCallback,
1512
useEffect,
1613
useMemo,
@@ -25,50 +22,6 @@ import { loadFile, saveFile } from './files'
2522
import { SmoothScroller } from './smooth-scroller'
2623
import { OnToolCall } from './tools'
2724

28-
/**
29-
* Hook to load/store values from local storage with an API similar
30-
* to `useState()`.
31-
*/
32-
export function useLocalStorage<T>(key: string, initialValue: T) {
33-
const queryClient = useQueryClient()
34-
const queryKey = ['local-storage', key]
35-
36-
const currentValue =
37-
typeof window !== 'undefined' ? (window.localStorage.getItem(key) ?? undefined) : undefined
38-
39-
const { data: storedValue = currentValue ? (JSON.parse(currentValue) as T) : initialValue } =
40-
useQuery({
41-
queryKey,
42-
queryFn: () => {
43-
if (typeof window === 'undefined') {
44-
return initialValue
45-
}
46-
47-
const item = window.localStorage.getItem(key)
48-
49-
if (!item) {
50-
window.localStorage.setItem(key, JSON.stringify(initialValue))
51-
return initialValue
52-
}
53-
54-
return JSON.parse(item) as T
55-
},
56-
})
57-
58-
const setValue: Dispatch<SetStateAction<T>> = (value) => {
59-
const valueToStore = value instanceof Function ? value(storedValue) : value
60-
61-
if (typeof window !== 'undefined') {
62-
window.localStorage.setItem(key, JSON.stringify(valueToStore))
63-
}
64-
65-
queryClient.setQueryData(queryKey, valueToStore)
66-
queryClient.invalidateQueries({ queryKey })
67-
}
68-
69-
return [storedValue, setValue] as const
70-
}
71-
7225
export function useDebounce<T>(value: T, delay: number) {
7326
const [debouncedValue, setDebouncedValue] = useState(value)
7427

0 commit comments

Comments
 (0)