@xyo-network/module-abstract 2.64.8 → 2.64.9

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 (43) hide show
  1. package/dist/cjs/AbstractIndirectModule.js +411 -0
  2. package/dist/cjs/AbstractIndirectModule.js.map +1 -0
  3. package/dist/cjs/AbstractModule.js +24 -371
  4. package/dist/cjs/AbstractModule.js.map +1 -1
  5. package/dist/cjs/QueryValidator/SupportedQueryValidator.js.map +1 -1
  6. package/dist/cjs/Resolver/CompositeModuleResolver.js +2 -3
  7. package/dist/cjs/Resolver/CompositeModuleResolver.js.map +1 -1
  8. package/dist/cjs/Resolver/ResolverEventEmitter.js.map +1 -1
  9. package/dist/cjs/Resolver/SimpleModuleResolver.js.map +1 -1
  10. package/dist/cjs/lib/duplicateModules.js.map +1 -1
  11. package/dist/docs.json +4173 -3806
  12. package/dist/esm/AbstractIndirectModule.js +373 -0
  13. package/dist/esm/AbstractIndirectModule.js.map +1 -0
  14. package/dist/esm/AbstractModule.js +16 -350
  15. package/dist/esm/AbstractModule.js.map +1 -1
  16. package/dist/esm/QueryValidator/SupportedQueryValidator.js.map +1 -1
  17. package/dist/esm/Resolver/CompositeModuleResolver.js +2 -3
  18. package/dist/esm/Resolver/CompositeModuleResolver.js.map +1 -1
  19. package/dist/esm/Resolver/ResolverEventEmitter.js.map +1 -1
  20. package/dist/esm/Resolver/SimpleModuleResolver.js.map +1 -1
  21. package/dist/esm/lib/duplicateModules.js.map +1 -1
  22. package/dist/types/AbstractIndirectModule.d.ts +66 -0
  23. package/dist/types/AbstractIndirectModule.d.ts.map +1 -0
  24. package/dist/types/AbstractModule.d.ts +7 -58
  25. package/dist/types/AbstractModule.d.ts.map +1 -1
  26. package/dist/types/QueryValidator/SupportedQueryValidator.d.ts +4 -4
  27. package/dist/types/QueryValidator/SupportedQueryValidator.d.ts.map +1 -1
  28. package/dist/types/Resolver/CompositeModuleResolver.d.ts +5 -5
  29. package/dist/types/Resolver/CompositeModuleResolver.d.ts.map +1 -1
  30. package/dist/types/Resolver/ResolverEventEmitter.d.ts +2 -2
  31. package/dist/types/Resolver/ResolverEventEmitter.d.ts.map +1 -1
  32. package/dist/types/Resolver/SimpleModuleResolver.d.ts +5 -5
  33. package/dist/types/Resolver/SimpleModuleResolver.d.ts.map +1 -1
  34. package/dist/types/lib/duplicateModules.d.ts +2 -2
  35. package/dist/types/lib/duplicateModules.d.ts.map +1 -1
  36. package/package.json +24 -24
  37. package/src/AbstractIndirectModule.ts +497 -0
  38. package/src/AbstractModule.ts +20 -460
  39. package/src/QueryValidator/SupportedQueryValidator.ts +3 -3
  40. package/src/Resolver/CompositeModuleResolver.ts +9 -10
  41. package/src/Resolver/ResolverEventEmitter.ts +5 -5
  42. package/src/Resolver/SimpleModuleResolver.ts +11 -11
  43. package/src/lib/duplicateModules.ts +2 -2
@@ -0,0 +1,497 @@
1
+ import { assertEx } from '@xylabs/assert'
2
+ import { exists } from '@xylabs/exists'
3
+ import { Account, HDWallet } from '@xyo-network/account'
4
+ import { AccountInstance } from '@xyo-network/account-model'
5
+ import { AddressPayload, AddressSchema } from '@xyo-network/address-payload-plugin'
6
+ import { ArchivistModule } from '@xyo-network/archivist-model'
7
+ import { BoundWitnessBuilder } from '@xyo-network/boundwitness-builder'
8
+ import { BoundWitness } from '@xyo-network/boundwitness-model'
9
+ import { ConfigPayload, ConfigSchema } from '@xyo-network/config-payload-plugin'
10
+ import { handleErrorAsync } from '@xyo-network/error'
11
+ import { ModuleManifestPayload, ModuleManifestPayloadSchema } from '@xyo-network/manifest-model'
12
+ import {
13
+ AccountModuleParams,
14
+ AddressPreviousHashPayload,
15
+ AddressPreviousHashSchema,
16
+ CreatableModule,
17
+ CreatableModuleFactory,
18
+ IndirectModule,
19
+ IndividualArchivistConfig,
20
+ ModuleAddressQuerySchema,
21
+ ModuleConfig,
22
+ ModuleDescribeQuerySchema,
23
+ ModuleDescriptionPayload,
24
+ ModuleDescriptionSchema,
25
+ ModuleDiscoverQuerySchema,
26
+ ModuleError,
27
+ ModuleEventData,
28
+ ModuleFilter,
29
+ ModuleParams,
30
+ ModuleQueriedEventArgs,
31
+ ModuleQuery,
32
+ ModuleQueryBase,
33
+ ModuleQueryResult,
34
+ ModuleSubscribeQuerySchema,
35
+ Query,
36
+ QueryBoundWitness,
37
+ SchemaString,
38
+ WalletModuleParams,
39
+ } from '@xyo-network/module-model'
40
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
41
+ import { Payload } from '@xyo-network/payload-model'
42
+ import { PayloadWrapper } from '@xyo-network/payload-wrapper'
43
+ import { Promisable, PromiseEx } from '@xyo-network/promise'
44
+ import { QueryPayload, QuerySchema } from '@xyo-network/query-payload-plugin'
45
+ import { IdLogger } from '@xyo-network/shared'
46
+ import compact from 'lodash/compact'
47
+
48
+ import { BaseEmitter } from './BaseEmitter'
49
+ import { ModuleErrorBuilder } from './Error'
50
+ import { duplicateModules, serializableField } from './lib'
51
+ import { ModuleFactory } from './ModuleFactory'
52
+ import { QueryBoundWitnessBuilder, QueryBoundWitnessWrapper } from './Query'
53
+ import { ModuleConfigQueryValidator, Queryable, SupportedQueryValidator } from './QueryValidator'
54
+ import { CompositeModuleResolver } from './Resolver'
55
+
56
+ /** @description Abstract class for modules that allow access only through querying and not through direct calls */
57
+
58
+ export abstract class AbstractIndirectModule<TParams extends ModuleParams = ModuleParams, TEventData extends ModuleEventData = ModuleEventData>
59
+ extends BaseEmitter<TParams, TEventData>
60
+ implements IndirectModule<TParams, TEventData>
61
+ {
62
+ static configSchemas: string[]
63
+
64
+ protected static privateConstructorKey = Date.now().toString()
65
+
66
+ readonly downResolver = new CompositeModuleResolver()
67
+ readonly upResolver = new CompositeModuleResolver()
68
+
69
+ protected _account: AccountInstance | undefined = undefined
70
+ protected readonly _baseModuleQueryAccountPaths: Record<ModuleQueryBase['schema'], string> = {
71
+ [ModuleAddressQuerySchema]: '1',
72
+ [ModuleDescribeQuerySchema]: '4',
73
+ [ModuleDiscoverQuerySchema]: '2',
74
+ [ModuleSubscribeQuerySchema]: '3',
75
+ }
76
+ protected readonly _queryAccounts: Record<ModuleQueryBase['schema'], AccountInstance | undefined> = {
77
+ [ModuleAddressQuerySchema]: undefined,
78
+ [ModuleDescribeQuerySchema]: undefined,
79
+ [ModuleDiscoverQuerySchema]: undefined,
80
+ [ModuleSubscribeQuerySchema]: undefined,
81
+ }
82
+ protected _started = false
83
+ protected readonly moduleConfigQueryValidator: Queryable
84
+ protected readonly supportedQueryValidator: Queryable
85
+
86
+ constructor(privateConstructorKey: string, params: TParams) {
87
+ assertEx(AbstractIndirectModule.privateConstructorKey === privateConstructorKey, 'Use create function instead of constructor')
88
+ // Clone params to prevent mutation of the incoming object
89
+ const mutatedParams = { ...params } as TParams
90
+ super(mutatedParams)
91
+
92
+ this.supportedQueryValidator = new SupportedQueryValidator(this as IndirectModule).queryable
93
+ this.moduleConfigQueryValidator = new ModuleConfigQueryValidator(mutatedParams?.config).queryable
94
+ }
95
+
96
+ static get configSchema(): string {
97
+ return this.configSchemas[0]
98
+ }
99
+
100
+ get account() {
101
+ return assertEx(this._account, 'Missing account')
102
+ }
103
+
104
+ get address() {
105
+ return this.account.address
106
+ }
107
+
108
+ get allowAnonymous() {
109
+ return !!this.config.security?.allowAnonymous
110
+ }
111
+
112
+ get config(): TParams['config'] {
113
+ return this.params.config
114
+ }
115
+
116
+ get ephemeralQueryAccountEnabled(): boolean {
117
+ return !!this.params.ephemeralQueryAccountEnabled
118
+ }
119
+
120
+ get queries(): string[] {
121
+ return [ModuleDiscoverQuerySchema, ModuleAddressQuerySchema, ModuleSubscribeQuerySchema]
122
+ }
123
+
124
+ get queryAccountPaths(): Readonly<Record<Query['schema'], string | undefined>> {
125
+ return { ...this._baseModuleQueryAccountPaths, ...this._queryAccountPaths }
126
+ }
127
+
128
+ get queryAccounts(): Readonly<Record<Query['schema'], AccountInstance | undefined>> {
129
+ return this._queryAccounts
130
+ }
131
+
132
+ protected abstract get _queryAccountPaths(): Record<Query['schema'], string>
133
+
134
+ static async create<TModule extends IndirectModule>(this: CreatableModule<TModule>, params?: TModule['params']) {
135
+ if (!this.configSchemas || this.configSchemas.length === 0) {
136
+ throw Error(`Missing configSchema [${params?.config?.schema}][${this.name}]`)
137
+ }
138
+ const schema: string = params?.config?.schema ?? this.configSchema
139
+ const allowedSchemas: string[] = this.configSchemas
140
+
141
+ assertEx(
142
+ allowedSchemas.filter((allowedSchema) => allowedSchema === schema).length > 0,
143
+ `Bad Config Schema [Received ${schema}] [Expected ${JSON.stringify(allowedSchemas)}]`,
144
+ )
145
+ const mutatedConfig: TModule['params']['config'] = { ...params?.config, schema }
146
+ params?.logger?.debug(`config: ${JSON.stringify(mutatedConfig, null, 2)}`)
147
+ const mutatedParams = { ...params, config: mutatedConfig } as TModule['params']
148
+ const newModule = new this(AbstractIndirectModule.privateConstructorKey, mutatedParams)
149
+ await newModule.loadAccount?.()
150
+ await newModule.start?.()
151
+ return newModule
152
+ }
153
+
154
+ static factory<TModule extends IndirectModule>(this: CreatableModule<TModule>, params?: TModule['params']): CreatableModuleFactory<TModule> {
155
+ return ModuleFactory.withParams(this, params)
156
+ }
157
+
158
+ addressPreviousHash(): Promisable<AddressPreviousHashPayload> {
159
+ return { address: this.address, previousHash: this._account?.previousHash, schema: AddressPreviousHashSchema }
160
+ }
161
+
162
+ async loadAccount() {
163
+ if (!this._account) {
164
+ const activeLogger = this.params.logger ?? AbstractIndirectModule.defaultLogger
165
+ let { account } = this.params as AccountModuleParams<TParams['config']>
166
+ const { wallet, accountDerivationPath } = this.params as WalletModuleParams<TParams['config']>
167
+ if (wallet) {
168
+ account = assertEx(accountDerivationPath ? await wallet.derivePath(accountDerivationPath) : wallet, 'Failed to derive account from path')
169
+ }
170
+ this.params.logger = activeLogger ? new IdLogger(activeLogger, () => `0x${account.address}`) : undefined
171
+ if (!account) console.warn(`AbstractModule.loadAccount: No account provided - Creating Random account [${this.config.schema}]`)
172
+ this._account = account ?? (await HDWallet.random())
173
+ }
174
+ this.downResolver.add(this as IndirectModule)
175
+ return this._account
176
+ }
177
+
178
+ manifest(): Promisable<ModuleManifestPayload> {
179
+ const name = assertEx(this.config.name, 'Calling manifest on un-named module is not supported')
180
+ return { config: { name, ...this.config }, schema: ModuleManifestPayloadSchema }
181
+ }
182
+
183
+ moduleAddressQuery(): Promisable<(AddressPayload | AddressPreviousHashPayload)[]> {
184
+ // Return array of all addresses and their previous hash
185
+ const queryAccounts = Object.entries(this.queryAccounts)
186
+ .filter((value): value is [string, AccountInstance] => {
187
+ return exists(value[1])
188
+ })
189
+ .map(([name, account]) => {
190
+ const address = account.address
191
+ const previousHash = account.previousHash
192
+ return [
193
+ { address, name, schema: AddressSchema },
194
+ { address, previousHash, schema: AddressPreviousHashSchema },
195
+ ]
196
+ })
197
+ const address = this.address
198
+ const name = this.config.name
199
+ const previousHash = this.address
200
+ const moduleAccount = name ? { address, name, schema: AddressSchema } : { address, schema: AddressSchema }
201
+ const moduleAccountPreviousHash = previousHash
202
+ ? { address, previousHash, schema: AddressPreviousHashSchema }
203
+ : { address, schema: AddressPreviousHashSchema }
204
+ return [moduleAccount, moduleAccountPreviousHash, ...queryAccounts].flat()
205
+ }
206
+
207
+ previousHash(): Promisable<string | undefined> {
208
+ return this.account.previousHash
209
+ }
210
+
211
+ async query<T extends QueryBoundWitness = QueryBoundWitness, TConfig extends ModuleConfig = ModuleConfig>(
212
+ query: T,
213
+ payloads?: Payload[],
214
+ queryConfig?: TConfig,
215
+ ): Promise<ModuleQueryResult> {
216
+ this.started('throw')
217
+ const result = await this.queryHandler(assertEx(QueryBoundWitnessWrapper.unwrap(query)), payloads, queryConfig)
218
+
219
+ const args: ModuleQueriedEventArgs = { module: this as IndirectModule, payloads, query, result }
220
+ await this.emit('moduleQueried', args)
221
+
222
+ return result
223
+ }
224
+
225
+ queryable<T extends QueryBoundWitness = QueryBoundWitness, TConfig extends ModuleConfig = ModuleConfig>(
226
+ query: T,
227
+ payloads?: Payload[],
228
+ queryConfig?: TConfig,
229
+ ): boolean {
230
+ if (!this.started('warn')) return false
231
+ const configValidator = queryConfig
232
+ ? new ModuleConfigQueryValidator(Object.assign({}, this.config, queryConfig)).queryable
233
+ : this.moduleConfigQueryValidator
234
+ const validators = [this.supportedQueryValidator, configValidator]
235
+
236
+ return validators.every((validator) => validator(query, payloads))
237
+ }
238
+
239
+ async resolve<TModule extends IndirectModule = IndirectModule>(filter?: ModuleFilter): Promise<TModule[]>
240
+ async resolve<TModule extends IndirectModule = IndirectModule>(nameOrAddress: string): Promise<TModule | undefined>
241
+ async resolve<TModule extends IndirectModule = IndirectModule>(
242
+ nameOrAddressOrFilter?: ModuleFilter | string,
243
+ ): Promise<TModule | TModule[] | undefined> {
244
+ switch (typeof nameOrAddressOrFilter) {
245
+ case 'string': {
246
+ const byAddress = Account.isAddress(nameOrAddressOrFilter)
247
+ ? (await this.resolve<TModule>({ address: [nameOrAddressOrFilter] })).pop()
248
+ : undefined
249
+ return byAddress ?? (await this.resolve<TModule>({ name: [nameOrAddressOrFilter] })).pop()
250
+ }
251
+ default: {
252
+ const filter: ModuleFilter | undefined = nameOrAddressOrFilter
253
+ return [...(await this.downResolver.resolve<TModule>(filter)), ...(await this.upResolver.resolve<TModule>(filter))].filter(duplicateModules)
254
+ }
255
+ }
256
+ }
257
+
258
+ async start(_timeout?: number): Promise<void> {
259
+ this.validateConfig()
260
+ await this.initializeQueryAccounts()
261
+ this._started = true
262
+ }
263
+
264
+ started(notStartedAction?: 'error' | 'throw' | 'warn' | 'log' | 'none') {
265
+ if (!this._started) {
266
+ switch (notStartedAction) {
267
+ case 'throw':
268
+ throw Error(`Module not Started [${this.address}]`)
269
+ case 'warn':
270
+ this.logger?.warn('Module not started')
271
+ break
272
+ case 'error':
273
+ this.logger?.error('Module not started')
274
+ break
275
+ case 'none':
276
+ break
277
+ case 'log':
278
+ default:
279
+ this.logger?.log('Module not started')
280
+ }
281
+ }
282
+ return this._started
283
+ }
284
+
285
+ subscribe(_queryAccount?: AccountInstance) {
286
+ return
287
+ }
288
+
289
+ protected bindHashes(hashes: string[], schema: SchemaString[], account?: AccountInstance) {
290
+ const promise = new PromiseEx((resolve) => {
291
+ const result = this.bindHashesInternal(hashes, schema, account)
292
+ resolve?.(result)
293
+ return result
294
+ }, account)
295
+ return promise
296
+ }
297
+
298
+ protected async bindHashesInternal(hashes: string[], schema: SchemaString[], account?: AccountInstance): Promise<BoundWitness> {
299
+ const builder = new BoundWitnessBuilder().hashes(hashes, schema).witness(this.account)
300
+ const result = (await (account ? builder.witness(account) : builder).build())[0]
301
+ this.logger?.debug(`result: ${JSON.stringify(result, null, 2)}`)
302
+ return result
303
+ }
304
+
305
+ protected bindQuery<T extends Query | PayloadWrapper<Query>>(
306
+ query: T,
307
+ payloads?: Payload[],
308
+ account?: AccountInstance,
309
+ ): PromiseEx<[QueryBoundWitness, Payload[], Payload[]], AccountInstance> {
310
+ const promise = new PromiseEx<[QueryBoundWitness, Payload[], Payload[]], AccountInstance>(async (resolve) => {
311
+ const result = await this.bindQueryInternal(query, payloads, account)
312
+ resolve?.(result)
313
+ return result
314
+ }, account)
315
+ return promise
316
+ }
317
+
318
+ protected async bindQueryInternal<T extends Query | PayloadWrapper<Query>>(
319
+ query: T,
320
+ payloads?: Payload[],
321
+ account?: AccountInstance,
322
+ ): Promise<[QueryBoundWitness, Payload[], Payload[]]> {
323
+ const builder = new QueryBoundWitnessBuilder().payloads(payloads).witness(this.account).query(query)
324
+ const result = await (account ? builder.witness(account) : builder).build()
325
+ return result
326
+ }
327
+
328
+ protected async bindQueryResult<T extends Query | PayloadWrapper<Query>>(
329
+ query: T,
330
+ payloads: Payload[],
331
+ additionalWitnesses: AccountInstance[] = [],
332
+ errors?: ModuleError[],
333
+ ): Promise<[ModuleQueryResult, AccountInstance[]]> {
334
+ const builder = new BoundWitnessBuilder().payloads(payloads).errors(errors)
335
+ const queryWitnessAccount = this.queryAccounts[query.schema as ModuleQueryBase['schema']]
336
+ const witnesses = [this.account, queryWitnessAccount, ...additionalWitnesses].filter(exists)
337
+ builder.witnesses(witnesses)
338
+ const result: ModuleQueryResult = [(await builder.build())[0], payloads, errors ?? []]
339
+ return [result, witnesses]
340
+ }
341
+
342
+ protected commitArchivist = () => this.getArchivist('commit')
343
+
344
+ protected async describe(): Promise<ModuleDescriptionPayload> {
345
+ const description: ModuleDescriptionPayload = {
346
+ address: this.address,
347
+ queries: this.queries,
348
+ schema: ModuleDescriptionSchema,
349
+ }
350
+ if (this.config.name) {
351
+ description.name = this.config.name
352
+ }
353
+
354
+ const discover = await this.discover()
355
+
356
+ description.children = compact(
357
+ discover?.map((payload) => {
358
+ const address = payload.schema === AddressSchema ? (payload as AddressPayload).address : undefined
359
+ return address != this.address ? address : undefined
360
+ }) ?? [],
361
+ )
362
+
363
+ return description
364
+ }
365
+
366
+ protected discover(): Promisable<Payload[]> {
367
+ const config = this.config
368
+ const address = new PayloadBuilder<AddressPayload>({ schema: AddressSchema }).fields({ address: this.address, name: this.config.name }).build()
369
+ const queries = this.queries.map((query) => {
370
+ return new PayloadBuilder<QueryPayload>({ schema: QuerySchema }).fields({ query }).build()
371
+ })
372
+ const configSchema: ConfigPayload = {
373
+ config: config.schema,
374
+ schema: ConfigSchema,
375
+ }
376
+ return compact([config, configSchema, address, ...queries])
377
+ }
378
+
379
+ protected async initializeQueryAccounts() {
380
+ // Ensure distinct/unique wallet paths
381
+ const paths = Object.values(this.queryAccountPaths).filter(exists)
382
+ const distinctPaths = new Set<string>(paths)
383
+ assertEx(distinctPaths.size === paths.length, `${this.config?.name ? this.config.name + ': ' : ''}Duplicate query account paths`)
384
+ // Create an account for query this module supports
385
+ const wallet = this.account as unknown as HDWallet
386
+ if (wallet?.derivePath) {
387
+ for (const key in this.queryAccountPaths) {
388
+ if (Object.prototype.hasOwnProperty.call(this.queryAccountPaths, key)) {
389
+ const query = key as ModuleQueryBase['schema']
390
+ const queryAccountPath = this.queryAccountPaths[query]
391
+ if (queryAccountPath) {
392
+ this._queryAccounts[query] = await wallet.derivePath?.(queryAccountPath)
393
+ }
394
+ }
395
+ }
396
+ }
397
+ }
398
+
399
+ protected async queryHandler<T extends QueryBoundWitness = QueryBoundWitness, TConfig extends ModuleConfig = ModuleConfig>(
400
+ query: T,
401
+ payloads?: Payload[],
402
+ queryConfig?: TConfig,
403
+ ): Promise<ModuleQueryResult> {
404
+ this.started('throw')
405
+ const wrapper = QueryBoundWitnessWrapper.parseQuery<ModuleQuery>(query, payloads)
406
+ if (!this.allowAnonymous) {
407
+ if (query.addresses.length === 0) {
408
+ console.warn(`Anonymous Queries not allowed, but running anyway [${this.config.name}], [${this.address}]`)
409
+ }
410
+ }
411
+ const queryPayload = await wrapper.getQuery()
412
+ assertEx(this.queryable(query, payloads, queryConfig))
413
+ const resultPayloads: Payload[] = []
414
+ const errorPayloads: ModuleError[] = []
415
+ const queryAccount = this.ephemeralQueryAccountEnabled ? await HDWallet.random() : undefined
416
+ try {
417
+ switch (queryPayload.schema) {
418
+ case ModuleDiscoverQuerySchema: {
419
+ resultPayloads.push(...(await this.discover()))
420
+ break
421
+ }
422
+ case ModuleDescribeQuerySchema: {
423
+ resultPayloads.push(await this.describe())
424
+ break
425
+ }
426
+ case ModuleAddressQuerySchema: {
427
+ resultPayloads.push(...(await this.moduleAddressQuery()))
428
+ break
429
+ }
430
+ case ModuleSubscribeQuerySchema: {
431
+ this.subscribe(queryAccount)
432
+ break
433
+ }
434
+ default:
435
+ console.error(`Unsupported Query [${(queryPayload as Payload).schema}]`)
436
+ }
437
+ } catch (ex) {
438
+ await handleErrorAsync(ex, async (error) => {
439
+ errorPayloads.push(
440
+ new ModuleErrorBuilder()
441
+ .sources([await wrapper.hashAsync()])
442
+ .name(this.config.name ?? '<Unknown>')
443
+ .query(query.schema)
444
+ .message(error.message)
445
+ .build(),
446
+ )
447
+ })
448
+ }
449
+ return (await this.bindQueryResult(queryPayload, resultPayloads, queryAccount ? [queryAccount] : [], errorPayloads))[0]
450
+ }
451
+
452
+ protected readArchivist = () => this.getArchivist('read')
453
+
454
+ protected stop(_timeout?: number): Promisable<this> {
455
+ this._started = false
456
+ return this
457
+ }
458
+
459
+ protected validateConfig(config?: unknown, parents: string[] = []): boolean {
460
+ return Object.entries(config ?? this.config ?? {}).reduce((valid, [key, value]) => {
461
+ switch (typeof value) {
462
+ case 'function':
463
+ this.logger?.warn(`Fields of type function not allowed in config [${parents?.join('.')}.${key}]`)
464
+ return false
465
+ case 'object': {
466
+ if (Array.isArray(value)) {
467
+ return (
468
+ value.reduce((valid, value) => {
469
+ return this.validateConfig(value, [...parents, key]) && valid
470
+ }, true) && valid
471
+ )
472
+ }
473
+
474
+ if (!serializableField(value)) {
475
+ this.logger?.warn(`Fields that are not serializable to JSON are not allowed in config [${parents?.join('.')}.${key}]`)
476
+ return false
477
+ }
478
+ return value ? this.validateConfig(value, [...parents, key]) && valid : true
479
+ }
480
+ default:
481
+ return valid
482
+ }
483
+ }, true)
484
+ }
485
+
486
+ protected writeArchivist = () => this.getArchivist('write')
487
+
488
+ private async getArchivist(kind: keyof IndividualArchivistConfig): Promise<ArchivistModule | undefined> {
489
+ if (!this.config.archivist) return undefined
490
+ const filter =
491
+ typeof this.config.archivist === 'string' || this.config.archivist instanceof String
492
+ ? (this.config.archivist as string)
493
+ : (this.config?.archivist?.[kind] as string)
494
+ const resolved = await this.upResolver.resolveOne(filter)
495
+ return resolved ? (resolved as ArchivistModule) : undefined
496
+ }
497
+ }