@xyo-network/diviner-payload-indexeddb 2.87.0-rc.6 → 2.87.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.
@@ -23,10 +23,14 @@ export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDiv
23
23
  * to `payloads`.
24
24
  */
25
25
  get storeName(): string;
26
- private get db();
27
26
  protected divineHandler(payloads?: TIn[]): Promise<TOut[]>;
28
27
  protected startHandler(): Promise<boolean>;
29
28
  private getKeyRangeValue;
30
29
  private selectBestIndex;
30
+ /**
31
+ * Checks that the desired DB/Store exists and is initialized
32
+ * @returns The initialized DB or undefined if it does not exist
33
+ */
34
+ private tryGetInitializedDb;
31
35
  }
32
36
  //# sourceMappingURL=Diviner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAoBxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED,OAAO,KAAK,EAAE,GAEb;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAwDhD,YAAY;IAKrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAoBxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAyDhD,YAAY;IAKrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;IA4BvB;;;OAGG;YACW,mBAAmB;CAoBlC"}
@@ -23,10 +23,14 @@ export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDiv
23
23
  * to `payloads`.
24
24
  */
25
25
  get storeName(): string;
26
- private get db();
27
26
  protected divineHandler(payloads?: TIn[]): Promise<TOut[]>;
28
27
  protected startHandler(): Promise<boolean>;
29
28
  private getKeyRangeValue;
30
29
  private selectBestIndex;
30
+ /**
31
+ * Checks that the desired DB/Store exists and is initialized
32
+ * @returns The initialized DB or undefined if it does not exist
33
+ */
34
+ private tryGetInitializedDb;
31
35
  }
32
36
  //# sourceMappingURL=Diviner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAoBxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED,OAAO,KAAK,EAAE,GAEb;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAwDhD,YAAY;IAKrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAoBxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAyDhD,YAAY;IAKrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;IA4BvB;;;OAGG;YACW,mBAAmB;CAoBlC"}
@@ -23,10 +23,14 @@ export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDiv
23
23
  * to `payloads`.
24
24
  */
25
25
  get storeName(): string;
26
- private get db();
27
26
  protected divineHandler(payloads?: TIn[]): Promise<TOut[]>;
28
27
  protected startHandler(): Promise<boolean>;
29
28
  private getKeyRangeValue;
30
29
  private selectBestIndex;
30
+ /**
31
+ * Checks that the desired DB/Store exists and is initialized
32
+ * @returns The initialized DB or undefined if it does not exist
33
+ */
34
+ private tryGetInitializedDb;
31
35
  }
32
36
  //# sourceMappingURL=Diviner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAoBxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED,OAAO,KAAK,EAAE,GAEb;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAwDhD,YAAY;IAKrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAoBxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAyDhD,YAAY;IAKrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;IA4BvB;;;OAGG;YACW,mBAAmB;CAoBlC"}
@@ -87,16 +87,15 @@ var IndexedDbPayloadDiviner = class extends import_diviner_payload_abstract.Payl
87
87
  get storeName() {
88
88
  return this.config?.storeName ?? import_archivist_indexeddb.IndexedDbArchivist.defaultStoreName;
89
89
  }
90
- get db() {
91
- return (0, import_assert.assertEx)(this._db, "DB not initialized");
92
- }
93
90
  async divineHandler(payloads) {
94
- const query = (0, import_assert.assertEx)(payloads?.filter(import_diviner_payload_model2.isPayloadDivinerQueryPayload)?.pop(), "Missing query payload");
91
+ const query = payloads?.filter(import_diviner_payload_model2.isPayloadDivinerQueryPayload)?.pop();
95
92
  if (!query)
96
93
  return [];
97
- this._db = await (0, import_idb.openDB)(this.dbName, this.dbVersion);
94
+ const db = await this.tryGetInitializedDb();
95
+ if (!db)
96
+ return [];
98
97
  const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query;
99
- const tx = this.db.transaction(this.storeName, "readonly");
98
+ const tx = db.transaction(this.storeName, "readonly");
100
99
  const store = tx.objectStore(this.storeName);
101
100
  const results = [];
102
101
  let parsedOffset = offset ?? 0;
@@ -170,5 +169,24 @@ var IndexedDbPayloadDiviner = class extends import_diviner_payload_abstract.Payl
170
169
  }
171
170
  return bestMatch.matchCount > 0 ? bestMatch.indexName : null;
172
171
  }
172
+ /**
173
+ * Checks that the desired DB/Store exists and is initialized
174
+ * @returns The initialized DB or undefined if it does not exist
175
+ */
176
+ async tryGetInitializedDb() {
177
+ if (this._db)
178
+ return this._db;
179
+ const dbs = await indexedDB.databases();
180
+ const dbExists = dbs.some((db2) => {
181
+ return db2.name === this.dbName && db2.version === this.dbVersion;
182
+ });
183
+ if (!dbExists)
184
+ return;
185
+ const db = await (0, import_idb.openDB)(this.dbName, this.dbVersion);
186
+ const storeExists = db.objectStoreNames.contains(this.storeName);
187
+ if (storeExists)
188
+ this._db = db;
189
+ return this._db;
190
+ }
173
191
  };
174
192
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts","../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["export * from './Config'\nexport * from './Diviner'\nexport * from './Params'\nexport * from './Schema'\n","import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n","import { containsAll } from '@xylabs/array'\nimport { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\ntype AnyPayload = Payload<Record<string, unknown>>\n\ntype ValueFilter = (payload?: AnyPayload | null) => boolean\n\nconst payloadValueFilter = (key: keyof AnyPayload, value?: unknown | unknown[]): ValueFilter | undefined => {\n if (!value) return undefined\n return (payload) => {\n if (!payload) return false\n const sourceValue = payload?.[key]\n if (sourceValue === undefined) return false\n return Array.isArray(sourceValue) && Array.isArray(value) ? containsAll(sourceValue, value) : sourceValue == value\n }\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the archivist's name and if archivist's name is not supplied,\n * 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 return this.config?.dbName ?? this.config?.archivist ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to the archivist default version.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\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 return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')\n if (!query) return []\n this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = this.db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)\n const valueFilters: ValueFilter[] = props\n ? Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n const value = cursor.value\n if (value) {\n // If we're filtering on more than just the schema\n if (valueFilters.length > 0) {\n // Ensure all filters pass\n if (valueFilters.every((filter) => filter(value))) {\n // Then save the value\n results.push(value)\n }\n } else {\n // Otherwise just save the value\n results.push(value)\n }\n }\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\n }\n\n protected override async startHandler() {\n await super.startHandler()\n return true\n }\n\n private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])\n return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n}\n"],"mappings":"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","names":["IndexedDbPayloadDivinerSchema","PayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","import_diviner_payload_model","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","db","assertEx","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","openDB","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","length","filterSchema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","PayloadHasher","jsonPayload","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has"]}
1
+ {"version":3,"sources":["../../src/index.ts","../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["export * from './Config'\nexport * from './Diviner'\nexport * from './Params'\nexport * from './Schema'\n","import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n","import { containsAll } from '@xylabs/array'\nimport { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\ntype AnyPayload = Payload<Record<string, unknown>>\n\ntype ValueFilter = (payload?: AnyPayload | null) => boolean\n\nconst payloadValueFilter = (key: keyof AnyPayload, value?: unknown | unknown[]): ValueFilter | undefined => {\n if (!value) return undefined\n return (payload) => {\n if (!payload) return false\n const sourceValue = payload?.[key]\n if (sourceValue === undefined) return false\n return Array.isArray(sourceValue) && Array.isArray(value) ? containsAll(sourceValue, value) : sourceValue == value\n }\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the archivist's name and if archivist's name is not supplied,\n * 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 return this.config?.dbName ?? this.config?.archivist ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to the archivist default version.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\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 return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = payloads?.filter(isPayloadDivinerQueryPayload)?.pop()\n if (!query) return []\n const db = await this.tryGetInitializedDb()\n if (!db) return []\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)\n const valueFilters: ValueFilter[] = props\n ? Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n const value = cursor.value\n if (value) {\n // If we're filtering on more than just the schema\n if (valueFilters.length > 0) {\n // Ensure all filters pass\n if (valueFilters.every((filter) => filter(value))) {\n // Then save the value\n results.push(value)\n }\n } else {\n // Otherwise just save the value\n results.push(value)\n }\n }\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\n }\n\n protected override async startHandler() {\n await super.startHandler()\n return true\n }\n\n private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])\n return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n\n /**\n * Checks that the desired DB/Store exists and is initialized\n * @returns The initialized DB or undefined if it does not exist\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // If we've already checked and found a successfully initialized\n // db and objectStore, return the cached value\n if (this._db) return this._db\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n const dbExists = dbs.some((db) => {\n // Check for the desired name/version\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB does not exist at the desired version, return undefined\n if (!dbExists) return\n // If the db does exist, open it\n const db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n // Check that the desired objectStore exists\n const storeExists = db.objectStoreNames.contains(this.storeName)\n // If the correct db/store exists, cache it for future calls\n if (storeExists) this._db = db\n return this._db\n }\n}\n"],"mappings":"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","names":["IndexedDbPayloadDivinerSchema","PayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","import_diviner_payload_model","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","db","tryGetInitializedDb","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","PayloadHasher","jsonPayload","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains"]}
@@ -61,16 +61,15 @@ var IndexedDbPayloadDiviner = class extends PayloadDiviner {
61
61
  get storeName() {
62
62
  return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName;
63
63
  }
64
- get db() {
65
- return assertEx(this._db, "DB not initialized");
66
- }
67
64
  async divineHandler(payloads) {
68
- const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), "Missing query payload");
65
+ const query = payloads?.filter(isPayloadDivinerQueryPayload)?.pop();
69
66
  if (!query)
70
67
  return [];
71
- this._db = await openDB(this.dbName, this.dbVersion);
68
+ const db = await this.tryGetInitializedDb();
69
+ if (!db)
70
+ return [];
72
71
  const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query;
73
- const tx = this.db.transaction(this.storeName, "readonly");
72
+ const tx = db.transaction(this.storeName, "readonly");
74
73
  const store = tx.objectStore(this.storeName);
75
74
  const results = [];
76
75
  let parsedOffset = offset ?? 0;
@@ -144,6 +143,25 @@ var IndexedDbPayloadDiviner = class extends PayloadDiviner {
144
143
  }
145
144
  return bestMatch.matchCount > 0 ? bestMatch.indexName : null;
146
145
  }
146
+ /**
147
+ * Checks that the desired DB/Store exists and is initialized
148
+ * @returns The initialized DB or undefined if it does not exist
149
+ */
150
+ async tryGetInitializedDb() {
151
+ if (this._db)
152
+ return this._db;
153
+ const dbs = await indexedDB.databases();
154
+ const dbExists = dbs.some((db2) => {
155
+ return db2.name === this.dbName && db2.version === this.dbVersion;
156
+ });
157
+ if (!dbExists)
158
+ return;
159
+ const db = await openDB(this.dbName, this.dbVersion);
160
+ const storeExists = db.objectStoreNames.contains(this.storeName);
161
+ if (storeExists)
162
+ this._db = db;
163
+ return this._db;
164
+ }
147
165
  };
148
166
  export {
149
167
  IndexedDbPayloadDiviner,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n","import { containsAll } from '@xylabs/array'\nimport { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\ntype AnyPayload = Payload<Record<string, unknown>>\n\ntype ValueFilter = (payload?: AnyPayload | null) => boolean\n\nconst payloadValueFilter = (key: keyof AnyPayload, value?: unknown | unknown[]): ValueFilter | undefined => {\n if (!value) return undefined\n return (payload) => {\n if (!payload) return false\n const sourceValue = payload?.[key]\n if (sourceValue === undefined) return false\n return Array.isArray(sourceValue) && Array.isArray(value) ? containsAll(sourceValue, value) : sourceValue == value\n }\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the archivist's name and if archivist's name is not supplied,\n * 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 return this.config?.dbName ?? this.config?.archivist ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to the archivist default version.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\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 return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')\n if (!query) return []\n this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = this.db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)\n const valueFilters: ValueFilter[] = props\n ? Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n const value = cursor.value\n if (value) {\n // If we're filtering on more than just the schema\n if (valueFilters.length > 0) {\n // Ensure all filters pass\n if (valueFilters.every((filter) => filter(value))) {\n // Then save the value\n results.push(value)\n }\n } else {\n // Otherwise just save the value\n results.push(value)\n }\n }\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\n }\n\n protected override async startHandler() {\n await super.startHandler()\n return true\n }\n\n private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])\n return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","containsAll","assertEx","exists","IndexedDbArchivist","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadHasher","openDB","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","db","assertEx","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","openDB","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","length","filterSchema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","PayloadHasher","jsonPayload","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has"]}
1
+ {"version":3,"sources":["../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n","import { containsAll } from '@xylabs/array'\nimport { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\ntype AnyPayload = Payload<Record<string, unknown>>\n\ntype ValueFilter = (payload?: AnyPayload | null) => boolean\n\nconst payloadValueFilter = (key: keyof AnyPayload, value?: unknown | unknown[]): ValueFilter | undefined => {\n if (!value) return undefined\n return (payload) => {\n if (!payload) return false\n const sourceValue = payload?.[key]\n if (sourceValue === undefined) return false\n return Array.isArray(sourceValue) && Array.isArray(value) ? containsAll(sourceValue, value) : sourceValue == value\n }\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the archivist's name and if archivist's name is not supplied,\n * 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 return this.config?.dbName ?? this.config?.archivist ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to the archivist default version.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\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 return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = payloads?.filter(isPayloadDivinerQueryPayload)?.pop()\n if (!query) return []\n const db = await this.tryGetInitializedDb()\n if (!db) return []\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)\n const valueFilters: ValueFilter[] = props\n ? Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n const value = cursor.value\n if (value) {\n // If we're filtering on more than just the schema\n if (valueFilters.length > 0) {\n // Ensure all filters pass\n if (valueFilters.every((filter) => filter(value))) {\n // Then save the value\n results.push(value)\n }\n } else {\n // Otherwise just save the value\n results.push(value)\n }\n }\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\n }\n\n protected override async startHandler() {\n await super.startHandler()\n return true\n }\n\n private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])\n return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n\n /**\n * Checks that the desired DB/Store exists and is initialized\n * @returns The initialized DB or undefined if it does not exist\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // If we've already checked and found a successfully initialized\n // db and objectStore, return the cached value\n if (this._db) return this._db\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n const dbExists = dbs.some((db) => {\n // Check for the desired name/version\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB does not exist at the desired version, return undefined\n if (!dbExists) return\n // If the db does exist, open it\n const db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n // Check that the desired objectStore exists\n const storeExists = db.objectStoreNames.contains(this.storeName)\n // If the correct db/store exists, cache it for future calls\n if (storeExists) this._db = db\n return this._db\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","containsAll","assertEx","exists","IndexedDbArchivist","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadHasher","openDB","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","db","tryGetInitializedDb","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","PayloadHasher","jsonPayload","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains"]}
@@ -23,10 +23,14 @@ export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDiv
23
23
  * to `payloads`.
24
24
  */
25
25
  get storeName(): string;
26
- private get db();
27
26
  protected divineHandler(payloads?: TIn[]): Promise<TOut[]>;
28
27
  protected startHandler(): Promise<boolean>;
29
28
  private getKeyRangeValue;
30
29
  private selectBestIndex;
30
+ /**
31
+ * Checks that the desired DB/Store exists and is initialized
32
+ * @returns The initialized DB or undefined if it does not exist
33
+ */
34
+ private tryGetInitializedDb;
31
35
  }
32
36
  //# sourceMappingURL=Diviner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAoBxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED,OAAO,KAAK,EAAE,GAEb;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAwDhD,YAAY;IAKrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAoBxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAyDhD,YAAY;IAKrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;IA4BvB;;;OAGG;YACW,mBAAmB;CAoBlC"}
@@ -23,10 +23,14 @@ export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDiv
23
23
  * to `payloads`.
24
24
  */
25
25
  get storeName(): string;
26
- private get db();
27
26
  protected divineHandler(payloads?: TIn[]): Promise<TOut[]>;
28
27
  protected startHandler(): Promise<boolean>;
29
28
  private getKeyRangeValue;
30
29
  private selectBestIndex;
30
+ /**
31
+ * Checks that the desired DB/Store exists and is initialized
32
+ * @returns The initialized DB or undefined if it does not exist
33
+ */
34
+ private tryGetInitializedDb;
31
35
  }
32
36
  //# sourceMappingURL=Diviner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAoBxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED,OAAO,KAAK,EAAE,GAEb;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAwDhD,YAAY;IAKrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAoBxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAyDhD,YAAY;IAKrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;IA4BvB;;;OAGG;YACW,mBAAmB;CAoBlC"}
@@ -23,10 +23,14 @@ export declare class IndexedDbPayloadDiviner<TParams extends IndexedDbPayloadDiv
23
23
  * to `payloads`.
24
24
  */
25
25
  get storeName(): string;
26
- private get db();
27
26
  protected divineHandler(payloads?: TIn[]): Promise<TOut[]>;
28
27
  protected startHandler(): Promise<boolean>;
29
28
  private getKeyRangeValue;
30
29
  private selectBestIndex;
30
+ /**
31
+ * Checks that the desired DB/Store exists and is initialized
32
+ * @returns The initialized DB or undefined if it does not exist
33
+ */
34
+ private tryGetInitializedDb;
31
35
  }
32
36
  //# sourceMappingURL=Diviner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAoBxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;IAED,OAAO,KAAK,EAAE,GAEb;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAwDhD,YAAY;IAKrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"Diviner.d.ts","sourceRoot":"","sources":["../../src/Diviner.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAgC,0BAA0B,EAAE,MAAM,oCAAoC,CAAA;AAG7G,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAA;AAIpD,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAA;AAoBxD,qBAAa,uBAAuB,CAClC,OAAO,SAAS,6BAA6B,GAAG,6BAA6B,EAC7E,GAAG,SAAS,0BAA0B,GAAG,0BAA0B,EACnE,IAAI,SAAS,OAAO,GAAG,OAAO,EAC9B,UAAU,SAAS,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CACxI,SAAQ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC;IACtD,OAAgB,aAAa,WAAwC;IAErE,OAAO,CAAC,GAAG,CAAwC;IAEnD;;;;;;OAMG;IACH,IAAI,MAAM,WAET;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED;;;OAGG;IACH,IAAI,SAAS,WAEZ;cAEwB,aAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;cAyDhD,YAAY;IAKrC,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;IA4BvB;;;OAGG;YACW,mBAAmB;CAoBlC"}
@@ -89,17 +89,16 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends import_div
89
89
  var _a;
90
90
  return ((_a = this.config) == null ? void 0 : _a.storeName) ?? import_archivist_indexeddb.IndexedDbArchivist.defaultStoreName;
91
91
  }
92
- get db() {
93
- return (0, import_assert.assertEx)(this._db, "DB not initialized");
94
- }
95
92
  async divineHandler(payloads) {
96
93
  var _a;
97
- const query = (0, import_assert.assertEx)((_a = payloads == null ? void 0 : payloads.filter(import_diviner_payload_model2.isPayloadDivinerQueryPayload)) == null ? void 0 : _a.pop(), "Missing query payload");
94
+ const query = (_a = payloads == null ? void 0 : payloads.filter(import_diviner_payload_model2.isPayloadDivinerQueryPayload)) == null ? void 0 : _a.pop();
98
95
  if (!query)
99
96
  return [];
100
- this._db = await (0, import_idb.openDB)(this.dbName, this.dbVersion);
97
+ const db = await this.tryGetInitializedDb();
98
+ if (!db)
99
+ return [];
101
100
  const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query;
102
- const tx = this.db.transaction(this.storeName, "readonly");
101
+ const tx = db.transaction(this.storeName, "readonly");
103
102
  const store = tx.objectStore(this.storeName);
104
103
  const results = [];
105
104
  let parsedOffset = offset ?? 0;
@@ -173,6 +172,25 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends import_div
173
172
  }
174
173
  return bestMatch.matchCount > 0 ? bestMatch.indexName : null;
175
174
  }
175
+ /**
176
+ * Checks that the desired DB/Store exists and is initialized
177
+ * @returns The initialized DB or undefined if it does not exist
178
+ */
179
+ async tryGetInitializedDb() {
180
+ if (this._db)
181
+ return this._db;
182
+ const dbs = await indexedDB.databases();
183
+ const dbExists = dbs.some((db2) => {
184
+ return db2.name === this.dbName && db2.version === this.dbVersion;
185
+ });
186
+ if (!dbExists)
187
+ return;
188
+ const db = await (0, import_idb.openDB)(this.dbName, this.dbVersion);
189
+ const storeExists = db.objectStoreNames.contains(this.storeName);
190
+ if (storeExists)
191
+ this._db = db;
192
+ return this._db;
193
+ }
176
194
  };
177
195
  __name(_IndexedDbPayloadDiviner, "IndexedDbPayloadDiviner");
178
196
  __publicField(_IndexedDbPayloadDiviner, "configSchemas", [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts","../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["export * from './Config'\nexport * from './Diviner'\nexport * from './Params'\nexport * from './Schema'\n","import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n","import { containsAll } from '@xylabs/array'\nimport { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\ntype AnyPayload = Payload<Record<string, unknown>>\n\ntype ValueFilter = (payload?: AnyPayload | null) => boolean\n\nconst payloadValueFilter = (key: keyof AnyPayload, value?: unknown | unknown[]): ValueFilter | undefined => {\n if (!value) return undefined\n return (payload) => {\n if (!payload) return false\n const sourceValue = payload?.[key]\n if (sourceValue === undefined) return false\n return Array.isArray(sourceValue) && Array.isArray(value) ? containsAll(sourceValue, value) : sourceValue == value\n }\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the archivist's name and if archivist's name is not supplied,\n * 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 return this.config?.dbName ?? this.config?.archivist ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to the archivist default version.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\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 return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')\n if (!query) return []\n this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = this.db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)\n const valueFilters: ValueFilter[] = props\n ? Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n const value = cursor.value\n if (value) {\n // If we're filtering on more than just the schema\n if (valueFilters.length > 0) {\n // Ensure all filters pass\n if (valueFilters.every((filter) => filter(value))) {\n // Then save the value\n results.push(value)\n }\n } else {\n // Otherwise just save the value\n results.push(value)\n }\n }\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\n }\n\n protected override async startHandler() {\n await super.startHandler()\n return true\n }\n\n private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])\n return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n}\n"],"mappings":"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","names":["IndexedDbPayloadDivinerSchema","PayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","import_diviner_payload_model","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","db","assertEx","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","openDB","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","length","filterSchema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","PayloadHasher","jsonPayload","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","configSchemas","IndexedDbPayloadDivinerConfigSchema"]}
1
+ {"version":3,"sources":["../../src/index.ts","../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["export * from './Config'\nexport * from './Diviner'\nexport * from './Params'\nexport * from './Schema'\n","import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n","import { containsAll } from '@xylabs/array'\nimport { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\ntype AnyPayload = Payload<Record<string, unknown>>\n\ntype ValueFilter = (payload?: AnyPayload | null) => boolean\n\nconst payloadValueFilter = (key: keyof AnyPayload, value?: unknown | unknown[]): ValueFilter | undefined => {\n if (!value) return undefined\n return (payload) => {\n if (!payload) return false\n const sourceValue = payload?.[key]\n if (sourceValue === undefined) return false\n return Array.isArray(sourceValue) && Array.isArray(value) ? containsAll(sourceValue, value) : sourceValue == value\n }\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the archivist's name and if archivist's name is not supplied,\n * 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 return this.config?.dbName ?? this.config?.archivist ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to the archivist default version.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\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 return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = payloads?.filter(isPayloadDivinerQueryPayload)?.pop()\n if (!query) return []\n const db = await this.tryGetInitializedDb()\n if (!db) return []\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)\n const valueFilters: ValueFilter[] = props\n ? Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n const value = cursor.value\n if (value) {\n // If we're filtering on more than just the schema\n if (valueFilters.length > 0) {\n // Ensure all filters pass\n if (valueFilters.every((filter) => filter(value))) {\n // Then save the value\n results.push(value)\n }\n } else {\n // Otherwise just save the value\n results.push(value)\n }\n }\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\n }\n\n protected override async startHandler() {\n await super.startHandler()\n return true\n }\n\n private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])\n return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n\n /**\n * Checks that the desired DB/Store exists and is initialized\n * @returns The initialized DB or undefined if it does not exist\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // If we've already checked and found a successfully initialized\n // db and objectStore, return the cached value\n if (this._db) return this._db\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n const dbExists = dbs.some((db) => {\n // Check for the desired name/version\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB does not exist at the desired version, return undefined\n if (!dbExists) return\n // If the db does exist, open it\n const db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n // Check that the desired objectStore exists\n const storeExists = db.objectStoreNames.contains(this.storeName)\n // If the correct db/store exists, cache it for future calls\n if (storeExists) this._db = db\n return this._db\n }\n}\n"],"mappings":"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","names":["IndexedDbPayloadDivinerSchema","PayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","import_diviner_payload_model","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","db","tryGetInitializedDb","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","PayloadHasher","jsonPayload","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","configSchemas","IndexedDbPayloadDivinerConfigSchema"]}
@@ -63,17 +63,16 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends PayloadDiv
63
63
  var _a;
64
64
  return ((_a = this.config) == null ? void 0 : _a.storeName) ?? IndexedDbArchivist.defaultStoreName;
65
65
  }
66
- get db() {
67
- return assertEx(this._db, "DB not initialized");
68
- }
69
66
  async divineHandler(payloads) {
70
67
  var _a;
71
- const query = assertEx((_a = payloads == null ? void 0 : payloads.filter(isPayloadDivinerQueryPayload)) == null ? void 0 : _a.pop(), "Missing query payload");
68
+ const query = (_a = payloads == null ? void 0 : payloads.filter(isPayloadDivinerQueryPayload)) == null ? void 0 : _a.pop();
72
69
  if (!query)
73
70
  return [];
74
- this._db = await openDB(this.dbName, this.dbVersion);
71
+ const db = await this.tryGetInitializedDb();
72
+ if (!db)
73
+ return [];
75
74
  const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query;
76
- const tx = this.db.transaction(this.storeName, "readonly");
75
+ const tx = db.transaction(this.storeName, "readonly");
77
76
  const store = tx.objectStore(this.storeName);
78
77
  const results = [];
79
78
  let parsedOffset = offset ?? 0;
@@ -147,6 +146,25 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends PayloadDiv
147
146
  }
148
147
  return bestMatch.matchCount > 0 ? bestMatch.indexName : null;
149
148
  }
149
+ /**
150
+ * Checks that the desired DB/Store exists and is initialized
151
+ * @returns The initialized DB or undefined if it does not exist
152
+ */
153
+ async tryGetInitializedDb() {
154
+ if (this._db)
155
+ return this._db;
156
+ const dbs = await indexedDB.databases();
157
+ const dbExists = dbs.some((db2) => {
158
+ return db2.name === this.dbName && db2.version === this.dbVersion;
159
+ });
160
+ if (!dbExists)
161
+ return;
162
+ const db = await openDB(this.dbName, this.dbVersion);
163
+ const storeExists = db.objectStoreNames.contains(this.storeName);
164
+ if (storeExists)
165
+ this._db = db;
166
+ return this._db;
167
+ }
150
168
  };
151
169
  __name(_IndexedDbPayloadDiviner, "IndexedDbPayloadDiviner");
152
170
  __publicField(_IndexedDbPayloadDiviner, "configSchemas", [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n","import { containsAll } from '@xylabs/array'\nimport { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\ntype AnyPayload = Payload<Record<string, unknown>>\n\ntype ValueFilter = (payload?: AnyPayload | null) => boolean\n\nconst payloadValueFilter = (key: keyof AnyPayload, value?: unknown | unknown[]): ValueFilter | undefined => {\n if (!value) return undefined\n return (payload) => {\n if (!payload) return false\n const sourceValue = payload?.[key]\n if (sourceValue === undefined) return false\n return Array.isArray(sourceValue) && Array.isArray(value) ? containsAll(sourceValue, value) : sourceValue == value\n }\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the archivist's name and if archivist's name is not supplied,\n * 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 return this.config?.dbName ?? this.config?.archivist ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to the archivist default version.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\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 return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')\n if (!query) return []\n this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = this.db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)\n const valueFilters: ValueFilter[] = props\n ? Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n const value = cursor.value\n if (value) {\n // If we're filtering on more than just the schema\n if (valueFilters.length > 0) {\n // Ensure all filters pass\n if (valueFilters.every((filter) => filter(value))) {\n // Then save the value\n results.push(value)\n }\n } else {\n // Otherwise just save the value\n results.push(value)\n }\n }\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\n }\n\n protected override async startHandler() {\n await super.startHandler()\n return true\n }\n\n private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])\n return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","containsAll","assertEx","exists","IndexedDbArchivist","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadHasher","openDB","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","db","assertEx","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","openDB","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","length","filterSchema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","PayloadHasher","jsonPayload","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","configSchemas","IndexedDbPayloadDivinerConfigSchema"]}
1
+ {"version":3,"sources":["../../src/Schema.ts","../../src/Config.ts","../../src/Diviner.ts"],"sourcesContent":["import { PayloadDivinerSchema } from '@xyo-network/diviner-payload-model'\n\nexport const IndexedDbPayloadDivinerSchema = `${PayloadDivinerSchema}.indexeddb`\nexport type IndexedDbPayloadDivinerSchema = typeof IndexedDbPayloadDivinerSchema\n","import { IndexDescription } from '@xyo-network/archivist-model'\nimport { DivinerConfig } from '@xyo-network/diviner-model'\n\nimport { IndexedDbPayloadDivinerSchema } from './Schema'\n\nexport const IndexedDbPayloadDivinerConfigSchema = `${IndexedDbPayloadDivinerSchema}.config`\nexport type IndexedDbPayloadDivinerConfigSchema = typeof IndexedDbPayloadDivinerConfigSchema\n\nexport type IndexedDbPayloadDivinerConfig = DivinerConfig<{\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: IndexedDbPayloadDivinerConfigSchema\n /**\n * The storage configuration\n * // TODO: Hoist to main diviner config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n","import { containsAll } from '@xylabs/array'\nimport { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerModule, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { AnyObject } from '@xyo-network/object'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, IDBPObjectStore, openDB } from 'idb'\n\nimport { IndexedDbPayloadDivinerConfigSchema } from './Config'\nimport { IndexedDbPayloadDivinerParams } from './Params'\n\ninterface PayloadStore {\n [s: string]: Payload\n}\n\ntype AnyPayload = Payload<Record<string, unknown>>\n\ntype ValueFilter = (payload?: AnyPayload | null) => boolean\n\nconst payloadValueFilter = (key: keyof AnyPayload, value?: unknown | unknown[]): ValueFilter | undefined => {\n if (!value) return undefined\n return (payload) => {\n if (!payload) return false\n const sourceValue = payload?.[key]\n if (sourceValue === undefined) return false\n return Array.isArray(sourceValue) && Array.isArray(value) ? containsAll(sourceValue, value) : sourceValue == value\n }\n}\n\nexport class IndexedDbPayloadDiviner<\n TParams extends IndexedDbPayloadDivinerParams = IndexedDbPayloadDivinerParams,\n TIn extends PayloadDivinerQueryPayload = PayloadDivinerQueryPayload,\n TOut extends Payload = Payload,\n TEventData extends DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut> = DivinerModuleEventData<DivinerModule<TParams>, TIn, TOut>,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [IndexedDbPayloadDivinerConfigSchema]\n\n private _db: IDBPDatabase<PayloadStore> | undefined\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the archivist's name and if archivist's name is not supplied,\n * 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 return this.config?.dbName ?? this.config?.archivist ?? IndexedDbArchivist.defaultDbName\n }\n\n /**\n * The database version. If not supplied via config, it defaults to the archivist default version.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\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 return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {\n const query = payloads?.filter(isPayloadDivinerQueryPayload)?.pop()\n if (!query) return []\n const db = await this.tryGetInitializedDb()\n if (!db) return []\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }\n const tx = db.transaction(this.storeName, 'readonly')\n const store = tx.objectStore(this.storeName)\n const results: TOut[] = []\n let parsedOffset = offset ?? 0\n const parsedLimit = limit ?? 10\n assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')\n const filterSchema = schemas?.[0]\n const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }\n const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'\n const suggestedIndex = this.selectBestIndex(filter, store)\n const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)\n const valueFilters: ValueFilter[] = props\n ? Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor = suggestedIndex\n ? // Conditionally filter on schemas\n await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)\n : // Just iterate all records\n await store.openCursor(suggestedIndex, direction)\n\n // Skip records until the offset is reached\n while (cursor && parsedOffset > 0) {\n cursor = await cursor.advance(parsedOffset)\n parsedOffset = 0 // Reset offset after skipping\n }\n // Collect results up to the limit\n while (cursor && results.length < parsedLimit) {\n const value = cursor.value\n if (value) {\n // If we're filtering on more than just the schema\n if (valueFilters.length > 0) {\n // Ensure all filters pass\n if (valueFilters.every((filter) => filter(value))) {\n // Then save the value\n results.push(value)\n }\n } else {\n // Otherwise just save the value\n results.push(value)\n }\n }\n cursor = await cursor.continue()\n }\n await tx.done\n // Remove any metadata before returning to the client\n return results.map((payload) => PayloadHasher.jsonPayload(payload))\n }\n\n protected override async startHandler() {\n await super.startHandler()\n return true\n }\n\n private getKeyRangeValue(indexName: string | null, query: AnyObject): unknown | unknown[] {\n if (!indexName) return []\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Extracting the relevant fields from the index name\n const indexFields = extractFields(indexName)\n\n // Collecting the values for these fields from the query object\n const keyRangeValue = indexFields.map((field) => query[field as keyof AnyObject])\n return keyRangeValue.length === 1 ? keyRangeValue[0] : keyRangeValue\n }\n\n private selectBestIndex(query: AnyObject, store: IDBPObjectStore<PayloadStore>): string | null {\n // List of available indexes\n const { indexNames } = store\n\n // Function to extract fields from an index name\n const extractFields = (indexName: string): string[] => {\n return indexName\n .slice(3)\n .split(IndexSeparator)\n .map((field) => field.toLowerCase())\n }\n\n // Convert query object keys to a set for easier comparison\n const queryKeys = new Set(Object.keys(query).map((key) => key.toLowerCase()))\n\n // Find the best matching index\n let bestMatch: { indexName: string; matchCount: number } = { indexName: '', matchCount: 0 }\n\n for (const indexName of indexNames) {\n const indexFields = extractFields(indexName)\n const matchCount = indexFields.filter((field) => queryKeys.has(field)).length\n if (matchCount > bestMatch.matchCount) {\n bestMatch = { indexName, matchCount }\n }\n }\n return bestMatch.matchCount > 0 ? bestMatch.indexName : null\n }\n\n /**\n * Checks that the desired DB/Store exists and is initialized\n * @returns The initialized DB or undefined if it does not exist\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // If we've already checked and found a successfully initialized\n // db and objectStore, return the cached value\n if (this._db) return this._db\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n const dbExists = dbs.some((db) => {\n // Check for the desired name/version\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB does not exist at the desired version, return undefined\n if (!dbExists) return\n // If the db does exist, open it\n const db = await openDB<PayloadStore>(this.dbName, this.dbVersion)\n // Check that the desired objectStore exists\n const storeExists = db.objectStoreNames.contains(this.storeName)\n // If the correct db/store exists, cache it for future calls\n if (storeExists) this._db = db\n return this._db\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","containsAll","assertEx","exists","IndexedDbArchivist","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadHasher","openDB","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","divineHandler","payloads","query","filter","isPayloadDivinerQueryPayload","pop","db","tryGetInitializedDb","schemas","limit","offset","hash","order","schema","_schema","sources","props","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","PayloadHasher","jsonPayload","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","configSchemas","IndexedDbPayloadDivinerConfigSchema"]}
package/package.json CHANGED
@@ -13,23 +13,23 @@
13
13
  "@xylabs/array": "^2.13.23",
14
14
  "@xylabs/assert": "^2.13.23",
15
15
  "@xylabs/exists": "^2.13.23",
16
- "@xyo-network/archivist-model": "~2.87.0-rc.6",
17
- "@xyo-network/diviner-model": "~2.87.0-rc.6",
18
- "@xyo-network/diviner-payload-abstract": "~2.87.0-rc.6",
19
- "@xyo-network/diviner-payload-model": "~2.87.0-rc.6",
20
- "@xyo-network/hash": "~2.87.0-rc.6",
21
- "@xyo-network/payload-model": "~2.87.0-rc.6",
16
+ "@xyo-network/archivist-model": "~2.87.0",
17
+ "@xyo-network/diviner-model": "~2.87.0",
18
+ "@xyo-network/diviner-payload-abstract": "~2.87.0",
19
+ "@xyo-network/diviner-payload-model": "~2.87.0",
20
+ "@xyo-network/hash": "~2.87.0",
21
+ "@xyo-network/payload-model": "~2.87.0",
22
22
  "idb": "^8.0.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@xylabs/ts-scripts-yarn3": "^3.2.33",
26
26
  "@xylabs/tsconfig": "^3.2.33",
27
- "@xyo-network/account": "~2.87.0-rc.6",
28
- "@xyo-network/archivist-indexeddb": "~2.87.0-rc.6",
29
- "@xyo-network/module-model": "~2.87.0-rc.6",
30
- "@xyo-network/node-memory": "~2.87.0-rc.6",
31
- "@xyo-network/object": "~2.87.0-rc.6",
32
- "@xyo-network/payload-builder": "~2.87.0-rc.6",
27
+ "@xyo-network/account": "~2.87.0",
28
+ "@xyo-network/archivist-indexeddb": "~2.87.0",
29
+ "@xyo-network/module-model": "~2.87.0",
30
+ "@xyo-network/node-memory": "~2.87.0",
31
+ "@xyo-network/object": "~2.87.0",
32
+ "@xyo-network/payload-builder": "~2.87.0",
33
33
  "fake-indexeddb": "^5.0.2",
34
34
  "typescript": "^5.3.3"
35
35
  },
@@ -72,7 +72,6 @@
72
72
  "url": "https://github.com/XYOracleNetwork/sdk-xyo-client-js.git"
73
73
  },
74
74
  "sideEffects": false,
75
- "version": "2.87.0-rc.6",
76
- "type": "module",
77
- "stableVersion": "2.86.1"
75
+ "version": "2.87.0",
76
+ "type": "module"
78
77
  }
package/src/Diviner.ts CHANGED
@@ -68,17 +68,14 @@ export class IndexedDbPayloadDiviner<
68
68
  return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName
69
69
  }
70
70
 
71
- private get db(): IDBPDatabase<PayloadStore> {
72
- return assertEx(this._db, 'DB not initialized')
73
- }
74
-
75
71
  protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {
76
- const query = assertEx(payloads?.filter(isPayloadDivinerQueryPayload)?.pop(), 'Missing query payload')
72
+ const query = payloads?.filter(isPayloadDivinerQueryPayload)?.pop()
77
73
  if (!query) return []
78
- this._db = await openDB<PayloadStore>(this.dbName, this.dbVersion)
74
+ const db = await this.tryGetInitializedDb()
75
+ if (!db) return []
79
76
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
80
77
  const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }
81
- const tx = this.db.transaction(this.storeName, 'readonly')
78
+ const tx = db.transaction(this.storeName, 'readonly')
82
79
  const store = tx.objectStore(this.storeName)
83
80
  const results: TOut[] = []
84
81
  let parsedOffset = offset ?? 0
@@ -178,4 +175,29 @@ export class IndexedDbPayloadDiviner<
178
175
  }
179
176
  return bestMatch.matchCount > 0 ? bestMatch.indexName : null
180
177
  }
178
+
179
+ /**
180
+ * Checks that the desired DB/Store exists and is initialized
181
+ * @returns The initialized DB or undefined if it does not exist
182
+ */
183
+ private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {
184
+ // If we've already checked and found a successfully initialized
185
+ // db and objectStore, return the cached value
186
+ if (this._db) return this._db
187
+ // Enumerate the DBs
188
+ const dbs = await indexedDB.databases()
189
+ const dbExists = dbs.some((db) => {
190
+ // Check for the desired name/version
191
+ return db.name === this.dbName && db.version === this.dbVersion
192
+ })
193
+ // If the DB does not exist at the desired version, return undefined
194
+ if (!dbExists) return
195
+ // If the db does exist, open it
196
+ const db = await openDB<PayloadStore>(this.dbName, this.dbVersion)
197
+ // Check that the desired objectStore exists
198
+ const storeExists = db.objectStoreNames.contains(this.storeName)
199
+ // If the correct db/store exists, cache it for future calls
200
+ if (storeExists) this._db = db
201
+ return this._db
202
+ }
181
203
  }