@xyo-network/diviner-payload-indexeddb 2.87.2 → 2.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Diviner.ts CHANGED
@@ -71,58 +71,59 @@ export class IndexedDbPayloadDiviner<
71
71
  protected override async divineHandler(payloads?: TIn[]): Promise<TOut[]> {
72
72
  const query = payloads?.filter(isPayloadDivinerQueryPayload)?.pop()
73
73
  if (!query) return []
74
- const db = await this.tryGetInitializedDb()
75
- if (!db) return []
76
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
77
- const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }
78
- const tx = db.transaction(this.storeName, 'readonly')
79
- const store = tx.objectStore(this.storeName)
80
- const results: TOut[] = []
81
- let parsedOffset = offset ?? 0
82
- const parsedLimit = limit ?? 10
83
- assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')
84
- const filterSchema = schemas?.[0]
85
- const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }
86
- const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'
87
- const suggestedIndex = this.selectBestIndex(filter, store)
88
- const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)
89
- const valueFilters: ValueFilter[] = props
90
- ? Object.entries(props)
91
- .map(([key, value]) => payloadValueFilter(key, value))
92
- .filter(exists)
93
- : []
94
- let cursor = suggestedIndex
95
- ? // Conditionally filter on schemas
96
- await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)
97
- : // Just iterate all records
98
- await store.openCursor(suggestedIndex, direction)
99
-
100
- // Skip records until the offset is reached
101
- while (cursor && parsedOffset > 0) {
102
- cursor = await cursor.advance(parsedOffset)
103
- parsedOffset = 0 // Reset offset after skipping
104
- }
105
- // Collect results up to the limit
106
- while (cursor && results.length < parsedLimit) {
107
- const value = cursor.value
108
- if (value) {
109
- // If we're filtering on more than just the schema
110
- if (valueFilters.length > 0) {
111
- // Ensure all filters pass
112
- if (valueFilters.every((filter) => filter(value))) {
113
- // Then save the value
74
+ const result = await this.tryUseDb(async (db) => {
75
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
76
+ const { schemas, limit, offset, hash, order, schema: _schema, sources, ...props } = query as unknown as TIn & { sources?: string[] }
77
+ const tx = db.transaction(this.storeName, 'readonly')
78
+ const store = tx.objectStore(this.storeName)
79
+ const results: TOut[] = []
80
+ let parsedOffset = offset ?? 0
81
+ const parsedLimit = limit ?? 10
82
+ assertEx((schemas?.length ?? 1) === 1, 'IndexedDbPayloadDiviner: Only one filter schema supported')
83
+ const filterSchema = schemas?.[0]
84
+ const filter = filterSchema ? { schema: filterSchema, ...props } : { ...props }
85
+ const direction: IDBCursorDirection = order === 'desc' ? 'prev' : 'next'
86
+ const suggestedIndex = this.selectBestIndex(filter, store)
87
+ const keyRangeValue = this.getKeyRangeValue(suggestedIndex, filter)
88
+ const valueFilters: ValueFilter[] = props
89
+ ? Object.entries(props)
90
+ .map(([key, value]) => payloadValueFilter(key, value))
91
+ .filter(exists)
92
+ : []
93
+ let cursor = suggestedIndex
94
+ ? // Conditionally filter on schemas
95
+ await store.index(suggestedIndex).openCursor(IDBKeyRange.only(keyRangeValue), direction)
96
+ : // Just iterate all records
97
+ await store.openCursor(suggestedIndex, direction)
98
+
99
+ // Skip records until the offset is reached
100
+ while (cursor && parsedOffset > 0) {
101
+ cursor = await cursor.advance(parsedOffset)
102
+ parsedOffset = 0 // Reset offset after skipping
103
+ }
104
+ // Collect results up to the limit
105
+ while (cursor && results.length < parsedLimit) {
106
+ const value = cursor.value
107
+ if (value) {
108
+ // If we're filtering on more than just the schema
109
+ if (valueFilters.length > 0) {
110
+ // Ensure all filters pass
111
+ if (valueFilters.every((filter) => filter(value))) {
112
+ // Then save the value
113
+ results.push(value)
114
+ }
115
+ } else {
116
+ // Otherwise just save the value
114
117
  results.push(value)
115
118
  }
116
- } else {
117
- // Otherwise just save the value
118
- results.push(value)
119
119
  }
120
+ cursor = await cursor.continue()
120
121
  }
121
- cursor = await cursor.continue()
122
- }
123
- await tx.done
124
- // Remove any metadata before returning to the client
125
- return results.map((payload) => PayloadHasher.jsonPayload(payload))
122
+ await tx.done
123
+ // Remove any metadata before returning to the client
124
+ return results.map((payload) => PayloadHasher.jsonPayload(payload))
125
+ })
126
+ return result ?? []
126
127
  }
127
128
 
128
129
  protected override async startHandler() {
@@ -177,27 +178,50 @@ export class IndexedDbPayloadDiviner<
177
178
  }
178
179
 
179
180
  /**
180
- * Checks that the desired DB/Store exists and is initialized
181
- * @returns The initialized DB or undefined if it does not exist
181
+ * Checks that the desired DB/objectStore exists and is initialized to the correct version
182
+ * @returns The initialized DB or undefined if it does not exist in the desired state
182
183
  */
183
184
  private async tryGetInitializedDb(): Promise<IDBPDatabase<PayloadStore> | undefined> {
184
- // If we've already checked and found a successfully initialized
185
- // db and objectStore, return the cached value
186
- if (this._db) return this._db
187
185
  // Enumerate the DBs
188
186
  const dbs = await indexedDB.databases()
187
+ // Check that the DB exists at the desired version
189
188
  const dbExists = dbs.some((db) => {
190
- // Check for the desired name/version
191
189
  return db.name === this.dbName && db.version === this.dbVersion
192
190
  })
193
- // If the DB does not exist at the desired version, return undefined
194
- if (!dbExists) return
195
- // If the db does exist, open it
196
- const db = await openDB<PayloadStore>(this.dbName, this.dbVersion)
197
- // Check that the desired objectStore exists
198
- const storeExists = db.objectStoreNames.contains(this.storeName)
199
- // If the correct db/store exists, cache it for future calls
200
- if (storeExists) this._db = db
201
- return this._db
191
+ // If the DB exists at the desired version
192
+ if (dbExists) {
193
+ // If the db does exist, open it
194
+ const db = await openDB<PayloadStore>(this.dbName, this.dbVersion)
195
+ // Check that the desired objectStore exists
196
+ const storeExists = db.objectStoreNames.contains(this.storeName)
197
+ // If the correct db/version/objectStore exists
198
+ if (storeExists) {
199
+ return db
200
+ } else {
201
+ // Otherwise close the db so the process that is going to update the
202
+ // db can open it
203
+ db.close()
204
+ }
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Executes a callback with the initialized DB and then closes the db
210
+ * @param callback The method to execute with the initialized DB
211
+ * @returns
212
+ */
213
+ private async tryUseDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T | undefined> {
214
+ // Get the initialized DB
215
+ const db = await this.tryGetInitializedDb()
216
+ if (db) {
217
+ try {
218
+ // Perform the callback
219
+ return await callback(db)
220
+ } finally {
221
+ // Close the DB
222
+ db.close()
223
+ }
224
+ }
225
+ return undefined
202
226
  }
203
227
  }