@xyo-network/module-abstract 3.18.9 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xyo-network/module-abstract",
3
- "version": "3.18.9",
3
+ "version": "4.0.0",
4
4
  "description": "Primary SDK for using XYO Protocol 2.0",
5
5
  "homepage": "https://xyo.network",
6
6
  "bugs": {
@@ -29,43 +29,44 @@
29
29
  "module": "dist/neutral/index.mjs",
30
30
  "types": "dist/types/index.d.ts",
31
31
  "dependencies": {
32
- "@xylabs/assert": "^4.11.21",
33
- "@xylabs/base": "^4.11.21",
34
- "@xylabs/error": "^4.11.21",
35
- "@xylabs/exists": "^4.11.21",
36
- "@xylabs/forget": "^4.11.21",
37
- "@xylabs/hex": "^4.11.21",
38
- "@xylabs/logger": "^4.11.21",
39
- "@xylabs/object": "^4.11.21",
40
- "@xylabs/promise": "^4.11.21",
41
- "@xylabs/telemetry": "^4.11.21",
42
- "@xylabs/typeof": "^4.11.21",
43
- "@xyo-network/account": "^3.18.9",
44
- "@xyo-network/account-model": "^3.18.9",
45
- "@xyo-network/archivist-model": "^3.18.9",
46
- "@xyo-network/boundwitness-builder": "^3.18.9",
47
- "@xyo-network/boundwitness-model": "^3.18.9",
48
- "@xyo-network/boundwitness-wrapper": "^3.18.9",
49
- "@xyo-network/config-payload-plugin": "^3.18.9",
50
- "@xyo-network/manifest-model": "^3.18.9",
51
- "@xyo-network/module-event-emitter": "^3.18.9",
52
- "@xyo-network/module-model": "^3.18.9",
53
- "@xyo-network/module-resolver": "^3.18.9",
54
- "@xyo-network/node-model": "^3.18.9",
55
- "@xyo-network/payload-builder": "^3.18.9",
56
- "@xyo-network/payload-model": "^3.18.9",
57
- "@xyo-network/query-payload-plugin": "^3.18.9",
58
- "@xyo-network/wallet-model": "^3.18.9",
32
+ "@xylabs/assert": "^4.12.30",
33
+ "@xylabs/base": "^4.12.30",
34
+ "@xylabs/creatable": "^4.12.30",
35
+ "@xylabs/error": "^4.12.30",
36
+ "@xylabs/exists": "^4.12.30",
37
+ "@xylabs/forget": "^4.12.30",
38
+ "@xylabs/hex": "^4.12.30",
39
+ "@xylabs/logger": "^4.12.30",
40
+ "@xylabs/object": "^4.12.30",
41
+ "@xylabs/promise": "^4.12.30",
42
+ "@xylabs/telemetry": "^4.12.30",
43
+ "@xylabs/typeof": "^4.12.30",
44
+ "@xyo-network/account": "^4.0.0",
45
+ "@xyo-network/account-model": "^4.0.0",
46
+ "@xyo-network/archivist-model": "^4.0.0",
47
+ "@xyo-network/boundwitness-builder": "^4.0.0",
48
+ "@xyo-network/boundwitness-model": "^4.0.0",
49
+ "@xyo-network/boundwitness-wrapper": "^4.0.0",
50
+ "@xyo-network/config-payload-plugin": "^4.0.0",
51
+ "@xyo-network/manifest-model": "^4.0.0",
52
+ "@xyo-network/module-event-emitter": "^4.0.0",
53
+ "@xyo-network/module-model": "^4.0.0",
54
+ "@xyo-network/module-resolver": "^4.0.0",
55
+ "@xyo-network/node-model": "^4.0.0",
56
+ "@xyo-network/payload-builder": "^4.0.0",
57
+ "@xyo-network/payload-model": "^4.0.0",
58
+ "@xyo-network/query-payload-plugin": "^4.0.0",
59
+ "@xyo-network/wallet-model": "^4.0.0",
59
60
  "async-mutex": "^0.5.0",
60
61
  "lru-cache": "^11.1.0"
61
62
  },
62
63
  "devDependencies": {
63
- "@xylabs/ts-scripts-yarn3": "^6.5.8",
64
- "@xylabs/tsconfig": "^6.5.8",
65
- "@xylabs/typeof": "^4.11.21",
66
- "@xylabs/vitest-extended": "^4.11.21",
64
+ "@xylabs/ts-scripts-yarn3": "^6.5.12",
65
+ "@xylabs/tsconfig": "^6.5.12",
66
+ "@xylabs/typeof": "^4.12.30",
67
+ "@xylabs/vitest-extended": "^4.12.30",
67
68
  "typescript": "^5.8.3",
68
- "vitest": "^3.2.3"
69
+ "vitest": "^3.2.4"
69
70
  },
70
71
  "publishConfig": {
71
72
  "access": "public"
@@ -1,6 +1,8 @@
1
1
  /* eslint-disable max-lines */
2
2
  import { assertEx } from '@xylabs/assert'
3
- import { Base, globallyUnique } from '@xylabs/base'
3
+ import { globallyUnique } from '@xylabs/base'
4
+ import type { CreatableInstance, CreatableStatus } from '@xylabs/creatable'
5
+ import { AbstractCreatable } from '@xylabs/creatable'
4
6
  import { handleError, handleErrorAsync } from '@xylabs/error'
5
7
  import { exists } from '@xylabs/exists'
6
8
  import { forget } from '@xylabs/forget'
@@ -8,16 +10,17 @@ import type { Address, Hash } from '@xylabs/hex'
8
10
  import type { Logger } from '@xylabs/logger'
9
11
  import {
10
12
  ConsoleLogger, IdLogger,
13
+ LevelLogger,
11
14
  LogLevel,
12
15
  } from '@xylabs/logger'
13
16
  import type { Promisable } from '@xylabs/promise'
14
17
  import { PromiseEx } from '@xylabs/promise'
15
18
  import { spanAsync } from '@xylabs/telemetry'
16
19
  import {
17
- isDefined, isString, isUndefined,
20
+ isDefined, isObject, isPromise, isString, isUndefined,
18
21
  } from '@xylabs/typeof'
19
22
  import { Account } from '@xyo-network/account'
20
- import type { AccountInstance } from '@xyo-network/account-model'
23
+ import { type AccountInstance, isAccountInstance } from '@xyo-network/account-model'
21
24
  import type { ArchivistInstance } from '@xyo-network/archivist-model'
22
25
  import { asArchivistInstance } from '@xyo-network/archivist-model'
23
26
  import { BoundWitnessBuilder, QueryBoundWitnessBuilder } from '@xyo-network/boundwitness-builder'
@@ -27,7 +30,6 @@ import { QueryBoundWitnessWrapper } from '@xyo-network/boundwitness-wrapper'
27
30
  import type { ConfigPayload } from '@xyo-network/config-payload-plugin'
28
31
  import { ConfigSchema } from '@xyo-network/config-payload-plugin'
29
32
  import type { ModuleManifestPayload } from '@xyo-network/manifest-model'
30
- import { ModuleBaseEmitter } from '@xyo-network/module-event-emitter'
31
33
  import type {
32
34
  AddressPayload,
33
35
  AddressPreviousHashPayload,
@@ -35,6 +37,7 @@ import type {
35
37
  AttachableModuleInstance,
36
38
  CreatableModule,
37
39
  CreatableModuleFactory,
40
+ CreatableModuleInstance,
38
41
  Labels,
39
42
  Module,
40
43
  ModuleBusyEventArgs,
@@ -48,11 +51,11 @@ import type {
48
51
  ModuleQueryHandlerResult,
49
52
  ModuleQueryResult,
50
53
  ModuleResolverInstance,
51
- ModuleStatus,
52
54
  } from '@xyo-network/module-model'
53
55
  import {
54
56
  AddressPreviousHashSchema,
55
57
  AddressSchema,
58
+ creatableModule,
56
59
  DeadModuleError,
57
60
  isModuleName,
58
61
  isSerializable,
@@ -80,8 +83,10 @@ import type { Queryable } from './QueryValidator/index.ts'
80
83
  import { ModuleConfigQueryValidator, SupportedQueryValidator } from './QueryValidator/index.ts'
81
84
 
82
85
  const MODULE_NOT_STARTED = 'Module not Started' as const
86
+
87
+ creatableModule()
83
88
  export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams, TEventData extends ModuleEventData = ModuleEventData>
84
- extends ModuleBaseEmitter<TParams, TEventData>
89
+ extends AbstractCreatable<TParams, TEventData>
85
90
  implements Module<TParams, TEventData> {
86
91
  static readonly allowRandomAccount: boolean = true
87
92
  static readonly configSchemas: Schema[] = [ModuleConfigSchema]
@@ -95,35 +100,22 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
95
100
 
96
101
  protected static privateConstructorKey = Date.now().toString()
97
102
 
98
- protected _account: AccountInstance
103
+ protected _account: AccountInstance | undefined
99
104
 
100
105
  // cache manifest based on maxDepth
101
106
  protected _cachedManifests = new LRUCache<number, ModuleManifestPayload>({ max: 10, ttl: 1000 * 60 * 5 })
102
107
 
103
- protected _globalReentrancyMutex: Mutex | undefined = undefined
108
+ protected _globalReentrancyMutex: Mutex | undefined
104
109
 
105
110
  protected _lastError?: ModuleDetailsError
106
111
 
107
- protected _startPromise: Promisable<boolean> | undefined = undefined
108
- protected _started: Promisable<boolean> | undefined = undefined
109
- protected readonly moduleConfigQueryValidator: Queryable
110
- protected readonly supportedQueryValidator: Queryable
112
+ protected _moduleConfigQueryValidator: Queryable | undefined
113
+ protected _startPromise: Promisable<boolean> | undefined
114
+ protected _supportedQueryValidator: Queryable | undefined
111
115
 
112
116
  private _busyCount = 0
113
- private _logger: Logger | undefined = undefined
114
- private _status: ModuleStatus = 'stopped'
115
-
116
- constructor(privateConstructorKey: string, params: TParams, account: AccountInstance) {
117
- assertEx(AbstractModule.privateConstructorKey === privateConstructorKey, () => 'Use create function instead of constructor')
118
- // Clone params to prevent mutation of the incoming object
119
- const mutatedParams = { ...params } as TParams
120
- super(mutatedParams)
121
-
122
- this._account = account
123
-
124
- this.supportedQueryValidator = new SupportedQueryValidator(this as Module).queryable
125
- this.moduleConfigQueryValidator = new ModuleConfigQueryValidator(mutatedParams?.config).queryable
126
- }
117
+ private _logger: Logger | undefined | null = undefined
118
+ private _status: CreatableStatus = 'creating'
127
119
 
128
120
  get account() {
129
121
  return assertEx(this._account, () => 'Missing account')
@@ -134,7 +126,7 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
134
126
  }
135
127
 
136
128
  get address() {
137
- return this._account?.address
129
+ return this.account.address
138
130
  }
139
131
 
140
132
  get allowAnonymous() {
@@ -153,12 +145,12 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
153
145
  return this.config.archivist
154
146
  }
155
147
 
156
- get config(): TParams['config'] {
157
- return this.params.config
148
+ get config(): TParams['config'] & { schema: Schema } {
149
+ return { ...this.params.config, schema: this.params.config.schema ?? ModuleConfigSchema }
158
150
  }
159
151
 
160
152
  get dead() {
161
- return this.status === 'dead'
153
+ return this.status === 'error'
162
154
  }
163
155
 
164
156
  get ephemeralQueryAccountEnabled(): boolean {
@@ -175,10 +167,13 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
175
167
  }
176
168
 
177
169
  override get logger() {
178
- const logLevel = this.config.logLevel
179
- this._logger
180
- = this._logger ?? this.params?.logger ?? (isDefined(logLevel) ? new ConsoleLogger(logLevel) : Base.defaultLogger)
181
- return this._logger
170
+ // we use null to prevent a second round of not creating a logger
171
+ if (isUndefined(this._logger)) {
172
+ const logLevel = this.config.logLevel
173
+ const newLogger = this._logger ?? (this.params?.logger ? new IdLogger(this.params.logger, () => `${this.constructor.name}[${this.id}]`) : null)
174
+ this._logger = (isObject(newLogger) && isDefined(logLevel)) ? new LevelLogger(newLogger, logLevel) : newLogger
175
+ }
176
+ return this._logger ?? undefined
182
177
  }
183
178
 
184
179
  get modName() {
@@ -201,7 +196,7 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
201
196
  return this._status
202
197
  }
203
198
 
204
- get statusReporter() {
199
+ override get statusReporter() {
205
200
  return this.params.statusReporter
206
201
  }
207
202
 
@@ -209,11 +204,21 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
209
204
  return this.config.timestamp ?? false
210
205
  }
211
206
 
212
- protected set status(value: ModuleStatus) {
213
- if (this._status !== 'dead') {
214
- this._status = value
207
+ protected get moduleConfigQueryValidator(): Queryable {
208
+ return assertEx(this._moduleConfigQueryValidator, () => 'ModuleConfigQueryValidator not initialized')
209
+ }
210
+
211
+ protected set status(value: CreatableStatus) {
212
+ this._status = value
213
+ if (value === 'error') {
214
+ this.statusReporter?.report(`${this.constructor.name}:${this.id}`, value, new Error('Module status changed to error'))
215
+ } else {
216
+ this.statusReporter?.report(`${this.constructor.name}:${this.id}`, value, 100)
215
217
  }
216
- this.statusReporter?.reportStatus(`${this.constructor.name}:${this.id}`, value)
218
+ }
219
+
220
+ protected get supportedQueryValidator(): Queryable {
221
+ return assertEx(this._supportedQueryValidator, () => 'SupportedQueryValidator not initialized')
217
222
  }
218
223
 
219
224
  abstract get downResolver(): ModuleResolverInstance
@@ -237,38 +242,24 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
237
242
  assertEx(thisFunc === rootFunc, () => `Override not allowed for [${functionName}] - override ${functionName}Handler instead`)
238
243
  }
239
244
 
240
- static async create<TModule extends AttachableModuleInstance>(
241
- this: CreatableModule<TModule>,
242
- params: Omit<TModule['params'], 'config'> & { config?: TModule['params']['config'] },
245
+ static override async createHandler<T extends CreatableInstance>(
246
+ inInstance: T,
243
247
  ) {
244
- this._noOverride('create')
245
- if (this.configSchemas.length === 0) {
246
- throw new Error(`Missing configSchema [${params?.config?.schema}][${this.name}]`)
247
- }
248
-
249
- assertEx(params?.config?.name === undefined || isModuleName(params.config.name), () => `Invalid module name: ${params?.config?.name}`)
250
-
251
- const { account } = params ?? {}
252
-
253
- const schema: Schema = params?.config?.schema ?? this.defaultConfigSchema
254
- const allowedSchemas: Schema[] = this.configSchemas
255
-
256
- assertEx(allowedSchemas.includes(schema), () => `Bad Config Schema [Received ${schema}] [Expected ${JSON.stringify(allowedSchemas)}]`)
257
- const mutatedConfig: TModule['params']['config'] = { ...params?.config, schema } as TModule['params']['config']
258
- params?.logger?.debug(`config: ${JSON.stringify(mutatedConfig, null, 2)}`)
259
- const mutatedParams: TModule['params'] = { ...params, config: mutatedConfig } as TModule['params']
260
-
261
- const activeLogger = params?.logger ?? AbstractModule.defaultLogger
262
- const generatedAccount = await AbstractModule.determineAccount({ account })
263
- const address = generatedAccount.address
264
- mutatedParams.logger = new IdLogger(activeLogger, () => `0x${address}`)
248
+ const instance = (await super.createHandler(inInstance))
249
+ if (instance instanceof AbstractModule) {
250
+ if (this.configSchemas.length === 0) {
251
+ throw new Error(`No allowed config schemas for [${this.name}]`)
252
+ }
265
253
 
266
- const newModule = new this(AbstractModule.privateConstructorKey, mutatedParams, generatedAccount, address)
254
+ const schema: Schema = instance.config.schema ?? this.defaultConfigSchema
255
+ const allowedSchemas: Schema[] = this.configSchemas
267
256
 
268
- if (!AbstractModule.enableLazyLoad) {
269
- await newModule.start?.()
257
+ assertEx(this.isAllowedSchema(schema), () => `Bad Config Schema [Received ${schema}] [Expected ${JSON.stringify(allowedSchemas)}]`)
258
+ } else {
259
+ throw new TypeError(`Invalid instance type [${instance.constructor.name}] for [${this.name}]`)
270
260
  }
271
- return newModule
261
+
262
+ return instance
272
263
  }
273
264
 
274
265
  static async determineAccount(params: {
@@ -279,13 +270,30 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
279
270
  return await determineAccount(params, this.allowRandomAccount)
280
271
  }
281
272
 
282
- static factory<TModule extends AttachableModuleInstance>(
273
+ static factory<TModule extends CreatableModuleInstance>(
283
274
  this: CreatableModule<TModule>,
284
- params: Omit<TModule['params'], 'config'> & { config?: TModule['params']['config'] },
275
+ params?: Partial<TModule['params']>,
285
276
  ): CreatableModuleFactory<TModule> {
286
277
  return ModuleFactory.withParams(this, params)
287
278
  }
288
279
 
280
+ static isAllowedSchema(schema: Schema): boolean {
281
+ return this.configSchemas.includes(schema)
282
+ }
283
+
284
+ static override async paramsHandler<T extends AttachableModuleInstance<ModuleParams, ModuleEventData>>(
285
+ inParams: Partial<T['params']> = {},
286
+ ) {
287
+ const superParams = await super.paramsHandler(inParams)
288
+ const params = {
289
+ ...superParams,
290
+ account: await this.determineAccount(superParams),
291
+ config: { schema: this.defaultConfigSchema, ...superParams.config },
292
+ logger: superParams.logger ?? this.defaultLogger,
293
+ } as T['params']
294
+ return params
295
+ }
296
+
289
297
  // eslint-disable-next-line sonarjs/no-identical-functions
290
298
  _getRootFunction(funcName: string) {
291
299
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -315,6 +323,26 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
315
323
  }
316
324
  }
317
325
 
326
+ override async createHandler() {
327
+ await super.createHandler()
328
+ assertEx(this.name === undefined || isModuleName(this.name), () => `Invalid module name: ${this.name}`)
329
+
330
+ if (this.params.account === 'random') {
331
+ this._account = await Account.random()
332
+ } else if (isAccountInstance(this.params.account)) {
333
+ this._account = this.params.account
334
+ }
335
+
336
+ assertEx(isAccountInstance(this._account), () => `Invalid account instance: ${this._account}`)
337
+
338
+ this._supportedQueryValidator = new SupportedQueryValidator(this as Module).queryable
339
+ this._moduleConfigQueryValidator = new ModuleConfigQueryValidator(this.config).queryable
340
+
341
+ if (!AbstractModule.enableLazyLoad) {
342
+ await this.start?.()
343
+ }
344
+ }
345
+
318
346
  override emit<TEventName extends keyof TEventData = keyof TEventData, TEventArgs extends TEventData[TEventName] = TEventData[TEventName]>(
319
347
  eventName: TEventName,
320
348
  eventArgs: TEventArgs,
@@ -418,43 +446,29 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
418
446
  return true
419
447
  }
420
448
 
421
- async start(timeout?: number): Promise<boolean> {
422
- this._noOverride('start')
423
- this.status = 'starting'
424
- let startingTime = 0
425
- const startingStatus = setInterval(() => {
426
- startingTime += 1000
427
- this.statusReporter?.reportStatus(`${this.constructor.name}:${this.id}`, 'starting', startingTime)
428
- }, 1000)
429
- // using promise as mutex
430
- this._startPromise = this._startPromise ?? await this.startHandler(timeout)
431
- const result = await this._startPromise
432
- this.status = result ? 'started' : 'dead'
433
- clearInterval(startingStatus)
434
- return result
435
- }
436
-
437
449
  async started(notStartedAction: 'error' | 'throw' | 'warn' | 'log' | 'none' = 'log', tryStart = true): Promise<boolean> {
438
- if (isDefined(this._started) && (await this._started) === true) {
450
+ if (isString(this.status) && this.status === 'started') {
439
451
  return true
440
452
  }
441
- if (isUndefined(this._started)) {
453
+ if (this.status === 'created' || this.status === 'stopped') {
442
454
  // using promise as mutex
443
- this._started = (async () => {
455
+ this._startPromise = this._startPromise ?? (async () => {
444
456
  if (tryStart) {
445
457
  try {
446
458
  await this.start()
447
459
  return true
448
460
  } catch (ex) {
449
461
  handleError(ex, (error) => {
450
- this.logger?.warn(`Autostart of Module Failed: ${error.message})`)
451
- this._started = undefined
462
+ this.status = 'error'
463
+ this.logger?.warn(`Autostart of Module Failed: ${error.message}`)
452
464
  })
465
+ } finally {
466
+ this._startPromise = undefined
453
467
  }
454
468
  }
455
469
  switch (notStartedAction) {
456
470
  case 'throw': {
457
- throw new Error(`${MODULE_NOT_STARTED} [${this.address}]`)
471
+ throw new Error(`${MODULE_NOT_STARTED} [${this.address}] current state: ${this.status}`)
458
472
  }
459
473
  case 'warn': {
460
474
  this.logger?.warn(MODULE_NOT_STARTED)
@@ -476,23 +490,10 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
476
490
  })()
477
491
  }
478
492
 
479
- if (isUndefined(this._started)) {
480
- throw 'Failed to create start promise'
493
+ if (isUndefined(this._startPromise)) {
494
+ throw new Error(`Failed to create start promise: ${this.status}`)
481
495
  }
482
- return await this._started
483
- }
484
-
485
- async stop(_timeout?: number): Promise<boolean> {
486
- this._noOverride('stop')
487
- return await spanAsync('start', async () => {
488
- return await this.busy(async () => {
489
- const result = await this.stopHandler()
490
- this._started = undefined
491
- this._startPromise = undefined
492
- this.status = result ? 'stopped' : 'dead'
493
- return result
494
- })
495
- }, this.tracer)
496
+ return await this._startPromise
496
497
  }
497
498
 
498
499
  protected _checkDead() {
@@ -501,15 +502,6 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
501
502
  }
502
503
  }
503
504
 
504
- // eslint-disable-next-line sonarjs/no-identical-functions
505
- protected _noOverride(functionName: string) {
506
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
507
- const thisFunc = (this as any)[functionName]
508
-
509
- const rootFunc = this._getRootFunction(functionName)
510
- assertEx(thisFunc === rootFunc, () => `Override not allowed for [${functionName}] - override ${functionName}Handler instead`)
511
- }
512
-
513
505
  protected async archivistInstance(): Promise<ArchivistInstance | undefined>
514
506
  protected async archivistInstance(required: true): Promise<ArchivistInstance>
515
507
  protected async archivistInstance(required = false): Promise<ArchivistInstance | undefined> {
@@ -693,20 +685,18 @@ export abstract class AbstractModule<TParams extends ModuleParams = ModuleParams
693
685
  return PayloadBuilder.omitPrivateStorageMeta(resultPayloads)
694
686
  }
695
687
 
696
- protected async startHandler(_timeout?: number): Promise<boolean> {
688
+ protected override async startHandler(): Promise<void> {
697
689
  this.validateConfig()
698
- await Promise.resolve()
699
- this._started = true
700
- return true
690
+ await super.startHandler()
701
691
  }
702
692
 
703
693
  protected async stateHandler(): Promise<Payload[]> {
704
694
  return [await this.manifestHandler(), ...(await this.generateConfigAndAddress()), await this.generateDescribe()]
705
695
  }
706
696
 
707
- protected stopHandler(_timeout?: number): Promisable<boolean> {
708
- this._started = undefined
709
- return true
697
+ protected override async stopHandler(): Promise<void> {
698
+ await super.stopHandler()
699
+ this._startPromise = undefined
710
700
  }
711
701
 
712
702
  protected subscribeHandler() {
@@ -1,4 +1,3 @@
1
- import { assertEx } from '@xylabs/assert'
2
1
  import { globallyUnique } from '@xylabs/base'
3
2
  import { exists } from '@xylabs/exists'
4
3
  import type { Address } from '@xylabs/hex'
@@ -69,18 +68,6 @@ export abstract class AbstractModuleInstance<TParams extends ModuleParams = Modu
69
68
  private _privateResolver?: CompositeModuleResolver
70
69
  private _upResolver?: CompositeModuleResolver
71
70
 
72
- constructor(privateConstructorKey: string, params: TParams, account: AccountInstance) {
73
- assertEx(AbstractModule.privateConstructorKey === privateConstructorKey, () => 'Use create function instead of constructor')
74
- // Clone params to prevent mutation of the incoming object
75
- const mutatedParams = { ...params } as TParams
76
- const addToResolvers = mutatedParams.addToResolvers ?? true
77
- super(privateConstructorKey, mutatedParams, account)
78
- if (addToResolvers) {
79
- this.upResolver.add(this)
80
- this.downResolver.add(this)
81
- }
82
- }
83
-
84
71
  get downResolver() {
85
72
  this._downResolver
86
73
  = this._downResolver
@@ -129,7 +116,7 @@ export abstract class AbstractModuleInstance<TParams extends ModuleParams = Modu
129
116
  addParent(mod: ModuleInstance) {
130
117
  const existingEntry = this._parents.find(parent => parent.address === mod.address)
131
118
  if (!existingEntry) {
132
- this._parents.push(asNodeInstance(mod, 'Only NodeInstances can be parents'))
119
+ this._parents.push(asNodeInstance(mod, 'Only NodeInstances can be parents', { required: true }))
133
120
  }
134
121
  }
135
122
 
@@ -145,6 +132,17 @@ export abstract class AbstractModuleInstance<TParams extends ModuleParams = Modu
145
132
  ).flat()
146
133
  }
147
134
 
135
+ override async createHandler() {
136
+ this.status = 'creating'
137
+ await super.createHandler()
138
+ const addToResolvers = this.params.addToResolvers ?? true
139
+ if (addToResolvers) {
140
+ this.upResolver.add(this)
141
+ this.downResolver.add(this)
142
+ }
143
+ this.status = 'created'
144
+ }
145
+
148
146
  manifest(maxDepth?: number): Promise<ModuleManifestPayload> {
149
147
  this._checkDead()
150
148
  return this.busy(async () => {
@@ -249,13 +247,6 @@ export abstract class AbstractModuleInstance<TParams extends ModuleParams = Modu
249
247
  return (await Promise.all((await this.parents()).map(parent => parent.publicChildren()))).flat().filter(duplicateModules)
250
248
  }
251
249
 
252
- /* override start(_timeout?: number): Promisable<boolean> {
253
- if (this.parents.length === 0) {
254
- this.logger.warn(`Module is being started without being attached to a parent: ${this.id} [${this.address}]`)
255
- }
256
- return super.start()
257
- } */
258
-
259
250
  state() {
260
251
  this._checkDead()
261
252
  return this.busy(async () => {
@@ -288,6 +279,7 @@ export abstract class AbstractModuleInstance<TParams extends ModuleParams = Modu
288
279
  }
289
280
  const result = {
290
281
  config: { name: modName, ...this.config },
282
+ name: modName,
291
283
  schema: ModuleManifestPayloadSchema,
292
284
  status: { address: this.address, children: childAddressToName },
293
285
  }
@@ -332,6 +324,18 @@ export abstract class AbstractModuleInstance<TParams extends ModuleParams = Modu
332
324
  return (await this.query(query[0], query[1])) as ModuleQueryResult<R>
333
325
  }
334
326
 
327
+ protected override startHandler() {
328
+ this._checkDead()
329
+ return this.busy(async () => {
330
+ if (this.status === 'started' || this.status === 'creating') {
331
+ return
332
+ }
333
+ this.status = 'starting'
334
+ await super.startHandler()
335
+ this.status = 'started'
336
+ })
337
+ }
338
+
335
339
  protected async storeToArchivists(payloads: Payload[]): Promise<Payload[]> {
336
340
  try {
337
341
  const archivists = await this.resolveArchivingArchivists()
@@ -1,16 +1,17 @@
1
+ import type { CreatableName, CreatableStatus } from '@xylabs/creatable'
1
2
  import type { Logger } from '@xylabs/logger'
2
- import type { ModuleStatus, ModuleStatusReporter } from '@xyo-network/module-model'
3
+ import type { ModuleStatusReporter } from '@xyo-network/module-model'
3
4
 
4
5
  export class LoggerModuleStatusReporter implements ModuleStatusReporter {
5
- private logger: Logger
6
+ protected logger: Logger
6
7
 
7
- private statusMap: Record<string, ModuleStatus> = {}
8
+ protected statusMap: Record<CreatableName, CreatableStatus> = {}
8
9
 
9
10
  constructor(logger: Logger) {
10
11
  this.logger = logger
11
12
  }
12
13
 
13
- reportStatus(name: string, status: ModuleStatus, progress?: number): void {
14
+ report(name: string, status: CreatableStatus, progress?: number | Error): void {
14
15
  this.statusMap[name] = status
15
16
  const starting = (Object.entries(this.statusMap).map(([, value]): number => value === 'starting' ? 1 : 0)).reduce((a, b) => a + b, 0)
16
17
  const started = (Object.entries(this.statusMap).map(([, value]): number => value === 'started' ? 1 : 0)).reduce((a, b) => a + b, 0)
@@ -7,7 +7,6 @@ import type { Schema } from '@xyo-network/payload-model'
7
7
 
8
8
  import type { Queryable, QueryValidator } from './QueryValidator.ts'
9
9
 
10
- // eslint-disable-next-line sonarjs/redundant-type-aliases
11
10
  export type SortedPipedAddressesString = string
12
11
 
13
12
  const delimiter = ''