@twin.org/engine-core 0.0.2-next.3 → 0.0.2-next.5

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.
@@ -4,6 +4,7 @@ var core = require('@twin.org/core');
4
4
  var entity = require('@twin.org/entity');
5
5
  var loggingConnectorConsole = require('@twin.org/logging-connector-console');
6
6
  var loggingModels = require('@twin.org/logging-models');
7
+ var loggingService = require('@twin.org/logging-service');
7
8
  var modules = require('@twin.org/modules');
8
9
  var promises = require('node:fs/promises');
9
10
  var path = require('node:path');
@@ -72,10 +73,16 @@ class EngineCore {
72
73
  * Name for the engine logger.
73
74
  */
74
75
  static LOGGER_TYPE_NAME = "engine";
76
+ /**
77
+ * Runtime name for the class in camel case.
78
+ * @internal
79
+ */
80
+ static _CLASS_NAME_CAMEL_CASE = core.StringHelper.camelCase("EngineCore");
75
81
  /**
76
82
  * Runtime name for the class.
83
+ * @internal
77
84
  */
78
- CLASS_NAME = "EngineCore";
85
+ static _CLASS_NAME = "EngineCore";
79
86
  /**
80
87
  * The core context.
81
88
  */
@@ -137,7 +144,7 @@ class EngineCore {
137
144
  this._typeInitialisers = [];
138
145
  this._context = {
139
146
  config: options.config,
140
- defaultTypes: {},
147
+ registeredInstances: {},
141
148
  componentInstances: [],
142
149
  state: { componentStates: {} },
143
150
  stateDirty: false
@@ -174,9 +181,9 @@ class EngineCore {
174
181
  return false;
175
182
  }
176
183
  this.setupEngineLogger();
177
- this.logInfo(core.I18n.formatMessage("engineCore.starting"));
184
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.starting`));
178
185
  if (this._context.config.debug) {
179
- this.logInfo(core.I18n.formatMessage("engineCore.debuggingEnabled"));
186
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.debuggingEnabled`));
180
187
  }
181
188
  let canContinue;
182
189
  try {
@@ -186,11 +193,11 @@ class EngineCore {
186
193
  await this.initialiseTypeConfig(type, typeConfig, module, method);
187
194
  }
188
195
  await this.bootstrap();
189
- this.logInfo(core.I18n.formatMessage("engineCore.componentsStarting"));
196
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.componentsStarting`));
190
197
  for (const instance of this._context.componentInstances) {
191
198
  if (core.Is.function(instance.component.start)) {
192
199
  const instanceName = this.getInstanceName(instance);
193
- this.logInfo(core.I18n.formatMessage("engineCore.componentStarting", {
200
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.componentStarting`, {
194
201
  element: instance.instanceType
195
202
  }));
196
203
  const componentState = this._context.state.componentStates[instanceName] ?? {};
@@ -202,9 +209,9 @@ class EngineCore {
202
209
  }
203
210
  }
204
211
  }
205
- this.logInfo(core.I18n.formatMessage("engineCore.componentsComplete"));
212
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.componentsComplete`));
206
213
  }
207
- this.logInfo(core.I18n.formatMessage("engineCore.started"));
214
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.started`));
208
215
  this._isStarted = true;
209
216
  }
210
217
  catch (err) {
@@ -223,14 +230,16 @@ class EngineCore {
223
230
  * @returns Nothing.
224
231
  */
225
232
  async stop() {
226
- this.logInfo(core.I18n.formatMessage("engineCore.stopping"));
227
- this.logInfo(core.I18n.formatMessage("engineCore.componentsStopping"));
233
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.stopping`));
234
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.componentsStopping`));
228
235
  for (const instance of this._context.componentInstances) {
229
236
  if (core.Is.function(instance.component.stop)) {
230
237
  const instanceName = this.getInstanceName(instance);
231
238
  const componentState = this._context.state.componentStates[instanceName] ?? {};
232
239
  const lastState = core.ObjectHelper.clone(componentState);
233
- this.logInfo(core.I18n.formatMessage("engineCore.componentStopping", { element: instance.instanceType }));
240
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.componentStopping`, {
241
+ element: instance.instanceType
242
+ }));
234
243
  try {
235
244
  await instance.component.stop(this._context.state.nodeIdentity, this._loggerTypeName, componentState);
236
245
  if (!core.ObjectHelper.equal(lastState, componentState)) {
@@ -239,15 +248,15 @@ class EngineCore {
239
248
  }
240
249
  }
241
250
  catch (err) {
242
- this.logError(new core.GeneralError(this.CLASS_NAME, "componentStopFailed", {
251
+ this.logError(new core.GeneralError(EngineCore._CLASS_NAME, "componentStopFailed", {
243
252
  component: instance.instanceType
244
253
  }, core.BaseError.fromError(err)));
245
254
  }
246
255
  }
247
256
  }
248
257
  await this.stateSave();
249
- this.logInfo(core.I18n.formatMessage("engineCore.componentsStopped"));
250
- this.logInfo(core.I18n.formatMessage("engineCore.stopped"));
258
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.componentsStopped`));
259
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.stopped`));
251
260
  }
252
261
  /**
253
262
  * Log info.
@@ -255,7 +264,7 @@ class EngineCore {
255
264
  */
256
265
  logInfo(message) {
257
266
  this._engineLoggingConnector?.log({
258
- source: this.CLASS_NAME,
267
+ source: EngineCore._CLASS_NAME,
259
268
  level: "info",
260
269
  message
261
270
  });
@@ -274,7 +283,7 @@ class EngineCore {
274
283
  message += `\n${formattedError.stack}`;
275
284
  }
276
285
  this._engineLoggingConnector?.log({
277
- source: this.CLASS_NAME,
286
+ source: EngineCore._CLASS_NAME,
278
287
  level: "error",
279
288
  message
280
289
  });
@@ -295,11 +304,48 @@ class EngineCore {
295
304
  return this._context.state;
296
305
  }
297
306
  /**
298
- * Get the types for the component.
299
- * @returns The default types.
307
+ * Get all the registered instances.
308
+ * @returns The registered instances.
300
309
  */
301
- getDefaultTypes() {
302
- return this._context.defaultTypes;
310
+ getRegisteredInstances() {
311
+ return this._context.registeredInstances;
312
+ }
313
+ /**
314
+ * Get the registered instance type for the component/connector.
315
+ * @param componentConnectorType The type of the component/connector.
316
+ * @param features The requested features of the component, if not specified the default entry will be retrieved.
317
+ * @returns The instance type matching the criteria if one is registered.
318
+ * @throws If a matching instance was not found.
319
+ */
320
+ getRegisteredInstanceType(componentConnectorType, features) {
321
+ core.Guards.stringValue(EngineCore._CLASS_NAME, "componentConnectorType", componentConnectorType);
322
+ const registeredType = this.getRegisteredInstanceTypeOptional(componentConnectorType, features);
323
+ if (!core.Is.stringValue(registeredType)) {
324
+ throw new core.GeneralError(EngineCore._CLASS_NAME, "instanceTypeNotFound", {
325
+ type: componentConnectorType,
326
+ features: (features ?? ["default"]).join(",")
327
+ });
328
+ }
329
+ return registeredType;
330
+ }
331
+ /**
332
+ * Get the registered instance type for the component/connector if it exists.
333
+ * @param componentConnectorType The type of the component/connector.
334
+ * @param features The requested features of the component, if not specified the default entry will be retrieved.
335
+ * @returns The instance type matching the criteria if one is registered.
336
+ */
337
+ getRegisteredInstanceTypeOptional(componentConnectorType, features) {
338
+ let registeredType;
339
+ const registeredTypes = this._context.registeredInstances[componentConnectorType];
340
+ if (core.Is.arrayValue(registeredTypes)) {
341
+ if (core.Is.arrayValue(features)) {
342
+ registeredType = registeredTypes.find(t => t.features?.every(f => features.includes(f)))?.type;
343
+ }
344
+ else {
345
+ registeredType = registeredTypes[0]?.type;
346
+ }
347
+ }
348
+ return registeredType;
303
349
  }
304
350
  /**
305
351
  * Get the data required to create a clone of the engine.
@@ -326,10 +372,10 @@ class EngineCore {
326
372
  * @param silent Should the clone be silent.
327
373
  */
328
374
  populateClone(cloneData, silent) {
329
- core.Guards.object(this.CLASS_NAME, "cloneData", cloneData);
330
- core.Guards.object(this.CLASS_NAME, "cloneData.config", cloneData.config);
331
- core.Guards.object(this.CLASS_NAME, "cloneData.state", cloneData.state);
332
- core.Guards.array(this.CLASS_NAME, "cloneData.typeInitialisers", cloneData.typeInitialisers);
375
+ core.Guards.object(EngineCore._CLASS_NAME, "cloneData", cloneData);
376
+ core.Guards.object(EngineCore._CLASS_NAME, "cloneData.config", cloneData.config);
377
+ core.Guards.object(EngineCore._CLASS_NAME, "cloneData.state", cloneData.state);
378
+ core.Guards.array(EngineCore._CLASS_NAME, "cloneData.typeInitialisers", cloneData.typeInitialisers);
333
379
  this._loggerTypeName = cloneData.loggerTypeName;
334
380
  this._skipBootstrap = true;
335
381
  if (silent ?? false) {
@@ -337,7 +383,7 @@ class EngineCore {
337
383
  }
338
384
  this._context = {
339
385
  config: cloneData.config,
340
- defaultTypes: {},
386
+ registeredInstances: {},
341
387
  componentInstances: [],
342
388
  state: { componentStates: {} },
343
389
  stateDirty: false
@@ -360,9 +406,20 @@ class EngineCore {
360
406
  const instanceMethod = await modules.ModuleHelper.getModuleEntry(module, method);
361
407
  for (let i = 0; i < typeConfig.length; i++) {
362
408
  const instanceType = instanceMethod(this, this._context, typeConfig[i], typeConfig[i].overrideInstanceType);
363
- if (core.Is.stringValue(instanceType) &&
364
- (core.Is.empty(this._context.defaultTypes[typeKey]) || typeConfig[i].isDefault)) {
365
- this._context.defaultTypes[typeKey] = instanceType;
409
+ if (core.Is.stringValue(instanceType)) {
410
+ this._context.registeredInstances[typeKey] ??= [];
411
+ if (typeConfig[i].isDefault ?? false) {
412
+ this._context.registeredInstances[typeKey].unshift({
413
+ type: instanceType,
414
+ features: typeConfig[i].features
415
+ });
416
+ }
417
+ else {
418
+ this._context.registeredInstances[typeKey].push({
419
+ type: instanceType,
420
+ features: typeConfig[i].features
421
+ });
422
+ }
366
423
  }
367
424
  }
368
425
  }
@@ -373,7 +430,7 @@ class EngineCore {
373
430
  */
374
431
  setupEngineLogger() {
375
432
  const silent = this._context.config.silent ?? false;
376
- const engineLogger = silent
433
+ const engineLoggerConnector = silent
377
434
  ? new loggingModels.SilentLoggingConnector()
378
435
  : new loggingConnectorConsole.ConsoleLoggingConnector({
379
436
  config: {
@@ -383,11 +440,24 @@ class EngineCore {
383
440
  });
384
441
  this._context.componentInstances.push({
385
442
  instanceType: this._loggerTypeName,
386
- component: engineLogger
443
+ component: engineLoggerConnector
387
444
  });
388
- loggingModels.LoggingConnectorFactory.register(this._loggerTypeName, () => engineLogger);
389
- this._engineLoggingConnector = engineLogger;
390
- this._context.defaultTypes.loggingConnector = this._loggerTypeName;
445
+ loggingModels.LoggingConnectorFactory.register(this._loggerTypeName, () => engineLoggerConnector);
446
+ this._engineLoggingConnector = engineLoggerConnector;
447
+ this._context.registeredInstances.loggingConnector = [
448
+ {
449
+ type: this._loggerTypeName
450
+ }
451
+ ];
452
+ const engineLoggerComponent = new loggingService.LoggingService({
453
+ loggingConnectorType: this._loggerTypeName
454
+ });
455
+ core.ComponentFactory.register("logging-service", () => engineLoggerComponent);
456
+ this._context.registeredInstances.loggingComponent = [
457
+ {
458
+ type: "logging-service"
459
+ }
460
+ ];
391
461
  }
392
462
  /**
393
463
  * Load the state.
@@ -436,12 +506,12 @@ class EngineCore {
436
506
  */
437
507
  async bootstrap() {
438
508
  if (!this._skipBootstrap) {
439
- this.logInfo(core.I18n.formatMessage("engineCore.bootstrapStarted"));
509
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.bootstrapStarted`));
440
510
  // First bootstrap the components.
441
511
  for (const instance of this._context.componentInstances) {
442
512
  if (core.Is.function(instance.component.bootstrap)) {
443
513
  const instanceName = this.getInstanceName(instance);
444
- this.logInfo(core.I18n.formatMessage("engineCore.bootstrapping", {
514
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.bootstrapping`, {
445
515
  element: instanceName
446
516
  }));
447
517
  const componentState = this._context.state.componentStates[instanceName] ?? {};
@@ -449,7 +519,7 @@ class EngineCore {
449
519
  const bootstrapSuccess = await instance.component.bootstrap(this._loggerTypeName, componentState);
450
520
  // If the bootstrap method failed then throw an error
451
521
  if (!bootstrapSuccess) {
452
- throw new core.GeneralError(this.CLASS_NAME, "bootstrapFailed", {
522
+ throw new core.GeneralError(EngineCore._CLASS_NAME, "bootstrapFailed", {
453
523
  component: `${instance.component.CLASS_NAME}:${instance.instanceType}`
454
524
  });
455
525
  }
@@ -463,7 +533,7 @@ class EngineCore {
463
533
  if (core.Is.function(this._customBootstrap)) {
464
534
  await this._customBootstrap(this, this._context);
465
535
  }
466
- this.logInfo(core.I18n.formatMessage("engineCore.bootstrapComplete"));
536
+ this.logInfo(core.I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.bootstrapComplete`));
467
537
  }
468
538
  }
469
539
  /**
@@ -1,7 +1,8 @@
1
- import { I18n, StringHelper, Is, ObjectHelper, BaseError, GeneralError, ErrorHelper, Guards } from '@twin.org/core';
1
+ import { I18n, StringHelper, Is, ObjectHelper, BaseError, GeneralError, ErrorHelper, Guards, ComponentFactory } from '@twin.org/core';
2
2
  import { EntitySchemaFactory } from '@twin.org/entity';
3
3
  import { ConsoleLoggingConnector } from '@twin.org/logging-connector-console';
4
4
  import { SilentLoggingConnector, LoggingConnectorFactory } from '@twin.org/logging-models';
5
+ import { LoggingService } from '@twin.org/logging-service';
5
6
  import { ModuleHelper } from '@twin.org/modules';
6
7
  import { readFile, mkdir, writeFile, stat } from 'node:fs/promises';
7
8
  import path from 'node:path';
@@ -70,10 +71,16 @@ class EngineCore {
70
71
  * Name for the engine logger.
71
72
  */
72
73
  static LOGGER_TYPE_NAME = "engine";
74
+ /**
75
+ * Runtime name for the class in camel case.
76
+ * @internal
77
+ */
78
+ static _CLASS_NAME_CAMEL_CASE = StringHelper.camelCase("EngineCore");
73
79
  /**
74
80
  * Runtime name for the class.
81
+ * @internal
75
82
  */
76
- CLASS_NAME = "EngineCore";
83
+ static _CLASS_NAME = "EngineCore";
77
84
  /**
78
85
  * The core context.
79
86
  */
@@ -135,7 +142,7 @@ class EngineCore {
135
142
  this._typeInitialisers = [];
136
143
  this._context = {
137
144
  config: options.config,
138
- defaultTypes: {},
145
+ registeredInstances: {},
139
146
  componentInstances: [],
140
147
  state: { componentStates: {} },
141
148
  stateDirty: false
@@ -172,9 +179,9 @@ class EngineCore {
172
179
  return false;
173
180
  }
174
181
  this.setupEngineLogger();
175
- this.logInfo(I18n.formatMessage("engineCore.starting"));
182
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.starting`));
176
183
  if (this._context.config.debug) {
177
- this.logInfo(I18n.formatMessage("engineCore.debuggingEnabled"));
184
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.debuggingEnabled`));
178
185
  }
179
186
  let canContinue;
180
187
  try {
@@ -184,11 +191,11 @@ class EngineCore {
184
191
  await this.initialiseTypeConfig(type, typeConfig, module, method);
185
192
  }
186
193
  await this.bootstrap();
187
- this.logInfo(I18n.formatMessage("engineCore.componentsStarting"));
194
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.componentsStarting`));
188
195
  for (const instance of this._context.componentInstances) {
189
196
  if (Is.function(instance.component.start)) {
190
197
  const instanceName = this.getInstanceName(instance);
191
- this.logInfo(I18n.formatMessage("engineCore.componentStarting", {
198
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.componentStarting`, {
192
199
  element: instance.instanceType
193
200
  }));
194
201
  const componentState = this._context.state.componentStates[instanceName] ?? {};
@@ -200,9 +207,9 @@ class EngineCore {
200
207
  }
201
208
  }
202
209
  }
203
- this.logInfo(I18n.formatMessage("engineCore.componentsComplete"));
210
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.componentsComplete`));
204
211
  }
205
- this.logInfo(I18n.formatMessage("engineCore.started"));
212
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.started`));
206
213
  this._isStarted = true;
207
214
  }
208
215
  catch (err) {
@@ -221,14 +228,16 @@ class EngineCore {
221
228
  * @returns Nothing.
222
229
  */
223
230
  async stop() {
224
- this.logInfo(I18n.formatMessage("engineCore.stopping"));
225
- this.logInfo(I18n.formatMessage("engineCore.componentsStopping"));
231
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.stopping`));
232
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.componentsStopping`));
226
233
  for (const instance of this._context.componentInstances) {
227
234
  if (Is.function(instance.component.stop)) {
228
235
  const instanceName = this.getInstanceName(instance);
229
236
  const componentState = this._context.state.componentStates[instanceName] ?? {};
230
237
  const lastState = ObjectHelper.clone(componentState);
231
- this.logInfo(I18n.formatMessage("engineCore.componentStopping", { element: instance.instanceType }));
238
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.componentStopping`, {
239
+ element: instance.instanceType
240
+ }));
232
241
  try {
233
242
  await instance.component.stop(this._context.state.nodeIdentity, this._loggerTypeName, componentState);
234
243
  if (!ObjectHelper.equal(lastState, componentState)) {
@@ -237,15 +246,15 @@ class EngineCore {
237
246
  }
238
247
  }
239
248
  catch (err) {
240
- this.logError(new GeneralError(this.CLASS_NAME, "componentStopFailed", {
249
+ this.logError(new GeneralError(EngineCore._CLASS_NAME, "componentStopFailed", {
241
250
  component: instance.instanceType
242
251
  }, BaseError.fromError(err)));
243
252
  }
244
253
  }
245
254
  }
246
255
  await this.stateSave();
247
- this.logInfo(I18n.formatMessage("engineCore.componentsStopped"));
248
- this.logInfo(I18n.formatMessage("engineCore.stopped"));
256
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.componentsStopped`));
257
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.stopped`));
249
258
  }
250
259
  /**
251
260
  * Log info.
@@ -253,7 +262,7 @@ class EngineCore {
253
262
  */
254
263
  logInfo(message) {
255
264
  this._engineLoggingConnector?.log({
256
- source: this.CLASS_NAME,
265
+ source: EngineCore._CLASS_NAME,
257
266
  level: "info",
258
267
  message
259
268
  });
@@ -272,7 +281,7 @@ class EngineCore {
272
281
  message += `\n${formattedError.stack}`;
273
282
  }
274
283
  this._engineLoggingConnector?.log({
275
- source: this.CLASS_NAME,
284
+ source: EngineCore._CLASS_NAME,
276
285
  level: "error",
277
286
  message
278
287
  });
@@ -293,11 +302,48 @@ class EngineCore {
293
302
  return this._context.state;
294
303
  }
295
304
  /**
296
- * Get the types for the component.
297
- * @returns The default types.
305
+ * Get all the registered instances.
306
+ * @returns The registered instances.
298
307
  */
299
- getDefaultTypes() {
300
- return this._context.defaultTypes;
308
+ getRegisteredInstances() {
309
+ return this._context.registeredInstances;
310
+ }
311
+ /**
312
+ * Get the registered instance type for the component/connector.
313
+ * @param componentConnectorType The type of the component/connector.
314
+ * @param features The requested features of the component, if not specified the default entry will be retrieved.
315
+ * @returns The instance type matching the criteria if one is registered.
316
+ * @throws If a matching instance was not found.
317
+ */
318
+ getRegisteredInstanceType(componentConnectorType, features) {
319
+ Guards.stringValue(EngineCore._CLASS_NAME, "componentConnectorType", componentConnectorType);
320
+ const registeredType = this.getRegisteredInstanceTypeOptional(componentConnectorType, features);
321
+ if (!Is.stringValue(registeredType)) {
322
+ throw new GeneralError(EngineCore._CLASS_NAME, "instanceTypeNotFound", {
323
+ type: componentConnectorType,
324
+ features: (features ?? ["default"]).join(",")
325
+ });
326
+ }
327
+ return registeredType;
328
+ }
329
+ /**
330
+ * Get the registered instance type for the component/connector if it exists.
331
+ * @param componentConnectorType The type of the component/connector.
332
+ * @param features The requested features of the component, if not specified the default entry will be retrieved.
333
+ * @returns The instance type matching the criteria if one is registered.
334
+ */
335
+ getRegisteredInstanceTypeOptional(componentConnectorType, features) {
336
+ let registeredType;
337
+ const registeredTypes = this._context.registeredInstances[componentConnectorType];
338
+ if (Is.arrayValue(registeredTypes)) {
339
+ if (Is.arrayValue(features)) {
340
+ registeredType = registeredTypes.find(t => t.features?.every(f => features.includes(f)))?.type;
341
+ }
342
+ else {
343
+ registeredType = registeredTypes[0]?.type;
344
+ }
345
+ }
346
+ return registeredType;
301
347
  }
302
348
  /**
303
349
  * Get the data required to create a clone of the engine.
@@ -324,10 +370,10 @@ class EngineCore {
324
370
  * @param silent Should the clone be silent.
325
371
  */
326
372
  populateClone(cloneData, silent) {
327
- Guards.object(this.CLASS_NAME, "cloneData", cloneData);
328
- Guards.object(this.CLASS_NAME, "cloneData.config", cloneData.config);
329
- Guards.object(this.CLASS_NAME, "cloneData.state", cloneData.state);
330
- Guards.array(this.CLASS_NAME, "cloneData.typeInitialisers", cloneData.typeInitialisers);
373
+ Guards.object(EngineCore._CLASS_NAME, "cloneData", cloneData);
374
+ Guards.object(EngineCore._CLASS_NAME, "cloneData.config", cloneData.config);
375
+ Guards.object(EngineCore._CLASS_NAME, "cloneData.state", cloneData.state);
376
+ Guards.array(EngineCore._CLASS_NAME, "cloneData.typeInitialisers", cloneData.typeInitialisers);
331
377
  this._loggerTypeName = cloneData.loggerTypeName;
332
378
  this._skipBootstrap = true;
333
379
  if (silent ?? false) {
@@ -335,7 +381,7 @@ class EngineCore {
335
381
  }
336
382
  this._context = {
337
383
  config: cloneData.config,
338
- defaultTypes: {},
384
+ registeredInstances: {},
339
385
  componentInstances: [],
340
386
  state: { componentStates: {} },
341
387
  stateDirty: false
@@ -358,9 +404,20 @@ class EngineCore {
358
404
  const instanceMethod = await ModuleHelper.getModuleEntry(module, method);
359
405
  for (let i = 0; i < typeConfig.length; i++) {
360
406
  const instanceType = instanceMethod(this, this._context, typeConfig[i], typeConfig[i].overrideInstanceType);
361
- if (Is.stringValue(instanceType) &&
362
- (Is.empty(this._context.defaultTypes[typeKey]) || typeConfig[i].isDefault)) {
363
- this._context.defaultTypes[typeKey] = instanceType;
407
+ if (Is.stringValue(instanceType)) {
408
+ this._context.registeredInstances[typeKey] ??= [];
409
+ if (typeConfig[i].isDefault ?? false) {
410
+ this._context.registeredInstances[typeKey].unshift({
411
+ type: instanceType,
412
+ features: typeConfig[i].features
413
+ });
414
+ }
415
+ else {
416
+ this._context.registeredInstances[typeKey].push({
417
+ type: instanceType,
418
+ features: typeConfig[i].features
419
+ });
420
+ }
364
421
  }
365
422
  }
366
423
  }
@@ -371,7 +428,7 @@ class EngineCore {
371
428
  */
372
429
  setupEngineLogger() {
373
430
  const silent = this._context.config.silent ?? false;
374
- const engineLogger = silent
431
+ const engineLoggerConnector = silent
375
432
  ? new SilentLoggingConnector()
376
433
  : new ConsoleLoggingConnector({
377
434
  config: {
@@ -381,11 +438,24 @@ class EngineCore {
381
438
  });
382
439
  this._context.componentInstances.push({
383
440
  instanceType: this._loggerTypeName,
384
- component: engineLogger
441
+ component: engineLoggerConnector
385
442
  });
386
- LoggingConnectorFactory.register(this._loggerTypeName, () => engineLogger);
387
- this._engineLoggingConnector = engineLogger;
388
- this._context.defaultTypes.loggingConnector = this._loggerTypeName;
443
+ LoggingConnectorFactory.register(this._loggerTypeName, () => engineLoggerConnector);
444
+ this._engineLoggingConnector = engineLoggerConnector;
445
+ this._context.registeredInstances.loggingConnector = [
446
+ {
447
+ type: this._loggerTypeName
448
+ }
449
+ ];
450
+ const engineLoggerComponent = new LoggingService({
451
+ loggingConnectorType: this._loggerTypeName
452
+ });
453
+ ComponentFactory.register("logging-service", () => engineLoggerComponent);
454
+ this._context.registeredInstances.loggingComponent = [
455
+ {
456
+ type: "logging-service"
457
+ }
458
+ ];
389
459
  }
390
460
  /**
391
461
  * Load the state.
@@ -434,12 +504,12 @@ class EngineCore {
434
504
  */
435
505
  async bootstrap() {
436
506
  if (!this._skipBootstrap) {
437
- this.logInfo(I18n.formatMessage("engineCore.bootstrapStarted"));
507
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.bootstrapStarted`));
438
508
  // First bootstrap the components.
439
509
  for (const instance of this._context.componentInstances) {
440
510
  if (Is.function(instance.component.bootstrap)) {
441
511
  const instanceName = this.getInstanceName(instance);
442
- this.logInfo(I18n.formatMessage("engineCore.bootstrapping", {
512
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.bootstrapping`, {
443
513
  element: instanceName
444
514
  }));
445
515
  const componentState = this._context.state.componentStates[instanceName] ?? {};
@@ -447,7 +517,7 @@ class EngineCore {
447
517
  const bootstrapSuccess = await instance.component.bootstrap(this._loggerTypeName, componentState);
448
518
  // If the bootstrap method failed then throw an error
449
519
  if (!bootstrapSuccess) {
450
- throw new GeneralError(this.CLASS_NAME, "bootstrapFailed", {
520
+ throw new GeneralError(EngineCore._CLASS_NAME, "bootstrapFailed", {
451
521
  component: `${instance.component.CLASS_NAME}:${instance.instanceType}`
452
522
  });
453
523
  }
@@ -461,7 +531,7 @@ class EngineCore {
461
531
  if (Is.function(this._customBootstrap)) {
462
532
  await this._customBootstrap(this, this._context);
463
533
  }
464
- this.logInfo(I18n.formatMessage("engineCore.bootstrapComplete"));
534
+ this.logInfo(I18n.formatMessage(`${EngineCore._CLASS_NAME_CAMEL_CASE}.bootstrapComplete`));
465
535
  }
466
536
  }
467
537
  /**
@@ -9,10 +9,6 @@ export declare class EngineCore<C extends IEngineCoreConfig = IEngineCoreConfig,
9
9
  * Name for the engine logger.
10
10
  */
11
11
  static readonly LOGGER_TYPE_NAME: string;
12
- /**
13
- * Runtime name for the class.
14
- */
15
- readonly CLASS_NAME: string;
16
12
  /**
17
13
  * The core context.
18
14
  */
@@ -61,12 +57,30 @@ export declare class EngineCore<C extends IEngineCoreConfig = IEngineCoreConfig,
61
57
  */
62
58
  getState(): S;
63
59
  /**
64
- * Get the types for the component.
65
- * @returns The default types.
60
+ * Get all the registered instances.
61
+ * @returns The registered instances.
66
62
  */
67
- getDefaultTypes(): {
68
- [type: string]: string;
63
+ getRegisteredInstances(): {
64
+ [name: string]: {
65
+ type: string;
66
+ features?: string[];
67
+ }[];
69
68
  };
69
+ /**
70
+ * Get the registered instance type for the component/connector.
71
+ * @param componentConnectorType The type of the component/connector.
72
+ * @param features The requested features of the component, if not specified the default entry will be retrieved.
73
+ * @returns The instance type matching the criteria if one is registered.
74
+ * @throws If a matching instance was not found.
75
+ */
76
+ getRegisteredInstanceType(componentConnectorType: string, features?: string[]): string;
77
+ /**
78
+ * Get the registered instance type for the component/connector if it exists.
79
+ * @param componentConnectorType The type of the component/connector.
80
+ * @param features The requested features of the component, if not specified the default entry will be retrieved.
81
+ * @returns The instance type matching the criteria if one is registered.
82
+ */
83
+ getRegisteredInstanceTypeOptional(componentConnectorType: string, features?: string[]): string | undefined;
70
84
  /**
71
85
  * Get the data required to create a clone of the engine.
72
86
  * @returns The clone data.
package/docs/changelog.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @twin.org/engine-core - Changelog
2
2
 
3
+ ## [0.0.2-next.5](https://github.com/twinfoundation/engine/compare/engine-core-v0.0.2-next.4...engine-core-v0.0.2-next.5) (2025-08-14)
4
+
5
+
6
+ ### Features
7
+
8
+ * add synchronised storage support ([5142e34](https://github.com/twinfoundation/engine/commit/5142e3488f09195cf9f48a9c6c6d1014231a4c2c))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/engine-models bumped from 0.0.2-next.4 to 0.0.2-next.5
16
+
17
+ ## [0.0.2-next.4](https://github.com/twinfoundation/engine/compare/engine-core-v0.0.2-next.3...engine-core-v0.0.2-next.4) (2025-07-25)
18
+
19
+
20
+ ### Features
21
+
22
+ * add default logging component for web server ([8ad94f0](https://github.com/twinfoundation/engine/commit/8ad94f0d2d9a5241a8854b1e59fb9a55ce310142))
23
+
24
+
25
+ ### Dependencies
26
+
27
+ * The following workspace dependencies were updated
28
+ * dependencies
29
+ * @twin.org/engine-models bumped from 0.0.2-next.3 to 0.0.2-next.4
30
+
3
31
  ## [0.0.2-next.3](https://github.com/twinfoundation/engine/compare/engine-core-v0.0.2-next.2...engine-core-v0.0.2-next.3) (2025-07-24)
4
32
 
5
33
 
@@ -46,14 +46,6 @@ Name for the engine logger.
46
46
 
47
47
  ***
48
48
 
49
- ### CLASS\_NAME
50
-
51
- > `readonly` **CLASS\_NAME**: `string`
52
-
53
- Runtime name for the class.
54
-
55
- ***
56
-
57
49
  ### \_context
58
50
 
59
51
  > `protected` **\_context**: `IEngineCoreContext`\<`C`, `S`\>
@@ -224,21 +216,89 @@ The state of the engine.
224
216
 
225
217
  ***
226
218
 
227
- ### getDefaultTypes()
219
+ ### getRegisteredInstances()
228
220
 
229
- > **getDefaultTypes**(): `object`
221
+ > **getRegisteredInstances**(): `object`
230
222
 
231
- Get the types for the component.
223
+ Get all the registered instances.
232
224
 
233
225
  #### Returns
234
226
 
235
227
  `object`
236
228
 
237
- The default types.
229
+ The registered instances.
230
+
231
+ #### Implementation of
232
+
233
+ `IEngineCore.getRegisteredInstances`
234
+
235
+ ***
236
+
237
+ ### getRegisteredInstanceType()
238
+
239
+ > **getRegisteredInstanceType**(`componentConnectorType`, `features?`): `string`
240
+
241
+ Get the registered instance type for the component/connector.
242
+
243
+ #### Parameters
244
+
245
+ ##### componentConnectorType
246
+
247
+ `string`
248
+
249
+ The type of the component/connector.
250
+
251
+ ##### features?
252
+
253
+ `string`[]
254
+
255
+ The requested features of the component, if not specified the default entry will be retrieved.
256
+
257
+ #### Returns
258
+
259
+ `string`
260
+
261
+ The instance type matching the criteria if one is registered.
262
+
263
+ #### Throws
264
+
265
+ If a matching instance was not found.
266
+
267
+ #### Implementation of
268
+
269
+ `IEngineCore.getRegisteredInstanceType`
270
+
271
+ ***
272
+
273
+ ### getRegisteredInstanceTypeOptional()
274
+
275
+ > **getRegisteredInstanceTypeOptional**(`componentConnectorType`, `features?`): `undefined` \| `string`
276
+
277
+ Get the registered instance type for the component/connector if it exists.
278
+
279
+ #### Parameters
280
+
281
+ ##### componentConnectorType
282
+
283
+ `string`
284
+
285
+ The type of the component/connector.
286
+
287
+ ##### features?
288
+
289
+ `string`[]
290
+
291
+ The requested features of the component, if not specified the default entry will be retrieved.
292
+
293
+ #### Returns
294
+
295
+ `undefined` \| `string`
296
+
297
+ The instance type matching the criteria if one is registered.
238
298
 
239
299
  #### Implementation of
240
300
 
241
- `IEngineCore.getDefaultTypes`
301
+ `IEngineCore.getRegisteredInstanceTypeOptional`
242
302
 
243
303
  ***
244
304
 
package/locales/en.json CHANGED
@@ -6,7 +6,8 @@
6
6
  "entityStorageCustomMissing": "Entity storage custom \"{typeCustom}\" missing for component \"{storageName}\"",
7
7
  "entityStorageMissing": "Entity storage configuration missing for component \"{storageName}\"",
8
8
  "bootstrapFailed": "Bootstrap failed for component \"{component}\", please check the logs for more information",
9
- "componentStopFailed": "Failed to stop component \"{component}\""
9
+ "componentStopFailed": "Failed to stop component \"{component}\"",
10
+ "instanceTypeNotFound": "Instance type not found for \"{type}\" with features \"{features}\""
10
11
  },
11
12
  "fileStateStorage": {
12
13
  "failedLoading": "Failed to load file state storage from \"{filename}\"",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/engine-core",
3
- "version": "0.0.2-next.3",
3
+ "version": "0.0.2-next.5",
4
4
  "description": "Engine core.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,10 +17,11 @@
17
17
  "@twin.org/core": "next",
18
18
  "@twin.org/crypto": "next",
19
19
  "@twin.org/data-core": "next",
20
- "@twin.org/engine-models": "0.0.2-next.3",
20
+ "@twin.org/engine-models": "0.0.2-next.5",
21
21
  "@twin.org/entity": "next",
22
22
  "@twin.org/logging-connector-console": "next",
23
23
  "@twin.org/logging-models": "next",
24
+ "@twin.org/logging-service": "next",
24
25
  "@twin.org/modules": "next",
25
26
  "@twin.org/nameof": "next"
26
27
  },