@xyo-network/image-thumbnail-plugin 2.75.5 → 2.75.7

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.
Files changed (40) hide show
  1. package/dist/docs.json +4946 -4911
  2. package/dist/node/Diviner/Config.d.cts +23 -0
  3. package/dist/node/Diviner/Config.d.cts.map +1 -1
  4. package/dist/node/Diviner/Config.d.mts +23 -0
  5. package/dist/node/Diviner/Config.d.mts.map +1 -1
  6. package/dist/node/Diviner/Config.d.ts +23 -0
  7. package/dist/node/Diviner/Config.d.ts.map +1 -1
  8. package/dist/node/Diviner/Config.js.map +1 -1
  9. package/dist/node/Diviner/Config.mjs.map +1 -1
  10. package/dist/node/Diviner/Diviner.d.cts +38 -20
  11. package/dist/node/Diviner/Diviner.d.cts.map +1 -1
  12. package/dist/node/Diviner/Diviner.d.mts +38 -20
  13. package/dist/node/Diviner/Diviner.d.mts.map +1 -1
  14. package/dist/node/Diviner/Diviner.d.ts +38 -20
  15. package/dist/node/Diviner/Diviner.d.ts.map +1 -1
  16. package/dist/node/Diviner/Diviner.js +169 -139
  17. package/dist/node/Diviner/Diviner.js.map +1 -1
  18. package/dist/node/Diviner/Diviner.mjs +168 -138
  19. package/dist/node/Diviner/Diviner.mjs.map +1 -1
  20. package/dist/node/Diviner/index.js +169 -139
  21. package/dist/node/Diviner/index.js.map +1 -1
  22. package/dist/node/Diviner/index.mjs +168 -138
  23. package/dist/node/Diviner/index.mjs.map +1 -1
  24. package/dist/node/Plugin.d.cts +3 -0
  25. package/dist/node/Plugin.d.cts.map +1 -1
  26. package/dist/node/Plugin.d.mts +3 -0
  27. package/dist/node/Plugin.d.mts.map +1 -1
  28. package/dist/node/Plugin.d.ts +3 -0
  29. package/dist/node/Plugin.d.ts.map +1 -1
  30. package/dist/node/Plugin.js +175 -145
  31. package/dist/node/Plugin.js.map +1 -1
  32. package/dist/node/Plugin.mjs +172 -142
  33. package/dist/node/Plugin.mjs.map +1 -1
  34. package/dist/node/index.js +175 -145
  35. package/dist/node/index.js.map +1 -1
  36. package/dist/node/index.mjs +172 -142
  37. package/dist/node/index.mjs.map +1 -1
  38. package/package.json +22 -16
  39. package/src/Diviner/Config.ts +24 -0
  40. package/src/Diviner/Diviner.ts +233 -158
@@ -4,10 +4,34 @@ import { ImageThumbnailSchema } from '@xyo-network/image-thumbnail-payload-plugi
4
4
  export const ImageThumbnailDivinerConfigSchema = `${ImageThumbnailSchema}.diviner.config` as const
5
5
  export type ImageThumbnailDivinerConfigSchema = typeof ImageThumbnailDivinerConfigSchema
6
6
 
7
+ /**
8
+ * Describes an Archivist/Diviner combination
9
+ * that enables searching signed payloads
10
+ */
11
+ export interface SearchableStorage {
12
+ archivist: string
13
+ boundWitnessDiviner: string
14
+ payloadDiviner: string
15
+ }
16
+
7
17
  export type ImageThumbnailDivinerConfig = DivinerConfig<{
18
+ /** @deprecated Use appropriate Storage */
8
19
  archivist?: string
20
+ /**
21
+ * Where the diviner should store it's index
22
+ */
23
+ indexStore?: SearchableStorage
24
+ /** @deprecated Use appropriate Storage */
9
25
  payloadDiviner?: string
10
26
  payloadDivinerLimit?: number
11
27
  pollFrequency?: number
12
28
  schema: ImageThumbnailDivinerConfigSchema
29
+ /**
30
+ * Where the diviner should persist its internal state
31
+ */
32
+ stateStore?: SearchableStorage
33
+ /**
34
+ * Where the diviner should look for stored thumbnails
35
+ */
36
+ thumbnailStore?: SearchableStorage
13
37
  }>
@@ -1,159 +1,259 @@
1
1
  import { assertEx } from '@xylabs/assert'
2
- import { delay } from '@xylabs/delay'
3
- import { compact } from '@xylabs/lodash'
2
+ import { exists } from '@xylabs/exists'
4
3
  import { AbstractDiviner } from '@xyo-network/abstract-diviner'
5
- import { ArchivistInstance, asArchivistInstance } from '@xyo-network/archivist-model'
4
+ import { asArchivistInstance, withArchivistModule } from '@xyo-network/archivist-model'
5
+ import { ArchivistWrapper } from '@xyo-network/archivist-wrapper'
6
+ import { isBoundWitness } from '@xyo-network/boundwitness-model'
6
7
  import { PayloadHasher } from '@xyo-network/core'
7
- import { asDivinerInstance, DivinerConfigSchema, DivinerInstance } from '@xyo-network/diviner-model'
8
+ import { BoundWitnessDivinerQueryPayload, BoundWitnessDivinerQuerySchema } from '@xyo-network/diviner-boundwitness-model'
9
+ import { asDivinerInstance, DivinerConfigSchema } from '@xyo-network/diviner-model'
8
10
  import { PayloadDivinerQueryPayload, PayloadDivinerQuerySchema } from '@xyo-network/diviner-payload-model'
9
- import { ImageThumbnail, ImageThumbnailSchema } from '@xyo-network/image-thumbnail-payload-plugin'
10
- import { UrlPayload } from '@xyo-network/url-payload-plugin'
11
+ import { DivinerWrapper } from '@xyo-network/diviner-wrapper'
12
+ import { ImageThumbnailSchema, isImageThumbnail } from '@xyo-network/image-thumbnail-payload-plugin'
13
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
14
+ import { isPayloadOfSchemaType, Payload } from '@xyo-network/payload-model'
15
+ import { isUrlPayload } from '@xyo-network/url-payload-plugin'
16
+ import { isTimestamp, TimestampSchema } from '@xyo-network/witness-timestamp'
11
17
 
12
- import { ImageThumbnailDivinerConfigSchema } from './Config'
18
+ import { ImageThumbnailDivinerConfig, ImageThumbnailDivinerConfigSchema } from './Config'
13
19
  import { ImageThumbnailDivinerParams } from './Params'
14
20
 
21
+ /**
22
+ * TODO: Once the shape settles, make a generic payload so that it
23
+ * can be used for other modules
24
+ */
25
+ interface State<T> {
26
+ state: T
27
+ }
28
+
29
+ interface ImageThumbnailDivinerState {
30
+ offset: number
31
+ }
32
+
33
+ const ModuleStateSchema = 'network.xyo.module.state' as const
34
+ type ModuleStateSchema = typeof ModuleStateSchema
35
+
36
+ type ModuleState = Payload<State<ImageThumbnailDivinerState>, ModuleStateSchema>
37
+
38
+ const isModuleState = isPayloadOfSchemaType<ModuleState>(ModuleStateSchema)
39
+
40
+ type ConfigStoreKey = 'indexStore' | 'stateStore' | 'thumbnailStore'
41
+
42
+ type ConfigStore = Extract<keyof ImageThumbnailDivinerConfig, ConfigStoreKey>
43
+
44
+ const ImageThumbnailResultIndexSchema = `${ImageThumbnailSchema}.index` as const
45
+ type ImageThumbnailResultIndexSchema = typeof ImageThumbnailResultIndexSchema
46
+
47
+ interface ImageThumbnailResultInfo {
48
+ sources: string[]
49
+ // TODO: Something richer than HTTP status code that allows for info about failure modes
50
+ status: number
51
+ timestamp: number
52
+ url: string
53
+ }
54
+
55
+ type ImageThumbnailResult = Payload<ImageThumbnailResultInfo, ImageThumbnailResultIndexSchema>
56
+
57
+ const isImageThumbnailResult = isPayloadOfSchemaType<ImageThumbnailResult>(ImageThumbnailResultIndexSchema)
58
+
59
+ /**
60
+ * The fields that will need to be indexed on in the underlying store
61
+ */
62
+ type QueryableImageThumbnailResultProperties = Extract<keyof ImageThumbnailResult, 'url' | 'timestamp' | 'status'>
63
+
64
+ /**
65
+ * The query that will be used to retrieve the results from the underlying store
66
+ */
67
+ type ImageThumbnailResultQuery = PayloadDivinerQueryPayload & { schemas: [ImageThumbnailSchema] } & Pick<
68
+ ImageThumbnailResult,
69
+ QueryableImageThumbnailResultProperties
70
+ >
71
+
72
+ const moduleName = 'ImageThumbnailDiviner'
73
+
15
74
  export class ImageThumbnailDiviner<TParams extends ImageThumbnailDivinerParams = ImageThumbnailDivinerParams> extends AbstractDiviner<TParams> {
16
75
  static override configSchemas = [ImageThumbnailDivinerConfigSchema, DivinerConfigSchema]
17
76
 
18
- private _archivistInstance: Promise<ArchivistInstance> | undefined
19
- private _initializeArchivistConnectionIfNeededPromise: Promise<void> | undefined
20
- private _map: Record<string, string> | undefined
21
- private _payloadDivinerInstance: Promise<DivinerInstance> | undefined
22
77
  private _pollId?: string | number | NodeJS.Timeout
23
78
 
24
- // static override get configSchema() {
25
- // return ImageThumbnailDivinerConfigSchema
26
- // }
27
-
28
- get archivist() {
29
- return this.config.archivist
79
+ get payloadDivinerLimit() {
80
+ return this.config.payloadDivinerLimit ?? 1_0000
30
81
  }
31
82
 
32
- get payloadDiviner() {
33
- return this.config.payloadDiviner
83
+ get pollFrequency() {
84
+ return this.config.pollFrequency ?? 10_000
34
85
  }
35
86
 
36
- get payloadDivinerLimit() {
37
- return this.config.payloadDivinerLimit ?? 10000
87
+ protected backgroundDivine = async (): Promise<void> => {
88
+ // Load last state
89
+ const lastState = (await this.retrieveState()) ?? { offset: 0 }
90
+ const { offset } = lastState
91
+ // Get next batch of results
92
+ const boundWitnessDiviner = await this.getBoundWitnessDivinerForStore('thumbnailStore')
93
+ const query = new PayloadBuilder<BoundWitnessDivinerQueryPayload>({ schema: BoundWitnessDivinerQuerySchema }).fields({
94
+ limit: this.payloadDivinerLimit,
95
+ offset,
96
+ order: 'asc',
97
+ payload_schemas: [ImageThumbnailSchema, TimestampSchema],
98
+ })
99
+ const batch = await boundWitnessDiviner.divine([query])
100
+ if (batch.length === 0) return
101
+ const imageThumbnailTimestampTuples = batch
102
+ .filter(isBoundWitness)
103
+ .map((bw) => {
104
+ const imageThumbnailIndexes = bw.payload_schemas?.map((schema, index) => (schema === ImageThumbnailSchema ? index : undefined)).filter(exists)
105
+ const timestampIndex = bw.payload_schemas?.findIndex((schema) => schema === TimestampSchema)
106
+ if (!imageThumbnailIndexes.length || timestampIndex === -1) return undefined
107
+ const imageThumbnails = bw.payload_hashes.map((hash, index) => (imageThumbnailIndexes.includes(index) ? hash : undefined)).filter(exists)
108
+ const timestamp = bw.payload_hashes?.[timestampIndex]
109
+ return imageThumbnails.map((imageThumbnail) => [imageThumbnail, timestamp] as const)
110
+ })
111
+ .flat()
112
+ .filter(exists)
113
+ const archivist = await this.getArchivistForStore('thumbnailStore')
114
+ const payloadTuples = (
115
+ await Promise.all(
116
+ imageThumbnailTimestampTuples.map(async ([imageThumbnailHash, timestampHash]) => {
117
+ const results = await archivist.get([imageThumbnailHash, timestampHash])
118
+ const imageThumbnailPayload = results.find(isImageThumbnail)
119
+ const timestampPayload = results.find(isTimestamp)
120
+ if (!imageThumbnailPayload || !timestampPayload) return undefined
121
+ const calculatedImageThumbnailHash = await PayloadHasher.hashAsync(imageThumbnailPayload)
122
+ const calculatedTimestampHash = await PayloadHasher.hashAsync(timestampPayload)
123
+ if (imageThumbnailHash !== calculatedImageThumbnailHash || timestampHash !== calculatedTimestampHash) return undefined
124
+ return [imageThumbnailHash, imageThumbnailPayload, timestampHash, timestampPayload] as const
125
+ }),
126
+ )
127
+ ).filter(exists)
128
+ // Build index results
129
+ const indexedResults = payloadTuples.map(([thumbnailHash, thumbnailPayload, timestampHash, timestampPayload]) => {
130
+ const { sourceUrl: url } = thumbnailPayload
131
+ const { timestamp } = timestampPayload
132
+ const status = thumbnailPayload.http?.status ?? -1
133
+ const sources = [thumbnailHash, timestampHash]
134
+ const result = new PayloadBuilder<ImageThumbnailResult>({ schema: ImageThumbnailResultIndexSchema })
135
+ .fields({ sources, status, timestamp, url })
136
+ .build()
137
+ return result
138
+ })
139
+ // Insert index results
140
+ const indexArchivist = await this.getArchivistForStore('indexStore')
141
+ await indexArchivist.insert(indexedResults)
142
+ // Update state
143
+ const nextOffset = offset + batch.length + 1
144
+ const currentState = { ...lastState, offset: nextOffset }
145
+ await this.commitState(currentState)
38
146
  }
39
147
 
40
- get pollFrequency() {
41
- return this.config.pollFrequency
148
+ /**
149
+ * Commit the internal state of the Diviner process. This is similar
150
+ * to a transaction completion in a database and should only be called
151
+ * when results have been successfully persisted to the appropriate
152
+ * external stores.
153
+ */
154
+ protected async commitState(state: ImageThumbnailDivinerState) {
155
+ const stateStore = assertEx(this.config.stateStore?.archivist, `${moduleName}: No stateStore configured`)
156
+ const module = assertEx(await this.resolve(stateStore), `${moduleName}: Failed to resolve stateStore`)
157
+ await withArchivistModule(module, async (archivist) => {
158
+ const mod = ArchivistWrapper.wrap(archivist, this.account)
159
+ const payload = new PayloadBuilder<ModuleState>({ schema: ModuleStateSchema }).fields({ state }).build()
160
+ await mod.insert([payload])
161
+ })
42
162
  }
43
163
 
44
- //using promise as mutex
45
- async getArchivistInstance(): Promise<ArchivistInstance> {
46
- //if previously checked, but not found, clear promise
47
- if (this._archivistInstance && !(await this._archivistInstance)) {
48
- this._archivistInstance = undefined
49
- }
50
- this._archivistInstance =
51
- this._archivistInstance ??
52
- (async () => {
53
- const module = this.archivist ? await this.resolve(this.archivist) : undefined
54
- return asArchivistInstance(module, 'Provided archivist address did not resolve to an Archivist')
55
- })()
56
- return this._archivistInstance
164
+ protected override async divineHandler(payloads: Payload[] = []): Promise<ImageThumbnailResult[]> {
165
+ const urls = payloads.filter(isUrlPayload).map((urlPayload) => urlPayload.url)
166
+ const diviner = await this.getPayloadDivinerForStore('indexStore')
167
+ const results = (
168
+ await Promise.all(
169
+ urls.map(async (url) => {
170
+ const query = new PayloadBuilder<ImageThumbnailResultQuery>({ schema: PayloadDivinerQuerySchema })
171
+ // TODO: Expose status, limit (and possibly offset) to caller. Currently only exposing URL
172
+ .fields({ limit: 1, offset: 0, order: 'desc', url })
173
+ .build()
174
+ return await diviner.divine([query])
175
+ }),
176
+ )
177
+ )
178
+ .flat()
179
+ .filter(isImageThumbnailResult)
180
+ return results
57
181
  }
58
182
 
59
- //using promise as mutex
60
- async getPayloadDivinerInstance(): Promise<DivinerInstance | undefined> {
61
- const payloadDivinerAddress = this.payloadDiviner
62
- if (payloadDivinerAddress) {
63
- //if previously checked, but not found, clear promise
64
- if (this._payloadDivinerInstance && !(await this._payloadDivinerInstance)) {
65
- this._payloadDivinerInstance = undefined
66
- }
67
- this._payloadDivinerInstance =
68
- this._payloadDivinerInstance ??
69
- (async () => {
70
- const module = await this.resolve(payloadDivinerAddress)
71
- return asDivinerInstance(module, 'Provided payload diviner address did not resolve to a Diviner')
72
- })()
73
-
74
- return this._payloadDivinerInstance
75
- }
183
+ protected async getArchivistForStore(store: ConfigStore, wrap?: boolean) {
184
+ const name = assertEx(this.config?.[store]?.archivist, () => `${moduleName}: Config for ${store}.archivist not specified`)
185
+ const mod = assertEx(await this.resolve(name), () => `${moduleName}: Failed to resolve ${store}.archivist`)
186
+ return wrap ? ArchivistWrapper.wrap(mod, this.account) : asArchivistInstance(mod, () => `${moduleName}: ${store}.archivist is not an Archivist`)
76
187
  }
77
188
 
78
- protected override async divineHandler(payloads: UrlPayload[] = []): Promise<ImageThumbnail[]> {
79
- await this.initializeArchivistConnectionIfNeeded()
80
- const urls = payloads.map((urlPayload) => urlPayload.url)
81
- const map = await this.getSafeMap()
82
- const archivist = await this.getArchivistInstance()
83
- const hashes = compact(urls.map((url) => map?.[url]))
84
- return (await archivist.get(hashes)).filter((payload): payload is ImageThumbnail => payload.schema === ImageThumbnailSchema)
189
+ protected async getBoundWitnessDivinerForStore(store: ConfigStore, wrap?: boolean) {
190
+ const name = assertEx(this.config?.[store]?.boundWitnessDiviner, () => `${moduleName}: Config for ${store}.boundWitnessDiviner not specified`)
191
+ const mod = assertEx(await this.resolve(name), () => `${moduleName}: Failed to resolve ${store}.boundWitnessDiviner`)
192
+ return wrap
193
+ ? DivinerWrapper.wrap(mod, this.account)
194
+ : asDivinerInstance(mod, () => `${moduleName}: ${store}.boundWitnessDiviner is not a Diviner`)
85
195
  }
86
196
 
87
- //using promise as mutex
88
- protected initializeArchivistConnectionIfNeeded() {
89
- this._initializeArchivistConnectionIfNeededPromise =
90
- this._initializeArchivistConnectionIfNeededPromise ??
91
- (async () => {
92
- if (!this._map) {
93
- await this.attachArchivistEvents()
94
- console.log('initializeArchivistConnectionIfNeeded: attachArchivistEvents done')
95
- await this.poll()
96
- console.log('initializeArchivistConnectionIfNeeded: poll done')
97
- }
98
- })()
99
- return this._initializeArchivistConnectionIfNeededPromise
197
+ protected async getPayloadDivinerForStore(store: ConfigStore, wrap?: boolean) {
198
+ const name = assertEx(this.config?.[store]?.payloadDiviner, () => `${moduleName}: Config for ${store}.payloadDiviner not specified`)
199
+ const mod = assertEx(await this.resolve(name), () => `${moduleName}: Failed to resolve ${store}.payloadDiviner`)
200
+ return wrap ? DivinerWrapper.wrap(mod, this.account) : asDivinerInstance(mod, () => `${moduleName}: ${store}.payloadDiviner is not a Diviner`)
100
201
  }
101
202
 
102
- protected async loadMap() {
103
- if (this.payloadDiviner) {
104
- return await this.loadMapWithPayloadDiviner()
105
- } else {
106
- return await this.loadMapWithAll()
203
+ /**
204
+ * Retrieves the last state of the Diviner process. Used to recover state after
205
+ * preemptions, reboots, etc.
206
+ */
207
+ protected async retrieveState(): Promise<ImageThumbnailDivinerState | undefined> {
208
+ let hash: string = ''
209
+ const diviner = await this.getBoundWitnessDivinerForStore('stateStore')
210
+ const query = new PayloadBuilder<BoundWitnessDivinerQueryPayload>({ schema: BoundWitnessDivinerQuerySchema }).fields({
211
+ address: this.account.address,
212
+ limit: 1,
213
+ offset: 0,
214
+ order: 'desc',
215
+ payload_schemas: [ModuleStateSchema],
216
+ })
217
+ const boundWitnesses = await diviner.divine([query])
218
+ if (boundWitnesses.length > 0) {
219
+ const boundWitness = boundWitnesses[0]
220
+ if (isBoundWitness(boundWitness)) {
221
+ // Find the index for this address in the BoundWitness that is a ModuleState
222
+ hash = boundWitness.addresses
223
+ .map((address, index) => ({ address, index }))
224
+ .filter(({ address }) => address === this.account.address)
225
+ .reduce(
226
+ (prev, curr) => (boundWitness.payload_schemas?.[curr?.index] === ModuleStateSchema ? boundWitness.payload_hashes[curr?.index] : prev),
227
+ '',
228
+ )
229
+ }
107
230
  }
108
- }
109
231
 
110
- protected async loadMapWithAll() {
111
- if (await this.started()) {
112
- const archivist = await this.getArchivistInstance()
113
- assertEx(archivist.all, "Archivist does not support 'all'")
114
- const allPayloads = (await archivist.all?.()) ?? []
115
- const imagePayloadPairs = await Promise.all(
116
- allPayloads
117
- .filter((payload): payload is ImageThumbnail => payload.schema === ImageThumbnailSchema)
118
- .map<Promise<[string, ImageThumbnail]>>(async (payload) => [await PayloadHasher.hashAsync(payload), payload]),
232
+ // If we able to located the last state
233
+ if (hash) {
234
+ // Get last state
235
+ const stateStoreArchivist = assertEx(this.config.stateStore?.archivist, `${moduleName}: No stateStore archivist configured`)
236
+ await withArchivistModule(
237
+ assertEx(await this.resolve(stateStoreArchivist), `${moduleName}: Failed to resolve stateStore archivist`),
238
+ async (mod) => {
239
+ const archivist = ArchivistWrapper.wrap(mod, this.account)
240
+ const payloads = await archivist.get([hash])
241
+ if (payloads.length > 0) {
242
+ const payload = payloads[0]
243
+ if (isModuleState(payload)) {
244
+ return payload.state
245
+ }
246
+ }
247
+ },
119
248
  )
120
- this._map = imagePayloadPairs.reduce<Record<string, string>>((prev, [hash, payload]) => {
121
- prev[payload.sourceUrl] = hash
122
- return prev
123
- }, {})
124
249
  }
250
+ return undefined
125
251
  }
126
252
 
127
- protected async loadMapWithPayloadDiviner() {
128
- console.log('loadMapWithPayloadDiviner: started')
129
- if (await this.started()) {
130
- const diviner = await this.getPayloadDivinerInstance()
131
- let offset: number | undefined = undefined
132
- let moreAvailable = true
133
- if (diviner) {
134
- const newMap: Record<string, string> = {}
135
- while (moreAvailable) {
136
- const payloadDivinerQuery: PayloadDivinerQueryPayload = {
137
- limit: this.payloadDivinerLimit,
138
- offset,
139
- schema: PayloadDivinerQuerySchema,
140
- schemas: [ImageThumbnailSchema],
141
- }
142
- const payloads = await diviner.divine([payloadDivinerQuery])
143
- offset = (offset ?? 0) + payloads.length
144
- moreAvailable = payloads.length > 0
145
- console.log(`loadMapWithPayloadDiviner.offset: ${offset}`)
146
- console.log(`loadMapWithPayloadDiviner.moreAvailable: ${moreAvailable}`)
147
- const imagePayloadPairs = await Promise.all(
148
- payloads
149
- .filter((payload): payload is ImageThumbnail => payload.schema === ImageThumbnailSchema)
150
- .map<Promise<[string, ImageThumbnail]>>(async (payload) => [await PayloadHasher.hashAsync(payload), payload]),
151
- )
152
- imagePayloadPairs.forEach(([hash, payload]) => (newMap[payload.sourceUrl] = hash))
153
- }
154
- this._map = newMap
155
- }
156
- }
253
+ protected override async startHandler(): Promise<boolean> {
254
+ await super.startHandler()
255
+ this.poll()
256
+ return true
157
257
  }
158
258
 
159
259
  protected override async stopHandler(_timeout?: number | undefined): Promise<boolean> {
@@ -164,42 +264,17 @@ export class ImageThumbnailDiviner<TParams extends ImageThumbnailDivinerParams =
164
264
  return await super.stopHandler()
165
265
  }
166
266
 
167
- private async attachArchivistEvents() {
168
- const archivist = await this.getArchivistInstance()
169
- const mapPromise = this.getSafeMap()
170
- archivist.on('inserted', async ({ payloads }) => {
171
- const map = await mapPromise
172
- const thumbnails = compact(payloads.filter((payload): payload is ImageThumbnail => payload.schema === ImageThumbnailSchema))
173
- await Promise.all(thumbnails.map(async (payload) => (map[payload.sourceUrl] = await PayloadHasher.hashAsync(payload))))
174
- })
175
- }
176
-
177
- private async getSafeMap() {
178
- let mapRetry = 100 //10 seconds max
179
- let map = this._map
180
- while (!map) {
181
- await delay(100)
182
- mapRetry = mapRetry - 1
183
- if (mapRetry === 0) {
184
- throw Error('Map Not Loaded')
267
+ private poll() {
268
+ this._pollId = setTimeout(async () => {
269
+ try {
270
+ await this.backgroundDivine()
271
+ } catch (e) {
272
+ console.log(e)
273
+ } finally {
274
+ if (this._pollId) clearTimeout(this._pollId)
275
+ this._pollId = undefined
276
+ this.poll()
185
277
  }
186
- map = this._map
187
- }
188
- return map
189
- }
190
-
191
- private async poll() {
192
- if (await this.started()) {
193
- const pollFrequency = this.pollFrequency
194
- if (pollFrequency) {
195
- this._pollId = setTimeout(async () => {
196
- this._pollId = undefined
197
- await this.loadMap()
198
- await this.poll()
199
- }, pollFrequency)
200
- } else {
201
- await this.loadMap()
202
- }
203
- }
278
+ }, this.pollFrequency)
204
279
  }
205
280
  }