@xyo-network/module-abstract 2.66.9 → 2.67.0-rc.2

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