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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "origin-snapshot",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Export and restore a browser origin's localStorage, sessionStorage, IndexedDB, and OPFS as a single gzip-compressed snapshot file.",
5
5
  "keywords": [
6
6
  "localstorage",
@@ -1,4 +1,4 @@
1
- import { serializeValue, deserializeValue } from "../serialize.js";
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
- 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;
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
- 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 {
49
- name: iname,
50
- keyPath: ix.keyPath,
51
- unique: ix.unique,
52
- multiEntry: ix.multiEntry,
53
- };
54
- }),
55
- };
56
-
57
- const outOfLine = store.keyPath === null;
58
- const values = await request(store.getAll());
59
- const keys = outOfLine ? await request(store.getAllKeys()) : null;
60
-
61
- const records = values.map((value, i) => {
62
- const rec = { value: serializeValue(value, bin) };
63
- if (outOfLine) rec.key = serializeValue(keys[i], bin);
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
- for (const dbInfo of dump) {
78
- if (clear) await deleteDatabase(dbInfo.name);
79
- const db = await openForRestore(dbInfo, clear);
80
- try {
81
- for (const storeInfo of dbInfo.stores) {
82
- await restoreStore(db, storeInfo, bin);
83
- }
84
- } finally {
85
- db.close();
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
- const tx = db.transaction(storeInfo.name, "readwrite");
97
- const store = tx.objectStore(storeInfo.name);
98
- const inline = storeInfo.keyPath !== null;
91
+ const tx = db.transaction(storeInfo.name, 'readwrite');
92
+ const store = tx.objectStore(storeInfo.name);
93
+ const inline = storeInfo.keyPath !== null;
99
94
 
100
- for (const rec of storeInfo.records) {
101
- const value = deserializeValue(rec.value, bin);
102
- if (inline) store.put(value);
103
- else store.put(value, deserializeValue(rec.key, bin));
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
- await transactionDone(tx);
101
+ await transactionDone(tx);
107
102
  }
108
103
 
109
104
  /** @param {string} name */
110
105
  function openExisting(name) {
111
- return new Promise((resolve, reject) => {
112
- const open = indexedDB.open(name);
113
- open.onsuccess = () => {
114
- const db = open.result;
115
- db.onversionchange = () => db.close();
116
- resolve(db);
117
- };
118
- open.onerror = () => reject(open.error);
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
- if (wasCleared) {
130
- return openAndUpgrade(dbInfo.name, dbInfo.version || 1, dbInfo.stores);
131
- }
124
+ if (wasCleared) {
125
+ return openAndUpgrade(dbInfo.name, dbInfo.version || 1, dbInfo.stores);
126
+ }
132
127
 
133
- const current = await openExisting(dbInfo.name);
134
- const needsUpgrade = dbInfo.stores.some((s) => !hasSchema(current, s));
135
- if (!needsUpgrade) return current;
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
- const targetVersion = Math.max(current.version + 1, dbInfo.version || 1);
138
- current.close();
139
- return openAndUpgrade(dbInfo.name, targetVersion, dbInfo.stores);
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
- if (!db.objectStoreNames.contains(storeInfo.name)) return false;
148
- if (storeInfo.indexes.length === 0) return true;
149
- const store = db
150
- .transaction(storeInfo.name, "readonly")
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
- return new Promise((resolve, reject) => {
163
- const open = indexedDB.open(name, version);
164
- open.onupgradeneeded = () => {
165
- const db = open.result;
166
- const tx = open.transaction;
167
- for (const s of stores) {
168
- const store = db.objectStoreNames.contains(s.name)
169
- ? tx.objectStore(s.name)
170
- : db.createObjectStore(s.name, {
171
- keyPath: s.keyPath,
172
- autoIncrement: s.autoIncrement,
173
- });
174
- for (const ix of s.indexes) {
175
- if (!store.indexNames.contains(ix.name)) {
176
- store.createIndex(ix.name, ix.keyPath, {
177
- unique: ix.unique,
178
- multiEntry: ix.multiEntry,
179
- });
180
- }
181
- }
182
- }
183
- };
184
- open.onsuccess = () => resolve(open.result);
185
- open.onerror = () => reject(open.error);
186
- // A blocked upgrade never fires success/error on its own — fail fast with an
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
- return new Promise((resolve, reject) => {
194
- const req = indexedDB.deleteDatabase(name);
195
- req.onsuccess = () => resolve(undefined);
196
- req.onerror = () => reject(req.error);
197
- req.onblocked = () => reject(blockedError(name));
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
- return new DOMException(
204
- `IndexedDB "${name}" is blocked by another open connection — close other tabs on this origin and retry`,
205
- "InvalidStateError",
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
- return new Promise((resolve, reject) => {
212
- req.onsuccess = () => resolve(req.result);
213
- req.onerror = () => reject(req.error);
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
- return new Promise((resolve, reject) => {
220
- tx.oncomplete = () => resolve(undefined);
221
- tx.onabort = tx.onerror = () => reject(tx.error);
222
- });
210
+ return new Promise((resolve, reject) => {
211
+ tx.oncomplete = () => resolve(undefined);
212
+ tx.onabort = tx.onerror = () => reject(tx.error);
213
+ });
223
214
  }