@freqhole/reliquary 0.2.14 → 0.2.15
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/dist/blobs/db.d.ts.map +1 -1
- package/dist/blobs/db.js +84 -30
- package/dist/blobs/db.test.js +38 -0
- package/package.json +1 -1
package/dist/blobs/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/blobs/db.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/blobs/db.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA6I7C,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAcjF;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAS1F;AA4BD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAE5F;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAE5F;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAchF;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAchE;AAQD,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAcrG;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAcxG;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAYrF;AAED,wBAAsB,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBrG;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAcnE;AAaD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,YAAY,CAAC;IAC1C,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3B;mDAC+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;CACnB;AAOD,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,aAAa,CAAC,CA8EtG"}
|
package/dist/blobs/db.js
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
const STORE_NAME = "blobs";
|
|
11
11
|
const REFS_STORE_NAME = "blob_canvas_refs";
|
|
12
12
|
const DB_VERSION = 3;
|
|
13
|
+
// kept in sync with the createIndex calls in onupgradeneeded below - used
|
|
14
|
+
// by openDb's version-fallback path to detect an already-existing database
|
|
15
|
+
// that's missing an index added by a newer db.ts than whatever build last
|
|
16
|
+
// touched it.
|
|
17
|
+
const BLOB_INDEXES = ["sha256", "blake3", "blob_type", "parent_blob_id", "size", "created_at"];
|
|
18
|
+
const REFS_INDEXES = ["blob_id", "canvas_doc_id"];
|
|
13
19
|
function openVersioned(dbName, version) {
|
|
14
20
|
return new Promise((resolve, reject) => {
|
|
15
21
|
const req = indexedDB.open(dbName, version);
|
|
@@ -18,26 +24,26 @@ function openVersioned(dbName, version) {
|
|
|
18
24
|
let store;
|
|
19
25
|
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
20
26
|
store = db.createObjectStore(STORE_NAME, { keyPath: "blob_id" });
|
|
21
|
-
store.createIndex("sha256", "sha256", { unique: false });
|
|
22
|
-
store.createIndex("blake3", "blake3", { unique: false });
|
|
23
|
-
store.createIndex("blob_type", "blob_type", { unique: false });
|
|
24
|
-
store.createIndex("parent_blob_id", "parent_blob_id", { unique: false });
|
|
25
27
|
}
|
|
26
28
|
else {
|
|
27
|
-
// pre-existing store from an earlier DB_VERSION -
|
|
28
|
-
//
|
|
29
|
+
// pre-existing store from an earlier DB_VERSION - or, for an app
|
|
30
|
+
// that used this same store/db name before adopting reliquary at
|
|
31
|
+
// all, a store that never had ANY of these indexes. new indexes
|
|
32
|
+
// can still be added via the in-flight version-change transaction
|
|
29
33
|
// (`req.transaction`), they just can't be added outside
|
|
30
|
-
// onupgradeneeded.
|
|
31
|
-
// never add the index for anyone who already has a v1/v2 database.
|
|
34
|
+
// onupgradeneeded.
|
|
32
35
|
store = req.transaction.objectStore(STORE_NAME);
|
|
33
36
|
}
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
// every index gets the "add if missing" treatment (not just the
|
|
38
|
+
// v3 ones below) - a pre-reliquary store sharing this db/store name
|
|
39
|
+
// would otherwise hit the `else` branch above with none of these
|
|
40
|
+
// indexes present, and every `.index(...)` lookup would throw
|
|
41
|
+
// "the specified index was not found" instead of falling through
|
|
42
|
+
// the resolver chain the way a missing record does.
|
|
43
|
+
for (const name of BLOB_INDEXES) {
|
|
44
|
+
if (!store.indexNames.contains(name)) {
|
|
45
|
+
store.createIndex(name, name, { unique: false });
|
|
46
|
+
}
|
|
41
47
|
}
|
|
42
48
|
if (!db.objectStoreNames.contains(REFS_STORE_NAME)) {
|
|
43
49
|
// tracks which canvas documents currently have a widget
|
|
@@ -46,8 +52,18 @@ function openVersioned(dbName, version) {
|
|
|
46
52
|
// canvas. composite key so re-adding an existing ref is a no-op
|
|
47
53
|
// (put with the same key overwrites, add() would throw).
|
|
48
54
|
const refs = db.createObjectStore(REFS_STORE_NAME, { keyPath: ["blob_id", "canvas_doc_id"] });
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
for (const name of REFS_INDEXES) {
|
|
56
|
+
refs.createIndex(name, name, { unique: false });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// same "add if missing" treatment for the refs store's own indexes.
|
|
61
|
+
const refs = req.transaction.objectStore(REFS_STORE_NAME);
|
|
62
|
+
for (const name of REFS_INDEXES) {
|
|
63
|
+
if (!refs.indexNames.contains(name)) {
|
|
64
|
+
refs.createIndex(name, name, { unique: false });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
51
67
|
}
|
|
52
68
|
};
|
|
53
69
|
req.onsuccess = () => resolve(req.result);
|
|
@@ -64,35 +80,63 @@ function openVersioned(dbName, version) {
|
|
|
64
80
|
// open per operation, matching the "open fresh, close when done" pattern
|
|
65
81
|
// used throughout this module.
|
|
66
82
|
const resolvedVersions = new Map();
|
|
83
|
+
// true only if both stores exist AND every index in BLOB_INDEXES/
|
|
84
|
+
// REFS_INDEXES is present on them. used to detect a database that's
|
|
85
|
+
// already sitting at the version we're about to (re)open with but never
|
|
86
|
+
// actually got every index this file expects - reopening at the exact
|
|
87
|
+
// same version is a no-op in indexedDB (onupgradeneeded only fires when
|
|
88
|
+
// the requested version is HIGHER than the database's current one), so
|
|
89
|
+
// that case has to be caught explicitly rather than left to
|
|
90
|
+
// onupgradeneeded's own "add if missing" logic.
|
|
91
|
+
function hasAllIndexes(db) {
|
|
92
|
+
if (!db.objectStoreNames.contains(STORE_NAME) || !db.objectStoreNames.contains(REFS_STORE_NAME)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const tx = db.transaction([STORE_NAME, REFS_STORE_NAME], "readonly");
|
|
96
|
+
const blobIndexes = tx.objectStore(STORE_NAME).indexNames;
|
|
97
|
+
const refsIndexes = tx.objectStore(REFS_STORE_NAME).indexNames;
|
|
98
|
+
return (BLOB_INDEXES.every((name) => blobIndexes.contains(name)) &&
|
|
99
|
+
REFS_INDEXES.every((name) => refsIndexes.contains(name)));
|
|
100
|
+
}
|
|
67
101
|
async function openDb(dbName) {
|
|
68
102
|
const cachedVersion = resolvedVersions.get(dbName);
|
|
69
103
|
if (cachedVersion !== undefined) {
|
|
70
104
|
return openVersioned(dbName, cachedVersion);
|
|
71
105
|
}
|
|
106
|
+
let db;
|
|
107
|
+
let currentVersion;
|
|
72
108
|
try {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return db;
|
|
109
|
+
db = await openVersioned(dbName, DB_VERSION);
|
|
110
|
+
currentVersion = DB_VERSION;
|
|
76
111
|
}
|
|
77
112
|
catch (err) {
|
|
78
113
|
if (!(err instanceof DOMException) || err.name !== "VersionError")
|
|
79
114
|
throw err;
|
|
80
|
-
// the database already exists at some higher version
|
|
81
|
-
// version-less to discover what that is, then reopen at that
|
|
82
|
-
// version
|
|
83
|
-
// never had one).
|
|
115
|
+
// the database already exists at some higher version than DB_VERSION -
|
|
116
|
+
// open it version-less to discover what that is, then reopen at that
|
|
117
|
+
// exact version.
|
|
84
118
|
const probe = await new Promise((resolve, reject) => {
|
|
85
119
|
const req = indexedDB.open(dbName);
|
|
86
120
|
req.onsuccess = () => resolve(req.result);
|
|
87
121
|
req.onerror = () => reject(req.error);
|
|
88
122
|
});
|
|
89
|
-
|
|
90
|
-
const hasStore = probe.objectStoreNames.contains(STORE_NAME) && probe.objectStoreNames.contains(REFS_STORE_NAME);
|
|
123
|
+
currentVersion = probe.version;
|
|
91
124
|
probe.close();
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
125
|
+
db = await openVersioned(dbName, currentVersion);
|
|
126
|
+
}
|
|
127
|
+
// a database can reach `currentVersion` under an earlier build that
|
|
128
|
+
// didn't yet create every index this file expects - opening at that
|
|
129
|
+
// exact same version again (either branch above) never fires
|
|
130
|
+
// onupgradeneeded, so the "add index if missing" repair in
|
|
131
|
+
// onupgradeneeded never gets a chance to run. detect that case here and
|
|
132
|
+
// force a real upgrade by reopening one version higher.
|
|
133
|
+
if (!hasAllIndexes(db)) {
|
|
134
|
+
db.close();
|
|
135
|
+
currentVersion += 1;
|
|
136
|
+
db = await openVersioned(dbName, currentVersion);
|
|
95
137
|
}
|
|
138
|
+
resolvedVersions.set(dbName, currentVersion);
|
|
139
|
+
return db;
|
|
96
140
|
}
|
|
97
141
|
export async function putRecord(dbName, record) {
|
|
98
142
|
const db = await openDb(dbName);
|
|
@@ -125,7 +169,17 @@ async function getByIndex(dbName, indexName, value) {
|
|
|
125
169
|
const db = await openDb(dbName);
|
|
126
170
|
return new Promise((resolve, reject) => {
|
|
127
171
|
const tx = db.transaction(STORE_NAME, "readonly");
|
|
128
|
-
const
|
|
172
|
+
const store = tx.objectStore(STORE_NAME);
|
|
173
|
+
// belt-and-suspenders: openDb's upgrade path should always ensure this
|
|
174
|
+
// index exists, but if it's somehow still missing, fall through the
|
|
175
|
+
// resolver chain (return null) rather than throwing - matches the
|
|
176
|
+
// "missing record" behavior every other step of resolveBlob already has.
|
|
177
|
+
if (!store.indexNames.contains(indexName)) {
|
|
178
|
+
tx.oncomplete = () => db.close();
|
|
179
|
+
resolve(null);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const req = store.index(indexName).get(value);
|
|
129
183
|
req.onsuccess = () => resolve(req.result ?? null);
|
|
130
184
|
req.onerror = () => reject(req.error);
|
|
131
185
|
tx.oncomplete = () => db.close();
|
package/dist/blobs/db.test.js
CHANGED
|
@@ -148,6 +148,44 @@ describe("pre-existing database at a higher version", () => {
|
|
|
148
148
|
expect(await getRecord(dbName, record.blob_id)).toEqual(record);
|
|
149
149
|
expect(await getRecordByBlake3(dbName, record.blake3)).toEqual(record);
|
|
150
150
|
});
|
|
151
|
+
it("repairs a missing index even when both stores already exist at DB_VERSION", async () => {
|
|
152
|
+
// reproduces a real-world case: a database that reached DB_VERSION
|
|
153
|
+
// under an earlier build of this module - one that created both
|
|
154
|
+
// stores but not yet the "sha256" index added later. since both
|
|
155
|
+
// stores already exist and the version already matches, naively
|
|
156
|
+
// reusing that version would never fire onupgradeneeded again,
|
|
157
|
+
// permanently leaving the missing index unrepaired - getRecordBySha256
|
|
158
|
+
// would then always report a miss instead of finding the record
|
|
159
|
+
// (getByIndex falls through gracefully rather than throwing, so a
|
|
160
|
+
// silent "never repaired" regression wouldn't otherwise be caught).
|
|
161
|
+
const dbName = "already-at-version-missing-index-db";
|
|
162
|
+
await new Promise((resolve, reject) => {
|
|
163
|
+
const req = indexedDB.open(dbName, 3);
|
|
164
|
+
req.onupgradeneeded = () => {
|
|
165
|
+
const db = req.result;
|
|
166
|
+
const store = db.createObjectStore("blobs", { keyPath: "blob_id" });
|
|
167
|
+
// deliberately missing: "sha256" index.
|
|
168
|
+
store.createIndex("blake3", "blake3", { unique: false });
|
|
169
|
+
store.createIndex("blob_type", "blob_type", { unique: false });
|
|
170
|
+
store.createIndex("parent_blob_id", "parent_blob_id", { unique: false });
|
|
171
|
+
store.createIndex("size", "size", { unique: false });
|
|
172
|
+
store.createIndex("created_at", "created_at", { unique: false });
|
|
173
|
+
const refs = db.createObjectStore("blob_canvas_refs", {
|
|
174
|
+
keyPath: ["blob_id", "canvas_doc_id"],
|
|
175
|
+
});
|
|
176
|
+
refs.createIndex("blob_id", "blob_id", { unique: false });
|
|
177
|
+
refs.createIndex("canvas_doc_id", "canvas_doc_id", { unique: false });
|
|
178
|
+
};
|
|
179
|
+
req.onsuccess = () => {
|
|
180
|
+
req.result.close();
|
|
181
|
+
resolve();
|
|
182
|
+
};
|
|
183
|
+
req.onerror = () => reject(req.error);
|
|
184
|
+
});
|
|
185
|
+
const record = makeRecord();
|
|
186
|
+
await putRecord(dbName, record);
|
|
187
|
+
expect(await getRecordBySha256(dbName, record.sha256)).toEqual(record);
|
|
188
|
+
});
|
|
151
189
|
});
|
|
152
190
|
describe("listBlobs", () => {
|
|
153
191
|
async function seedThree() {
|