@xyo-network/archivist-indexeddb 3.6.7 → 3.6.9

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.
@@ -1,4 +1,5 @@
1
1
  import { Hash, Hex } from '@xylabs/hex';
2
+ import { ObjectStore } from '@xylabs/indexed-db';
2
3
  import { AbstractArchivist } from '@xyo-network/archivist-abstract';
3
4
  import { ArchivistModuleEventData, ArchivistNextOptions } from '@xyo-network/archivist-model';
4
5
  import { Payload, Schema, WithStorageMeta } from '@xyo-network/payload-model';
@@ -49,7 +50,7 @@ export declare class IndexedDbArchivist<TParams extends IndexedDbArchivistParams
49
50
  protected allHandler(): Promise<WithStorageMeta<Payload>[]>;
50
51
  protected clearHandler(): Promise<void>;
51
52
  protected deleteHandler(hashes: Hash[]): Promise<Hash[]>;
52
- protected getFromCursor(db: IDBPDatabase<PayloadStore>, storeName: string, order?: 'asc' | 'desc', limit?: number, cursor?: Hex): Promise<WithStorageMeta[]>;
53
+ protected getFromCursor(db: IDBPDatabase<ObjectStore>, storeName: string, order?: 'asc' | 'desc', limit?: number, cursor?: Hex): Promise<WithStorageMeta[]>;
53
54
  /**
54
55
  * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
55
56
  * @param db The db instance to use
@@ -58,18 +59,11 @@ export declare class IndexedDbArchivist<TParams extends IndexedDbArchivistParams
58
59
  * @param key The key to get from the index
59
60
  * @returns The primary key and the payload, or undefined if not found
60
61
  */
61
- protected getFromIndexWithPrimaryKey(db: IDBPDatabase<PayloadStore>, storeName: string, indexName: string, key: IDBValidKey): Promise<[number, WithStorageMeta] | undefined>;
62
+ protected getFromIndexWithPrimaryKey(db: IDBPDatabase<ObjectStore>, storeName: string, indexName: string, key: IDBValidKey): Promise<[number, WithStorageMeta] | undefined>;
62
63
  protected getHandler(hashes: string[]): Promise<WithStorageMeta[]>;
63
64
  protected insertHandler(payloads: WithStorageMeta<Payload>[]): Promise<WithStorageMeta<Payload>[]>;
64
65
  protected nextHandler(options?: ArchivistNextOptions): Promise<WithStorageMeta<Payload>[]>;
65
66
  protected startHandler(): Promise<boolean>;
66
- private checkIndexes;
67
- private checkObjectStore;
68
- /**
69
- * Returns that the desired DB/Store initialized to the correct version
70
- * @returns The initialized DB
71
- */
72
- private getInitializedDb;
73
67
  /**
74
68
  * Executes a callback with the initialized DB and then closes the db
75
69
  * @param callback The method to execute with the initialized DB
@@ -1 +1 @@
1
- {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import type { ArchivistConfig } from '@xyo-network/archivist-model';
2
2
  import { IndexedDbArchivistSchema } from './Schema.ts';
3
3
  export type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`;
4
4
  export declare const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema;
5
- export type IndexedDbArchivistConfig = ArchivistConfig<{
5
+ export type IndexedDbArchivistConfig<TStoreName extends string = string> = ArchivistConfig<{
6
6
  /**
7
7
  * The database name
8
8
  */
@@ -15,6 +15,6 @@ export type IndexedDbArchivistConfig = ArchivistConfig<{
15
15
  /**
16
16
  * The name of the object store
17
17
  */
18
- storeName?: string;
18
+ storeName?: TStoreName;
19
19
  }>;
20
20
  //# sourceMappingURL=Config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/Config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAEnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAEtD,MAAM,MAAM,8BAA8B,GAAG,GAAG,wBAAwB,SAAS,CAAA;AACjF,eAAO,MAAM,8BAA8B,EAAE,8BAAqE,CAAA;AAElH,MAAM,MAAM,wBAAwB,GAAG,eAAe,CAAC;IACrD;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,8BAA8B,CAAA;IACtC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAC,CAAA"}
1
+ {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../src/Config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAEnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAEtD,MAAM,MAAM,8BAA8B,GAAG,GAAG,wBAAwB,SAAS,CAAA;AACjF,eAAO,MAAM,8BAA8B,EAAE,8BAAqE,CAAA;AAElH,MAAM,MAAM,wBAAwB,CAAC,UAAU,SAAS,MAAM,GAAG,MAAM,IAAI,eAAe,CAAC;IACzF;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,8BAA8B,CAAA;IACtC;;OAEG;IACH,SAAS,CAAC,EAAE,UAAU,CAAA;CACvB,CAAC,CAAA"}
@@ -5,11 +5,11 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
5
5
  import { uniq } from "@xylabs/array";
6
6
  import { assertEx } from "@xylabs/assert";
7
7
  import { exists } from "@xylabs/exists";
8
+ import { withDb, withReadOnlyStore, withReadWriteStore } from "@xylabs/indexed-db";
8
9
  import { AbstractArchivist } from "@xyo-network/archivist-abstract";
9
- import { ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, ArchivistNextQuerySchema, buildStandardIndexName as buildStandardIndexName2 } from "@xyo-network/archivist-model";
10
+ import { ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, ArchivistNextQuerySchema, buildStandardIndexName } from "@xyo-network/archivist-model";
10
11
  import { creatableModule } from "@xyo-network/module-model";
11
12
  import { PayloadBuilder } from "@xyo-network/payload-builder";
12
- import { openDB as openDB2 } from "idb";
13
13
 
14
14
  // src/Schema.ts
15
15
  var IndexedDbArchivistSchema = "network.xyo.archivist.indexeddb";
@@ -17,89 +17,6 @@ var IndexedDbArchivistSchema = "network.xyo.archivist.indexeddb";
17
17
  // src/Config.ts
18
18
  var IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`;
19
19
 
20
- // src/IndexedDbHelpers.ts
21
- import { buildStandardIndexName } from "@xyo-network/archivist-model";
22
- import { openDB } from "idb";
23
- function createStore(db, storeName, indexes, logger) {
24
- logger?.log(`Creating store ${storeName}`);
25
- const store = db.createObjectStore(storeName, {
26
- // If it isn't explicitly set, create a value by auto incrementing.
27
- autoIncrement: true
28
- });
29
- store.name = storeName;
30
- for (const { key, multiEntry, unique } of indexes) {
31
- const indexKeys = Object.keys(key);
32
- const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys;
33
- const indexName = buildStandardIndexName({
34
- key,
35
- unique
36
- });
37
- console.log("createIndex", indexName, keys, {
38
- multiEntry,
39
- unique
40
- });
41
- store.createIndex(indexName, keys, {
42
- multiEntry,
43
- unique
44
- });
45
- }
46
- }
47
- __name(createStore, "createStore");
48
- async function getExistingIndexes(db, storeName) {
49
- return await useReadOnlyStore(db, storeName, (store) => {
50
- return [
51
- ...store.indexNames
52
- ].map((indexName) => {
53
- const index = store.index(indexName);
54
- const key = {};
55
- if (Array.isArray(index.keyPath)) {
56
- for (const keyPath of index.keyPath) {
57
- key[keyPath] = 1;
58
- }
59
- } else {
60
- key[index.keyPath] = 1;
61
- }
62
- const desc = {
63
- name: indexName,
64
- key,
65
- unique: index.unique,
66
- multiEntry: index.multiEntry
67
- };
68
- return desc;
69
- });
70
- });
71
- }
72
- __name(getExistingIndexes, "getExistingIndexes");
73
- async function useDb(dbName, callback) {
74
- const db = await openDB(dbName);
75
- try {
76
- return await callback(db);
77
- } finally {
78
- db.close();
79
- }
80
- }
81
- __name(useDb, "useDb");
82
- async function useReadOnlyStore(db, storeName, callback) {
83
- const transaction = db.transaction(storeName, "readonly");
84
- const store = transaction.objectStore(storeName);
85
- try {
86
- return await callback(store);
87
- } finally {
88
- await transaction.done;
89
- }
90
- }
91
- __name(useReadOnlyStore, "useReadOnlyStore");
92
- async function useReadWriteStore(db, storeName, callback) {
93
- const transaction = db.transaction(storeName, "readwrite");
94
- const store = transaction.objectStore(storeName);
95
- try {
96
- return await callback(store);
97
- } finally {
98
- await transaction.done;
99
- }
100
- }
101
- __name(useReadWriteStore, "useReadWriteStore");
102
-
103
20
  // src/Archivist.ts
104
21
  function _ts_decorate(decorators, target, key, desc) {
105
22
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -149,13 +66,13 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
149
66
  unique: true
150
67
  };
151
68
  // eslint-disable-next-line @typescript-eslint/member-ordering
152
- static hashIndexName = buildStandardIndexName2(_IndexedDbArchivist.hashIndex);
69
+ static hashIndexName = buildStandardIndexName(_IndexedDbArchivist.hashIndex);
153
70
  // eslint-disable-next-line @typescript-eslint/member-ordering
154
- static dataHashIndexName = buildStandardIndexName2(_IndexedDbArchivist.dataHashIndex);
71
+ static dataHashIndexName = buildStandardIndexName(_IndexedDbArchivist.dataHashIndex);
155
72
  // eslint-disable-next-line @typescript-eslint/member-ordering
156
- static schemaIndexName = buildStandardIndexName2(_IndexedDbArchivist.schemaIndex);
73
+ static schemaIndexName = buildStandardIndexName(_IndexedDbArchivist.schemaIndex);
157
74
  // eslint-disable-next-line @typescript-eslint/member-ordering
158
- static sequenceIndexName = buildStandardIndexName2(_IndexedDbArchivist.sequenceIndex);
75
+ static sequenceIndexName = buildStandardIndexName(_IndexedDbArchivist.sequenceIndex);
159
76
  _dbName;
160
77
  _dbVersion;
161
78
  _storeName;
@@ -260,8 +177,8 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
260
177
  });
261
178
  }
262
179
  async getFromCursor(db, storeName, order = "asc", limit = 10, cursor) {
263
- return await useReadOnlyStore(db, storeName, async (store) => {
264
- const sequenceIndex = assertEx(store.index(_IndexedDbArchivist.sequenceIndexName), () => "Failed to get sequence index");
180
+ return await withReadOnlyStore(db, storeName, async (store) => {
181
+ const sequenceIndex = assertEx(store?.index(_IndexedDbArchivist.sequenceIndexName), () => "Failed to get sequence index");
265
182
  let sequenceCursor;
266
183
  const parsedCursor = cursor ? order === "asc" ? IDBKeyRange.lowerBound(cursor, false) : IDBKeyRange.upperBound(cursor, false) : null;
267
184
  sequenceCursor = await sequenceIndex.openCursor(parsedCursor, order === "desc" ? "prev" : "next");
@@ -297,18 +214,20 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
297
214
  * @returns The primary key and the payload, or undefined if not found
298
215
  */
299
216
  async getFromIndexWithPrimaryKey(db, storeName, indexName, key) {
300
- return await useReadOnlyStore(db, storeName, async (store) => {
301
- const index = store.index(indexName);
302
- const cursor = await index.openCursor(key);
303
- if (cursor) {
304
- const singleValue = cursor.value;
305
- if (typeof cursor.primaryKey !== "number") {
306
- throw new TypeError("primaryKey must be a number");
217
+ return await withReadOnlyStore(db, storeName, async (store) => {
218
+ if (store) {
219
+ const index = store.index(indexName);
220
+ const cursor = await index.openCursor(key);
221
+ if (cursor) {
222
+ const singleValue = cursor.value;
223
+ if (typeof cursor.primaryKey !== "number") {
224
+ throw new TypeError("primaryKey must be a number");
225
+ }
226
+ return [
227
+ cursor.primaryKey,
228
+ singleValue
229
+ ];
307
230
  }
308
- return [
309
- cursor.primaryKey,
310
- singleValue
311
- ];
312
231
  }
313
232
  });
314
233
  }
@@ -333,15 +252,19 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
333
252
  }
334
253
  async insertHandler(payloads) {
335
254
  return await this.useDb(async (db) => {
336
- return await useReadWriteStore(db, this.storeName, async (store) => {
337
- const inserted = [];
338
- await Promise.all(payloads.map(async (payload) => {
339
- if (!await store.index(_IndexedDbArchivist.hashIndexName).get(payload._hash)) {
340
- await store.put(payload);
341
- inserted.push(payload);
342
- }
343
- }));
344
- return inserted;
255
+ return await withReadWriteStore(db, this.storeName, async (store) => {
256
+ if (store) {
257
+ const inserted = [];
258
+ await Promise.all(payloads.map(async (payload) => {
259
+ if (!await store.index(_IndexedDbArchivist.hashIndexName).get(payload._hash)) {
260
+ await store.put(payload);
261
+ inserted.push(payload);
262
+ }
263
+ }));
264
+ return inserted;
265
+ } else {
266
+ throw new Error("Failed to get store");
267
+ }
345
268
  });
346
269
  });
347
270
  }
@@ -357,94 +280,17 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
357
280
  });
358
281
  return true;
359
282
  }
360
- async checkIndexes(db) {
361
- const { indexes, storeName } = this;
362
- if (db.objectStoreNames.contains(storeName)) {
363
- const existingIndexes = await getExistingIndexes(db, storeName);
364
- const existingIndexNames = new Set(existingIndexes.map(({ name }) => name).filter(exists));
365
- for (const { key, unique } of indexes) {
366
- const indexName = buildStandardIndexName2({
367
- key,
368
- unique
369
- });
370
- if (!existingIndexNames.has(indexName)) {
371
- this._dbVersion = this._dbVersion === void 0 ? 0 : this._dbVersion + 1;
372
- break;
373
- }
374
- }
375
- return existingIndexes;
376
- }
377
- return [];
378
- }
379
- async checkObjectStore() {
380
- const { dbName, storeName } = this;
381
- return await useDb(dbName, (db) => {
382
- if (db.version >= (this._dbVersion ?? 0)) {
383
- this._dbVersion = db.version;
384
- }
385
- if (db.objectStoreNames.contains(storeName)) {
386
- return this.checkIndexes(db);
387
- } else {
388
- this._dbVersion = (this._dbVersion ?? 0) + 1;
389
- return [];
390
- }
391
- });
392
- }
393
- /**
394
- * Returns that the desired DB/Store initialized to the correct version
395
- * @returns The initialized DB
396
- */
397
- async getInitializedDb() {
398
- const existingIndexes = await this.checkObjectStore();
399
- const { dbName, dbVersion, indexes, storeName, logger } = this;
400
- return await openDB2(dbName, dbVersion, {
401
- blocked(currentVersion, blockedVersion, event) {
402
- logger.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event);
403
- },
404
- blocking(currentVersion, blockedVersion, event) {
405
- logger.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event);
406
- },
407
- terminated() {
408
- logger.log("IndexedDbArchivist: Terminated");
409
- },
410
- upgrade(database, oldVersion, newVersion, transaction) {
411
- if (oldVersion !== newVersion) {
412
- logger.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`);
413
- const objectStores = transaction.objectStoreNames;
414
- for (const name of objectStores) {
415
- try {
416
- database.deleteObjectStore(name);
417
- } catch {
418
- logger.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`);
419
- }
420
- }
421
- }
422
- const existingIndexesToKeep = existingIndexes.filter(({ name: existingName }) => !indexes.some(({ name }) => name === existingName));
423
- console.log("existingIndexes", existingIndexes);
424
- console.log("existingIndexesToKeep", existingIndexesToKeep);
425
- console.log("indexes", indexes);
426
- const indexesToCreate = indexes.map((idx) => ({
427
- ...idx,
428
- name: buildStandardIndexName2(idx)
429
- })).reduce((acc, idx) => acc.set(idx.name, idx), /* @__PURE__ */ new Map()).values();
430
- createStore(database, storeName, [
431
- ...indexesToCreate
432
- ], logger);
433
- }
434
- });
435
- }
436
283
  /**
437
284
  * Executes a callback with the initialized DB and then closes the db
438
285
  * @param callback The method to execute with the initialized DB
439
286
  * @returns
440
287
  */
441
288
  async useDb(callback) {
442
- const db = await this.getInitializedDb();
443
- try {
289
+ return await withDb(this.dbName, async (db) => {
444
290
  return await callback(db);
445
- } finally {
446
- db.close();
447
- }
291
+ }, {
292
+ [this.storeName]: this.indexes
293
+ }, this.logger);
448
294
  }
449
295
  };
450
296
  IndexedDbArchivist = _ts_decorate([
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts","../../src/IndexedDbHelpers.ts"],"sourcesContent":["import { uniq } from '@xylabs/array'\nimport { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { Hash, Hex } from '@xylabs/hex'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n ArchivistNextOptions,\n ArchivistNextQuerySchema,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport {\n Payload, Schema, SequenceConstants, WithStorageMeta,\n} from '@xyo-network/payload-model'\nimport {\n IDBPCursorWithValue, IDBPDatabase, openDB,\n} from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config.ts'\nimport {\n createStore,\n getExistingIndexes,\n useDb, useReadOnlyStore, useReadWriteStore,\n} from './IndexedDbHelpers.ts'\nimport { IndexedDbArchivistParams } from './Params.ts'\n\nexport interface PayloadStore {\n [s: string]: WithStorageMeta\n}\n\n@creatableModule()\nexport class IndexedDbArchivist<\n TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,\n TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,\n> extends AbstractArchivist<TParams, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbArchivistConfigSchema]\n static override readonly defaultConfigSchema: Schema = IndexedDbArchivistConfigSchema\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly dataHashIndex: IndexDescription = {\n key: { _dataHash: 1 }, multiEntry: false, unique: false,\n }\n\n private static readonly hashIndex: IndexDescription = {\n key: { _hash: 1 }, multiEntry: false, unique: true,\n }\n\n private static readonly schemaIndex: IndexDescription = {\n key: { schema: 1 }, multiEntry: false, unique: false,\n }\n\n private static readonly sequenceIndex: IndexDescription = {\n key: { _sequence: 1 }, multiEntry: false, unique: true,\n }\n\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly sequenceIndexName = buildStandardIndexName(IndexedDbArchivist.sequenceIndex)\n\n private _dbName?: string\n private _dbVersion?: number\n private _storeName?: string\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the module name (not guaranteed to be unique) and if module\n * name is not supplied, it defaults to `archivist`. This behavior\n * biases towards a single, isolated DB per archivist which seems to\n * make the most sense for 99% of use cases.\n */\n get dbName() {\n if (!this._dbName) {\n if (this.config?.dbName) {\n this._dbName = this.config?.dbName\n } else {\n if (this.config?.name) {\n this.logger.warn('No dbName provided, using module name: ', this.config?.name)\n this._dbName = this.config?.name\n } else {\n this.logger.warn('No dbName provided, using default name: ', IndexedDbArchivist.defaultDbName)\n this._dbName = IndexedDbArchivist.defaultDbName\n }\n }\n }\n return assertEx(this._dbName)\n }\n\n /**\n * The database version. If not supplied via config, it defaults to 1.\n */\n get dbVersion() {\n this._dbVersion = this._dbVersion ?? this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\n return this._dbVersion\n }\n\n override get queries() {\n return [\n ArchivistNextQuerySchema,\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ...super.queries,\n ]\n }\n\n /**\n * The name of the object store. If not supplied via config, it defaults\n * to `payloads`.\n */\n get storeName() {\n if (!this._storeName) {\n if (this.config?.storeName) {\n this._storeName = this.config?.storeName\n } else {\n this.logger.warn('No storeName provided, using default name: ', IndexedDbArchivist.defaultStoreName)\n this._storeName = IndexedDbArchivist.defaultStoreName\n }\n }\n return assertEx(this._storeName)\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [\n IndexedDbArchivist.dataHashIndex,\n IndexedDbArchivist.hashIndex,\n IndexedDbArchivist.schemaIndex,\n IndexedDbArchivist.sequenceIndex,\n ...(this.config?.storage?.indexes ?? []),\n ]\n }\n\n protected override async allHandler(): Promise<WithStorageMeta<Payload>[]> {\n // Get all payloads from the store\n const payloads = await this.useDb(db => db.getAll(this.storeName))\n // Remove any metadata before returning to the client\n return payloads\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.useDb(db => db.clear(this.storeName))\n }\n\n protected override async deleteHandler(hashes: Hash[]): Promise<Hash[]> {\n // Filter duplicates to prevent unnecessary DB queries\n const uniqueHashes = [...new Set(hashes)]\n const pairs = await PayloadBuilder.hashPairs(await this.getHandler(uniqueHashes))\n const hashesToDelete = (await Promise.all(pairs.map(async (pair) => {\n const dataHash0 = await PayloadBuilder.dataHash(pair[0])\n return [dataHash0, pair[1]]\n }))).flat()\n // Remove any duplicates\n const distinctHashes = [...new Set(hashesToDelete)]\n return await this.useDb(async (db) => {\n // Only return hashes that were successfully deleted\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n // Check if the hash exists\n const existing\n = (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash))\n ?? (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))\n // If it does exist\n if (existing) {\n // Delete it\n await db.delete(this.storeName, existing)\n // Return the hash so it gets added to the list of deleted hashes\n return hash\n }\n }),\n )\n return found.filter(exists).filter(hash => uniqueHashes.includes(hash))\n })\n }\n\n protected async getFromCursor(\n db: IDBPDatabase<PayloadStore>,\n storeName: string,\n order: 'asc' | 'desc' = 'asc',\n limit: number = 10,\n cursor?: Hex,\n ): Promise<WithStorageMeta[]> {\n // TODO: We have to handle the case where the cursor is not found, and then find the correct cursor to start with (thunked cursor)\n\n return await useReadOnlyStore(db, storeName, async (store) => {\n const sequenceIndex = assertEx(store.index(IndexedDbArchivist.sequenceIndexName), () => 'Failed to get sequence index')\n let sequenceCursor: IDBPCursorWithValue<PayloadStore, [string]> | null | undefined\n const parsedCursor = cursor\n ? order === 'asc'\n ? IDBKeyRange.lowerBound(cursor, false)\n : IDBKeyRange.upperBound(cursor, false)\n : null\n\n sequenceCursor = await sequenceIndex.openCursor(\n parsedCursor,\n order === 'desc' ? 'prev' : 'next',\n )\n\n if (cursor) {\n sequenceCursor = await sequenceCursor?.advance(1)\n }\n\n let remaining = limit\n const result: WithStorageMeta[] = []\n while (remaining) {\n const value = sequenceCursor?.value\n if (value) {\n result.push(value)\n }\n try {\n sequenceCursor = await sequenceCursor?.advance(1)\n } catch {\n break\n }\n if (sequenceCursor === null) {\n break\n }\n remaining--\n }\n return result\n })\n }\n\n /**\n * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)\n * @param db The db instance to use\n * @param storeName The name of the store to use\n * @param indexName The index to use\n * @param key The key to get from the index\n * @returns The primary key and the payload, or undefined if not found\n */\n protected async getFromIndexWithPrimaryKey(\n db: IDBPDatabase<PayloadStore>,\n storeName: string,\n indexName: string,\n key: IDBValidKey,\n ): Promise<[number, WithStorageMeta] | undefined> {\n return await useReadOnlyStore(db, storeName, async (store) => {\n const index = store.index(indexName)\n const cursor = await index.openCursor(key)\n if (cursor) {\n const singleValue = cursor.value\n // NOTE: It's known to be a number because we are using IndexedDB supplied auto-incrementing keys\n if (typeof cursor.primaryKey !== 'number') {\n throw new TypeError('primaryKey must be a number')\n }\n\n return [cursor.primaryKey, singleValue]\n }\n })\n }\n\n protected override async getHandler(hashes: string[]): Promise<WithStorageMeta[]> {\n const payloads = await this.useDb(db =>\n Promise.all(\n // Filter duplicates to prevent unnecessary DB queries\n uniq(hashes).map(async (hash) => {\n // Find by hash\n const payload = await this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.hashIndexName, hash)\n // If found, return\n if (payload) return payload\n // Otherwise, find by data hash\n return this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.dataHashIndexName, hash)\n }),\n ))\n\n const found = new Set<string>()\n return (\n payloads\n // Filter out not found\n .filter(exists)\n // Sort by primary key\n .sort((a, b) => a![0] - b![0])\n // Filter out duplicates by hash\n .filter(([_key, payload]) => {\n if (found.has(payload._hash)) {\n return false\n } else {\n found.add(payload._hash)\n return true\n }\n })\n // Return just the payloads\n .map(([_key, payload]) => payload)\n )\n }\n\n protected override async insertHandler(payloads: WithStorageMeta<Payload>[]): Promise<WithStorageMeta<Payload>[]> {\n return await this.useDb(async (db) => {\n // Perform all inserts via a single transaction to ensure atomicity\n // with respect to checking for the pre-existence of the hash.\n // This is done to prevent duplicate root hashes due to race\n // conditions between checking vs insertion.\n return await useReadWriteStore(db, this.storeName, async (store) => {\n // Return only the payloads that were successfully inserted\n const inserted: WithStorageMeta<Payload>[] = []\n await Promise.all(\n payloads.map(async (payload) => {\n // only insert if hash does not already exist\n if (!await store.index(IndexedDbArchivist.hashIndexName).get(payload._hash)) {\n // Insert the payload\n await store.put(payload)\n // Add it to the inserted list\n inserted.push(payload)\n }\n }),\n )\n return inserted\n })\n })\n }\n\n protected override async nextHandler(options?: ArchivistNextOptions): Promise<WithStorageMeta<Payload>[]> {\n const {\n limit, cursor, order,\n } = options ?? {}\n return await this.useDb(async (db) => {\n return await this.getFromCursor(db, this.storeName, order, limit ?? 10, cursor)\n })\n }\n\n protected override async startHandler() {\n await super.startHandler()\n // NOTE: We could defer this creation to first access but we\n // want to fail fast here in case something is wrong\n await this.useDb(() => {})\n return true\n }\n\n private async checkIndexes(db: IDBPDatabase<PayloadStore>): Promise<IndexDescription[]> {\n const { indexes, storeName } = this\n if (db.objectStoreNames.contains(storeName)) {\n const existingIndexes = await getExistingIndexes(db, storeName)\n const existingIndexNames = new Set(existingIndexes.map(({ name }) => name).filter(exists))\n for (const { key, unique } of indexes) {\n const indexName = buildStandardIndexName({ key, unique })\n if (!existingIndexNames.has(indexName)) {\n // the index is missing, so trigger an upgrade\n this._dbVersion = this._dbVersion === undefined ? 0 : this._dbVersion + 1\n break\n }\n }\n return existingIndexes\n }\n return []\n }\n\n private async checkObjectStore(): Promise<IndexDescription[]> {\n const { dbName, storeName } = this\n return await useDb(dbName, (db) => {\n // we check the version here to see if someone else upgraded it past where we think we are\n if (db.version >= (this._dbVersion ?? 0)) {\n this._dbVersion = db.version\n }\n if (db.objectStoreNames.contains(storeName)) {\n return this.checkIndexes(db)\n } else {\n this._dbVersion = (this._dbVersion ?? 0) + 1\n return []\n }\n })\n }\n\n /**\n * Returns that the desired DB/Store initialized to the correct version\n * @returns The initialized DB\n */\n private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {\n const existingIndexes = await this.checkObjectStore()\n const {\n dbName, dbVersion, indexes, storeName, logger,\n } = this\n return await openDB<PayloadStore>(dbName, dbVersion, {\n blocked(currentVersion, blockedVersion, event) {\n logger.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)\n },\n blocking(currentVersion, blockedVersion, event) {\n logger.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)\n },\n terminated() {\n logger.log('IndexedDbArchivist: Terminated')\n },\n upgrade(database, oldVersion, newVersion, transaction) {\n // NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end\n // state but, out of an abundance of caution, we will just delete (so we know where we are starting\n // from a known good point) and recreate the desired state. This prioritizes resilience over data\n // retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser\n // state is extremely hard to debug, this seems like fair tradeoff for now.\n if (oldVersion !== newVersion) {\n logger.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)\n // Delete any existing databases that are not the current version\n const objectStores = transaction.objectStoreNames\n for (const name of objectStores) {\n try {\n database.deleteObjectStore(name)\n } catch {\n logger.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)\n }\n }\n }\n // keep any indexes that were there before but are not required by this config\n // we do this incase there are two or more configs trying to use the db and they have mismatched indexes, so they do not erase each other's indexes\n const existingIndexesToKeep = existingIndexes.filter(({ name: existingName }) => !indexes.some(({ name }) => name === existingName))\n console.log('existingIndexes', existingIndexes)\n console.log('existingIndexesToKeep', existingIndexesToKeep)\n console.log('indexes', indexes)\n const indexesToCreate = indexes.map(idx => ({\n ...idx,\n name: buildStandardIndexName(idx),\n // eslint-disable-next-line unicorn/no-array-reduce\n })).reduce((acc, idx) => acc.set(idx.name, idx), new Map<string, IndexDescription>()).values()\n createStore(database, storeName, [...indexesToCreate], logger)\n },\n })\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {\n // Get the initialized DB\n const db = await this.getInitializedDb()\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import type { ArchivistConfig } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema.ts'\n\nexport type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\nexport const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\n\nexport type IndexedDbArchivistConfig = ArchivistConfig<{\n /**\n * The database name\n */\n dbName?: string\n /**\n * The version of the DB, defaults to 1\n */\n dbVersion?: number\n schema: IndexedDbArchivistConfigSchema\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n","import type { Logger } from '@xylabs/logger'\nimport type { IndexDescription, IndexDirection } from '@xyo-network/archivist-model'\nimport { buildStandardIndexName } from '@xyo-network/archivist-model'\nimport type { IDBPDatabase, IDBPObjectStore } from 'idb'\nimport { openDB } from 'idb'\n\nimport type { PayloadStore } from './Archivist.ts'\n\nexport function createStore(db: IDBPDatabase<PayloadStore>, storeName: string, indexes: IndexDescription[], logger?: Logger) {\n logger?.log(`Creating store ${storeName}`)\n // Create the store\n const store = db.createObjectStore(storeName, {\n // If it isn't explicitly set, create a value by auto incrementing.\n autoIncrement: true,\n })\n // Name the store\n store.name = storeName\n // Create an index on the hash\n for (const {\n key, multiEntry, unique,\n } of indexes) {\n const indexKeys = Object.keys(key)\n const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys\n const indexName = buildStandardIndexName({ key, unique })\n console.log('createIndex', indexName, keys, { multiEntry, unique })\n store.createIndex(indexName, keys, { multiEntry, unique })\n }\n}\n\nexport async function getExistingIndexes(db: IDBPDatabase<PayloadStore>, storeName: string): Promise<IndexDescription[]> {\n return await useReadOnlyStore(db, storeName, (store) => {\n return [...store.indexNames].map((indexName) => {\n const index = store.index(indexName)\n const key: Record<string, IndexDirection> = {}\n if (Array.isArray(index.keyPath)) {\n for (const keyPath of index.keyPath) {\n key[keyPath] = 1\n }\n } else {\n key[index.keyPath] = 1\n }\n const desc: IndexDescription = {\n name: indexName,\n key,\n unique: index.unique,\n multiEntry: index.multiEntry,\n }\n return desc\n })\n })\n}\n\nexport async function useDb<T>(dbName: string, callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {\n const db = await openDB<PayloadStore>(dbName)\n try {\n return await callback(db)\n } finally {\n db.close()\n }\n}\n\nexport async function useReadOnlyStore<T>(\n db: IDBPDatabase<PayloadStore>,\n storeName: string,\n callback: (store: IDBPObjectStore<PayloadStore, [string], string, 'readonly'>) => Promise<T> | T,\n): Promise<T> {\n const transaction = db.transaction(storeName, 'readonly')\n const store = transaction.objectStore(storeName)\n try {\n return await callback(store)\n } finally {\n await transaction.done\n }\n}\n\nexport async function useReadWriteStore<T>(\n db: IDBPDatabase<PayloadStore>,\n storeName: string,\n callback: (store: IDBPObjectStore<PayloadStore, [string], string, 'readwrite'>) => Promise<T> | T,\n): Promise<T> {\n const transaction = db.transaction(storeName, 'readwrite')\n const store = transaction.objectStore(storeName)\n try {\n return await callback(store)\n } finally {\n await transaction.done\n }\n}\n"],"mappings":"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","names":["uniq","assertEx","exists","AbstractArchivist","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","ArchivistNextQuerySchema","buildStandardIndexName","creatableModule","PayloadBuilder","openDB","IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","buildStandardIndexName","openDB","createStore","db","storeName","indexes","logger","log","store","createObjectStore","autoIncrement","name","key","multiEntry","unique","indexKeys","Object","keys","length","indexName","buildStandardIndexName","console","createIndex","getExistingIndexes","useReadOnlyStore","indexNames","map","index","Array","isArray","keyPath","desc","useDb","dbName","callback","openDB","close","transaction","objectStore","done","useReadWriteStore","IndexedDbArchivist","AbstractArchivist","configSchemas","IndexedDbArchivistConfigSchema","defaultConfigSchema","defaultDbName","defaultDbVersion","defaultStoreName","dataHashIndex","key","_dataHash","multiEntry","unique","hashIndex","_hash","schemaIndex","schema","sequenceIndex","_sequence","hashIndexName","buildStandardIndexName","dataHashIndexName","schemaIndexName","sequenceIndexName","_dbName","_dbVersion","_storeName","dbName","config","name","logger","warn","assertEx","dbVersion","queries","ArchivistNextQuerySchema","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","indexes","storage","allHandler","payloads","useDb","db","getAll","clearHandler","clear","deleteHandler","hashes","uniqueHashes","Set","pairs","PayloadBuilder","hashPairs","getHandler","hashesToDelete","Promise","all","map","pair","dataHash0","dataHash","flat","distinctHashes","found","hash","existing","getKeyFromIndex","delete","filter","exists","includes","getFromCursor","order","limit","cursor","useReadOnlyStore","store","index","sequenceCursor","parsedCursor","IDBKeyRange","lowerBound","upperBound","openCursor","advance","remaining","result","value","push","getFromIndexWithPrimaryKey","indexName","singleValue","primaryKey","TypeError","uniq","payload","sort","a","b","_key","has","add","insertHandler","useReadWriteStore","inserted","get","put","nextHandler","options","startHandler","checkIndexes","objectStoreNames","contains","existingIndexes","getExistingIndexes","existingIndexNames","undefined","checkObjectStore","version","getInitializedDb","openDB","blocked","currentVersion","blockedVersion","event","blocking","terminated","log","upgrade","database","oldVersion","newVersion","transaction","objectStores","deleteObjectStore","existingIndexesToKeep","existingName","some","console","indexesToCreate","idx","reduce","acc","set","Map","values","createStore","callback","close"]}
1
+ {"version":3,"sources":["../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["import { uniq } from '@xylabs/array'\nimport { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { Hash, Hex } from '@xylabs/hex'\nimport {\n ObjectStore,\n withDb,\n withReadOnlyStore, withReadWriteStore,\n} from '@xylabs/indexed-db'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n ArchivistNextOptions,\n ArchivistNextQuerySchema,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport {\n Payload, Schema, WithStorageMeta,\n} from '@xyo-network/payload-model'\nimport { IDBPCursorWithValue, IDBPDatabase } from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config.ts'\nimport { IndexedDbArchivistParams } from './Params.ts'\n\nexport interface PayloadStore {\n [s: string]: WithStorageMeta\n}\n\n@creatableModule()\nexport class IndexedDbArchivist<\n TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,\n TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,\n> extends AbstractArchivist<TParams, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbArchivistConfigSchema]\n static override readonly defaultConfigSchema: Schema = IndexedDbArchivistConfigSchema\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly dataHashIndex: IndexDescription = {\n key: { _dataHash: 1 }, multiEntry: false, unique: false,\n }\n\n private static readonly hashIndex: IndexDescription = {\n key: { _hash: 1 }, multiEntry: false, unique: true,\n }\n\n private static readonly schemaIndex: IndexDescription = {\n key: { schema: 1 }, multiEntry: false, unique: false,\n }\n\n private static readonly sequenceIndex: IndexDescription = {\n key: { _sequence: 1 }, multiEntry: false, unique: true,\n }\n\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly sequenceIndexName = buildStandardIndexName(IndexedDbArchivist.sequenceIndex)\n\n private _dbName?: string\n private _dbVersion?: number\n private _storeName?: string\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the module name (not guaranteed to be unique) and if module\n * name is not supplied, it defaults to `archivist`. This behavior\n * biases towards a single, isolated DB per archivist which seems to\n * make the most sense for 99% of use cases.\n */\n get dbName() {\n if (!this._dbName) {\n if (this.config?.dbName) {\n this._dbName = this.config?.dbName\n } else {\n if (this.config?.name) {\n this.logger.warn('No dbName provided, using module name: ', this.config?.name)\n this._dbName = this.config?.name\n } else {\n this.logger.warn('No dbName provided, using default name: ', IndexedDbArchivist.defaultDbName)\n this._dbName = IndexedDbArchivist.defaultDbName\n }\n }\n }\n return assertEx(this._dbName)\n }\n\n /**\n * The database version. If not supplied via config, it defaults to 1.\n */\n get dbVersion() {\n this._dbVersion = this._dbVersion ?? this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\n return this._dbVersion\n }\n\n override get queries() {\n return [\n ArchivistNextQuerySchema,\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ...super.queries,\n ]\n }\n\n /**\n * The name of the object store. If not supplied via config, it defaults\n * to `payloads`.\n */\n get storeName() {\n if (!this._storeName) {\n if (this.config?.storeName) {\n this._storeName = this.config?.storeName\n } else {\n this.logger.warn('No storeName provided, using default name: ', IndexedDbArchivist.defaultStoreName)\n this._storeName = IndexedDbArchivist.defaultStoreName\n }\n }\n return assertEx(this._storeName)\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [\n IndexedDbArchivist.dataHashIndex,\n IndexedDbArchivist.hashIndex,\n IndexedDbArchivist.schemaIndex,\n IndexedDbArchivist.sequenceIndex,\n ...(this.config?.storage?.indexes ?? []),\n ]\n }\n\n protected override async allHandler(): Promise<WithStorageMeta<Payload>[]> {\n // Get all payloads from the store\n const payloads = await this.useDb(db => db.getAll(this.storeName))\n // Remove any metadata before returning to the client\n return payloads\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.useDb(db => db.clear(this.storeName))\n }\n\n protected override async deleteHandler(hashes: Hash[]): Promise<Hash[]> {\n // Filter duplicates to prevent unnecessary DB queries\n const uniqueHashes = [...new Set(hashes)]\n const pairs = await PayloadBuilder.hashPairs(await this.getHandler(uniqueHashes))\n const hashesToDelete = (await Promise.all(pairs.map(async (pair) => {\n const dataHash0 = await PayloadBuilder.dataHash(pair[0])\n return [dataHash0, pair[1]]\n }))).flat()\n // Remove any duplicates\n const distinctHashes = [...new Set(hashesToDelete)]\n return await this.useDb(async (db) => {\n // Only return hashes that were successfully deleted\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n // Check if the hash exists\n const existing\n = (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash))\n ?? (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))\n // If it does exist\n if (existing) {\n // Delete it\n await db.delete(this.storeName, existing)\n // Return the hash so it gets added to the list of deleted hashes\n return hash\n }\n }),\n )\n return found.filter(exists).filter(hash => uniqueHashes.includes(hash))\n })\n }\n\n protected async getFromCursor(\n db: IDBPDatabase<ObjectStore>,\n storeName: string,\n order: 'asc' | 'desc' = 'asc',\n limit: number = 10,\n cursor?: Hex,\n ): Promise<WithStorageMeta[]> {\n // TODO: We have to handle the case where the cursor is not found, and then find the correct cursor to start with (thunked cursor)\n\n return await withReadOnlyStore(db, storeName, async (store) => {\n const sequenceIndex = assertEx(store?.index(IndexedDbArchivist.sequenceIndexName), () => 'Failed to get sequence index')\n let sequenceCursor: IDBPCursorWithValue<ObjectStore, [string]> | null | undefined\n const parsedCursor = cursor\n ? order === 'asc'\n ? IDBKeyRange.lowerBound(cursor, false)\n : IDBKeyRange.upperBound(cursor, false)\n : null\n\n sequenceCursor = await sequenceIndex.openCursor(\n parsedCursor,\n order === 'desc' ? 'prev' : 'next',\n )\n\n if (cursor) {\n sequenceCursor = await sequenceCursor?.advance(1)\n }\n\n let remaining = limit\n const result: WithStorageMeta[] = []\n while (remaining) {\n const value = sequenceCursor?.value\n if (value) {\n result.push(value)\n }\n try {\n sequenceCursor = await sequenceCursor?.advance(1)\n } catch {\n break\n }\n if (sequenceCursor === null) {\n break\n }\n remaining--\n }\n return result\n })\n }\n\n /**\n * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)\n * @param db The db instance to use\n * @param storeName The name of the store to use\n * @param indexName The index to use\n * @param key The key to get from the index\n * @returns The primary key and the payload, or undefined if not found\n */\n protected async getFromIndexWithPrimaryKey(\n db: IDBPDatabase<ObjectStore>,\n storeName: string,\n indexName: string,\n key: IDBValidKey,\n ): Promise<[number, WithStorageMeta] | undefined> {\n return await withReadOnlyStore(db, storeName, async (store) => {\n if (store) {\n const index = store.index(indexName)\n const cursor = await index.openCursor(key)\n if (cursor) {\n const singleValue = cursor.value\n // NOTE: It's known to be a number because we are using IndexedDB supplied auto-incrementing keys\n if (typeof cursor.primaryKey !== 'number') {\n throw new TypeError('primaryKey must be a number')\n }\n\n return [cursor.primaryKey, singleValue]\n }\n }\n })\n }\n\n protected override async getHandler(hashes: string[]): Promise<WithStorageMeta[]> {\n const payloads = await this.useDb(db =>\n Promise.all(\n // Filter duplicates to prevent unnecessary DB queries\n uniq(hashes).map(async (hash) => {\n // Find by hash\n const payload = await this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.hashIndexName, hash)\n // If found, return\n if (payload) return payload\n // Otherwise, find by data hash\n return this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.dataHashIndexName, hash)\n }),\n ))\n\n const found = new Set<string>()\n return (\n payloads\n // Filter out not found\n .filter(exists)\n // Sort by primary key\n .sort((a, b) => a![0] - b![0])\n // Filter out duplicates by hash\n .filter(([_key, payload]) => {\n if (found.has(payload._hash)) {\n return false\n } else {\n found.add(payload._hash)\n return true\n }\n })\n // Return just the payloads\n .map(([_key, payload]) => payload)\n )\n }\n\n protected override async insertHandler(payloads: WithStorageMeta<Payload>[]): Promise<WithStorageMeta<Payload>[]> {\n return await this.useDb(async (db) => {\n // Perform all inserts via a single transaction to ensure atomicity\n // with respect to checking for the pre-existence of the hash.\n // This is done to prevent duplicate root hashes due to race\n // conditions between checking vs insertion.\n return await withReadWriteStore(db, this.storeName, async (store) => {\n // Return only the payloads that were successfully inserted\n if (store) {\n const inserted: WithStorageMeta<Payload>[] = []\n await Promise.all(\n payloads.map(async (payload) => {\n // only insert if hash does not already exist\n if (!await store.index(IndexedDbArchivist.hashIndexName).get(payload._hash)) {\n // Insert the payload\n await store.put(payload)\n // Add it to the inserted list\n inserted.push(payload)\n }\n }),\n )\n return inserted\n } else {\n throw new Error('Failed to get store')\n }\n })\n })\n }\n\n protected override async nextHandler(options?: ArchivistNextOptions): Promise<WithStorageMeta<Payload>[]> {\n const {\n limit, cursor, order,\n } = options ?? {}\n return await this.useDb(async (db) => {\n return await this.getFromCursor(db, this.storeName, order, limit ?? 10, cursor)\n })\n }\n\n protected override async startHandler() {\n await super.startHandler()\n // NOTE: We could defer this creation to first access but we\n // want to fail fast here in case something is wrong\n await this.useDb(() => {})\n return true\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async useDb<T>(callback: (db: IDBPDatabase<ObjectStore>) => Promise<T> | T): Promise<T> {\n return await withDb<ObjectStore, T>(this.dbName, async (db) => {\n return await callback(db)\n }, { [this.storeName]: this.indexes }, this.logger)\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import type { ArchivistConfig } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema.ts'\n\nexport type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\nexport const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\n\nexport type IndexedDbArchivistConfig<TStoreName extends string = string> = ArchivistConfig<{\n /**\n * The database name\n */\n dbName?: string\n /**\n * The version of the DB, defaults to 1\n */\n dbVersion?: number\n schema: IndexedDbArchivistConfigSchema\n /**\n * The name of the object store\n */\n storeName?: TStoreName\n}>\n"],"mappings":"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","names":["uniq","assertEx","exists","withDb","withReadOnlyStore","withReadWriteStore","AbstractArchivist","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","ArchivistNextQuerySchema","buildStandardIndexName","creatableModule","PayloadBuilder","IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","configSchemas","IndexedDbArchivistConfigSchema","defaultConfigSchema","defaultDbName","defaultDbVersion","defaultStoreName","dataHashIndex","key","_dataHash","multiEntry","unique","hashIndex","_hash","schemaIndex","schema","sequenceIndex","_sequence","hashIndexName","buildStandardIndexName","dataHashIndexName","schemaIndexName","sequenceIndexName","_dbName","_dbVersion","_storeName","dbName","config","name","logger","warn","assertEx","dbVersion","queries","ArchivistNextQuerySchema","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","indexes","storage","allHandler","payloads","useDb","db","getAll","clearHandler","clear","deleteHandler","hashes","uniqueHashes","Set","pairs","PayloadBuilder","hashPairs","getHandler","hashesToDelete","Promise","all","map","pair","dataHash0","dataHash","flat","distinctHashes","found","hash","existing","getKeyFromIndex","delete","filter","exists","includes","getFromCursor","order","limit","cursor","withReadOnlyStore","store","index","sequenceCursor","parsedCursor","IDBKeyRange","lowerBound","upperBound","openCursor","advance","remaining","result","value","push","getFromIndexWithPrimaryKey","indexName","singleValue","primaryKey","TypeError","uniq","payload","sort","a","b","_key","has","add","insertHandler","withReadWriteStore","inserted","get","put","Error","nextHandler","options","startHandler","callback","withDb"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xyo-network/archivist-indexeddb",
3
- "version": "3.6.7",
3
+ "version": "3.6.9",
4
4
  "description": "Primary SDK for using XYO Protocol 2.0",
5
5
  "homepage": "https://xyo.network",
6
6
  "bugs": {
@@ -29,26 +29,26 @@
29
29
  "module": "dist/browser/index.mjs",
30
30
  "types": "dist/browser/index.d.ts",
31
31
  "dependencies": {
32
- "@xylabs/array": "^4.4.27",
33
- "@xylabs/assert": "^4.4.27",
34
- "@xylabs/exists": "^4.4.27",
35
- "@xylabs/hex": "^4.4.27",
36
- "@xylabs/logger": "^4.4.27",
37
- "@xyo-network/archivist-abstract": "^3.6.7",
38
- "@xyo-network/archivist-model": "^3.6.7",
39
- "@xyo-network/module-model": "^3.6.7",
40
- "@xyo-network/payload-builder": "^3.6.7",
41
- "@xyo-network/payload-model": "^3.6.7",
32
+ "@xylabs/array": "^4.4.34",
33
+ "@xylabs/assert": "^4.4.34",
34
+ "@xylabs/exists": "^4.4.34",
35
+ "@xylabs/hex": "^4.4.34",
36
+ "@xylabs/indexed-db": "^4.4.34",
37
+ "@xyo-network/archivist-abstract": "^3.6.9",
38
+ "@xyo-network/archivist-model": "^3.6.9",
39
+ "@xyo-network/module-model": "^3.6.9",
40
+ "@xyo-network/payload-builder": "^3.6.9",
41
+ "@xyo-network/payload-model": "^3.6.9",
42
42
  "idb": "^8.0.1"
43
43
  },
44
44
  "devDependencies": {
45
- "@xylabs/delay": "^4.4.27",
46
- "@xylabs/object": "^4.4.27",
45
+ "@xylabs/delay": "^4.4.34",
46
+ "@xylabs/object": "^4.4.34",
47
47
  "@xylabs/ts-scripts-yarn3": "^4.2.6",
48
48
  "@xylabs/tsconfig": "^4.2.6",
49
- "@xyo-network/account": "^3.6.7",
50
- "@xyo-network/id-payload-plugin": "^3.6.7",
51
- "@xyo-network/payload-wrapper": "^3.6.7",
49
+ "@xyo-network/account": "^3.6.9",
50
+ "@xyo-network/id-payload-plugin": "^3.6.9",
51
+ "@xyo-network/payload-wrapper": "^3.6.9",
52
52
  "fake-indexeddb": "^6.0.0",
53
53
  "typescript": "^5.7.2",
54
54
  "vitest": "^2.1.8"
package/src/Archivist.ts CHANGED
@@ -2,6 +2,11 @@ import { uniq } from '@xylabs/array'
2
2
  import { assertEx } from '@xylabs/assert'
3
3
  import { exists } from '@xylabs/exists'
4
4
  import { Hash, Hex } from '@xylabs/hex'
5
+ import {
6
+ ObjectStore,
7
+ withDb,
8
+ withReadOnlyStore, withReadWriteStore,
9
+ } from '@xylabs/indexed-db'
5
10
  import { AbstractArchivist } from '@xyo-network/archivist-abstract'
6
11
  import {
7
12
  ArchivistAllQuerySchema,
@@ -17,18 +22,11 @@ import {
17
22
  import { creatableModule } from '@xyo-network/module-model'
18
23
  import { PayloadBuilder } from '@xyo-network/payload-builder'
19
24
  import {
20
- Payload, Schema, SequenceConstants, WithStorageMeta,
25
+ Payload, Schema, WithStorageMeta,
21
26
  } from '@xyo-network/payload-model'
22
- import {
23
- IDBPCursorWithValue, IDBPDatabase, openDB,
24
- } from 'idb'
27
+ import { IDBPCursorWithValue, IDBPDatabase } from 'idb'
25
28
 
26
29
  import { IndexedDbArchivistConfigSchema } from './Config.ts'
27
- import {
28
- createStore,
29
- getExistingIndexes,
30
- useDb, useReadOnlyStore, useReadWriteStore,
31
- } from './IndexedDbHelpers.ts'
32
30
  import { IndexedDbArchivistParams } from './Params.ts'
33
31
 
34
32
  export interface PayloadStore {
@@ -189,7 +187,7 @@ export class IndexedDbArchivist<
189
187
  }
190
188
 
191
189
  protected async getFromCursor(
192
- db: IDBPDatabase<PayloadStore>,
190
+ db: IDBPDatabase<ObjectStore>,
193
191
  storeName: string,
194
192
  order: 'asc' | 'desc' = 'asc',
195
193
  limit: number = 10,
@@ -197,9 +195,9 @@ export class IndexedDbArchivist<
197
195
  ): Promise<WithStorageMeta[]> {
198
196
  // TODO: We have to handle the case where the cursor is not found, and then find the correct cursor to start with (thunked cursor)
199
197
 
200
- return await useReadOnlyStore(db, storeName, async (store) => {
201
- const sequenceIndex = assertEx(store.index(IndexedDbArchivist.sequenceIndexName), () => 'Failed to get sequence index')
202
- let sequenceCursor: IDBPCursorWithValue<PayloadStore, [string]> | null | undefined
198
+ return await withReadOnlyStore(db, storeName, async (store) => {
199
+ const sequenceIndex = assertEx(store?.index(IndexedDbArchivist.sequenceIndexName), () => 'Failed to get sequence index')
200
+ let sequenceCursor: IDBPCursorWithValue<ObjectStore, [string]> | null | undefined
203
201
  const parsedCursor = cursor
204
202
  ? order === 'asc'
205
203
  ? IDBKeyRange.lowerBound(cursor, false)
@@ -245,22 +243,24 @@ export class IndexedDbArchivist<
245
243
  * @returns The primary key and the payload, or undefined if not found
246
244
  */
247
245
  protected async getFromIndexWithPrimaryKey(
248
- db: IDBPDatabase<PayloadStore>,
246
+ db: IDBPDatabase<ObjectStore>,
249
247
  storeName: string,
250
248
  indexName: string,
251
249
  key: IDBValidKey,
252
250
  ): Promise<[number, WithStorageMeta] | undefined> {
253
- return await useReadOnlyStore(db, storeName, async (store) => {
254
- const index = store.index(indexName)
255
- const cursor = await index.openCursor(key)
256
- if (cursor) {
257
- const singleValue = cursor.value
258
- // NOTE: It's known to be a number because we are using IndexedDB supplied auto-incrementing keys
259
- if (typeof cursor.primaryKey !== 'number') {
260
- throw new TypeError('primaryKey must be a number')
261
- }
251
+ return await withReadOnlyStore(db, storeName, async (store) => {
252
+ if (store) {
253
+ const index = store.index(indexName)
254
+ const cursor = await index.openCursor(key)
255
+ if (cursor) {
256
+ const singleValue = cursor.value
257
+ // NOTE: It's known to be a number because we are using IndexedDB supplied auto-incrementing keys
258
+ if (typeof cursor.primaryKey !== 'number') {
259
+ throw new TypeError('primaryKey must be a number')
260
+ }
262
261
 
263
- return [cursor.primaryKey, singleValue]
262
+ return [cursor.primaryKey, singleValue]
263
+ }
264
264
  }
265
265
  })
266
266
  }
@@ -306,21 +306,25 @@ export class IndexedDbArchivist<
306
306
  // with respect to checking for the pre-existence of the hash.
307
307
  // This is done to prevent duplicate root hashes due to race
308
308
  // conditions between checking vs insertion.
309
- return await useReadWriteStore(db, this.storeName, async (store) => {
309
+ return await withReadWriteStore(db, this.storeName, async (store) => {
310
310
  // Return only the payloads that were successfully inserted
311
- const inserted: WithStorageMeta<Payload>[] = []
312
- await Promise.all(
313
- payloads.map(async (payload) => {
311
+ if (store) {
312
+ const inserted: WithStorageMeta<Payload>[] = []
313
+ await Promise.all(
314
+ payloads.map(async (payload) => {
314
315
  // only insert if hash does not already exist
315
- if (!await store.index(IndexedDbArchivist.hashIndexName).get(payload._hash)) {
316
- // Insert the payload
317
- await store.put(payload)
318
- // Add it to the inserted list
319
- inserted.push(payload)
320
- }
321
- }),
322
- )
323
- return inserted
316
+ if (!await store.index(IndexedDbArchivist.hashIndexName).get(payload._hash)) {
317
+ // Insert the payload
318
+ await store.put(payload)
319
+ // Add it to the inserted list
320
+ inserted.push(payload)
321
+ }
322
+ }),
323
+ )
324
+ return inserted
325
+ } else {
326
+ throw new Error('Failed to get store')
327
+ }
324
328
  })
325
329
  })
326
330
  }
@@ -342,107 +346,14 @@ export class IndexedDbArchivist<
342
346
  return true
343
347
  }
344
348
 
345
- private async checkIndexes(db: IDBPDatabase<PayloadStore>): Promise<IndexDescription[]> {
346
- const { indexes, storeName } = this
347
- if (db.objectStoreNames.contains(storeName)) {
348
- const existingIndexes = await getExistingIndexes(db, storeName)
349
- const existingIndexNames = new Set(existingIndexes.map(({ name }) => name).filter(exists))
350
- for (const { key, unique } of indexes) {
351
- const indexName = buildStandardIndexName({ key, unique })
352
- if (!existingIndexNames.has(indexName)) {
353
- // the index is missing, so trigger an upgrade
354
- this._dbVersion = this._dbVersion === undefined ? 0 : this._dbVersion + 1
355
- break
356
- }
357
- }
358
- return existingIndexes
359
- }
360
- return []
361
- }
362
-
363
- private async checkObjectStore(): Promise<IndexDescription[]> {
364
- const { dbName, storeName } = this
365
- return await useDb(dbName, (db) => {
366
- // we check the version here to see if someone else upgraded it past where we think we are
367
- if (db.version >= (this._dbVersion ?? 0)) {
368
- this._dbVersion = db.version
369
- }
370
- if (db.objectStoreNames.contains(storeName)) {
371
- return this.checkIndexes(db)
372
- } else {
373
- this._dbVersion = (this._dbVersion ?? 0) + 1
374
- return []
375
- }
376
- })
377
- }
378
-
379
- /**
380
- * Returns that the desired DB/Store initialized to the correct version
381
- * @returns The initialized DB
382
- */
383
- private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {
384
- const existingIndexes = await this.checkObjectStore()
385
- const {
386
- dbName, dbVersion, indexes, storeName, logger,
387
- } = this
388
- return await openDB<PayloadStore>(dbName, dbVersion, {
389
- blocked(currentVersion, blockedVersion, event) {
390
- logger.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)
391
- },
392
- blocking(currentVersion, blockedVersion, event) {
393
- logger.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)
394
- },
395
- terminated() {
396
- logger.log('IndexedDbArchivist: Terminated')
397
- },
398
- upgrade(database, oldVersion, newVersion, transaction) {
399
- // NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end
400
- // state but, out of an abundance of caution, we will just delete (so we know where we are starting
401
- // from a known good point) and recreate the desired state. This prioritizes resilience over data
402
- // retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser
403
- // state is extremely hard to debug, this seems like fair tradeoff for now.
404
- if (oldVersion !== newVersion) {
405
- logger.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)
406
- // Delete any existing databases that are not the current version
407
- const objectStores = transaction.objectStoreNames
408
- for (const name of objectStores) {
409
- try {
410
- database.deleteObjectStore(name)
411
- } catch {
412
- logger.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)
413
- }
414
- }
415
- }
416
- // keep any indexes that were there before but are not required by this config
417
- // we do this incase there are two or more configs trying to use the db and they have mismatched indexes, so they do not erase each other's indexes
418
- const existingIndexesToKeep = existingIndexes.filter(({ name: existingName }) => !indexes.some(({ name }) => name === existingName))
419
- console.log('existingIndexes', existingIndexes)
420
- console.log('existingIndexesToKeep', existingIndexesToKeep)
421
- console.log('indexes', indexes)
422
- const indexesToCreate = indexes.map(idx => ({
423
- ...idx,
424
- name: buildStandardIndexName(idx),
425
- // eslint-disable-next-line unicorn/no-array-reduce
426
- })).reduce((acc, idx) => acc.set(idx.name, idx), new Map<string, IndexDescription>()).values()
427
- createStore(database, storeName, [...indexesToCreate], logger)
428
- },
429
- })
430
- }
431
-
432
349
  /**
433
350
  * Executes a callback with the initialized DB and then closes the db
434
351
  * @param callback The method to execute with the initialized DB
435
352
  * @returns
436
353
  */
437
- private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {
438
- // Get the initialized DB
439
- const db = await this.getInitializedDb()
440
- try {
441
- // Perform the callback
354
+ private async useDb<T>(callback: (db: IDBPDatabase<ObjectStore>) => Promise<T> | T): Promise<T> {
355
+ return await withDb<ObjectStore, T>(this.dbName, async (db) => {
442
356
  return await callback(db)
443
- } finally {
444
- // Close the DB
445
- db.close()
446
- }
357
+ }, { [this.storeName]: this.indexes }, this.logger)
447
358
  }
448
359
  }
package/src/Config.ts CHANGED
@@ -5,7 +5,7 @@ import { IndexedDbArchivistSchema } from './Schema.ts'
5
5
  export type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`
6
6
  export const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`
7
7
 
8
- export type IndexedDbArchivistConfig = ArchivistConfig<{
8
+ export type IndexedDbArchivistConfig<TStoreName extends string = string> = ArchivistConfig<{
9
9
  /**
10
10
  * The database name
11
11
  */
@@ -18,5 +18,5 @@ export type IndexedDbArchivistConfig = ArchivistConfig<{
18
18
  /**
19
19
  * The name of the object store
20
20
  */
21
- storeName?: string
21
+ storeName?: TStoreName
22
22
  }>
@@ -1,10 +0,0 @@
1
- import type { Logger } from '@xylabs/logger';
2
- import type { IndexDescription } from '@xyo-network/archivist-model';
3
- import type { IDBPDatabase, IDBPObjectStore } from 'idb';
4
- import type { PayloadStore } from './Archivist.ts';
5
- export declare function createStore(db: IDBPDatabase<PayloadStore>, storeName: string, indexes: IndexDescription[], logger?: Logger): void;
6
- export declare function getExistingIndexes(db: IDBPDatabase<PayloadStore>, storeName: string): Promise<IndexDescription[]>;
7
- export declare function useDb<T>(dbName: string, callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T>;
8
- export declare function useReadOnlyStore<T>(db: IDBPDatabase<PayloadStore>, storeName: string, callback: (store: IDBPObjectStore<PayloadStore, [string], string, 'readonly'>) => Promise<T> | T): Promise<T>;
9
- export declare function useReadWriteStore<T>(db: IDBPDatabase<PayloadStore>, storeName: string, callback: (store: IDBPObjectStore<PayloadStore, [string], string, 'readwrite'>) => Promise<T> | T): Promise<T>;
10
- //# sourceMappingURL=IndexedDbHelpers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"IndexedDbHelpers.d.ts","sourceRoot":"","sources":["../../src/IndexedDbHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,8BAA8B,CAAA;AAEpF,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,KAAK,CAAA;AAGxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAElD,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,QAmB1H;AAED,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAqBvH;AAED,wBAAsB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOvH;AAED,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,YAAY,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAC/F,OAAO,CAAC,CAAC,CAAC,CAQZ;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,YAAY,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAChG,OAAO,CAAC,CAAC,CAAC,CAQZ"}
@@ -1,88 +0,0 @@
1
- import type { Logger } from '@xylabs/logger'
2
- import type { IndexDescription, IndexDirection } from '@xyo-network/archivist-model'
3
- import { buildStandardIndexName } from '@xyo-network/archivist-model'
4
- import type { IDBPDatabase, IDBPObjectStore } from 'idb'
5
- import { openDB } from 'idb'
6
-
7
- import type { PayloadStore } from './Archivist.ts'
8
-
9
- export function createStore(db: IDBPDatabase<PayloadStore>, storeName: string, indexes: IndexDescription[], logger?: Logger) {
10
- logger?.log(`Creating store ${storeName}`)
11
- // Create the store
12
- const store = db.createObjectStore(storeName, {
13
- // If it isn't explicitly set, create a value by auto incrementing.
14
- autoIncrement: true,
15
- })
16
- // Name the store
17
- store.name = storeName
18
- // Create an index on the hash
19
- for (const {
20
- key, multiEntry, unique,
21
- } of indexes) {
22
- const indexKeys = Object.keys(key)
23
- const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys
24
- const indexName = buildStandardIndexName({ key, unique })
25
- console.log('createIndex', indexName, keys, { multiEntry, unique })
26
- store.createIndex(indexName, keys, { multiEntry, unique })
27
- }
28
- }
29
-
30
- export async function getExistingIndexes(db: IDBPDatabase<PayloadStore>, storeName: string): Promise<IndexDescription[]> {
31
- return await useReadOnlyStore(db, storeName, (store) => {
32
- return [...store.indexNames].map((indexName) => {
33
- const index = store.index(indexName)
34
- const key: Record<string, IndexDirection> = {}
35
- if (Array.isArray(index.keyPath)) {
36
- for (const keyPath of index.keyPath) {
37
- key[keyPath] = 1
38
- }
39
- } else {
40
- key[index.keyPath] = 1
41
- }
42
- const desc: IndexDescription = {
43
- name: indexName,
44
- key,
45
- unique: index.unique,
46
- multiEntry: index.multiEntry,
47
- }
48
- return desc
49
- })
50
- })
51
- }
52
-
53
- export async function useDb<T>(dbName: string, callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {
54
- const db = await openDB<PayloadStore>(dbName)
55
- try {
56
- return await callback(db)
57
- } finally {
58
- db.close()
59
- }
60
- }
61
-
62
- export async function useReadOnlyStore<T>(
63
- db: IDBPDatabase<PayloadStore>,
64
- storeName: string,
65
- callback: (store: IDBPObjectStore<PayloadStore, [string], string, 'readonly'>) => Promise<T> | T,
66
- ): Promise<T> {
67
- const transaction = db.transaction(storeName, 'readonly')
68
- const store = transaction.objectStore(storeName)
69
- try {
70
- return await callback(store)
71
- } finally {
72
- await transaction.done
73
- }
74
- }
75
-
76
- export async function useReadWriteStore<T>(
77
- db: IDBPDatabase<PayloadStore>,
78
- storeName: string,
79
- callback: (store: IDBPObjectStore<PayloadStore, [string], string, 'readwrite'>) => Promise<T> | T,
80
- ): Promise<T> {
81
- const transaction = db.transaction(storeName, 'readwrite')
82
- const store = transaction.objectStore(storeName)
83
- try {
84
- return await callback(store)
85
- } finally {
86
- await transaction.done
87
- }
88
- }