@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.
- package/dist/browser/Diviner.d.cts +5 -1
- package/dist/browser/Diviner.d.cts.map +1 -1
- package/dist/browser/Diviner.d.mts +5 -1
- package/dist/browser/Diviner.d.mts.map +1 -1
- package/dist/browser/Diviner.d.ts +5 -1
- package/dist/browser/Diviner.d.ts.map +1 -1
- package/dist/browser/index.cjs +24 -6
- package/dist/browser/index.cjs.map +1 -1
- package/dist/browser/index.js +24 -6
- package/dist/browser/index.js.map +1 -1
- package/dist/node/Diviner.d.cts +5 -1
- package/dist/node/Diviner.d.cts.map +1 -1
- package/dist/node/Diviner.d.mts +5 -1
- package/dist/node/Diviner.d.mts.map +1 -1
- package/dist/node/Diviner.d.ts +5 -1
- package/dist/node/Diviner.d.ts.map +1 -1
- package/dist/node/index.cjs +24 -6
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.js +24 -6
- package/dist/node/index.js.map +1 -1
- package/package.json +14 -15
- package/src/Diviner.ts +29 -7
|
@@ -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;
|
|
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;
|
|
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;
|
|
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"}
|
package/dist/browser/index.cjs
CHANGED
|
@@ -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 =
|
|
91
|
+
const query = payloads?.filter(import_diviner_payload_model2.isPayloadDivinerQueryPayload)?.pop();
|
|
95
92
|
if (!query)
|
|
96
93
|
return [];
|
|
97
|
-
|
|
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 =
|
|
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"]}
|
package/dist/browser/index.js
CHANGED
|
@@ -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 =
|
|
65
|
+
const query = payloads?.filter(isPayloadDivinerQueryPayload)?.pop();
|
|
69
66
|
if (!query)
|
|
70
67
|
return [];
|
|
71
|
-
|
|
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 =
|
|
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"]}
|
package/dist/node/Diviner.d.cts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/node/Diviner.d.mts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/node/Diviner.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/node/index.cjs
CHANGED
|
@@ -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 = (
|
|
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
|
-
|
|
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 =
|
|
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", [
|
package/dist/node/index.cjs.map
CHANGED
|
@@ -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"]}
|
package/dist/node/index.js
CHANGED
|
@@ -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 =
|
|
68
|
+
const query = (_a = payloads == null ? void 0 : payloads.filter(isPayloadDivinerQueryPayload)) == null ? void 0 : _a.pop();
|
|
72
69
|
if (!query)
|
|
73
70
|
return [];
|
|
74
|
-
|
|
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 =
|
|
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", [
|
package/dist/node/index.js.map
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
72
|
+
const query = payloads?.filter(isPayloadDivinerQueryPayload)?.pop()
|
|
77
73
|
if (!query) return []
|
|
78
|
-
|
|
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 =
|
|
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
|
}
|