origin-snapshot 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/storage/indexeddb.js +135 -144
package/package.json
CHANGED
package/src/storage/indexeddb.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { serializeValue, deserializeValue } from
|
|
1
|
+
import { serializeValue, deserializeValue } from '../serialize.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @param {Array<Blob | Uint8Array>} bin
|
|
@@ -6,28 +6,28 @@ import { serializeValue, deserializeValue } from "../serialize.js";
|
|
|
6
6
|
* @returns {Promise<object[]>}
|
|
7
7
|
*/
|
|
8
8
|
export async function dumpDatabases(bin, names) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
9
|
+
let list = names;
|
|
10
|
+
if (!list) {
|
|
11
|
+
list =
|
|
12
|
+
typeof indexedDB.databases === 'function'
|
|
13
|
+
? (await indexedDB.databases()).map((d) => d.name).filter(Boolean)
|
|
14
|
+
: [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const dump = [];
|
|
18
|
+
for (const name of list) {
|
|
19
|
+
const db = await openExisting(name);
|
|
20
|
+
try {
|
|
21
|
+
const stores = [];
|
|
22
|
+
for (const storeName of [...db.objectStoreNames]) {
|
|
23
|
+
stores.push(await dumpStore(db, storeName, bin));
|
|
24
|
+
}
|
|
25
|
+
dump.push({ name: db.name, version: db.version, stores });
|
|
26
|
+
} finally {
|
|
27
|
+
db.close();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return dump;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -36,36 +36,31 @@ export async function dumpDatabases(bin, names) {
|
|
|
36
36
|
* @param {Array<Blob | Uint8Array>} bin
|
|
37
37
|
*/
|
|
38
38
|
async function dumpStore(db, storeName, bin) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return rec;
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
await transactionDone(tx);
|
|
68
|
-
return { ...schema, records };
|
|
39
|
+
const tx = db.transaction(storeName, 'readonly');
|
|
40
|
+
const store = tx.objectStore(storeName);
|
|
41
|
+
|
|
42
|
+
const schema = {
|
|
43
|
+
name: storeName,
|
|
44
|
+
keyPath: store.keyPath,
|
|
45
|
+
autoIncrement: store.autoIncrement,
|
|
46
|
+
indexes: [...store.indexNames].map((iname) => {
|
|
47
|
+
const ix = store.index(iname);
|
|
48
|
+
return { name: iname, keyPath: ix.keyPath, unique: ix.unique, multiEntry: ix.multiEntry };
|
|
49
|
+
})
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const outOfLine = store.keyPath === null;
|
|
53
|
+
const values = await request(store.getAll());
|
|
54
|
+
const keys = outOfLine ? await request(store.getAllKeys()) : null;
|
|
55
|
+
|
|
56
|
+
const records = values.map((value, i) => {
|
|
57
|
+
const rec = { value: serializeValue(value, bin) };
|
|
58
|
+
if (outOfLine) rec.key = serializeValue(keys[i], bin);
|
|
59
|
+
return rec;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await transactionDone(tx);
|
|
63
|
+
return { ...schema, records };
|
|
69
64
|
}
|
|
70
65
|
|
|
71
66
|
/**
|
|
@@ -74,17 +69,17 @@ async function dumpStore(db, storeName, bin) {
|
|
|
74
69
|
* @param {{ clear?: boolean }} [options]
|
|
75
70
|
*/
|
|
76
71
|
export async function restoreDatabases(dump, bin, { clear = true } = {}) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
72
|
+
for (const dbInfo of dump) {
|
|
73
|
+
if (clear) await deleteDatabase(dbInfo.name);
|
|
74
|
+
const db = await openForRestore(dbInfo, clear);
|
|
75
|
+
try {
|
|
76
|
+
for (const storeInfo of dbInfo.stores) {
|
|
77
|
+
await restoreStore(db, storeInfo, bin);
|
|
78
|
+
}
|
|
79
|
+
} finally {
|
|
80
|
+
db.close();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
88
83
|
}
|
|
89
84
|
|
|
90
85
|
/**
|
|
@@ -93,50 +88,50 @@ export async function restoreDatabases(dump, bin, { clear = true } = {}) {
|
|
|
93
88
|
* @param {Uint8Array[]} bin
|
|
94
89
|
*/
|
|
95
90
|
async function restoreStore(db, storeInfo, bin) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
const tx = db.transaction(storeInfo.name, 'readwrite');
|
|
92
|
+
const store = tx.objectStore(storeInfo.name);
|
|
93
|
+
const inline = storeInfo.keyPath !== null;
|
|
99
94
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
95
|
+
for (const rec of storeInfo.records) {
|
|
96
|
+
const value = deserializeValue(rec.value, bin);
|
|
97
|
+
if (inline) store.put(value);
|
|
98
|
+
else store.put(value, deserializeValue(rec.key, bin));
|
|
99
|
+
}
|
|
105
100
|
|
|
106
|
-
|
|
101
|
+
await transactionDone(tx);
|
|
107
102
|
}
|
|
108
103
|
|
|
109
104
|
/** @param {string} name */
|
|
110
105
|
function openExisting(name) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const open = indexedDB.open(name);
|
|
108
|
+
open.onsuccess = () => {
|
|
109
|
+
const db = open.result;
|
|
110
|
+
db.onversionchange = () => db.close();
|
|
111
|
+
resolve(db);
|
|
112
|
+
};
|
|
113
|
+
open.onerror = () => reject(open.error);
|
|
114
|
+
});
|
|
120
115
|
}
|
|
121
116
|
|
|
122
117
|
/**
|
|
123
118
|
*
|
|
124
119
|
* @param {any} dbInfo
|
|
125
|
-
* @param {boolean} wasCleared
|
|
120
|
+
* @param {boolean} wasCleared
|
|
126
121
|
* @returns {Promise<IDBDatabase>}
|
|
127
122
|
*/
|
|
128
123
|
async function openForRestore(dbInfo, wasCleared) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
124
|
+
if (wasCleared) {
|
|
125
|
+
return openAndUpgrade(dbInfo.name, dbInfo.version || 1, dbInfo.stores);
|
|
126
|
+
}
|
|
132
127
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
128
|
+
const current = await openExisting(dbInfo.name);
|
|
129
|
+
const needsUpgrade = dbInfo.stores.some((s) => !hasSchema(current, s));
|
|
130
|
+
if (!needsUpgrade) return current;
|
|
136
131
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
132
|
+
const targetVersion = Math.max(current.version + 1, dbInfo.version || 1);
|
|
133
|
+
current.close();
|
|
134
|
+
return openAndUpgrade(dbInfo.name, targetVersion, dbInfo.stores);
|
|
140
135
|
}
|
|
141
136
|
|
|
142
137
|
/**
|
|
@@ -144,12 +139,10 @@ async function openForRestore(dbInfo, wasCleared) {
|
|
|
144
139
|
* @param {any} storeInfo
|
|
145
140
|
*/
|
|
146
141
|
function hasSchema(db, storeInfo) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
.objectStore(storeInfo.name);
|
|
152
|
-
return storeInfo.indexes.every((ix) => store.indexNames.contains(ix.name));
|
|
142
|
+
if (!db.objectStoreNames.contains(storeInfo.name)) return false;
|
|
143
|
+
if (storeInfo.indexes.length === 0) return true;
|
|
144
|
+
const store = db.transaction(storeInfo.name, 'readonly').objectStore(storeInfo.name);
|
|
145
|
+
return storeInfo.indexes.every((ix) => store.indexNames.contains(ix.name));
|
|
153
146
|
}
|
|
154
147
|
|
|
155
148
|
/**
|
|
@@ -159,65 +152,63 @@ function hasSchema(db, storeInfo) {
|
|
|
159
152
|
* @returns {Promise<IDBDatabase>}
|
|
160
153
|
*/
|
|
161
154
|
function openAndUpgrade(name, version, stores) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
// actionable error instead of leaving the restore hung forever.
|
|
188
|
-
open.onblocked = () => reject(blockedError(name));
|
|
189
|
-
});
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
const open = indexedDB.open(name, version);
|
|
157
|
+
open.onupgradeneeded = () => {
|
|
158
|
+
const db = open.result;
|
|
159
|
+
const tx = open.transaction;
|
|
160
|
+
for (const s of stores) {
|
|
161
|
+
const store = db.objectStoreNames.contains(s.name)
|
|
162
|
+
? tx.objectStore(s.name)
|
|
163
|
+
: db.createObjectStore(s.name, { keyPath: s.keyPath, autoIncrement: s.autoIncrement });
|
|
164
|
+
for (const ix of s.indexes) {
|
|
165
|
+
if (!store.indexNames.contains(ix.name)) {
|
|
166
|
+
store.createIndex(ix.name, ix.keyPath, {
|
|
167
|
+
unique: ix.unique,
|
|
168
|
+
multiEntry: ix.multiEntry
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
open.onsuccess = () => resolve(open.result);
|
|
175
|
+
open.onerror = () => reject(open.error);
|
|
176
|
+
// A blocked upgrade never fires success/error on its own — fail fast with an
|
|
177
|
+
// actionable error instead of leaving the restore hung forever.
|
|
178
|
+
open.onblocked = () => reject(blockedError(name));
|
|
179
|
+
});
|
|
190
180
|
}
|
|
181
|
+
|
|
191
182
|
/** @param {string} name */
|
|
192
183
|
function deleteDatabase(name) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
184
|
+
return new Promise((resolve, reject) => {
|
|
185
|
+
const req = indexedDB.deleteDatabase(name);
|
|
186
|
+
req.onsuccess = () => resolve(undefined);
|
|
187
|
+
req.onerror = () => reject(req.error);
|
|
188
|
+
req.onblocked = () => reject(blockedError(name));
|
|
189
|
+
});
|
|
199
190
|
}
|
|
200
191
|
|
|
201
192
|
/** @param {string} name */
|
|
202
193
|
function blockedError(name) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
194
|
+
return new DOMException(
|
|
195
|
+
`IndexedDB "${name}" is blocked by another open connection — close other tabs on this origin and retry`,
|
|
196
|
+
'InvalidStateError'
|
|
197
|
+
);
|
|
207
198
|
}
|
|
208
199
|
|
|
209
200
|
/** @param {IDBRequest} req */
|
|
210
201
|
function request(req) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
req.onsuccess = () => resolve(req.result);
|
|
204
|
+
req.onerror = () => reject(req.error);
|
|
205
|
+
});
|
|
215
206
|
}
|
|
216
207
|
|
|
217
208
|
/** @param {IDBTransaction} tx */
|
|
218
209
|
function transactionDone(tx) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
210
|
+
return new Promise((resolve, reject) => {
|
|
211
|
+
tx.oncomplete = () => resolve(undefined);
|
|
212
|
+
tx.onabort = tx.onerror = () => reject(tx.error);
|
|
213
|
+
});
|
|
223
214
|
}
|