@xyo-network/module-abstract 5.3.22 → 5.3.25

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.
@@ -1,672 +0,0 @@
1
- /* eslint-disable max-lines */
2
- import type {
3
- Address, CreatableInstance, Hash, Logger,
4
- Promisable,
5
- } from '@xylabs/sdk-js'
6
- import {
7
- AbstractCreatable,
8
- assertEx,
9
- ConsoleLogger, exists,
10
- forget,
11
- globallyUnique,
12
- handleErrorAsync,
13
- IdLogger,
14
- isDefined, isObject, isString, isUndefined, LevelLogger,
15
- LogLevel,
16
- PromiseEx,
17
- } from '@xylabs/sdk-js'
18
- import { Account } from '@xyo-network/account'
19
- import { type AccountInstance, isAccountInstance } from '@xyo-network/account-model'
20
- import type { ArchivistInstance } from '@xyo-network/archivist-model'
21
- import { asArchivistInstance } from '@xyo-network/archivist-model'
22
- import { BoundWitnessBuilder, QueryBoundWitnessBuilder } from '@xyo-network/boundwitness-builder'
23
- import type { BoundWitness, QueryBoundWitness } from '@xyo-network/boundwitness-model'
24
- import { isQueryBoundWitness } from '@xyo-network/boundwitness-model'
25
- import { QueryBoundWitnessWrapper } from '@xyo-network/boundwitness-wrapper'
26
- import type { ConfigPayload } from '@xyo-network/config-payload-plugin'
27
- import { ConfigSchema } from '@xyo-network/config-payload-plugin'
28
- import type { ModuleManifestPayload } from '@xyo-network/manifest-model'
29
- import type {
30
- AddressPayload,
31
- AddressPreviousHashPayload,
32
- ArchivingModuleConfig,
33
- AttachableModuleInstance,
34
- CreatableModule,
35
- CreatableModuleFactory,
36
- CreatableModuleInstance,
37
- Labels,
38
- ModuleBusyEventArgs,
39
- ModuleConfig,
40
- ModuleDescriptionPayload,
41
- ModuleDetailsError,
42
- ModuleEventData,
43
- ModuleManifestQuery,
44
- ModuleQueriedEventArgs,
45
- ModuleQueries,
46
- ModuleQueryHandlerResult,
47
- ModuleQueryResult,
48
- ModuleResolverInstance,
49
- QueryableModule,
50
- QueryableModuleParams,
51
- } from '@xyo-network/module-model'
52
- import {
53
- AddressPreviousHashSchema,
54
- AddressSchema,
55
- creatableModule,
56
- DeadModuleError,
57
- isModuleName,
58
- isSerializable,
59
- ModuleAddressQuerySchema,
60
- ModuleConfigSchema,
61
- ModuleDescriptionSchema,
62
- ModuleFactory,
63
- ModuleManifestQuerySchema,
64
- ModuleStateQuerySchema,
65
- ModuleSubscribeQuerySchema,
66
- ObjectResolverPriority,
67
- } from '@xyo-network/module-model'
68
- import { PayloadBuilder } from '@xyo-network/payload-builder'
69
- import {
70
- asSchema,
71
- type ModuleError, type Payload, type Query, type Schema,
72
- } from '@xyo-network/payload-model'
73
- import { QuerySchema } from '@xyo-network/query-payload-plugin'
74
- import type { WalletInstance } from '@xyo-network/wallet-model'
75
- import { Mutex } from 'async-mutex'
76
- import { LRUCache } from 'lru-cache'
77
-
78
- import { determineAccount } from './determineAccount.ts'
79
- import { ModuleErrorBuilder } from './Error.ts'
80
- import type { Queryable } from './QueryValidator/index.ts'
81
- import { ModuleConfigQueryValidator, SupportedQueryValidator } from './QueryValidator/index.ts'
82
-
83
- export const DefaultModuleQueries = [ModuleAddressQuerySchema, ModuleSubscribeQuerySchema, ModuleManifestQuerySchema, ModuleStateQuerySchema]
84
-
85
- creatableModule()
86
- export abstract class AbstractModule<TParams extends QueryableModuleParams = QueryableModuleParams, TEventData extends ModuleEventData = ModuleEventData>
87
- extends AbstractCreatable<TParams, TEventData>
88
- implements QueryableModule<TParams, TEventData> {
89
- static readonly allowRandomAccount: boolean = true
90
- static readonly configSchemas: Schema[] = [ModuleConfigSchema]
91
- static readonly defaultConfigSchema: Schema = ModuleConfigSchema
92
- // eslint-disable-next-line sonarjs/public-static-readonly
93
- static override defaultLogger: Logger = new ConsoleLogger(LogLevel.warn)
94
- // eslint-disable-next-line sonarjs/public-static-readonly
95
- static enableLazyLoad = false
96
- static readonly labels: Labels = {}
97
- static readonly uniqueName = globallyUnique('AbstractModule', AbstractModule, 'xyo')
98
-
99
- protected static privateConstructorKey = Date.now().toString()
100
-
101
- protected _account: AccountInstance | undefined
102
-
103
- // cache manifest based on maxDepth
104
- protected _cachedManifests = new LRUCache<number, ModuleManifestPayload>({ max: 10, ttl: 1000 * 60 * 5 })
105
-
106
- protected _globalReentrancyMutex: Mutex | undefined
107
-
108
- protected _lastError?: ModuleDetailsError
109
-
110
- protected _moduleConfigQueryValidator: Queryable | undefined
111
- protected _supportedQueryValidator: Queryable | undefined
112
-
113
- private _busyCount = 0
114
- private _logger: Logger | undefined | null = undefined
115
-
116
- get account() {
117
- return assertEx(this._account, () => 'Missing account')
118
- }
119
-
120
- get additionalSigners(): AccountInstance[] {
121
- return this.params.additionalSigners ?? []
122
- }
123
-
124
- get address() {
125
- return this.account.address
126
- }
127
-
128
- get allowAnonymous() {
129
- return !!this.config.security?.allowAnonymous
130
- }
131
-
132
- get allowNameResolution() {
133
- return this.params.allowNameResolution ?? true
134
- }
135
-
136
- get archiving(): ArchivingModuleConfig['archiving'] | undefined {
137
- return this.config.archiving
138
- }
139
-
140
- get archivist() {
141
- return this.config.archivist
142
- }
143
-
144
- get config(): TParams['config'] & { schema: Schema } {
145
- return { ...this.params.config, schema: this.params.config.schema ?? ModuleConfigSchema }
146
- }
147
-
148
- get dead() {
149
- return this.status === 'error'
150
- }
151
-
152
- get ephemeralQueryAccountEnabled(): boolean {
153
- return !!this.params.ephemeralQueryAccountEnabled
154
- }
155
-
156
- get globalReentrancyMutex() {
157
- this._globalReentrancyMutex = this._globalReentrancyMutex ?? (this.reentrancy?.scope === 'global' ? new Mutex() : undefined)
158
- return this._globalReentrancyMutex
159
- }
160
-
161
- get id() {
162
- return this.modName ?? this.address
163
- }
164
-
165
- override get logger() {
166
- // we use null to prevent a second round of not creating a logger
167
- if (isUndefined(this._logger)) {
168
- const logLevel = this.config.logLevel
169
- const newLogger = this._logger ?? (this.params?.logger ? new IdLogger(this.params.logger, () => `${this.constructor.name}[${this.id}]`) : null)
170
- this._logger = (isObject(newLogger) && isDefined(logLevel)) ? new LevelLogger(newLogger, logLevel) : newLogger
171
- }
172
- return this._logger ?? undefined
173
- }
174
-
175
- get modName() {
176
- return this.config.name
177
- }
178
-
179
- get priority() {
180
- return ObjectResolverPriority.Normal
181
- }
182
-
183
- get queries(): Schema[] {
184
- return [...DefaultModuleQueries]
185
- }
186
-
187
- get reentrancy() {
188
- return this.config.reentrancy
189
- }
190
-
191
- override get statusReporter() {
192
- return this.params.statusReporter
193
- }
194
-
195
- get timestamp() {
196
- return this.config.timestamp ?? false
197
- }
198
-
199
- protected get moduleConfigQueryValidator(): Queryable {
200
- return assertEx(this._moduleConfigQueryValidator, () => 'ModuleConfigQueryValidator not initialized')
201
- }
202
-
203
- protected get supportedQueryValidator(): Queryable {
204
- return assertEx(this._supportedQueryValidator, () => 'SupportedQueryValidator not initialized')
205
- }
206
-
207
- abstract get downResolver(): ModuleResolverInstance
208
-
209
- abstract get upResolver(): ModuleResolverInstance
210
-
211
- static _getRootFunction(funcName: string) {
212
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
- let anyThis = this as any
214
- while (anyThis.__proto__[funcName]) {
215
- anyThis = anyThis.__proto__
216
- }
217
- return anyThis[funcName]
218
- }
219
-
220
- static _noOverride(functionName: string) {
221
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
222
- const thisFunc = (this as any)[functionName]
223
-
224
- const rootFunc = this._getRootFunction(functionName)
225
- assertEx(thisFunc === rootFunc, () => `Override not allowed for [${functionName}] - override ${functionName}Handler instead`)
226
- }
227
-
228
- static override async createHandler<T extends CreatableInstance>(
229
- inInstance: T,
230
- ) {
231
- const instance = (await super.createHandler(inInstance))
232
- if (instance instanceof AbstractModule) {
233
- if (this.configSchemas.length === 0) {
234
- throw new Error(`No allowed config schemas for [${this.name}]`)
235
- }
236
-
237
- const schema: Schema = instance.config.schema ?? this.defaultConfigSchema
238
- const allowedSchemas: Schema[] = this.configSchemas
239
-
240
- assertEx(this.isAllowedSchema(schema), () => `Bad Config Schema [Received ${schema}] [Expected ${JSON.stringify(allowedSchemas)}]`)
241
- } else {
242
- throw new TypeError(`Invalid instance type [${instance.constructor.name}] for [${this.name}]`)
243
- }
244
-
245
- return instance
246
- }
247
-
248
- static async determineAccount(params: {
249
- account?: AccountInstance | 'random'
250
- accountPath?: string
251
- wallet?: WalletInstance
252
- }): Promise<AccountInstance> {
253
- return await determineAccount(params, this.allowRandomAccount)
254
- }
255
-
256
- static factory<TModule extends CreatableModuleInstance>(
257
- this: CreatableModule<TModule>,
258
- params?: Partial<TModule['params']>,
259
- ): CreatableModuleFactory<TModule> {
260
- return ModuleFactory.withParams<TModule>(this, params)
261
- }
262
-
263
- static isAllowedSchema(schema: Schema): boolean {
264
- return this.configSchemas.includes(schema)
265
- }
266
-
267
- static override async paramsHandler<T extends AttachableModuleInstance<QueryableModuleParams, ModuleEventData>>(
268
- inParams: Partial<T['params']> = {},
269
- ) {
270
- const superParams = await super.paramsHandler(inParams)
271
- const params = {
272
- ...superParams,
273
- account: await this.determineAccount(superParams),
274
- config: { schema: this.defaultConfigSchema, ...superParams.config },
275
- logger: superParams.logger ?? this.defaultLogger,
276
- } as T['params']
277
- return params
278
- }
279
-
280
- // eslint-disable-next-line sonarjs/no-identical-functions
281
- _getRootFunction(funcName: string) {
282
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
283
- let anyThis = this as any
284
- while (anyThis.__proto__[funcName]) {
285
- anyThis = anyThis.__proto__
286
- }
287
- return anyThis[funcName]
288
- }
289
-
290
- async busy<R>(closure: () => Promise<R>) {
291
- if (this._busyCount <= 0) {
292
- this._busyCount = 0
293
- const args: ModuleBusyEventArgs = { busy: true, mod: this }
294
- await this.emit('moduleBusy', args)
295
- }
296
- this._busyCount++
297
- try {
298
- return await closure()
299
- } finally {
300
- this._busyCount--
301
- if (this._busyCount <= 0) {
302
- this._busyCount = 0
303
- const args: ModuleBusyEventArgs = { busy: false, mod: this }
304
- await this.emit('moduleBusy', args)
305
- }
306
- }
307
- }
308
-
309
- override async createHandler() {
310
- await super.createHandler()
311
- assertEx(this.name === undefined || isModuleName(this.name), () => `Invalid module name: ${this.name}`)
312
-
313
- if (this.params.account === 'random') {
314
- this._account = await Account.random()
315
- } else if (isAccountInstance(this.params.account)) {
316
- this._account = this.params.account
317
- }
318
-
319
- assertEx(isAccountInstance(this._account), () => `Invalid account instance: ${this._account}`)
320
-
321
- this._supportedQueryValidator = new SupportedQueryValidator(this as QueryableModule).queryable
322
- this._moduleConfigQueryValidator = new ModuleConfigQueryValidator(this.config).queryable
323
-
324
- if (!AbstractModule.enableLazyLoad) {
325
- setTimeout(() => forget(this.start()), 200)
326
- }
327
- }
328
-
329
- override emit<TEventName extends keyof TEventData = keyof TEventData, TEventArgs extends TEventData[TEventName] = TEventData[TEventName]>(
330
- eventName: TEventName,
331
- eventArgs: TEventArgs,
332
- ) {
333
- return super.emit(eventName, eventArgs)
334
- }
335
-
336
- isSupportedQuery(query: Schema, assert: boolean | string = false): boolean {
337
- // check if ever supported
338
- if (!this.queries.includes(query)) {
339
- if (assert !== false) {
340
- assertEx(false, () => `Query not supported [${isString(assert) ? assert : query}] on [${this.modName}, ${this.address}] (queries list)`)
341
- }
342
- return false
343
- }
344
- // check if config allows it
345
- const supported = Array.isArray(this.config.allowedQueries) ? this.config.allowedQueries.includes(query) : true
346
- if (assert !== false) {
347
- assertEx(supported, () => `Query not supported [${isString(assert) ? assert : query}] on [${this.modName}, ${this.address}] (config)`)
348
- }
349
- return supported
350
- }
351
-
352
- previousHash(): Promisable<string | undefined> {
353
- this._checkDead()
354
- return this.account.previousHash
355
- }
356
-
357
- async query<T extends QueryBoundWitness = QueryBoundWitness, TConfig extends ModuleConfig = ModuleConfig>(
358
- query: T,
359
- payloads?: Payload[],
360
- queryConfig?: TConfig,
361
- ): Promise<ModuleQueryResult> {
362
- this._checkDead()
363
- this._noOverride('query')
364
- return await this.spanAsync('query', async () => {
365
- const sourceQuery = assertEx(isQueryBoundWitness(query) ? query : undefined, () => 'Unable to parse query')
366
- return await this.busy(async () => {
367
- const resultPayloads: Payload[] = []
368
- const errorPayloads: ModuleError[] = []
369
- const queryAccount = this.ephemeralQueryAccountEnabled ? await Account.random() : undefined
370
-
371
- try {
372
- await this.startedAsync('throw')
373
- if (!this.allowAnonymous && query.addresses.length === 0) {
374
- throw new Error(`Anonymous Queries not allowed, but running anyway [${this.modName}], [${this.address}]`)
375
- }
376
- if (queryConfig?.allowedQueries) {
377
- assertEx(queryConfig?.allowedQueries.includes(sourceQuery.schema), () => `Query not allowed [${sourceQuery.schema}]`)
378
- }
379
- resultPayloads.push(...(await this.queryHandler(sourceQuery, payloads, queryConfig)))
380
- } catch (ex) {
381
- await handleErrorAsync(ex, async (err) => {
382
- const error = err as ModuleDetailsError
383
- this._lastError = error
384
- // this.status = 'dead'
385
- errorPayloads.push(
386
- new ModuleErrorBuilder()
387
- .meta({ $sources: [await PayloadBuilder.dataHash(sourceQuery)] })
388
- .name(this.modName ?? '<Unknown>')
389
- .query(sourceQuery.schema)
390
- .details(error.details)
391
- .message(error.message)
392
- .build(),
393
- )
394
- })
395
- }
396
- if (this.timestamp) {
397
- const timestamp = { schema: asSchema('network.xyo.timestamp', true), timestamp: Date.now() }
398
- resultPayloads.push(timestamp)
399
- }
400
- const result = await this.bindQueryResult(sourceQuery, resultPayloads, queryAccount ? [queryAccount] : [], errorPayloads)
401
- const args: ModuleQueriedEventArgs = {
402
- mod: this, payloads, query: sourceQuery, result,
403
- }
404
- await this.emit('moduleQueried', args)
405
- return result
406
- })
407
- }, { timeBudgetLimit: 200 })
408
- }
409
-
410
- async queryable<T extends QueryBoundWitness = QueryBoundWitness, TConfig extends ModuleConfig = ModuleConfig>(
411
- query: T,
412
- payloads?: Payload[],
413
- queryConfig?: TConfig,
414
- ): Promise<boolean> {
415
- if (this.dead) {
416
- return false
417
- }
418
- if (!(this.started('warn'))) return false
419
- const configValidator
420
- = queryConfig ? new ModuleConfigQueryValidator(Object.assign({}, this.config, queryConfig)).queryable : this.moduleConfigQueryValidator
421
- const validators = [this.supportedQueryValidator, configValidator]
422
-
423
- const results = await Promise.all(validators.map(validator => validator(query, payloads)))
424
- for (const result of results) {
425
- if (!result) {
426
- return false
427
- }
428
- }
429
- return true
430
- }
431
-
432
- protected _checkDead() {
433
- if (this.dead) {
434
- throw new DeadModuleError(this.id, this._lastError)
435
- }
436
- }
437
-
438
- protected async archivistInstance(): Promise<ArchivistInstance | undefined>
439
- protected async archivistInstance(required: true): Promise<ArchivistInstance>
440
- protected async archivistInstance(required = false): Promise<ArchivistInstance | undefined> {
441
- const archivist = this.archivist
442
- if (isUndefined(archivist)) {
443
- if (required) {
444
- throw new Error('No archivist specified')
445
- }
446
- return undefined
447
- }
448
- const resolved = (await this.upResolver.resolve(archivist)) ?? (await this.downResolver.resolve(archivist))
449
- if (required) {
450
- assertEx(resolved, () => `Unable to resolve archivist [${archivist}]`)
451
- }
452
- return resolved ? asArchivistInstance(resolved, () => `Specified archivist is not an Archivist [${archivist}]`) : undefined
453
- }
454
-
455
- protected bindHashes(hashes: Hash[], schema: Schema[], account?: AccountInstance) {
456
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
457
- return new PromiseEx((resolve) => {
458
- const result = this.bindHashesInternal(hashes, schema, account)
459
- resolve?.(result)
460
- return result
461
- }, account)
462
- }
463
-
464
- protected async bindHashesInternal(hashes: Hash[], schema: Schema[], account: AccountInstance = this.account): Promise<BoundWitness> {
465
- const builder = new BoundWitnessBuilder().hashes(hashes, schema).signer(account)
466
- const result: BoundWitness = (await builder.build())[0]
467
- this.logger?.debug(`result: ${JSON.stringify(result, null, 2)}`)
468
- return result
469
- }
470
-
471
- protected bindQuery<T extends Query>(
472
- query: T,
473
- payloads?: Payload[],
474
- account?: AccountInstance,
475
- additionalSigners?: AccountInstance[],
476
- ): PromiseEx<[QueryBoundWitness, Payload[], Payload[]], AccountInstance> {
477
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
478
- return new PromiseEx<[QueryBoundWitness, Payload[], Payload[]], AccountInstance>(async (resolve) => {
479
- const result = await this.bindQueryInternal(query, payloads, account, additionalSigners)
480
- resolve?.(result)
481
- return result
482
- }, account)
483
- }
484
-
485
- protected async bindQueryInternal<T extends Query>(
486
- query: T,
487
- payloads?: Payload[],
488
- account: AccountInstance = this.account,
489
- additionalSigners: AccountInstance[] = [],
490
- ): Promise<[QueryBoundWitness, Payload[], Payload[]]> {
491
- const accounts = [account, ...additionalSigners].filter(exists)
492
- const builder = new QueryBoundWitnessBuilder().payloads(payloads).signers(accounts).query(query)
493
-
494
- const [bw, payloadsOut, errors] = await builder.build()
495
- return [bw, [...payloadsOut], errors]
496
- }
497
-
498
- protected async bindQueryResult<T extends Query>(
499
- query: T,
500
- payloads: Payload[],
501
- additionalWitnesses: AccountInstance[] = [],
502
- errors?: ModuleError[],
503
- ): Promise<ModuleQueryResult> {
504
- const queryDataHash = await PayloadBuilder.dataHash(query)
505
- const builder = new BoundWitnessBuilder().payloads(payloads).errors(errors).sourceQuery(queryDataHash)
506
- const witnesses = [this.account, ...additionalWitnesses].filter(exists)
507
- builder.signers(witnesses)
508
- const result = [
509
- PayloadBuilder.omitPrivateStorageMeta((await builder.build())[0]),
510
- PayloadBuilder.omitPrivateStorageMeta(payloads),
511
- PayloadBuilder.omitPrivateStorageMeta(errors ?? []),
512
- ] as ModuleQueryResult
513
- if (this.archiving && this.isAllowedArchivingQuery(query.schema)) {
514
- forget(this.storeToArchivists(result.flat()))
515
- }
516
- return result
517
- }
518
-
519
- protected generateConfigAndAddress(_maxDepth?: number): Promisable<Payload[]> {
520
- const config = this.config
521
- const address: AddressPayload = { schema: AddressSchema, address: this.address }
522
- const queries = this.queries.map((query) => {
523
- return { schema: QuerySchema, query }
524
- })
525
- const configSchema: ConfigPayload = {
526
- config: config.schema,
527
- schema: ConfigSchema,
528
- }
529
- return ([config, configSchema, address, ...queries]).filter(exists)
530
- }
531
-
532
- protected async generateDescribe(): Promise<ModuleDescriptionPayload> {
533
- const description: ModuleDescriptionPayload = {
534
- address: this.address,
535
- name: this.modName ?? `Unnamed ${this.constructor.name}`,
536
- queries: this.queries,
537
- schema: ModuleDescriptionSchema,
538
- }
539
-
540
- const discover = await this.generateConfigAndAddress()
541
-
542
- description.children = (
543
- discover?.map((payload) => {
544
- const address = payload.schema === AddressSchema ? (payload as AddressPayload).address : undefined
545
- return address == this.address ? undefined : address
546
- }) ?? []
547
- ).filter(exists)
548
-
549
- return description
550
- }
551
-
552
- /** @deprecated use archivistInstance() instead */
553
- protected async getArchivist(): Promise<ArchivistInstance | undefined> {
554
- return await this.archivistInstance()
555
- }
556
-
557
- protected isAllowedArchivingQuery(schema: Schema): boolean {
558
- const queries = this.archiving?.queries
559
- if (queries) {
560
- return queries.includes(schema)
561
- }
562
- return true
563
- }
564
-
565
- protected manifestHandler(_maxDepth: number = 1, _ignoreAddresses: Address[] = []): Promisable<ModuleManifestPayload> {
566
- throw new Error('Not supported')
567
- }
568
-
569
- protected moduleAddressHandler(): Promisable<(AddressPreviousHashPayload | AddressPayload)[]> {
570
- const address = this.address
571
- const name = this.modName
572
- const previousHash = this.account.previousHash
573
- const moduleAccount = isDefined(name)
574
- ? {
575
- address, name, schema: AddressSchema,
576
- }
577
- : { address, schema: AddressSchema }
578
- const moduleAccountPreviousHash = isDefined(previousHash)
579
- ? {
580
- address, previousHash, schema: AddressPreviousHashSchema,
581
- }
582
- : { address, schema: AddressSchema }
583
- return [moduleAccount, moduleAccountPreviousHash]
584
- }
585
-
586
- protected async queryHandler<T extends QueryBoundWitness = QueryBoundWitness, TConfig extends ModuleConfig = ModuleConfig>(
587
- query: T,
588
- payloads?: Payload[],
589
- queryConfig?: TConfig,
590
- ): Promise<ModuleQueryHandlerResult> {
591
- await this.startedAsync('throw')
592
- const wrapper = QueryBoundWitnessWrapper.parseQuery<ModuleQueries>(query, payloads)
593
- const queryPayload = await wrapper.getQuery()
594
- assertEx(await this.queryable(query, payloads, queryConfig))
595
- const resultPayloads: Payload[] = []
596
- switch (queryPayload.schema) {
597
- case ModuleManifestQuerySchema: {
598
- const typedQueryPayload = queryPayload as ModuleManifestQuery
599
- resultPayloads.push(await this.manifestHandler(typedQueryPayload.maxDepth))
600
- break
601
- }
602
- case ModuleAddressQuerySchema: {
603
- resultPayloads.push(...(await this.moduleAddressHandler()))
604
- break
605
- }
606
- case ModuleStateQuerySchema: {
607
- resultPayloads.push(...(await this.stateHandler()))
608
- break
609
- }
610
- case ModuleSubscribeQuerySchema: {
611
- this.subscribeHandler()
612
- break
613
- }
614
- default: {
615
- throw new Error(`Unsupported Query [${(queryPayload as Payload).schema}]`)
616
- }
617
- }
618
- return PayloadBuilder.omitPrivateStorageMeta(resultPayloads) as ModuleQueryHandlerResult
619
- }
620
-
621
- protected override async startHandler(): Promise<void> {
622
- this.validateConfig()
623
- await super.startHandler()
624
- }
625
-
626
- protected async stateHandler(): Promise<Payload[]> {
627
- return [await this.manifestHandler(), ...(await this.generateConfigAndAddress()), await this.generateDescribe()]
628
- }
629
-
630
- protected override async stopHandler(): Promise<void> {
631
- await super.stopHandler()
632
- this._startPromise = undefined
633
- }
634
-
635
- protected subscribeHandler() {
636
- return
637
- }
638
-
639
- protected validateConfig(config?: unknown, parents: string[] = []): boolean {
640
- // eslint-disable-next-line unicorn/no-array-reduce
641
- return Object.entries(config ?? this.config ?? {}).reduce((valid, [key, value]) => {
642
- switch (typeof value) {
643
- case 'function': {
644
- this.logger?.warn(`Fields of type function not allowed in config [${parents?.join('.')}.${key}]`)
645
- return false
646
- }
647
- case 'object': {
648
- if (Array.isArray(value)) {
649
- return (
650
- // eslint-disable-next-line unicorn/no-array-reduce
651
- value.reduce((valid, value) => {
652
- return this.validateConfig(value, [...parents, key]) && valid
653
- }, true) && valid
654
- )
655
- }
656
-
657
- if (!isSerializable(value)) {
658
- this.logger?.warn(`Fields that are not serializable to JSON are not allowed in config [${parents?.join('.')}.${key}]`)
659
- return false
660
- }
661
- return value ? this.validateConfig(value, [...parents, key]) && valid : true
662
- }
663
- default: {
664
- return valid
665
- }
666
- }
667
- }, true)
668
- }
669
-
670
- protected abstract certifyParents(): Promise<Payload[]>
671
- protected abstract storeToArchivists(payloads: Payload[]): Promise<Payload[]>
672
- }