@@ -9,6 +9,9 @@ export type Database = {
9
9
isHidden : boolean
10
10
}
11
11
12
+ // Hardcoding to v16 (PGlite v0.2.0+) for now, in the future we should
13
+ // dynamically check this somehow
14
+ const currentPgVersion = '16'
12
15
const prefix = 'playground'
13
16
14
17
let metaDbPromise : Promise < PGliteInterface > | undefined
@@ -20,6 +23,8 @@ export async function getMetaDb() {
20
23
}
21
24
22
25
async function run ( ) {
26
+ await handleUnsupportedPGVersion ( 'meta' )
27
+
23
28
const metaDb = new PGlite ( `idb://meta` , {
24
29
extensions : {
25
30
vector,
@@ -47,6 +52,7 @@ export async function getDb(id: string) {
47
52
48
53
async function run ( ) {
49
54
const metaDb = await getMetaDb ( )
55
+
50
56
const {
51
57
rows : [ database ] ,
52
58
} = await metaDb . query < Database > ( 'select * from databases where id = $1' , [ id ] )
@@ -55,7 +61,11 @@ export async function getDb(id: string) {
55
61
throw new Error ( `Database with ID '${ id } ' doesn't exist` )
56
62
}
57
63
58
- const db = new PGlite ( `idb://${ prefix } -${ id } ` , {
64
+ const dbPath = `${ prefix } -${ id } `
65
+
66
+ await handleUnsupportedPGVersion ( dbPath )
67
+
68
+ const db = new PGlite ( `idb://${ dbPath } ` , {
59
69
extensions : {
60
70
vector,
61
71
} ,
@@ -88,19 +98,124 @@ export async function closeDb(id: string) {
88
98
export async function deleteDb ( id : string ) {
89
99
await closeDb ( id )
90
100
91
- // TODO: fix issue where PGlite holds on the IndexedDB preventing delete
101
+ // TODO: fix issue where PGlite holds on to the IndexedDB preventing delete
92
102
// Once fixed, turn this into an `await` so we can forward legitimate errors
93
- new Promise < void > ( ( resolve , reject ) => {
94
- const req = indexedDB . deleteDatabase ( `/pglite/${ prefix } -${ id } ` )
103
+ deleteIndexedDb ( `/pglite/${ prefix } -${ id } ` )
104
+ }
105
+
106
+ /**
107
+ * Peeks into the files of an IndexedDB-backed PGlite database
108
+ * and returns the Postgres version it was created under (via `./PG_VERSION`).
109
+ *
110
+ * Useful to detect version compatibility since it doesn't require instantiating
111
+ * a PGlite instance.
112
+ */
113
+ export async function getPGliteDBVersion ( id : string ) {
114
+ const dbPath = `/pglite/${ id } `
115
+ const versionPath = `${ dbPath } /PG_VERSION`
116
+
117
+ const dbs = await indexedDB . databases ( )
118
+ const databaseExists = dbs . some ( ( db ) => db . name === dbPath )
119
+
120
+ if ( ! databaseExists ) {
121
+ return undefined
122
+ }
123
+
124
+ try {
125
+ return await new Promise < string > ( ( resolve , reject ) => {
126
+ const req = indexedDB . open ( dbPath )
127
+
128
+ req . onsuccess = async ( ) => {
129
+ const db = req . result
130
+
131
+ try {
132
+ const transaction = db . transaction ( [ 'FILE_DATA' ] , 'readonly' )
133
+ const objectStore = transaction . objectStore ( 'FILE_DATA' )
134
+
135
+ const getReq : IDBRequest < { contents : Int8Array } > = objectStore . get ( versionPath )
136
+
137
+ getReq . onerror = ( ) => {
138
+ db . close ( )
139
+ reject (
140
+ getReq . error
141
+ ? `An error occurred when retrieving '${ versionPath } ' from IndexedDB database: ${ getReq . error . message } `
142
+ : `An unknown error occurred when retrieving '${ versionPath } ' from IndexedDB database`
143
+ )
144
+ }
145
+
146
+ getReq . onsuccess = ( ) => {
147
+ const decoder = new TextDecoder ( )
148
+ const version = decoder . decode ( getReq . result . contents ) . trim ( )
149
+ db . close ( )
150
+ resolve ( version )
151
+ }
152
+ } catch ( err ) {
153
+ db . close ( )
154
+ reject (
155
+ err && err instanceof Error
156
+ ? `An error occurred when opening 'FILE_DATA' object store from IndexedDB database: ${ err . message } `
157
+ : `An unknown error occurred when opening 'FILE_DATA' object store from IndexedDB database`
158
+ )
159
+ }
160
+ }
161
+ req . onerror = ( ) => {
162
+ reject (
163
+ req . error
164
+ ? `An error occurred when opening IndexedDB database: ${ req . error . message } `
165
+ : 'An unknown error occurred when opening IndexedDB database'
166
+ )
167
+ }
168
+ req . onblocked = ( ) => {
169
+ reject ( 'IndexedDB database was blocked when opening' )
170
+ }
171
+ } )
172
+ } catch ( err ) {
173
+ // If the retrieval failed, the DB is corrupt or not initialized, return undefined
174
+ return undefined
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Handles scenario where client had created DB with an old version of PGlite (likely 0.1.5, PG v15).
180
+ * For now we'll simply delete and recreate it, which loses data (as 0.1.5 was only used before official release).
181
+ *
182
+ * In the future we need to come up with an upgrade strategy.
183
+ */
184
+ export async function handleUnsupportedPGVersion ( dbPath : string ) {
185
+ const dbs = await indexedDB . databases ( )
186
+ const databaseExists = dbs . some ( ( db ) => db . name === `/pglite/${ dbPath } ` )
187
+
188
+ if ( databaseExists ) {
189
+ const version = await getPGliteDBVersion ( dbPath )
190
+
191
+ console . debug ( `PG version of '${ dbPath } ' DB is ${ version } ` )
192
+
193
+ if ( version !== currentPgVersion ) {
194
+ console . warn (
195
+ `DB '${ dbPath } ' is on PG version ${ version } , deleting and replacing with version ${ currentPgVersion } `
196
+ )
197
+
198
+ await deleteIndexedDb ( `/pglite/${ dbPath } ` )
199
+ }
200
+ }
201
+ }
202
+
203
+ export async function deleteIndexedDb ( name : string ) {
204
+ await new Promise < void > ( ( resolve , reject ) => {
205
+ const req = indexedDB . deleteDatabase ( name )
95
206
96
207
req . onsuccess = ( ) => {
97
208
resolve ( )
98
209
}
99
210
req . onerror = ( ) => {
100
- reject ( 'An error occurred when deleted database' )
211
+ reject (
212
+ req . error
213
+ ? `An error occurred when deleting IndexedDB database: ${ req . error . message } `
214
+ : 'An unknown error occurred when deleting IndexedDB database'
215
+ )
101
216
}
102
217
req . onblocked = ( ) => {
103
- reject ( 'Database is blocked' )
218
+ reject ( 'IndexedDB database was blocked when deleting ' )
104
219
}
105
220
} )
106
221
}
0 commit comments