@xyo-network/diviner-payload-indexeddb 2.104.0 → 2.104.1
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/index.cjs +5 -10
- package/dist/browser/index.cjs.map +1 -1
- package/dist/browser/index.js +5 -10
- package/dist/browser/index.js.map +1 -1
- package/dist/neutral/index.cjs +5 -10
- package/dist/neutral/index.cjs.map +1 -1
- package/dist/neutral/index.js +5 -10
- package/dist/neutral/index.js.map +1 -1
- package/dist/node/index.cjs +6 -14
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.js +6 -14
- package/dist/node/index.js.map +1 -1
- package/package.json +14 -14
package/dist/browser/index.cjs
CHANGED
|
@@ -46,14 +46,11 @@ var import_diviner_payload_model2 = require("@xyo-network/diviner-payload-model"
|
|
|
46
46
|
var import_payload_builder = require("@xyo-network/payload-builder");
|
|
47
47
|
var import_idb = require("idb");
|
|
48
48
|
var payloadValueFilter = /* @__PURE__ */ __name((key, value) => {
|
|
49
|
-
if (!value)
|
|
50
|
-
return void 0;
|
|
49
|
+
if (!value) return void 0;
|
|
51
50
|
return (payload) => {
|
|
52
|
-
if (!payload)
|
|
53
|
-
return false;
|
|
51
|
+
if (!payload) return false;
|
|
54
52
|
const sourceValue = payload?.[key];
|
|
55
|
-
if (sourceValue === void 0)
|
|
56
|
-
return false;
|
|
53
|
+
if (sourceValue === void 0) return false;
|
|
57
54
|
return Array.isArray(sourceValue) && Array.isArray(value) ? (0, import_array.containsAll)(sourceValue, value) : sourceValue == value;
|
|
58
55
|
};
|
|
59
56
|
}, "payloadValueFilter");
|
|
@@ -92,8 +89,7 @@ var IndexedDbPayloadDiviner = class extends import_diviner_payload_abstract.Payl
|
|
|
92
89
|
}
|
|
93
90
|
async divineHandler(payloads) {
|
|
94
91
|
const query = payloads?.find(import_diviner_payload_model2.isPayloadDivinerQueryPayload);
|
|
95
|
-
if (!query)
|
|
96
|
-
return [];
|
|
92
|
+
if (!query) return [];
|
|
97
93
|
const result = await this.tryUseDb(async (db) => {
|
|
98
94
|
const { schemas, limit, offset, order, ...props } = (0, import_object.removeFields)(query, [
|
|
99
95
|
"hash",
|
|
@@ -153,8 +149,7 @@ var IndexedDbPayloadDiviner = class extends import_diviner_payload_abstract.Payl
|
|
|
153
149
|
return true;
|
|
154
150
|
}
|
|
155
151
|
getKeyRangeValue(indexName, query) {
|
|
156
|
-
if (!indexName)
|
|
157
|
-
return [];
|
|
152
|
+
if (!indexName) return [];
|
|
158
153
|
const extractFields = /* @__PURE__ */ __name((indexName2) => {
|
|
159
154
|
return indexName2.slice(3).split(import_archivist_model.IndexSeparator).map((field) => field.toLowerCase());
|
|
160
155
|
}, "extractFields");
|
|
@@ -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` as const\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` as const\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 { Hash } from '@xylabs/hex'\nimport { AnyObject, removeFields } from '@xylabs/object'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema, WithMeta } 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<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbPayloadDivinerConfigSchema]\n static override readonly defaultConfigSchema: Schema = 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?.find(isPayloadDivinerQueryPayload) as TIn\n if (!query) return []\n const result = await this.tryUseDb(async (db) => {\n const { schemas, limit, offset, order, ...props } = removeFields(query as unknown as WithMeta<TIn> & { sources?: Hash[] }, [\n 'hash',\n 'schema',\n '$meta',\n '$hash',\n ])\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[] =\n props ?\n Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor =\n 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 try {\n cursor = await cursor.continue()\n } catch {\n break\n }\n }\n await tx.done\n // Remove any metadata before returning to the client\n return await Promise.all(results.map((payload) => PayloadBuilder.build(payload)))\n })\n return result ?? []\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/objectStore exists and is initialized to the correct version\n * @returns The initialized DB or undefined if it does not exist in the desired state\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n // Check that the DB exists at the desired version\n const dbExists = dbs.some((db) => {\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB exists at the desired version\n if (dbExists) {\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/version/objectStore exists\n if (storeExists) {\n return db\n } else {\n // Otherwise close the db so the process that is going to update the\n // db can open it\n db.close()\n }\n }\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {\n // Get the initialized DB\n const db = await this.tryGetInitializedDb()\n if (db) {\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n return undefined\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","defaultConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","divineHandler","payloads","query","find","isPayloadDivinerQueryPayload","result","tryUseDb","db","schemas","limit","offset","order","props","removeFields","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","filter","schema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","Promise","all","PayloadBuilder","build","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","tryGetInitializedDb","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","close","callback"]}
|
|
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` as const\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` as const\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 { Hash } from '@xylabs/hex'\nimport { AnyObject, removeFields } from '@xylabs/object'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema, WithMeta } 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<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbPayloadDivinerConfigSchema]\n static override readonly defaultConfigSchema: Schema = 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?.find(isPayloadDivinerQueryPayload) as TIn\n if (!query) return []\n const result = await this.tryUseDb(async (db) => {\n const { schemas, limit, offset, order, ...props } = removeFields(query as unknown as WithMeta<TIn> & { sources?: Hash[] }, [\n 'hash',\n 'schema',\n '$meta',\n '$hash',\n ])\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[] =\n props ?\n Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor =\n 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 try {\n cursor = await cursor.continue()\n } catch {\n break\n }\n }\n await tx.done\n // Remove any metadata before returning to the client\n return await Promise.all(results.map((payload) => PayloadBuilder.build(payload)))\n })\n return result ?? []\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/objectStore exists and is initialized to the correct version\n * @returns The initialized DB or undefined if it does not exist in the desired state\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n // Check that the DB exists at the desired version\n const dbExists = dbs.some((db) => {\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB exists at the desired version\n if (dbExists) {\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/version/objectStore exists\n if (storeExists) {\n return db\n } else {\n // Otherwise close the db so the process that is going to update the\n // db can open it\n db.close()\n }\n }\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {\n // Get the initialized DB\n const db = await this.tryGetInitializedDb()\n if (db) {\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n return undefined\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;ACAA,mCAAqC;AAE9B,IAAMA,gCAAgC,GAAGC,iDAAAA;;;ACGzC,IAAMC,sCAAsC,GAAGC,6BAAAA;;;ACLtD,mBAA4B;AAC5B,oBAAyB;AACzB,oBAAuB;AAEvB,oBAAwC;AACxC,iCAAmC;AACnC,6BAA+B;AAE/B,sCAA+B;AAC/B,IAAAC,gCAAyE;AACzE,6BAA+B;AAE/B,iBAAsD;AAatD,IAAMC,qBAAqB,wBAACC,KAAuBC,UAAAA;AACjD,MAAI,CAACA,MAAO,QAAOC;AACnB,SAAO,CAACC,YAAAA;AACN,QAAI,CAACA,QAAS,QAAO;AACrB,UAAMC,cAAcD,UAAUH,GAAAA;AAC9B,QAAII,gBAAgBF,OAAW,QAAO;AACtC,WAAOG,MAAMC,QAAQF,WAAAA,KAAgBC,MAAMC,QAAQL,KAAAA,QAASM,0BAAYH,aAAaH,KAAAA,IAASG,eAAeH;EAC/G;AACF,GAR2B;AAUpB,IAAMO,0BAAN,cASGC,+CAAAA;EA5CV,OA4CUA;;;EACR,OAAyBC,gBAA0B;OAAI,MAAMA;IAAeC;;EAC5E,OAAyBC,sBAA8BD;EAE/CE;;;;;;;;EASR,IAAIC,SAAS;AACX,WAAO,KAAKC,QAAQD,UAAU,KAAKC,QAAQC,aAAaC,8CAAmBC;EAC7E;;;;EAKA,IAAIC,YAAY;AACd,WAAO,KAAKJ,QAAQI,aAAaF,8CAAmBG;EACtD;;;;;EAMA,IAAIC,YAAY;AACd,WAAO,KAAKN,QAAQM,aAAaJ,8CAAmBK;EACtD;EAEA,MAAyBC,cAAcC,UAAmC;AACxE,UAAMC,QAAQD,UAAUE,KAAKC,0DAAAA;AAC7B,QAAI,CAACF,MAAO,QAAO,CAAA;AACnB,UAAMG,SAAS,MAAM,KAAKC,SAAS,OAAOC,OAAAA;AACxC,YAAM,EAAEC,SAASC,OAAOC,QAAQC,OAAO,GAAGC,MAAAA,QAAUC,4BAAaX,OAA0D;QACzH;QACA;QACA;QACA;OACD;AACD,YAAMY,KAAKP,GAAGQ,YAAY,KAAKjB,WAAW,UAAA;AAC1C,YAAMkB,QAAQF,GAAGG,YAAY,KAAKnB,SAAS;AAC3C,YAAMoB,UAAkB,CAAA;AACxB,UAAIC,eAAeT,UAAU;AAC7B,YAAMU,cAAcX,SAAS;AAC7BY,mCAAUb,SAASc,UAAU,OAAO,GAAG,MAAM,2DAAA;AAC7C,YAAMC,eAAef,UAAU,CAAA;AAC/B,YAAMgB,SAASD,eAAe;QAAEE,QAAQF;QAAc,GAAGX;MAAM,IAAI;QAAE,GAAGA;MAAM;AAC9E,YAAMc,YAAgCf,UAAU,SAAS,SAAS;AAClE,YAAMgB,iBAAiB,KAAKC,gBAAgBJ,QAAQR,KAAAA;AACpD,YAAMa,gBAAgB,KAAKC,iBAAiBH,gBAAgBH,MAAAA;AAC5D,YAAMO,eACJnB,QACEoB,OAAOC,QAAQrB,KAAAA,EACZsB,IAAI,CAAC,CAACzD,KAAKC,KAAAA,MAAWF,mBAAmBC,KAAKC,KAAAA,CAAAA,EAC9C8C,OAAOW,oBAAAA,IACV,CAAA;AACJ,UAAIC,SACFT;;QAEE,MAAMX,MAAMqB,MAAMV,cAAAA,EAAgBW,WAAWC,YAAYC,KAAKX,aAAAA,GAAgBH,SAAAA;UAE9E,MAAMV,MAAMsB,WAAWX,gBAAgBD,SAAAA;AAG3C,aAAOU,UAAUjB,eAAe,GAAG;AACjCiB,iBAAS,MAAMA,OAAOK,QAAQtB,YAAAA;AAC9BA,uBAAe;MACjB;AAEA,aAAOiB,UAAUlB,QAAQI,SAASF,aAAa;AAC7C,cAAM1C,QAAQ0D,OAAO1D;AACrB,YAAIA,OAAO;AAET,cAAIqD,aAAaT,SAAS,GAAG;AAE3B,gBAAIS,aAAaW,MAAM,CAAClB,YAAWA,QAAO9C,KAAAA,CAAAA,GAAS;AAEjDwC,sBAAQyB,KAAKjE,KAAAA;YACf;UACF,OAAO;AAELwC,oBAAQyB,KAAKjE,KAAAA;UACf;QACF;AACA,YAAI;AACF0D,mBAAS,MAAMA,OAAOQ,SAAQ;QAChC,QAAQ;AACN;QACF;MACF;AACA,YAAM9B,GAAG+B;AAET,aAAO,MAAMC,QAAQC,IAAI7B,QAAQgB,IAAI,CAACtD,YAAYoE,sCAAeC,MAAMrE,OAAAA,CAAAA,CAAAA;IACzE,CAAA;AACA,WAAOyB,UAAU,CAAA;EACnB;EAEA,MAAyB6C,eAAe;AACtC,UAAM,MAAMA,aAAAA;AACZ,WAAO;EACT;EAEQpB,iBAAiBqB,WAA0BjD,OAAuC;AACxF,QAAI,CAACiD,UAAW,QAAO,CAAA;AAEvB,UAAMC,gBAAgB,wBAACD,eAAAA;AACrB,aAAOA,WACJE,MAAM,CAAA,EACNC,MAAMC,qCAAAA,EACNrB,IAAI,CAACsB,UAAUA,MAAMC,YAAW,CAAA;IACrC,GALsB;AAQtB,UAAMC,cAAcN,cAAcD,SAAAA;AAGlC,UAAMtB,gBAAgB6B,YAAYxB,IAAI,CAACsB,UAAUtD,MAAMsD,KAAAA,CAAyB;AAChF,WAAO3B,cAAcP,WAAW,IAAIO,cAAc,CAAA,IAAKA;EACzD;EAEQD,gBAAgB1B,OAAkBc,OAAqD;AAE7F,UAAM,EAAE2C,WAAU,IAAK3C;AAGvB,UAAMoC,gBAAgB,wBAACD,cAAAA;AACrB,aAAOA,UACJE,MAAM,CAAA,EACNC,MAAMC,qCAAAA,EACNrB,IAAI,CAACsB,UAAUA,MAAMC,YAAW,CAAA;IACrC,GALsB;AAQtB,UAAMG,YAAY,IAAIC,IAAI7B,OAAO8B,KAAK5D,KAAAA,EAAOgC,IAAI,CAACzD,QAAQA,IAAIgF,YAAW,CAAA,CAAA;AAGzE,QAAIM,YAAuD;MAAEZ,WAAW;MAAIa,YAAY;IAAE;AAE1F,eAAWb,aAAaQ,YAAY;AAClC,YAAMD,cAAcN,cAAcD,SAAAA;AAClC,YAAMa,aAAaN,YAAYlC,OAAO,CAACgC,UAAUI,UAAUK,IAAIT,KAAAA,CAAAA,EAAQlC;AACvE,UAAI0C,aAAaD,UAAUC,YAAY;AACrCD,oBAAY;UAAEZ;UAAWa;QAAW;MACtC;IACF;AACA,WAAOD,UAAUC,aAAa,IAAID,UAAUZ,YAAY;EAC1D;;;;;EAMA,MAAce,sBAAuE;AAEnF,UAAMC,MAAM,MAAMC,UAAUC,UAAS;AAErC,UAAMC,WAAWH,IAAII,KAAK,CAAChE,OAAAA;AACzB,aAAOA,GAAGiE,SAAS,KAAKjF,UAAUgB,GAAGkE,YAAY,KAAK7E;IACxD,CAAA;AAEA,QAAI0E,UAAU;AAEZ,YAAM/D,KAAK,UAAMmE,mBAAqB,KAAKnF,QAAQ,KAAKK,SAAS;AAEjE,YAAM+E,cAAcpE,GAAGqE,iBAAiBC,SAAS,KAAK/E,SAAS;AAE/D,UAAI6E,aAAa;AACf,eAAOpE;MACT,OAAO;AAGLA,WAAGuE,MAAK;MACV;IACF;EACF;;;;;;EAOA,MAAcxE,SAAYyE,UAAsF;AAE9G,UAAMxE,KAAK,MAAM,KAAK2D,oBAAmB;AACzC,QAAI3D,IAAI;AACN,UAAI;AAEF,eAAO,MAAMwE,SAASxE,EAAAA;MACxB,UAAA;AAEEA,WAAGuE,MAAK;MACV;IACF;AACA,WAAOnG;EACT;AACF;","names":["IndexedDbPayloadDivinerSchema","PayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","import_diviner_payload_model","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","defaultConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","divineHandler","payloads","query","find","isPayloadDivinerQueryPayload","result","tryUseDb","db","schemas","limit","offset","order","props","removeFields","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","filter","schema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","Promise","all","PayloadBuilder","build","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","tryGetInitializedDb","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","close","callback"]}
|
package/dist/browser/index.js
CHANGED
|
@@ -20,14 +20,11 @@ import { isPayloadDivinerQueryPayload } from "@xyo-network/diviner-payload-model
|
|
|
20
20
|
import { PayloadBuilder } from "@xyo-network/payload-builder";
|
|
21
21
|
import { openDB } from "idb";
|
|
22
22
|
var payloadValueFilter = /* @__PURE__ */ __name((key, value) => {
|
|
23
|
-
if (!value)
|
|
24
|
-
return void 0;
|
|
23
|
+
if (!value) return void 0;
|
|
25
24
|
return (payload) => {
|
|
26
|
-
if (!payload)
|
|
27
|
-
return false;
|
|
25
|
+
if (!payload) return false;
|
|
28
26
|
const sourceValue = payload?.[key];
|
|
29
|
-
if (sourceValue === void 0)
|
|
30
|
-
return false;
|
|
27
|
+
if (sourceValue === void 0) return false;
|
|
31
28
|
return Array.isArray(sourceValue) && Array.isArray(value) ? containsAll(sourceValue, value) : sourceValue == value;
|
|
32
29
|
};
|
|
33
30
|
}, "payloadValueFilter");
|
|
@@ -66,8 +63,7 @@ var IndexedDbPayloadDiviner = class extends PayloadDiviner {
|
|
|
66
63
|
}
|
|
67
64
|
async divineHandler(payloads) {
|
|
68
65
|
const query = payloads?.find(isPayloadDivinerQueryPayload);
|
|
69
|
-
if (!query)
|
|
70
|
-
return [];
|
|
66
|
+
if (!query) return [];
|
|
71
67
|
const result = await this.tryUseDb(async (db) => {
|
|
72
68
|
const { schemas, limit, offset, order, ...props } = removeFields(query, [
|
|
73
69
|
"hash",
|
|
@@ -127,8 +123,7 @@ var IndexedDbPayloadDiviner = class extends PayloadDiviner {
|
|
|
127
123
|
return true;
|
|
128
124
|
}
|
|
129
125
|
getKeyRangeValue(indexName, query) {
|
|
130
|
-
if (!indexName)
|
|
131
|
-
return [];
|
|
126
|
+
if (!indexName) return [];
|
|
132
127
|
const extractFields = /* @__PURE__ */ __name((indexName2) => {
|
|
133
128
|
return indexName2.slice(3).split(IndexSeparator).map((field) => field.toLowerCase());
|
|
134
129
|
}, "extractFields");
|
|
@@ -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` as const\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` as const\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 { Hash } from '@xylabs/hex'\nimport { AnyObject, removeFields } from '@xylabs/object'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema, WithMeta } 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<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbPayloadDivinerConfigSchema]\n static override readonly defaultConfigSchema: Schema = 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?.find(isPayloadDivinerQueryPayload) as TIn\n if (!query) return []\n const result = await this.tryUseDb(async (db) => {\n const { schemas, limit, offset, order, ...props } = removeFields(query as unknown as WithMeta<TIn> & { sources?: Hash[] }, [\n 'hash',\n 'schema',\n '$meta',\n '$hash',\n ])\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[] =\n props ?\n Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor =\n 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 try {\n cursor = await cursor.continue()\n } catch {\n break\n }\n }\n await tx.done\n // Remove any metadata before returning to the client\n return await Promise.all(results.map((payload) => PayloadBuilder.build(payload)))\n })\n return result ?? []\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/objectStore exists and is initialized to the correct version\n * @returns The initialized DB or undefined if it does not exist in the desired state\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n // Check that the DB exists at the desired version\n const dbExists = dbs.some((db) => {\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB exists at the desired version\n if (dbExists) {\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/version/objectStore exists\n if (storeExists) {\n return db\n } else {\n // Otherwise close the db so the process that is going to update the\n // db can open it\n db.close()\n }\n }\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {\n // Get the initialized DB\n const db = await this.tryGetInitializedDb()\n if (db) {\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n return undefined\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","containsAll","assertEx","exists","removeFields","IndexedDbArchivist","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadBuilder","openDB","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","defaultConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","divineHandler","payloads","query","find","isPayloadDivinerQueryPayload","result","tryUseDb","db","schemas","limit","offset","order","props","removeFields","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","filter","schema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","Promise","all","PayloadBuilder","build","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","tryGetInitializedDb","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","close","callback"]}
|
|
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` as const\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` as const\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 { Hash } from '@xylabs/hex'\nimport { AnyObject, removeFields } from '@xylabs/object'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema, WithMeta } 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<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbPayloadDivinerConfigSchema]\n static override readonly defaultConfigSchema: Schema = 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?.find(isPayloadDivinerQueryPayload) as TIn\n if (!query) return []\n const result = await this.tryUseDb(async (db) => {\n const { schemas, limit, offset, order, ...props } = removeFields(query as unknown as WithMeta<TIn> & { sources?: Hash[] }, [\n 'hash',\n 'schema',\n '$meta',\n '$hash',\n ])\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[] =\n props ?\n Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor =\n 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 try {\n cursor = await cursor.continue()\n } catch {\n break\n }\n }\n await tx.done\n // Remove any metadata before returning to the client\n return await Promise.all(results.map((payload) => PayloadBuilder.build(payload)))\n })\n return result ?? []\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/objectStore exists and is initialized to the correct version\n * @returns The initialized DB or undefined if it does not exist in the desired state\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n // Check that the DB exists at the desired version\n const dbExists = dbs.some((db) => {\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB exists at the desired version\n if (dbExists) {\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/version/objectStore exists\n if (storeExists) {\n return db\n } else {\n // Otherwise close the db so the process that is going to update the\n // db can open it\n db.close()\n }\n }\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {\n // Get the initialized DB\n const db = await this.tryGetInitializedDb()\n if (db) {\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n return undefined\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","containsAll","assertEx","exists","removeFields","IndexedDbArchivist","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadBuilder","openDB","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","defaultConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","divineHandler","payloads","query","find","isPayloadDivinerQueryPayload","result","tryUseDb","db","schemas","limit","offset","order","props","removeFields","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","filter","schema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","Promise","all","PayloadBuilder","build","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","tryGetInitializedDb","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","close","callback"]}
|
package/dist/neutral/index.cjs
CHANGED
|
@@ -46,14 +46,11 @@ var import_diviner_payload_model2 = require("@xyo-network/diviner-payload-model"
|
|
|
46
46
|
var import_payload_builder = require("@xyo-network/payload-builder");
|
|
47
47
|
var import_idb = require("idb");
|
|
48
48
|
var payloadValueFilter = /* @__PURE__ */ __name((key, value) => {
|
|
49
|
-
if (!value)
|
|
50
|
-
return void 0;
|
|
49
|
+
if (!value) return void 0;
|
|
51
50
|
return (payload) => {
|
|
52
|
-
if (!payload)
|
|
53
|
-
return false;
|
|
51
|
+
if (!payload) return false;
|
|
54
52
|
const sourceValue = payload?.[key];
|
|
55
|
-
if (sourceValue === void 0)
|
|
56
|
-
return false;
|
|
53
|
+
if (sourceValue === void 0) return false;
|
|
57
54
|
return Array.isArray(sourceValue) && Array.isArray(value) ? (0, import_array.containsAll)(sourceValue, value) : sourceValue == value;
|
|
58
55
|
};
|
|
59
56
|
}, "payloadValueFilter");
|
|
@@ -92,8 +89,7 @@ var IndexedDbPayloadDiviner = class extends import_diviner_payload_abstract.Payl
|
|
|
92
89
|
}
|
|
93
90
|
async divineHandler(payloads) {
|
|
94
91
|
const query = payloads?.find(import_diviner_payload_model2.isPayloadDivinerQueryPayload);
|
|
95
|
-
if (!query)
|
|
96
|
-
return [];
|
|
92
|
+
if (!query) return [];
|
|
97
93
|
const result = await this.tryUseDb(async (db) => {
|
|
98
94
|
const { schemas, limit, offset, order, ...props } = (0, import_object.removeFields)(query, [
|
|
99
95
|
"hash",
|
|
@@ -153,8 +149,7 @@ var IndexedDbPayloadDiviner = class extends import_diviner_payload_abstract.Payl
|
|
|
153
149
|
return true;
|
|
154
150
|
}
|
|
155
151
|
getKeyRangeValue(indexName, query) {
|
|
156
|
-
if (!indexName)
|
|
157
|
-
return [];
|
|
152
|
+
if (!indexName) return [];
|
|
158
153
|
const extractFields = /* @__PURE__ */ __name((indexName2) => {
|
|
159
154
|
return indexName2.slice(3).split(import_archivist_model.IndexSeparator).map((field) => field.toLowerCase());
|
|
160
155
|
}, "extractFields");
|
|
@@ -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` as const\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` as const\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 { Hash } from '@xylabs/hex'\nimport { AnyObject, removeFields } from '@xylabs/object'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema, WithMeta } 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<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbPayloadDivinerConfigSchema]\n static override readonly defaultConfigSchema: Schema = 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?.find(isPayloadDivinerQueryPayload) as TIn\n if (!query) return []\n const result = await this.tryUseDb(async (db) => {\n const { schemas, limit, offset, order, ...props } = removeFields(query as unknown as WithMeta<TIn> & { sources?: Hash[] }, [\n 'hash',\n 'schema',\n '$meta',\n '$hash',\n ])\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[] =\n props ?\n Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor =\n 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 try {\n cursor = await cursor.continue()\n } catch {\n break\n }\n }\n await tx.done\n // Remove any metadata before returning to the client\n return await Promise.all(results.map((payload) => PayloadBuilder.build(payload)))\n })\n return result ?? []\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/objectStore exists and is initialized to the correct version\n * @returns The initialized DB or undefined if it does not exist in the desired state\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n // Check that the DB exists at the desired version\n const dbExists = dbs.some((db) => {\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB exists at the desired version\n if (dbExists) {\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/version/objectStore exists\n if (storeExists) {\n return db\n } else {\n // Otherwise close the db so the process that is going to update the\n // db can open it\n db.close()\n }\n }\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {\n // Get the initialized DB\n const db = await this.tryGetInitializedDb()\n if (db) {\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n return undefined\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","defaultConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","divineHandler","payloads","query","find","isPayloadDivinerQueryPayload","result","tryUseDb","db","schemas","limit","offset","order","props","removeFields","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","filter","schema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","Promise","all","PayloadBuilder","build","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","tryGetInitializedDb","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","close","callback"]}
|
|
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` as const\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` as const\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 { Hash } from '@xylabs/hex'\nimport { AnyObject, removeFields } from '@xylabs/object'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema, WithMeta } 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<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbPayloadDivinerConfigSchema]\n static override readonly defaultConfigSchema: Schema = 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?.find(isPayloadDivinerQueryPayload) as TIn\n if (!query) return []\n const result = await this.tryUseDb(async (db) => {\n const { schemas, limit, offset, order, ...props } = removeFields(query as unknown as WithMeta<TIn> & { sources?: Hash[] }, [\n 'hash',\n 'schema',\n '$meta',\n '$hash',\n ])\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[] =\n props ?\n Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor =\n 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 try {\n cursor = await cursor.continue()\n } catch {\n break\n }\n }\n await tx.done\n // Remove any metadata before returning to the client\n return await Promise.all(results.map((payload) => PayloadBuilder.build(payload)))\n })\n return result ?? []\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/objectStore exists and is initialized to the correct version\n * @returns The initialized DB or undefined if it does not exist in the desired state\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n // Check that the DB exists at the desired version\n const dbExists = dbs.some((db) => {\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB exists at the desired version\n if (dbExists) {\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/version/objectStore exists\n if (storeExists) {\n return db\n } else {\n // Otherwise close the db so the process that is going to update the\n // db can open it\n db.close()\n }\n }\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {\n // Get the initialized DB\n const db = await this.tryGetInitializedDb()\n if (db) {\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n return undefined\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","defaultConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","divineHandler","payloads","query","find","isPayloadDivinerQueryPayload","result","tryUseDb","db","schemas","limit","offset","order","props","removeFields","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","filter","schema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","Promise","all","PayloadBuilder","build","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","tryGetInitializedDb","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","close","callback"]}
|
package/dist/neutral/index.js
CHANGED
|
@@ -20,14 +20,11 @@ import { isPayloadDivinerQueryPayload } from "@xyo-network/diviner-payload-model
|
|
|
20
20
|
import { PayloadBuilder } from "@xyo-network/payload-builder";
|
|
21
21
|
import { openDB } from "idb";
|
|
22
22
|
var payloadValueFilter = /* @__PURE__ */ __name((key, value) => {
|
|
23
|
-
if (!value)
|
|
24
|
-
return void 0;
|
|
23
|
+
if (!value) return void 0;
|
|
25
24
|
return (payload) => {
|
|
26
|
-
if (!payload)
|
|
27
|
-
return false;
|
|
25
|
+
if (!payload) return false;
|
|
28
26
|
const sourceValue = payload?.[key];
|
|
29
|
-
if (sourceValue === void 0)
|
|
30
|
-
return false;
|
|
27
|
+
if (sourceValue === void 0) return false;
|
|
31
28
|
return Array.isArray(sourceValue) && Array.isArray(value) ? containsAll(sourceValue, value) : sourceValue == value;
|
|
32
29
|
};
|
|
33
30
|
}, "payloadValueFilter");
|
|
@@ -66,8 +63,7 @@ var IndexedDbPayloadDiviner = class extends PayloadDiviner {
|
|
|
66
63
|
}
|
|
67
64
|
async divineHandler(payloads) {
|
|
68
65
|
const query = payloads?.find(isPayloadDivinerQueryPayload);
|
|
69
|
-
if (!query)
|
|
70
|
-
return [];
|
|
66
|
+
if (!query) return [];
|
|
71
67
|
const result = await this.tryUseDb(async (db) => {
|
|
72
68
|
const { schemas, limit, offset, order, ...props } = removeFields(query, [
|
|
73
69
|
"hash",
|
|
@@ -127,8 +123,7 @@ var IndexedDbPayloadDiviner = class extends PayloadDiviner {
|
|
|
127
123
|
return true;
|
|
128
124
|
}
|
|
129
125
|
getKeyRangeValue(indexName, query) {
|
|
130
|
-
if (!indexName)
|
|
131
|
-
return [];
|
|
126
|
+
if (!indexName) return [];
|
|
132
127
|
const extractFields = /* @__PURE__ */ __name((indexName2) => {
|
|
133
128
|
return indexName2.slice(3).split(IndexSeparator).map((field) => field.toLowerCase());
|
|
134
129
|
}, "extractFields");
|
|
@@ -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` as const\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` as const\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 { Hash } from '@xylabs/hex'\nimport { AnyObject, removeFields } from '@xylabs/object'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema, WithMeta } 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<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbPayloadDivinerConfigSchema]\n static override readonly defaultConfigSchema: Schema = 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?.find(isPayloadDivinerQueryPayload) as TIn\n if (!query) return []\n const result = await this.tryUseDb(async (db) => {\n const { schemas, limit, offset, order, ...props } = removeFields(query as unknown as WithMeta<TIn> & { sources?: Hash[] }, [\n 'hash',\n 'schema',\n '$meta',\n '$hash',\n ])\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[] =\n props ?\n Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor =\n 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 try {\n cursor = await cursor.continue()\n } catch {\n break\n }\n }\n await tx.done\n // Remove any metadata before returning to the client\n return await Promise.all(results.map((payload) => PayloadBuilder.build(payload)))\n })\n return result ?? []\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/objectStore exists and is initialized to the correct version\n * @returns The initialized DB or undefined if it does not exist in the desired state\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n // Check that the DB exists at the desired version\n const dbExists = dbs.some((db) => {\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB exists at the desired version\n if (dbExists) {\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/version/objectStore exists\n if (storeExists) {\n return db\n } else {\n // Otherwise close the db so the process that is going to update the\n // db can open it\n db.close()\n }\n }\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {\n // Get the initialized DB\n const db = await this.tryGetInitializedDb()\n if (db) {\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n return undefined\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","containsAll","assertEx","exists","removeFields","IndexedDbArchivist","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadBuilder","openDB","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","defaultConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","divineHandler","payloads","query","find","isPayloadDivinerQueryPayload","result","tryUseDb","db","schemas","limit","offset","order","props","removeFields","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","filter","schema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","Promise","all","PayloadBuilder","build","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","tryGetInitializedDb","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","close","callback"]}
|
|
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` as const\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` as const\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 { Hash } from '@xylabs/hex'\nimport { AnyObject, removeFields } from '@xylabs/object'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema, WithMeta } 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<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbPayloadDivinerConfigSchema]\n static override readonly defaultConfigSchema: Schema = 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?.find(isPayloadDivinerQueryPayload) as TIn\n if (!query) return []\n const result = await this.tryUseDb(async (db) => {\n const { schemas, limit, offset, order, ...props } = removeFields(query as unknown as WithMeta<TIn> & { sources?: Hash[] }, [\n 'hash',\n 'schema',\n '$meta',\n '$hash',\n ])\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[] =\n props ?\n Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor =\n 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 try {\n cursor = await cursor.continue()\n } catch {\n break\n }\n }\n await tx.done\n // Remove any metadata before returning to the client\n return await Promise.all(results.map((payload) => PayloadBuilder.build(payload)))\n })\n return result ?? []\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/objectStore exists and is initialized to the correct version\n * @returns The initialized DB or undefined if it does not exist in the desired state\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n // Check that the DB exists at the desired version\n const dbExists = dbs.some((db) => {\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB exists at the desired version\n if (dbExists) {\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/version/objectStore exists\n if (storeExists) {\n return db\n } else {\n // Otherwise close the db so the process that is going to update the\n // db can open it\n db.close()\n }\n }\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {\n // Get the initialized DB\n const db = await this.tryGetInitializedDb()\n if (db) {\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n return undefined\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","containsAll","assertEx","exists","removeFields","IndexedDbArchivist","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadBuilder","openDB","payloadValueFilter","key","value","undefined","payload","sourceValue","Array","isArray","containsAll","IndexedDbPayloadDiviner","PayloadDiviner","configSchemas","IndexedDbPayloadDivinerConfigSchema","defaultConfigSchema","_db","dbName","config","archivist","IndexedDbArchivist","defaultDbName","dbVersion","defaultDbVersion","storeName","defaultStoreName","divineHandler","payloads","query","find","isPayloadDivinerQueryPayload","result","tryUseDb","db","schemas","limit","offset","order","props","removeFields","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","filter","schema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","Promise","all","PayloadBuilder","build","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","tryGetInitializedDb","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","close","callback"]}
|
package/dist/node/index.cjs
CHANGED
|
@@ -20,10 +20,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
20
20
|
return to;
|
|
21
21
|
};
|
|
22
22
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
23
|
-
var __publicField = (obj, key, value) =>
|
|
24
|
-
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
25
|
-
return value;
|
|
26
|
-
};
|
|
23
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
27
24
|
var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj);
|
|
28
25
|
|
|
29
26
|
// src/index.ts
|
|
@@ -54,14 +51,11 @@ var import_diviner_payload_model2 = require("@xyo-network/diviner-payload-model"
|
|
|
54
51
|
var import_payload_builder = require("@xyo-network/payload-builder");
|
|
55
52
|
var import_idb = require("idb");
|
|
56
53
|
var payloadValueFilter = /* @__PURE__ */ __name((key, value) => {
|
|
57
|
-
if (!value)
|
|
58
|
-
return void 0;
|
|
54
|
+
if (!value) return void 0;
|
|
59
55
|
return (payload) => {
|
|
60
|
-
if (!payload)
|
|
61
|
-
return false;
|
|
56
|
+
if (!payload) return false;
|
|
62
57
|
const sourceValue = payload == null ? void 0 : payload[key];
|
|
63
|
-
if (sourceValue === void 0)
|
|
64
|
-
return false;
|
|
58
|
+
if (sourceValue === void 0) return false;
|
|
65
59
|
return Array.isArray(sourceValue) && Array.isArray(value) ? (0, import_array.containsAll)(sourceValue, value) : sourceValue == value;
|
|
66
60
|
};
|
|
67
61
|
}, "payloadValueFilter");
|
|
@@ -95,8 +89,7 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends import_div
|
|
|
95
89
|
}
|
|
96
90
|
async divineHandler(payloads) {
|
|
97
91
|
const query = payloads == null ? void 0 : payloads.find(import_diviner_payload_model2.isPayloadDivinerQueryPayload);
|
|
98
|
-
if (!query)
|
|
99
|
-
return [];
|
|
92
|
+
if (!query) return [];
|
|
100
93
|
const result = await this.tryUseDb(async (db) => {
|
|
101
94
|
const { schemas, limit, offset, order, ...props } = (0, import_object.removeFields)(query, [
|
|
102
95
|
"hash",
|
|
@@ -156,8 +149,7 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends import_div
|
|
|
156
149
|
return true;
|
|
157
150
|
}
|
|
158
151
|
getKeyRangeValue(indexName, query) {
|
|
159
|
-
if (!indexName)
|
|
160
|
-
return [];
|
|
152
|
+
if (!indexName) return [];
|
|
161
153
|
const extractFields = /* @__PURE__ */ __name((indexName2) => {
|
|
162
154
|
return indexName2.slice(3).split(import_archivist_model.IndexSeparator).map((field) => field.toLowerCase());
|
|
163
155
|
}, "extractFields");
|
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` as const\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` as const\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 { Hash } from '@xylabs/hex'\nimport { AnyObject, removeFields } from '@xylabs/object'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema, WithMeta } 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<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbPayloadDivinerConfigSchema]\n static override readonly defaultConfigSchema: Schema = 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?.find(isPayloadDivinerQueryPayload) as TIn\n if (!query) return []\n const result = await this.tryUseDb(async (db) => {\n const { schemas, limit, offset, order, ...props } = removeFields(query as unknown as WithMeta<TIn> & { sources?: Hash[] }, [\n 'hash',\n 'schema',\n '$meta',\n '$hash',\n ])\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[] =\n props ?\n Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor =\n 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 try {\n cursor = await cursor.continue()\n } catch {\n break\n }\n }\n await tx.done\n // Remove any metadata before returning to the client\n return await Promise.all(results.map((payload) => PayloadBuilder.build(payload)))\n })\n return result ?? []\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/objectStore exists and is initialized to the correct version\n * @returns The initialized DB or undefined if it does not exist in the desired state\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n // Check that the DB exists at the desired version\n const dbExists = dbs.some((db) => {\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB exists at the desired version\n if (dbExists) {\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/version/objectStore exists\n if (storeExists) {\n return db\n } else {\n // Otherwise close the db so the process that is going to update the\n // db can open it\n db.close()\n }\n }\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {\n // Get the initialized DB\n const db = await this.tryGetInitializedDb()\n if (db) {\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n return undefined\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","find","isPayloadDivinerQueryPayload","result","tryUseDb","db","schemas","limit","offset","order","props","removeFields","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","filter","schema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","Promise","all","PayloadBuilder","build","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","tryGetInitializedDb","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","close","callback","configSchemas","IndexedDbPayloadDivinerConfigSchema","defaultConfigSchema"]}
|
|
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` as const\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` as const\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 { Hash } from '@xylabs/hex'\nimport { AnyObject, removeFields } from '@xylabs/object'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema, WithMeta } 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<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbPayloadDivinerConfigSchema]\n static override readonly defaultConfigSchema: Schema = 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?.find(isPayloadDivinerQueryPayload) as TIn\n if (!query) return []\n const result = await this.tryUseDb(async (db) => {\n const { schemas, limit, offset, order, ...props } = removeFields(query as unknown as WithMeta<TIn> & { sources?: Hash[] }, [\n 'hash',\n 'schema',\n '$meta',\n '$hash',\n ])\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[] =\n props ?\n Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor =\n 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 try {\n cursor = await cursor.continue()\n } catch {\n break\n }\n }\n await tx.done\n // Remove any metadata before returning to the client\n return await Promise.all(results.map((payload) => PayloadBuilder.build(payload)))\n })\n return result ?? []\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/objectStore exists and is initialized to the correct version\n * @returns The initialized DB or undefined if it does not exist in the desired state\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n // Check that the DB exists at the desired version\n const dbExists = dbs.some((db) => {\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB exists at the desired version\n if (dbExists) {\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/version/objectStore exists\n if (storeExists) {\n return db\n } else {\n // Otherwise close the db so the process that is going to update the\n // db can open it\n db.close()\n }\n }\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {\n // Get the initialized DB\n const db = await this.tryGetInitializedDb()\n if (db) {\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n return undefined\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","find","isPayloadDivinerQueryPayload","result","tryUseDb","db","schemas","limit","offset","order","props","removeFields","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","filter","schema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","Promise","all","PayloadBuilder","build","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","tryGetInitializedDb","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","close","callback","configSchemas","IndexedDbPayloadDivinerConfigSchema","defaultConfigSchema"]}
|
package/dist/node/index.js
CHANGED
|
@@ -3,10 +3,7 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
3
3
|
var __reflectGet = Reflect.get;
|
|
4
4
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
5
5
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
|
-
var __publicField = (obj, key, value) =>
|
|
7
|
-
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
8
|
-
return value;
|
|
9
|
-
};
|
|
6
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
10
7
|
var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj);
|
|
11
8
|
|
|
12
9
|
// src/Schema.ts
|
|
@@ -28,14 +25,11 @@ import { isPayloadDivinerQueryPayload } from "@xyo-network/diviner-payload-model
|
|
|
28
25
|
import { PayloadBuilder } from "@xyo-network/payload-builder";
|
|
29
26
|
import { openDB } from "idb";
|
|
30
27
|
var payloadValueFilter = /* @__PURE__ */ __name((key, value) => {
|
|
31
|
-
if (!value)
|
|
32
|
-
return void 0;
|
|
28
|
+
if (!value) return void 0;
|
|
33
29
|
return (payload) => {
|
|
34
|
-
if (!payload)
|
|
35
|
-
return false;
|
|
30
|
+
if (!payload) return false;
|
|
36
31
|
const sourceValue = payload == null ? void 0 : payload[key];
|
|
37
|
-
if (sourceValue === void 0)
|
|
38
|
-
return false;
|
|
32
|
+
if (sourceValue === void 0) return false;
|
|
39
33
|
return Array.isArray(sourceValue) && Array.isArray(value) ? containsAll(sourceValue, value) : sourceValue == value;
|
|
40
34
|
};
|
|
41
35
|
}, "payloadValueFilter");
|
|
@@ -69,8 +63,7 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends PayloadDiv
|
|
|
69
63
|
}
|
|
70
64
|
async divineHandler(payloads) {
|
|
71
65
|
const query = payloads == null ? void 0 : payloads.find(isPayloadDivinerQueryPayload);
|
|
72
|
-
if (!query)
|
|
73
|
-
return [];
|
|
66
|
+
if (!query) return [];
|
|
74
67
|
const result = await this.tryUseDb(async (db) => {
|
|
75
68
|
const { schemas, limit, offset, order, ...props } = removeFields(query, [
|
|
76
69
|
"hash",
|
|
@@ -130,8 +123,7 @@ var _IndexedDbPayloadDiviner = class _IndexedDbPayloadDiviner extends PayloadDiv
|
|
|
130
123
|
return true;
|
|
131
124
|
}
|
|
132
125
|
getKeyRangeValue(indexName, query) {
|
|
133
|
-
if (!indexName)
|
|
134
|
-
return [];
|
|
126
|
+
if (!indexName) return [];
|
|
135
127
|
const extractFields = /* @__PURE__ */ __name((indexName2) => {
|
|
136
128
|
return indexName2.slice(3).split(IndexSeparator).map((field) => field.toLowerCase());
|
|
137
129
|
}, "extractFields");
|
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` as const\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` as const\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 { Hash } from '@xylabs/hex'\nimport { AnyObject, removeFields } from '@xylabs/object'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema, WithMeta } 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<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbPayloadDivinerConfigSchema]\n static override readonly defaultConfigSchema: Schema = 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?.find(isPayloadDivinerQueryPayload) as TIn\n if (!query) return []\n const result = await this.tryUseDb(async (db) => {\n const { schemas, limit, offset, order, ...props } = removeFields(query as unknown as WithMeta<TIn> & { sources?: Hash[] }, [\n 'hash',\n 'schema',\n '$meta',\n '$hash',\n ])\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[] =\n props ?\n Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor =\n 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 try {\n cursor = await cursor.continue()\n } catch {\n break\n }\n }\n await tx.done\n // Remove any metadata before returning to the client\n return await Promise.all(results.map((payload) => PayloadBuilder.build(payload)))\n })\n return result ?? []\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/objectStore exists and is initialized to the correct version\n * @returns The initialized DB or undefined if it does not exist in the desired state\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n // Check that the DB exists at the desired version\n const dbExists = dbs.some((db) => {\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB exists at the desired version\n if (dbExists) {\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/version/objectStore exists\n if (storeExists) {\n return db\n } else {\n // Otherwise close the db so the process that is going to update the\n // db can open it\n db.close()\n }\n }\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {\n // Get the initialized DB\n const db = await this.tryGetInitializedDb()\n if (db) {\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n return undefined\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","containsAll","assertEx","exists","removeFields","IndexedDbArchivist","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadBuilder","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","find","isPayloadDivinerQueryPayload","result","tryUseDb","db","schemas","limit","offset","order","props","removeFields","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","filter","schema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","Promise","all","PayloadBuilder","build","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","tryGetInitializedDb","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","close","callback","configSchemas","IndexedDbPayloadDivinerConfigSchema","defaultConfigSchema"]}
|
|
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` as const\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` as const\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 { Hash } from '@xylabs/hex'\nimport { AnyObject, removeFields } from '@xylabs/object'\nimport { IndexedDbArchivist } from '@xyo-network/archivist-indexeddb'\nimport { IndexSeparator } from '@xyo-network/archivist-model'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { PayloadDiviner } from '@xyo-network/diviner-payload-abstract'\nimport { isPayloadDivinerQueryPayload, PayloadDivinerQueryPayload } from '@xyo-network/diviner-payload-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema, WithMeta } 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<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends PayloadDiviner<TParams, TIn, TOut, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbPayloadDivinerConfigSchema]\n static override readonly defaultConfigSchema: Schema = 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?.find(isPayloadDivinerQueryPayload) as TIn\n if (!query) return []\n const result = await this.tryUseDb(async (db) => {\n const { schemas, limit, offset, order, ...props } = removeFields(query as unknown as WithMeta<TIn> & { sources?: Hash[] }, [\n 'hash',\n 'schema',\n '$meta',\n '$hash',\n ])\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[] =\n props ?\n Object.entries(props)\n .map(([key, value]) => payloadValueFilter(key, value))\n .filter(exists)\n : []\n let cursor =\n 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 try {\n cursor = await cursor.continue()\n } catch {\n break\n }\n }\n await tx.done\n // Remove any metadata before returning to the client\n return await Promise.all(results.map((payload) => PayloadBuilder.build(payload)))\n })\n return result ?? []\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/objectStore exists and is initialized to the correct version\n * @returns The initialized DB or undefined if it does not exist in the desired state\n */\n private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {\n // Enumerate the DBs\n const dbs = await indexedDB.databases()\n // Check that the DB exists at the desired version\n const dbExists = dbs.some((db) => {\n return db.name === this.dbName && db.version === this.dbVersion\n })\n // If the DB exists at the desired version\n if (dbExists) {\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/version/objectStore exists\n if (storeExists) {\n return db\n } else {\n // Otherwise close the db so the process that is going to update the\n // db can open it\n db.close()\n }\n }\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {\n // Get the initialized DB\n const db = await this.tryGetInitializedDb()\n if (db) {\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n return undefined\n }\n}\n"],"mappings":"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","names":["PayloadDivinerSchema","IndexedDbPayloadDivinerSchema","IndexedDbPayloadDivinerConfigSchema","IndexedDbPayloadDivinerSchema","containsAll","assertEx","exists","removeFields","IndexedDbArchivist","IndexSeparator","PayloadDiviner","isPayloadDivinerQueryPayload","PayloadBuilder","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","find","isPayloadDivinerQueryPayload","result","tryUseDb","db","schemas","limit","offset","order","props","removeFields","tx","transaction","store","objectStore","results","parsedOffset","parsedLimit","assertEx","length","filterSchema","filter","schema","direction","suggestedIndex","selectBestIndex","keyRangeValue","getKeyRangeValue","valueFilters","Object","entries","map","exists","cursor","index","openCursor","IDBKeyRange","only","advance","every","push","continue","done","Promise","all","PayloadBuilder","build","startHandler","indexName","extractFields","slice","split","IndexSeparator","field","toLowerCase","indexFields","indexNames","queryKeys","Set","keys","bestMatch","matchCount","has","tryGetInitializedDb","dbs","indexedDB","databases","dbExists","some","name","version","openDB","storeExists","objectStoreNames","contains","close","callback","configSchemas","IndexedDbPayloadDivinerConfigSchema","defaultConfigSchema"]}
|
package/package.json
CHANGED
|
@@ -15,22 +15,22 @@
|
|
|
15
15
|
"@xylabs/exists": "^3.5.1",
|
|
16
16
|
"@xylabs/hex": "^3.5.1",
|
|
17
17
|
"@xylabs/object": "^3.5.1",
|
|
18
|
-
"@xyo-network/archivist-indexeddb": "~2.104.
|
|
19
|
-
"@xyo-network/archivist-model": "~2.104.
|
|
20
|
-
"@xyo-network/diviner-model": "~2.104.
|
|
21
|
-
"@xyo-network/diviner-payload-abstract": "~2.104.
|
|
22
|
-
"@xyo-network/diviner-payload-model": "~2.104.
|
|
23
|
-
"@xyo-network/module-model": "~2.104.
|
|
24
|
-
"@xyo-network/payload-builder": "~2.104.
|
|
25
|
-
"@xyo-network/payload-model": "~2.104.
|
|
18
|
+
"@xyo-network/archivist-indexeddb": "~2.104.1",
|
|
19
|
+
"@xyo-network/archivist-model": "~2.104.1",
|
|
20
|
+
"@xyo-network/diviner-model": "~2.104.1",
|
|
21
|
+
"@xyo-network/diviner-payload-abstract": "~2.104.1",
|
|
22
|
+
"@xyo-network/diviner-payload-model": "~2.104.1",
|
|
23
|
+
"@xyo-network/module-model": "~2.104.1",
|
|
24
|
+
"@xyo-network/payload-builder": "~2.104.1",
|
|
25
|
+
"@xyo-network/payload-model": "~2.104.1",
|
|
26
26
|
"idb": "^8.0.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@xylabs/ts-scripts-yarn3": "^3.
|
|
30
|
-
"@xylabs/tsconfig": "^3.
|
|
31
|
-
"@xyo-network/account": "~2.104.
|
|
32
|
-
"@xyo-network/archivist-indexeddb": "~2.104.
|
|
33
|
-
"@xyo-network/node-memory": "~2.104.
|
|
29
|
+
"@xylabs/ts-scripts-yarn3": "^3.11.2",
|
|
30
|
+
"@xylabs/tsconfig": "^3.11.2",
|
|
31
|
+
"@xyo-network/account": "~2.104.1",
|
|
32
|
+
"@xyo-network/archivist-indexeddb": "~2.104.1",
|
|
33
|
+
"@xyo-network/node-memory": "~2.104.1",
|
|
34
34
|
"fake-indexeddb": "^5.0.2",
|
|
35
35
|
"typescript": "^5.4.5"
|
|
36
36
|
},
|
|
@@ -73,6 +73,6 @@
|
|
|
73
73
|
"url": "https://github.com/XYOracleNetwork/sdk-xyo-client-js.git"
|
|
74
74
|
},
|
|
75
75
|
"sideEffects": false,
|
|
76
|
-
"version": "2.104.
|
|
76
|
+
"version": "2.104.1",
|
|
77
77
|
"type": "module"
|
|
78
78
|
}
|