Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/indexedDB.ts
3292 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { toErrorMessage } from '../common/errorMessage.js';
7
import { ErrorNoTelemetry, getErrorMessage } from '../common/errors.js';
8
import { mark } from '../common/performance.js';
9
10
class MissingStoresError extends Error {
11
constructor(readonly db: IDBDatabase) {
12
super('Missing stores');
13
}
14
}
15
16
export class DBClosedError extends Error {
17
readonly code = 'DBClosed';
18
constructor(dbName: string) {
19
super(`IndexedDB database '${dbName}' is closed.`);
20
}
21
}
22
23
export class IndexedDB {
24
25
static async create(name: string, version: number | undefined, stores: string[]): Promise<IndexedDB> {
26
const database = await IndexedDB.openDatabase(name, version, stores);
27
return new IndexedDB(database, name);
28
}
29
30
private static async openDatabase(name: string, version: number | undefined, stores: string[]): Promise<IDBDatabase> {
31
mark(`code/willOpenDatabase/${name}`);
32
try {
33
return await IndexedDB.doOpenDatabase(name, version, stores);
34
} catch (err) {
35
if (err instanceof MissingStoresError) {
36
console.info(`Attempting to recreate the IndexedDB once.`, name);
37
38
try {
39
// Try to delete the db
40
await IndexedDB.deleteDatabase(err.db);
41
} catch (error) {
42
console.error(`Error while deleting the IndexedDB`, getErrorMessage(error));
43
throw error;
44
}
45
46
return await IndexedDB.doOpenDatabase(name, version, stores);
47
}
48
49
throw err;
50
} finally {
51
mark(`code/didOpenDatabase/${name}`);
52
}
53
}
54
55
private static doOpenDatabase(name: string, version: number | undefined, stores: string[]): Promise<IDBDatabase> {
56
return new Promise((c, e) => {
57
const request = indexedDB.open(name, version);
58
request.onerror = () => e(request.error);
59
request.onsuccess = () => {
60
const db = request.result;
61
for (const store of stores) {
62
if (!db.objectStoreNames.contains(store)) {
63
console.error(`Error while opening IndexedDB. Could not find '${store}'' object store`);
64
e(new MissingStoresError(db));
65
return;
66
}
67
}
68
c(db);
69
};
70
request.onupgradeneeded = () => {
71
const db = request.result;
72
for (const store of stores) {
73
if (!db.objectStoreNames.contains(store)) {
74
db.createObjectStore(store);
75
}
76
}
77
};
78
});
79
}
80
81
private static deleteDatabase(database: IDBDatabase): Promise<void> {
82
return new Promise((c, e) => {
83
// Close any opened connections
84
database.close();
85
86
// Delete the db
87
const deleteRequest = indexedDB.deleteDatabase(database.name);
88
deleteRequest.onerror = (err) => e(deleteRequest.error);
89
deleteRequest.onsuccess = () => c();
90
});
91
}
92
93
private database: IDBDatabase | null = null;
94
private readonly pendingTransactions: IDBTransaction[] = [];
95
96
constructor(database: IDBDatabase, private readonly name: string) {
97
this.database = database;
98
}
99
100
hasPendingTransactions(): boolean {
101
return this.pendingTransactions.length > 0;
102
}
103
104
close(): void {
105
if (this.pendingTransactions.length) {
106
this.pendingTransactions.splice(0, this.pendingTransactions.length).forEach(transaction => transaction.abort());
107
}
108
this.database?.close();
109
this.database = null;
110
}
111
112
runInTransaction<T>(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest<T>[]): Promise<T[]>;
113
runInTransaction<T>(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest<T>): Promise<T>;
114
async runInTransaction<T>(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest<T> | IDBRequest<T>[]): Promise<T | T[]> {
115
if (!this.database) {
116
throw new DBClosedError(this.name);
117
}
118
const transaction = this.database.transaction(store, transactionMode);
119
this.pendingTransactions.push(transaction);
120
return new Promise<T | T[]>((c, e) => {
121
transaction.oncomplete = () => {
122
if (Array.isArray(request)) {
123
c(request.map(r => r.result));
124
} else {
125
c(request.result);
126
}
127
};
128
transaction.onerror = () => e(transaction.error ? ErrorNoTelemetry.fromError(transaction.error) : new ErrorNoTelemetry('unknown error'));
129
transaction.onabort = () => e(transaction.error ? ErrorNoTelemetry.fromError(transaction.error) : new ErrorNoTelemetry('unknown error'));
130
const request = dbRequestFn(transaction.objectStore(store));
131
}).finally(() => this.pendingTransactions.splice(this.pendingTransactions.indexOf(transaction), 1));
132
}
133
134
async getKeyValues<V>(store: string, isValid: (value: unknown) => value is V): Promise<Map<string, V>> {
135
if (!this.database) {
136
throw new DBClosedError(this.name);
137
}
138
const transaction = this.database.transaction(store, 'readonly');
139
this.pendingTransactions.push(transaction);
140
return new Promise<Map<string, V>>(resolve => {
141
const items = new Map<string, V>();
142
143
const objectStore = transaction.objectStore(store);
144
145
// Open a IndexedDB Cursor to iterate over key/values
146
const cursor = objectStore.openCursor();
147
if (!cursor) {
148
return resolve(items); // this means the `ItemTable` was empty
149
}
150
151
// Iterate over rows of `ItemTable` until the end
152
cursor.onsuccess = () => {
153
if (cursor.result) {
154
155
// Keep cursor key/value in our map
156
if (isValid(cursor.result.value)) {
157
items.set(cursor.result.key.toString(), cursor.result.value);
158
}
159
160
// Advance cursor to next row
161
cursor.result.continue();
162
} else {
163
resolve(items); // reached end of table
164
}
165
};
166
167
// Error handlers
168
const onError = (error: Error | null) => {
169
console.error(`IndexedDB getKeyValues(): ${toErrorMessage(error, true)}`);
170
171
resolve(items);
172
};
173
cursor.onerror = () => onError(cursor.error);
174
transaction.onerror = () => onError(transaction.error);
175
}).finally(() => this.pendingTransactions.splice(this.pendingTransactions.indexOf(transaction), 1));
176
}
177
}
178
179