@syncbridge/common 0.6.0 → 0.6.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.
@@ -14,7 +14,7 @@ export declare abstract class BaseElement<TEvents extends BaseElement.Events = B
14
14
  get status(): ServiceStatus;
15
15
  get statusMessage(): string;
16
16
  protected _init(): Promise<void>;
17
- protected _start(abortSignal: AbortSignal): Promise<void>;
17
+ protected _start(abortSignal?: AbortSignal): Promise<void>;
18
18
  protected _stop(): Promise<void>;
19
19
  protected onStatusChange(): void;
20
20
  }
@@ -26,6 +26,7 @@ export class BaseElement extends Runnable {
26
26
  async _init() {
27
27
  for (const component of Object.values(this.components)) {
28
28
  component.on('status-change', (status, statusMessage) => {
29
+ component.on('component-status-change', (...args) => this.emitAsyncSafe('component-status-change', ...args));
29
30
  /** Reset counters */
30
31
  this._context.statusIndicators = this._context.statusIndicators || {};
31
32
  Object.keys(ServiceStatus).forEach(key => {
@@ -42,7 +43,8 @@ export class BaseElement extends Runnable {
42
43
  counter.componentNames.push(comp.name);
43
44
  });
44
45
  this.emitSafe('component-status-change', component, status, statusMessage);
45
- if (this._context.status !== this._context.calculatedStatus)
46
+ if (this._context.status !== this._context.calculatedStatus ||
47
+ this._context.statusMessage !== this._context.statusMessage)
46
48
  this.onStatusChange();
47
49
  });
48
50
  }
@@ -60,8 +62,6 @@ export class BaseElement extends Runnable {
60
62
  .map(component => component.stop()));
61
63
  }
62
64
  onStatusChange() {
63
- const oldStatus = this.status;
64
- const oldStatusMessage = this.statusMessage;
65
65
  this._context.calculatedStatus = this._context.status;
66
66
  this._context.calculatedStatusMessage = this._context.statusMessage;
67
67
  if (!(this.status === ServiceStatus.stopped ||
@@ -72,13 +72,10 @@ export class BaseElement extends Runnable {
72
72
  if (indicator?.count) {
73
73
  this._context.calculatedStatus = _status;
74
74
  this._context.calculatedStatusMessage = `${_status} components: ${indicator.componentNames}`;
75
- return;
75
+ break;
76
76
  }
77
77
  }
78
78
  }
79
- if (this.status !== oldStatus || this.statusMessage !== oldStatusMessage) {
80
- this.logger?.debug(this.status);
81
- super.onStatusChange();
82
- }
79
+ super.onStatusChange();
83
80
  }
84
81
  }
@@ -5,6 +5,7 @@ import { ServiceStatus } from '../models/index.js';
5
5
  */
6
6
  export declare abstract class Runnable<T extends Runnable.Events = Runnable.Events> extends AsyncEventEmitter<EventMap<T>> {
7
7
  protected _context: Runnable.Context;
8
+ private _startAbortController?;
8
9
  constructor(init?: Runnable.InitArgs);
9
10
  get status(): ServiceStatus;
10
11
  get statusMessage(): string;
@@ -34,7 +35,7 @@ export declare abstract class Runnable<T extends Runnable.Events = Runnable.Even
34
35
  /**
35
36
  *
36
37
  */
37
- protected abstract _start(abortSignal: AbortSignal): Promise<void>;
38
+ protected abstract _start(abortSignal?: AbortSignal): Promise<void>;
38
39
  /**
39
40
  *
40
41
  */
@@ -8,6 +8,7 @@ import { SbError } from './sb-error.js';
8
8
  */
9
9
  export class Runnable extends AsyncEventEmitter {
10
10
  _context;
11
+ _startAbortController;
11
12
  constructor(init) {
12
13
  super();
13
14
  this.setMaxListeners(1000);
@@ -67,6 +68,9 @@ export class Runnable extends AsyncEventEmitter {
67
68
  if (this.status === ServiceStatus.started)
68
69
  return;
69
70
  this.setStatus(ServiceStatus.starting);
71
+ const abortController = (this._startAbortController =
72
+ new AbortController());
73
+ abortSignal?.addEventListener('abort', () => abortController.abort());
70
74
  this._context.waitTimer = Promise.resolve()
71
75
  .then(async () => {
72
76
  await this.init();
@@ -74,8 +78,6 @@ export class Runnable extends AsyncEventEmitter {
74
78
  let promiseSettled = false;
75
79
  await new Promise((resolve, reject) => {
76
80
  let startWaitTimer;
77
- const abortController = new AbortController();
78
- abortSignal?.addEventListener('abort', () => abortController.abort());
79
81
  if (this._context.startMaxWaitMs) {
80
82
  startWaitTimer = setTimeout(() => {
81
83
  const err = new SbError('Start timeout', {
@@ -124,6 +126,7 @@ export class Runnable extends AsyncEventEmitter {
124
126
  });
125
127
  })
126
128
  .finally(() => {
129
+ this._startAbortController = undefined;
127
130
  this._context.waitTimer = undefined;
128
131
  });
129
132
  await this._context.waitTimer;
@@ -132,12 +135,13 @@ export class Runnable extends AsyncEventEmitter {
132
135
  *
133
136
  */
134
137
  async stop() {
135
- if (this._context.waitTimer)
136
- await this._context.waitTimer;
137
138
  if (this.status === ServiceStatus.stopped)
138
139
  return;
139
140
  this.setStatus(ServiceStatus.stopping);
140
141
  this.emit('stopping');
142
+ this._startAbortController?.abort();
143
+ if (this._context.waitTimer)
144
+ await this._context.waitTimer;
141
145
  this._context.waitTimer = Promise.resolve()
142
146
  .then(async () => {
143
147
  try {
@@ -1,11 +1,17 @@
1
- export declare class SbError extends Error {
1
+ import { ErrorIssue } from '@opra/common';
2
+ export declare class SbError extends Error implements ErrorIssue {
2
3
  readonly cause?: Error;
4
+ severity: ErrorIssue['severity'];
3
5
  code?: string;
4
6
  [index: string]: any;
5
7
  constructor(message?: string | Error, options?: SbErrorOptions);
6
8
  toJSON(): any;
7
9
  }
8
- export interface SbErrorOptions {
10
+ export interface SbErrorOptions extends Partial<ErrorIssue> {
9
11
  cause?: Error;
10
- [key: string]: any;
12
+ }
13
+ /**
14
+ *
15
+ */
16
+ export declare class SbValidationError extends SbError {
11
17
  }
@@ -1,5 +1,6 @@
1
1
  import { isPlainObject } from '@jsopen/objects';
2
2
  export class SbError extends Error {
3
+ severity = 'error';
3
4
  code;
4
5
  constructor(message, options) {
5
6
  super(typeof message === 'object' ? message.message || '' : message);
@@ -46,9 +47,13 @@ export class SbError extends Error {
46
47
  message: this.message,
47
48
  };
48
49
  for (const k of Object.keys(this)) {
49
- if (k !== 'stack')
50
- out[k] = this[k];
50
+ out[k] = this[k];
51
51
  }
52
52
  return out;
53
53
  }
54
54
  }
55
+ /**
56
+ *
57
+ */
58
+ export class SbValidationError extends SbError {
59
+ }
@@ -1,4 +1,5 @@
1
1
  import { ValidationError } from 'valgen';
2
+ import { SbValidationError } from './sb-error.js';
2
3
  export class StackExecutor {
3
4
  stack = [];
4
5
  issues = [];
@@ -30,20 +31,23 @@ export class StackExecutor {
30
31
  const location = this.stack.join('/');
31
32
  if (e instanceof ValidationError) {
32
33
  e.issues.forEach(x => {
33
- this.issues.push({
34
- ...x,
35
- severity: 'error',
36
- location: location + (x.location ? '/' + x.location : ''),
37
- });
34
+ const issue = new SbValidationError(x.message, x);
35
+ issue.severity = x.severity ?? 'error';
36
+ issue.location = location + (x.location ? '/' + x.location : '');
37
+ issue.stack = x.stack;
38
+ this.issues.push(issue);
38
39
  });
39
40
  }
40
41
  else {
41
- this.issues.push({
42
+ const issue = {
42
43
  message: e.message,
43
- ...e,
44
- severity: 'error',
44
+ severity: e.severity || 'error',
45
45
  location,
46
- });
46
+ stack: e.stack,
47
+ ...e,
48
+ };
49
+ Object.setPrototypeOf(issue, Object.getPrototypeOf(e));
50
+ this.issues.push(issue);
47
51
  }
48
52
  }
49
53
  }
package/constants.js CHANGED
@@ -1,4 +1,4 @@
1
- export const version = '0.6.0';
1
+ export const version = '0.6.2';
2
2
  export const OWN_ELEMENT_METADATA = Symbol.for('OWN_ELEMENT_METADATA');
3
3
  export const COMPONENT_OPTIONS = Symbol.for('COMPONENT_OPTIONS');
4
4
  export const PROCESSOR_OPTIONS = Symbol.for('PROCESSOR_OPTIONS');
package/index.d.ts CHANGED
@@ -16,5 +16,5 @@ export * from './models-document.js';
16
16
  export * from './processor-factory.js';
17
17
  export * from './registry/extension-package.js';
18
18
  export * from './registry/extension-registry.js';
19
+ export * from './utils/decode-profile.js';
19
20
  export * from './utils/materialize-metadata.js';
20
- export * from './utils/profile-utils.js';
package/index.js CHANGED
@@ -16,5 +16,5 @@ export * from './models-document.js';
16
16
  export * from './processor-factory.js';
17
17
  export * from './registry/extension-package.js';
18
18
  export * from './registry/extension-registry.js';
19
+ export * from './utils/decode-profile.js';
19
20
  export * from './utils/materialize-metadata.js';
20
- export * from './utils/profile-utils.js';
@@ -1,4 +1,5 @@
1
1
  import { ApiDocumentFactory } from '@opra/common';
2
+ import { SbError } from './classes/sb-error.js';
2
3
  import * as models from './models/index.js';
3
4
  let doc;
4
5
  export async function initializeModelsDocument() {
@@ -11,6 +12,8 @@ export async function initializeModelsDocument() {
11
12
  }
12
13
  export function getModelsDocument() {
13
14
  if (!doc)
14
- throw new Error('You must call initializeModelsDocument() first');
15
+ throw new SbError('You must call initializeModelsDocument() first', {
16
+ severity: 'fatal',
17
+ });
15
18
  return doc;
16
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncbridge/common",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "SyncBridge Common utilities",
5
5
  "author": "Panates Inc",
6
6
  "license": "MIT",
@@ -1,4 +1,5 @@
1
1
  import equal from 'fast-deep-equal';
2
+ import { SbError } from './classes/sb-error.js';
2
3
  import { StackExecutor } from './classes/stack-executor.js';
3
4
  import { initializeModelsDocument } from './models-document.js';
4
5
  import { ExtensionRegistry } from './registry/extension-registry.js';
@@ -94,9 +95,9 @@ export var ProcessorFactory;
94
95
  if (processor.stopped &&
95
96
  oldMetadata?.className &&
96
97
  oldMetadata?.className !== newMetadata.className)
97
- throw new Error('Can not change component class while processor running. You should stop it first.');
98
+ throw new SbError('Cannot change component class while processor running. You should stop it first.');
98
99
  /** Validate sub components */
99
- if (childMetadata.components) {
100
+ if (childMetadata.components && componentInstance) {
100
101
  _validateComponents({
101
102
  ...ctx,
102
103
  owner: componentInstance,
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import process from 'node:process';
5
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
6
6
  import { updateErrorMessage } from '@jsopen/objects';
7
+ import { SbError } from '../classes/sb-error.js';
7
8
  import { COMPONENT_OPTIONS, PROCESSOR_OPTIONS } from '../constants.js';
8
9
  import { isComponentBase, isProcessorBase } from '../utils/type-guartds.js';
9
10
  import { normalizeComponentMetadata, normalizeProcessorMetadata, } from './helpers.js';
@@ -26,10 +27,10 @@ export class ExtensionPackage {
26
27
  static async fromDirectory(directory) {
27
28
  let filename = path.join(directory, 'package.json');
28
29
  if (!fs.existsSync(filename))
29
- throw new TypeError(`Directory "${directory}" does not contain package.json`);
30
+ throw new SbError(`Directory "${directory}" does not contain package.json`);
30
31
  const pkgJson = JSON.parse(fs.readFileSync(filename, 'utf8'));
31
32
  if (!pkgJson.syncbridge)
32
- throw new TypeError(`"${pkgJson.name}" is not a SyncBridge extension package`);
33
+ throw new SbError(`"${pkgJson.name}" is not a SyncBridge extension package`);
33
34
  const out = new ExtensionPackage();
34
35
  out.name = pkgJson.name;
35
36
  out.version = pkgJson.version;
@@ -100,7 +101,7 @@ export class ExtensionPackage {
100
101
  let entryPoint = (resolver || import.meta.resolve)(specifier);
101
102
  const pkgJson = locatePkgJson(path.dirname(entryPoint));
102
103
  if (!pkgJson) {
103
- throw new TypeError(`Can't locate package.json file for "${specifier}"`);
104
+ throw new SbError(`Can't locate package.json file for "${specifier}"`);
104
105
  }
105
106
  if (path.isAbsolute(entryPoint) && process.platform === 'win32') {
106
107
  entryPoint = pathToFileURL(entryPoint).href;
@@ -178,11 +179,11 @@ export class ComponentExtension {
178
179
  const module = await import(this.pkg.entryPoint);
179
180
  const ctor = module[this.exportName];
180
181
  if (!ctor)
181
- throw new TypeError(`Exported component "${this.exportName}" not found in package "${this.pkg.name}"`);
182
+ throw new SbError(`Exported component "${this.exportName}" not found in package "${this.pkg.name}"`);
182
183
  if (typeof ctor !== 'function')
183
- throw new TypeError(`Exported component "${this.exportName}" is not a class`);
184
+ throw new SbError(`Exported component "${this.exportName}" is not a class`);
184
185
  if (!isComponentBase(ctor.prototype))
185
- throw new TypeError(`Exported component "${this.exportName}" does not extends ComponentBase`);
186
+ throw new SbError(`Exported component "${this.exportName}" does not extends ComponentBase`);
186
187
  this._ctor = ctor;
187
188
  return this._ctor;
188
189
  }
@@ -216,11 +217,11 @@ export class ProcessorExtension {
216
217
  const module = await import(this.pkg.entryPoint);
217
218
  const ctor = module[this.exportName];
218
219
  if (!ctor)
219
- throw new TypeError(`Exported processor "${this.exportName}" not found in package "${this.pkg.name}"`);
220
+ throw new SbError(`Exported processor "${this.exportName}" not found in package "${this.pkg.name}"`);
220
221
  if (typeof ctor !== 'function')
221
- throw new TypeError(`Exported processor "${this.exportName}" is not a class`);
222
+ throw new SbError(`Exported processor "${this.exportName}" is not a class`);
222
223
  if (!isProcessorBase(ctor.prototype))
223
- throw new TypeError(`Exported processor "${this.exportName}" does not extends ProcessorBase`);
224
+ throw new SbError(`Exported processor "${this.exportName}" does not extends ProcessorBase`);
224
225
  this._ctor = ctor;
225
226
  return this._ctor;
226
227
  }
@@ -1,4 +1,5 @@
1
1
  import semver from 'semver';
2
+ import { SbError } from '../classes/sb-error.js';
2
3
  import { COMPONENT_OPTIONS, PROCESSOR_OPTIONS } from '../constants.js';
3
4
  import { ComponentExtension, ExtensionPackage, ProcessorExtension, } from './extension-package.js';
4
5
  import { normalizeComponentMetadata, normalizeProcessorMetadata, } from './helpers.js';
@@ -24,7 +25,7 @@ export var ExtensionRegistry;
24
25
  const cmp = findComponent(className, version);
25
26
  if (cmp)
26
27
  return cmp;
27
- throw new Error(`Component "${className}" version "${version}" not found in registry`);
28
+ throw new SbError(`Component "${className}" version "${version}" not found in registry`);
28
29
  }
29
30
  ExtensionRegistry.getComponent = getComponent;
30
31
  /**
@@ -42,7 +43,7 @@ export var ExtensionRegistry;
42
43
  function getProcessor(className, version = '*') {
43
44
  const cmp = findProcessor(className, version);
44
45
  if (!cmp) {
45
- throw new Error(`Processor "${className}" version "${version}" not found in registry`);
46
+ throw new SbError(`Processor "${className}" version "${version}" not found in registry`);
46
47
  }
47
48
  return cmp;
48
49
  }
@@ -75,7 +76,7 @@ export var ExtensionRegistry;
75
76
  async function registerComponent(ctor) {
76
77
  const _metadata = Reflect.getMetadata(COMPONENT_OPTIONS, ctor);
77
78
  if (!_metadata)
78
- throw new TypeError(`Class "${ctor.name}" has no component metadata.`);
79
+ throw new SbError(`Class "${ctor.name}" has no component metadata.`);
79
80
  if (_metadata.abstract)
80
81
  return;
81
82
  const metadata = await normalizeComponentMetadata(_metadata);
@@ -100,7 +101,7 @@ export var ExtensionRegistry;
100
101
  async function registerProcessor(ctor) {
101
102
  const _metadata = Reflect.getMetadata(PROCESSOR_OPTIONS, ctor);
102
103
  if (!_metadata)
103
- throw new TypeError(`Class "${ctor.name}" has no processor metadata.`);
104
+ throw new SbError(`Class "${ctor.name}" has no processor metadata.`);
104
105
  if (_metadata.abstract)
105
106
  return;
106
107
  const metadata = await normalizeProcessorMetadata(_metadata);
@@ -1,5 +1,5 @@
1
1
  import { isAny, isBase64, isBoolean, isDateString, isEmail, isHex, isNumber, isString, isTime, isURL, validator, vg, } from 'valgen';
2
- import { SbError } from '../classes/sb-error.js';
2
+ import { SbValidationError } from '../classes/sb-error.js';
3
3
  import { StackExecutor } from '../classes/stack-executor.js';
4
4
  import { Profile, VariableType, } from '../models/index.js';
5
5
  import { getModelsDocument } from '../models-document.js';
@@ -14,7 +14,11 @@ export function decodeProfile(materializedMetadata, rawProfile, options) {
14
14
  issues
15
15
  .map(issue => `- ${issue.message}. Location: /${issue.location}`)
16
16
  .join('\n ');
17
- throw new SbError(msg, {
17
+ issues.forEach(issue => {
18
+ if (issue instanceof SbValidationError)
19
+ delete issue.stack;
20
+ });
21
+ throw new SbValidationError(msg, {
18
22
  workerId: rawProfile.id,
19
23
  issues,
20
24
  });
@@ -75,11 +79,7 @@ function _codecProcessElement(stackExecutor, codec, metadata, profile, options)
75
79
  for (const [k, defComponent] of Object.entries(metadata.components)) {
76
80
  if (!profile.components[k]) {
77
81
  if (defComponent.required) {
78
- stackExecutor.issues.push({
79
- message: `Component "${k}" is required`,
80
- severity: 'error',
81
- location: `/components/${k}`,
82
- });
82
+ stackExecutor.issues.push(new SbValidationError(`Component "${k}" is required`));
83
83
  }
84
84
  continue;
85
85
  }
@@ -1,5 +1,5 @@
1
1
  import { clone, merge, omitUndefined } from '@jsopen/objects';
2
- import { SbError } from '../classes/sb-error.js';
2
+ import { SbValidationError } from '../classes/sb-error.js';
3
3
  import { StackExecutor } from '../classes/stack-executor.js';
4
4
  import { VariableType, } from '../models/index.js';
5
5
  import { ExtensionRegistry } from '../registry/extension-registry.js';
@@ -13,7 +13,11 @@ export function materializeMetadata(profile) {
13
13
  issues
14
14
  .map(issue => `- ${issue.message}. Location: /${issue.location}`)
15
15
  .join('\n ');
16
- throw new SbError(msg, {
16
+ issues.forEach(issue => {
17
+ if (issue instanceof SbValidationError)
18
+ delete issue.stack;
19
+ });
20
+ throw new SbValidationError(msg, {
17
21
  workerId: profile.id,
18
22
  issues,
19
23
  });
@@ -67,12 +71,12 @@ function _materializeComponents(stackExecutor, metadata, profile) {
67
71
  const profileComponent = profile?.components?.[componentName];
68
72
  if (!profileComponent) {
69
73
  if (metadataComponent.required)
70
- throw new Error(`Component "${componentName}" should be configured`);
74
+ throw new SbValidationError(`Component "${componentName}" should be configured`);
71
75
  return;
72
76
  }
73
77
  /** istanbul ignore next */
74
78
  if (!profileComponent.className)
75
- throw new Error(`"className" property required`);
79
+ throw new SbValidationError(`"className" property required`);
76
80
  const reg = ExtensionRegistry.getComponent(profileComponent.className);
77
81
  materializedComponent.className = profileComponent.className;
78
82
  materializedComponent.variables = merge({}, [reg.metadata.variables || {}, metadataComponent.variables || {}], { deep: 'full' });
@@ -80,14 +84,14 @@ function _materializeComponents(stackExecutor, metadata, profile) {
80
84
  if (metadataComponent.className) {
81
85
  if (materializedComponent.className &&
82
86
  materializedComponent.className !== metadataComponent.className)
83
- throw new Error(`Selected component class "${profileComponent.className}" do not match with definition component class (${metadataComponent.className}).`);
87
+ throw new SbValidationError(`Selected component class "${profileComponent.className}" do not match with definition component class (${metadataComponent.className}).`);
84
88
  }
85
89
  else {
86
90
  const requiredInterfaces = metadataComponent.interfaces || [];
87
91
  const implementations = reg.metadata.interfaces || [];
88
92
  const filtered = requiredInterfaces.filter(x => !implementations.includes(x));
89
93
  if (filtered.length) {
90
- throw new Error(`Selected component "${profileComponent.className}" do not implements some of required interfaces (${filtered.join(',')}).`);
94
+ throw new SbValidationError(`Selected component "${profileComponent.className}" do not implements some of required interfaces (${filtered.join(',')}).`);
91
95
  }
92
96
  }
93
97
  if (materializedComponent.variables) {