@offlinejs/storage-indexeddb 0.1.0
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/index.d.ts +50 -0
- package/dist/index.js +294 -0
- package/dist/index.js.map +1 -0
- package/package.json +32 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { IndexableStorageAdapter, EntityRecord, QueryOptions, IndexDefinition, TransactionStore, StorageMigration } from '@offlinejs/types';
|
|
2
|
+
|
|
3
|
+
interface IndexedDBStorageOptions {
|
|
4
|
+
databaseName?: string;
|
|
5
|
+
version?: number;
|
|
6
|
+
}
|
|
7
|
+
declare class IndexedDBStorageAdapter implements IndexableStorageAdapter {
|
|
8
|
+
readonly name = "indexeddb";
|
|
9
|
+
readonly contractVersion = 1;
|
|
10
|
+
readonly capabilities: {
|
|
11
|
+
readonly indexes: true;
|
|
12
|
+
readonly migrations: true;
|
|
13
|
+
readonly persistence: "durable";
|
|
14
|
+
readonly transactions: "best-effort";
|
|
15
|
+
};
|
|
16
|
+
private readonly databaseName;
|
|
17
|
+
private readonly version;
|
|
18
|
+
private databasePromise?;
|
|
19
|
+
constructor(options?: IndexedDBStorageOptions);
|
|
20
|
+
get<TRecord extends EntityRecord>(collection: string, id: string): Promise<TRecord | null>;
|
|
21
|
+
set<TRecord extends EntityRecord>(collection: string, value: TRecord): Promise<void>;
|
|
22
|
+
delete(collection: string, id: string): Promise<void>;
|
|
23
|
+
find<TRecord extends EntityRecord>(collection: string, query?: QueryOptions<TRecord>): Promise<TRecord[]>;
|
|
24
|
+
clear(collection?: string): Promise<void>;
|
|
25
|
+
createIndex<TRecord extends EntityRecord>(definition: IndexDefinition<TRecord>): Promise<void>;
|
|
26
|
+
dropIndex(collection: string, name: string): Promise<void>;
|
|
27
|
+
listIndexes(collection?: string): Promise<IndexDefinition[]>;
|
|
28
|
+
transaction<TValue>(_scope: string[], run: (store: TransactionStore) => Promise<TValue>): Promise<TValue>;
|
|
29
|
+
migrate(migrations: StorageMigration[]): Promise<void>;
|
|
30
|
+
private findViaIndex;
|
|
31
|
+
private assertUniqueIndexes;
|
|
32
|
+
private writeIndexEntries;
|
|
33
|
+
private removeIndexEntries;
|
|
34
|
+
private getCollectionRows;
|
|
35
|
+
private getEntriesForCollection;
|
|
36
|
+
private store;
|
|
37
|
+
private indexStore;
|
|
38
|
+
private entryStore;
|
|
39
|
+
private entryLookup;
|
|
40
|
+
private objectStoreProxy;
|
|
41
|
+
private database;
|
|
42
|
+
private request;
|
|
43
|
+
private key;
|
|
44
|
+
private indexKey;
|
|
45
|
+
private lookupKey;
|
|
46
|
+
private entryId;
|
|
47
|
+
}
|
|
48
|
+
declare const createIndexedDBStorage: (options?: IndexedDBStorageOptions) => IndexedDBStorageAdapter;
|
|
49
|
+
|
|
50
|
+
export { IndexedDBStorageAdapter, type IndexedDBStorageOptions, createIndexedDBStorage };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { STORAGE_ADAPTER_CONTRACT_VERSION } from '@offlinejs/types';
|
|
2
|
+
import { clone, applyQuery, findMatchingIndex, getEqualityFilterLookups, serializeCompoundIndexValue, readIndexFields } from '@offlinejs/utils';
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
var STORE_NAME = "records";
|
|
6
|
+
var INDEX_STORE_NAME = "indexes";
|
|
7
|
+
var INDEX_ENTRIES_STORE = "index_entries";
|
|
8
|
+
var COLLECTION_INDEX = "collection";
|
|
9
|
+
var LOOKUP_INDEX = "lookup";
|
|
10
|
+
var IndexedDBStorageAdapter = class {
|
|
11
|
+
name = "indexeddb";
|
|
12
|
+
contractVersion = STORAGE_ADAPTER_CONTRACT_VERSION;
|
|
13
|
+
capabilities = {
|
|
14
|
+
indexes: true,
|
|
15
|
+
migrations: true,
|
|
16
|
+
persistence: "durable",
|
|
17
|
+
transactions: "best-effort"
|
|
18
|
+
};
|
|
19
|
+
databaseName;
|
|
20
|
+
version;
|
|
21
|
+
databasePromise;
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.databaseName = options.databaseName ?? "offlinejs";
|
|
24
|
+
this.version = options.version ?? 2;
|
|
25
|
+
}
|
|
26
|
+
async get(collection, id) {
|
|
27
|
+
const row = await this.request(
|
|
28
|
+
this.store("readonly").get(this.key(collection, id))
|
|
29
|
+
);
|
|
30
|
+
return row ? clone(row.value) : null;
|
|
31
|
+
}
|
|
32
|
+
async set(collection, value) {
|
|
33
|
+
const previous = await this.get(collection, value.id);
|
|
34
|
+
if (previous) {
|
|
35
|
+
await this.removeIndexEntries(collection, previous);
|
|
36
|
+
}
|
|
37
|
+
await this.assertUniqueIndexes(collection, value, previous?.id);
|
|
38
|
+
const row = {
|
|
39
|
+
collection,
|
|
40
|
+
id: value.id,
|
|
41
|
+
key: this.key(collection, value.id),
|
|
42
|
+
value: clone(value)
|
|
43
|
+
};
|
|
44
|
+
await this.request(this.store("readwrite").put(row));
|
|
45
|
+
await this.writeIndexEntries(collection, value);
|
|
46
|
+
}
|
|
47
|
+
async delete(collection, id) {
|
|
48
|
+
const previous = await this.get(collection, id);
|
|
49
|
+
if (previous) {
|
|
50
|
+
await this.removeIndexEntries(collection, previous);
|
|
51
|
+
}
|
|
52
|
+
await this.request(this.store("readwrite").delete(this.key(collection, id)));
|
|
53
|
+
}
|
|
54
|
+
async find(collection, query) {
|
|
55
|
+
const indexed = await this.findViaIndex(collection, query);
|
|
56
|
+
const records = indexed ?? (await this.getCollectionRows(collection)).map((row) => clone(row.value));
|
|
57
|
+
return applyQuery(records, query);
|
|
58
|
+
}
|
|
59
|
+
async clear(collection) {
|
|
60
|
+
if (!collection) {
|
|
61
|
+
await this.request(this.store("readwrite").clear());
|
|
62
|
+
await this.request(this.indexStore("readwrite").clear());
|
|
63
|
+
await this.request(this.entryStore("readwrite").clear());
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const rows = await this.getCollectionRows(collection);
|
|
67
|
+
const store = this.store("readwrite");
|
|
68
|
+
await Promise.all(rows.map((row) => this.request(store.delete(row.key))));
|
|
69
|
+
await Promise.all(
|
|
70
|
+
(await this.listIndexes(collection)).map(
|
|
71
|
+
(index) => this.request(this.indexStore("readwrite").delete(this.indexKey(collection, index.name)))
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
const entries = await this.getEntriesForCollection(collection);
|
|
75
|
+
await Promise.all(
|
|
76
|
+
entries.map((entry) => this.request(this.entryStore("readwrite").delete(entry.id)))
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
async createIndex(definition) {
|
|
80
|
+
const normalized = clone(definition);
|
|
81
|
+
await this.request(
|
|
82
|
+
this.indexStore("readwrite").put({
|
|
83
|
+
...normalized,
|
|
84
|
+
id: this.indexKey(definition.collection, definition.name)
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
const rows = await this.getCollectionRows(definition.collection);
|
|
88
|
+
for (const row of rows) {
|
|
89
|
+
await this.assertUniqueIndexes(definition.collection, row.value);
|
|
90
|
+
await this.writeIndexEntries(definition.collection, row.value, [normalized]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async dropIndex(collection, name) {
|
|
94
|
+
await this.request(this.indexStore("readwrite").delete(this.indexKey(collection, name)));
|
|
95
|
+
const entries = await this.getEntriesForCollection(collection);
|
|
96
|
+
await Promise.all(
|
|
97
|
+
entries.filter((entry) => entry.indexName === name).map((entry) => this.request(this.entryStore("readwrite").delete(entry.id)))
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
async listIndexes(collection) {
|
|
101
|
+
const rows = await this.request(
|
|
102
|
+
this.indexStore("readonly").getAll()
|
|
103
|
+
);
|
|
104
|
+
return rows.filter((row) => !collection || row.collection === collection).map((row) => {
|
|
105
|
+
const definition = { ...row };
|
|
106
|
+
delete definition.id;
|
|
107
|
+
return clone(definition);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async transaction(_scope, run) {
|
|
111
|
+
return run(this);
|
|
112
|
+
}
|
|
113
|
+
async migrate(migrations) {
|
|
114
|
+
const applied = new Set(
|
|
115
|
+
(await this.find("__migrations")).map((record) => record.id)
|
|
116
|
+
);
|
|
117
|
+
for (const migration of migrations) {
|
|
118
|
+
if (applied.has(migration.name)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
await migration.up(this);
|
|
122
|
+
await this.set("__migrations", { id: migration.name, appliedAt: Date.now() });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async findViaIndex(collection, query) {
|
|
126
|
+
const match = findMatchingIndex(
|
|
127
|
+
await this.listIndexes(collection),
|
|
128
|
+
getEqualityFilterLookups(query?.filters)
|
|
129
|
+
);
|
|
130
|
+
if (!match) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const lookup = this.lookupKey(
|
|
134
|
+
collection,
|
|
135
|
+
match.index.name,
|
|
136
|
+
serializeCompoundIndexValue(match.values)
|
|
137
|
+
);
|
|
138
|
+
const entries = await this.request(
|
|
139
|
+
this.entryLookup("readonly").getAll(lookup)
|
|
140
|
+
);
|
|
141
|
+
const records = [];
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
const record = await this.get(collection, entry.recordId);
|
|
144
|
+
if (record) {
|
|
145
|
+
records.push(record);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return records;
|
|
149
|
+
}
|
|
150
|
+
async assertUniqueIndexes(collection, record, ignoreId) {
|
|
151
|
+
for (const definition of await this.listIndexes(collection)) {
|
|
152
|
+
if (!definition.unique) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const lookup = this.lookupKey(
|
|
156
|
+
collection,
|
|
157
|
+
definition.name,
|
|
158
|
+
serializeCompoundIndexValue(readIndexFields(record, definition.fields))
|
|
159
|
+
);
|
|
160
|
+
const entries = await this.request(
|
|
161
|
+
this.entryLookup("readonly").getAll(lookup)
|
|
162
|
+
);
|
|
163
|
+
if (entries.some((entry) => entry.recordId !== record.id && entry.recordId !== ignoreId)) {
|
|
164
|
+
throw new Error(`Unique index "${definition.name}" violated for ${collection}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async writeIndexEntries(collection, record, definitions) {
|
|
169
|
+
const indexes = definitions ?? await this.listIndexes(collection);
|
|
170
|
+
for (const definition of indexes) {
|
|
171
|
+
const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));
|
|
172
|
+
const entry = {
|
|
173
|
+
collection,
|
|
174
|
+
id: this.entryId(collection, definition.name, valueKey, record.id),
|
|
175
|
+
indexName: definition.name,
|
|
176
|
+
lookup: this.lookupKey(collection, definition.name, valueKey),
|
|
177
|
+
recordId: record.id,
|
|
178
|
+
valueKey
|
|
179
|
+
};
|
|
180
|
+
await this.request(this.entryStore("readwrite").put(entry));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async removeIndexEntries(collection, record) {
|
|
184
|
+
for (const definition of await this.listIndexes(collection)) {
|
|
185
|
+
const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));
|
|
186
|
+
await this.request(
|
|
187
|
+
this.entryStore("readwrite").delete(
|
|
188
|
+
this.entryId(collection, definition.name, valueKey, record.id)
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async getCollectionRows(collection) {
|
|
194
|
+
const database = await this.database();
|
|
195
|
+
const transaction = database.transaction(STORE_NAME, "readonly");
|
|
196
|
+
const index = transaction.objectStore(STORE_NAME).index(COLLECTION_INDEX);
|
|
197
|
+
return this.request(index.getAll(collection));
|
|
198
|
+
}
|
|
199
|
+
async getEntriesForCollection(collection) {
|
|
200
|
+
const all = await this.request(this.entryStore("readonly").getAll());
|
|
201
|
+
return all.filter((entry) => entry.collection === collection);
|
|
202
|
+
}
|
|
203
|
+
store(mode) {
|
|
204
|
+
return this.objectStoreProxy(STORE_NAME, mode);
|
|
205
|
+
}
|
|
206
|
+
indexStore(mode) {
|
|
207
|
+
return this.objectStoreProxy(INDEX_STORE_NAME, mode);
|
|
208
|
+
}
|
|
209
|
+
entryStore(mode) {
|
|
210
|
+
return this.objectStoreProxy(INDEX_ENTRIES_STORE, mode);
|
|
211
|
+
}
|
|
212
|
+
entryLookup(mode) {
|
|
213
|
+
const databasePromise = this.database();
|
|
214
|
+
return {
|
|
215
|
+
getAll: (lookup) => databasePromise.then(
|
|
216
|
+
(database) => database.transaction(INDEX_ENTRIES_STORE, mode).objectStore(INDEX_ENTRIES_STORE).index(LOOKUP_INDEX).getAll(lookup)
|
|
217
|
+
)
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
objectStoreProxy(storeName, mode) {
|
|
221
|
+
const databasePromise = this.database();
|
|
222
|
+
return {
|
|
223
|
+
get: (key) => databasePromise.then(
|
|
224
|
+
(database) => database.transaction(storeName, mode).objectStore(storeName).get(key)
|
|
225
|
+
),
|
|
226
|
+
put: (value) => databasePromise.then(
|
|
227
|
+
(database) => database.transaction(storeName, mode).objectStore(storeName).put(value)
|
|
228
|
+
),
|
|
229
|
+
delete: (key) => databasePromise.then(
|
|
230
|
+
(database) => database.transaction(storeName, mode).objectStore(storeName).delete(key)
|
|
231
|
+
),
|
|
232
|
+
clear: () => databasePromise.then(
|
|
233
|
+
(database) => database.transaction(storeName, mode).objectStore(storeName).clear()
|
|
234
|
+
),
|
|
235
|
+
getAll: () => databasePromise.then(
|
|
236
|
+
(database) => database.transaction(storeName, mode).objectStore(storeName).getAll()
|
|
237
|
+
)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
async database() {
|
|
241
|
+
if (this.databasePromise) {
|
|
242
|
+
return this.databasePromise;
|
|
243
|
+
}
|
|
244
|
+
this.databasePromise = new Promise((resolve, reject) => {
|
|
245
|
+
if (!globalThis.indexedDB) {
|
|
246
|
+
reject(new Error("IndexedDB is not available in this runtime"));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const request = globalThis.indexedDB.open(this.databaseName, this.version);
|
|
250
|
+
request.onupgradeneeded = () => {
|
|
251
|
+
const database = request.result;
|
|
252
|
+
if (!database.objectStoreNames.contains(STORE_NAME)) {
|
|
253
|
+
const store = database.createObjectStore(STORE_NAME, { keyPath: "key" });
|
|
254
|
+
store.createIndex(COLLECTION_INDEX, COLLECTION_INDEX, { unique: false });
|
|
255
|
+
}
|
|
256
|
+
if (!database.objectStoreNames.contains(INDEX_STORE_NAME)) {
|
|
257
|
+
database.createObjectStore(INDEX_STORE_NAME, { keyPath: "id" });
|
|
258
|
+
}
|
|
259
|
+
if (!database.objectStoreNames.contains(INDEX_ENTRIES_STORE)) {
|
|
260
|
+
const entries = database.createObjectStore(INDEX_ENTRIES_STORE, { keyPath: "id" });
|
|
261
|
+
entries.createIndex(LOOKUP_INDEX, LOOKUP_INDEX, { unique: false });
|
|
262
|
+
entries.createIndex(COLLECTION_INDEX, COLLECTION_INDEX, { unique: false });
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
request.onsuccess = () => resolve(request.result);
|
|
266
|
+
request.onerror = () => reject(request.error ?? new Error("Failed to open IndexedDB"));
|
|
267
|
+
});
|
|
268
|
+
return this.databasePromise;
|
|
269
|
+
}
|
|
270
|
+
async request(requestOrPromise) {
|
|
271
|
+
const request = await requestOrPromise;
|
|
272
|
+
return new Promise((resolve, reject) => {
|
|
273
|
+
request.onsuccess = () => resolve(request.result);
|
|
274
|
+
request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed"));
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
key(collection, id) {
|
|
278
|
+
return `${collection}:${id}`;
|
|
279
|
+
}
|
|
280
|
+
indexKey(collection, name) {
|
|
281
|
+
return `${collection}:${name}`;
|
|
282
|
+
}
|
|
283
|
+
lookupKey(collection, indexName, valueKey) {
|
|
284
|
+
return `${collection}:${indexName}:${valueKey}`;
|
|
285
|
+
}
|
|
286
|
+
entryId(collection, indexName, valueKey, recordId) {
|
|
287
|
+
return `${collection}:${indexName}:${valueKey}:${recordId}`;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var createIndexedDBStorage = (options) => new IndexedDBStorageAdapter(options);
|
|
291
|
+
|
|
292
|
+
export { IndexedDBStorageAdapter, createIndexedDBStorage };
|
|
293
|
+
//# sourceMappingURL=index.js.map
|
|
294
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n STORAGE_ADAPTER_CONTRACT_VERSION,\n type EntityRecord,\n type IndexDefinition,\n type IndexableStorageAdapter,\n type QueryOptions,\n type StorageMigration,\n type TransactionStore\n} from \"@offlinejs/types\";\nimport {\n applyQuery,\n clone,\n findMatchingIndex,\n getEqualityFilterLookups,\n readIndexFields,\n serializeCompoundIndexValue\n} from \"@offlinejs/utils\";\n\ninterface IndexedDBRow {\n collection: string;\n id: string;\n key: string;\n value: EntityRecord;\n}\n\ninterface IndexEntryRow {\n collection: string;\n id: string;\n indexName: string;\n lookup: string;\n recordId: string;\n valueKey: string;\n}\n\nexport interface IndexedDBStorageOptions {\n databaseName?: string;\n version?: number;\n}\n\nconst STORE_NAME = \"records\";\nconst INDEX_STORE_NAME = \"indexes\";\nconst INDEX_ENTRIES_STORE = \"index_entries\";\nconst COLLECTION_INDEX = \"collection\";\nconst LOOKUP_INDEX = \"lookup\";\n\nexport class IndexedDBStorageAdapter implements IndexableStorageAdapter {\n readonly name = \"indexeddb\";\n readonly contractVersion = STORAGE_ADAPTER_CONTRACT_VERSION;\n readonly capabilities = {\n indexes: true,\n migrations: true,\n persistence: \"durable\",\n transactions: \"best-effort\"\n } as const;\n\n private readonly databaseName: string;\n private readonly version: number;\n private databasePromise?: Promise<IDBDatabase>;\n\n constructor(options: IndexedDBStorageOptions = {}) {\n this.databaseName = options.databaseName ?? \"offlinejs\";\n this.version = options.version ?? 2;\n }\n\n async get<TRecord extends EntityRecord>(collection: string, id: string): Promise<TRecord | null> {\n const row = await this.request<IndexedDBRow | undefined>(\n this.store(\"readonly\").get(this.key(collection, id))\n );\n\n return row ? clone(row.value as TRecord) : null;\n }\n\n async set<TRecord extends EntityRecord>(collection: string, value: TRecord): Promise<void> {\n const previous = await this.get(collection, value.id);\n if (previous) {\n await this.removeIndexEntries(collection, previous);\n }\n\n await this.assertUniqueIndexes(collection, value, previous?.id);\n\n const row: IndexedDBRow = {\n collection,\n id: value.id,\n key: this.key(collection, value.id),\n value: clone(value)\n };\n\n await this.request(this.store(\"readwrite\").put(row));\n await this.writeIndexEntries(collection, value);\n }\n\n async delete(collection: string, id: string): Promise<void> {\n const previous = await this.get(collection, id);\n if (previous) {\n await this.removeIndexEntries(collection, previous);\n }\n await this.request(this.store(\"readwrite\").delete(this.key(collection, id)));\n }\n\n async find<TRecord extends EntityRecord>(\n collection: string,\n query?: QueryOptions<TRecord>\n ): Promise<TRecord[]> {\n const indexed = await this.findViaIndex<TRecord>(collection, query);\n const records =\n indexed ??\n (await this.getCollectionRows(collection)).map((row) => clone(row.value as TRecord));\n\n return applyQuery(records, query);\n }\n\n async clear(collection?: string): Promise<void> {\n if (!collection) {\n await this.request(this.store(\"readwrite\").clear());\n await this.request(this.indexStore(\"readwrite\").clear());\n await this.request(this.entryStore(\"readwrite\").clear());\n return;\n }\n\n const rows = await this.getCollectionRows(collection);\n const store = this.store(\"readwrite\");\n\n await Promise.all(rows.map((row) => this.request(store.delete(row.key))));\n await Promise.all(\n (await this.listIndexes(collection)).map((index) =>\n this.request(this.indexStore(\"readwrite\").delete(this.indexKey(collection, index.name)))\n )\n );\n\n const entries = await this.getEntriesForCollection(collection);\n await Promise.all(\n entries.map((entry) => this.request(this.entryStore(\"readwrite\").delete(entry.id)))\n );\n }\n\n async createIndex<TRecord extends EntityRecord>(\n definition: IndexDefinition<TRecord>\n ): Promise<void> {\n const normalized = clone(definition as IndexDefinition);\n await this.request(\n this.indexStore(\"readwrite\").put({\n ...normalized,\n id: this.indexKey(definition.collection, definition.name)\n })\n );\n\n const rows = await this.getCollectionRows(definition.collection);\n for (const row of rows) {\n await this.assertUniqueIndexes(definition.collection, row.value);\n await this.writeIndexEntries(definition.collection, row.value, [normalized]);\n }\n }\n\n async dropIndex(collection: string, name: string): Promise<void> {\n await this.request(this.indexStore(\"readwrite\").delete(this.indexKey(collection, name)));\n const entries = await this.getEntriesForCollection(collection);\n await Promise.all(\n entries\n .filter((entry) => entry.indexName === name)\n .map((entry) => this.request(this.entryStore(\"readwrite\").delete(entry.id)))\n );\n }\n\n async listIndexes(collection?: string): Promise<IndexDefinition[]> {\n const rows = await this.request<Array<IndexDefinition & { id: string }>>(\n this.indexStore(\"readonly\").getAll()\n );\n\n return rows\n .filter((row) => !collection || row.collection === collection)\n .map((row) => {\n const definition = { ...row } as IndexDefinition & { id?: string };\n delete definition.id;\n return clone(definition);\n });\n }\n\n async transaction<TValue>(\n _scope: string[],\n run: (store: TransactionStore) => Promise<TValue>\n ): Promise<TValue> {\n return run(this);\n }\n\n async migrate(migrations: StorageMigration[]): Promise<void> {\n const applied = new Set(\n (await this.find<EntityRecord>(\"__migrations\")).map((record) => record.id)\n );\n\n for (const migration of migrations) {\n if (applied.has(migration.name)) {\n continue;\n }\n\n await migration.up(this);\n await this.set(\"__migrations\", { id: migration.name, appliedAt: Date.now() });\n }\n }\n\n private async findViaIndex<TRecord extends EntityRecord>(\n collection: string,\n query?: QueryOptions<TRecord>\n ): Promise<TRecord[] | null> {\n const match = findMatchingIndex(\n await this.listIndexes(collection),\n getEqualityFilterLookups(query?.filters)\n );\n\n if (!match) {\n return null;\n }\n\n const lookup = this.lookupKey(\n collection,\n match.index.name,\n serializeCompoundIndexValue(match.values)\n );\n const entries = await this.request<IndexEntryRow[]>(\n this.entryLookup(\"readonly\").getAll(lookup)\n );\n const records: TRecord[] = [];\n\n for (const entry of entries) {\n const record = await this.get<TRecord>(collection, entry.recordId);\n if (record) {\n records.push(record);\n }\n }\n\n return records;\n }\n\n private async assertUniqueIndexes(\n collection: string,\n record: EntityRecord,\n ignoreId?: string\n ): Promise<void> {\n for (const definition of await this.listIndexes(collection)) {\n if (!definition.unique) {\n continue;\n }\n\n const lookup = this.lookupKey(\n collection,\n definition.name,\n serializeCompoundIndexValue(readIndexFields(record, definition.fields))\n );\n const entries = await this.request<IndexEntryRow[]>(\n this.entryLookup(\"readonly\").getAll(lookup)\n );\n\n if (entries.some((entry) => entry.recordId !== record.id && entry.recordId !== ignoreId)) {\n throw new Error(`Unique index \"${definition.name}\" violated for ${collection}`);\n }\n }\n }\n\n private async writeIndexEntries(\n collection: string,\n record: EntityRecord,\n definitions?: IndexDefinition[]\n ): Promise<void> {\n const indexes = definitions ?? (await this.listIndexes(collection));\n\n for (const definition of indexes) {\n const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));\n const entry: IndexEntryRow = {\n collection,\n id: this.entryId(collection, definition.name, valueKey, record.id),\n indexName: definition.name,\n lookup: this.lookupKey(collection, definition.name, valueKey),\n recordId: record.id,\n valueKey\n };\n await this.request(this.entryStore(\"readwrite\").put(entry));\n }\n }\n\n private async removeIndexEntries(collection: string, record: EntityRecord): Promise<void> {\n for (const definition of await this.listIndexes(collection)) {\n const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));\n await this.request(\n this.entryStore(\"readwrite\").delete(\n this.entryId(collection, definition.name, valueKey, record.id)\n )\n );\n }\n }\n\n private async getCollectionRows(collection: string): Promise<IndexedDBRow[]> {\n const database = await this.database();\n const transaction = database.transaction(STORE_NAME, \"readonly\");\n const index = transaction.objectStore(STORE_NAME).index(COLLECTION_INDEX);\n\n return this.request<IndexedDBRow[]>(index.getAll(collection));\n }\n\n private async getEntriesForCollection(collection: string): Promise<IndexEntryRow[]> {\n const all = await this.request<IndexEntryRow[]>(this.entryStore(\"readonly\").getAll());\n return all.filter((entry) => entry.collection === collection);\n }\n\n private store(mode: IDBTransactionMode): IDBObjectStore {\n return this.objectStoreProxy(STORE_NAME, mode) as unknown as IDBObjectStore;\n }\n\n private indexStore(mode: IDBTransactionMode): IDBObjectStore {\n return this.objectStoreProxy(INDEX_STORE_NAME, mode) as unknown as IDBObjectStore;\n }\n\n private entryStore(mode: IDBTransactionMode): IDBObjectStore {\n return this.objectStoreProxy(INDEX_ENTRIES_STORE, mode) as unknown as IDBObjectStore;\n }\n\n private entryLookup(mode: IDBTransactionMode): {\n getAll: (lookup: string) => Promise<IDBRequest<IndexEntryRow[]>>;\n } {\n const databasePromise = this.database();\n return {\n getAll: (lookup: string) =>\n databasePromise.then((database) =>\n database\n .transaction(INDEX_ENTRIES_STORE, mode)\n .objectStore(INDEX_ENTRIES_STORE)\n .index(LOOKUP_INDEX)\n .getAll(lookup)\n )\n };\n }\n\n private objectStoreProxy(storeName: string, mode: IDBTransactionMode) {\n const databasePromise = this.database();\n return {\n get: (key: string) =>\n databasePromise.then((database) =>\n database.transaction(storeName, mode).objectStore(storeName).get(key)\n ),\n put: (value: unknown) =>\n databasePromise.then((database) =>\n database.transaction(storeName, mode).objectStore(storeName).put(value)\n ),\n delete: (key: string) =>\n databasePromise.then((database) =>\n database.transaction(storeName, mode).objectStore(storeName).delete(key)\n ),\n clear: () =>\n databasePromise.then((database) =>\n database.transaction(storeName, mode).objectStore(storeName).clear()\n ),\n getAll: () =>\n databasePromise.then((database) =>\n database.transaction(storeName, mode).objectStore(storeName).getAll()\n )\n };\n }\n\n private async database(): Promise<IDBDatabase> {\n if (this.databasePromise) {\n return this.databasePromise;\n }\n\n this.databasePromise = new Promise((resolve, reject) => {\n if (!globalThis.indexedDB) {\n reject(new Error(\"IndexedDB is not available in this runtime\"));\n return;\n }\n\n const request = globalThis.indexedDB.open(this.databaseName, this.version);\n\n request.onupgradeneeded = () => {\n const database = request.result;\n if (!database.objectStoreNames.contains(STORE_NAME)) {\n const store = database.createObjectStore(STORE_NAME, { keyPath: \"key\" });\n store.createIndex(COLLECTION_INDEX, COLLECTION_INDEX, { unique: false });\n }\n if (!database.objectStoreNames.contains(INDEX_STORE_NAME)) {\n database.createObjectStore(INDEX_STORE_NAME, { keyPath: \"id\" });\n }\n if (!database.objectStoreNames.contains(INDEX_ENTRIES_STORE)) {\n const entries = database.createObjectStore(INDEX_ENTRIES_STORE, { keyPath: \"id\" });\n entries.createIndex(LOOKUP_INDEX, LOOKUP_INDEX, { unique: false });\n entries.createIndex(COLLECTION_INDEX, COLLECTION_INDEX, { unique: false });\n }\n };\n\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new Error(\"Failed to open IndexedDB\"));\n });\n\n return this.databasePromise;\n }\n\n private async request<TValue>(\n requestOrPromise: IDBRequest<TValue> | Promise<IDBRequest<TValue>>\n ): Promise<TValue> {\n const request = await requestOrPromise;\n\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new Error(\"IndexedDB request failed\"));\n });\n }\n\n private key(collection: string, id: string): string {\n return `${collection}:${id}`;\n }\n\n private indexKey(collection: string, name: string): string {\n return `${collection}:${name}`;\n }\n\n private lookupKey(collection: string, indexName: string, valueKey: string): string {\n return `${collection}:${indexName}:${valueKey}`;\n }\n\n private entryId(\n collection: string,\n indexName: string,\n valueKey: string,\n recordId: string\n ): string {\n return `${collection}:${indexName}:${valueKey}:${recordId}`;\n }\n}\n\nexport const createIndexedDBStorage = (\n options?: IndexedDBStorageOptions\n): IndexedDBStorageAdapter => new IndexedDBStorageAdapter(options);\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@offlinejs/storage-indexeddb",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "IndexedDB OfflineJS storage adapter for durable browser persistence.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@offlinejs/types": "0.1.0",
|
|
18
|
+
"@offlinejs/utils": "0.1.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"tsup": "latest",
|
|
22
|
+
"typescript": "latest"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"registry": "https://registry.npmjs.org/"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"typecheck": "tsc --noEmit"
|
|
31
|
+
}
|
|
32
|
+
}
|