vrack2-core 1.0.4 → 1.1.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.
@@ -22,15 +22,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
- return new (P || (P = Promise))(function (resolve, reject) {
28
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
- step((generator = generator.apply(thisArg, _arguments || [])).next());
32
- });
33
- };
34
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
35
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
27
  };
@@ -124,35 +115,33 @@ class DeviceManager extends BootClass_1.default {
124
115
  *
125
116
  * @see IDeivceInfo
126
117
  */
127
- getDeviceInfo(vendor, device) {
128
- return __awaiter(this, void 0, void 0, function* () {
129
- const di = [vendor, device].join('.');
130
- const DeviceClass = yield this.get(di);
131
- const dev = new DeviceClass('1', di, this);
132
- const result = { actions: {}, metrics: {}, inputs: {}, outputs: {}, options: {}, description: '' };
133
- try {
134
- const preOptions = dev.checkOptions();
135
- for (const oName in preOptions)
136
- result.options[oName] = preOptions[oName].export();
137
- const dInputs = dev.inputs();
138
- for (const iName in dInputs)
139
- result.inputs[iName] = dInputs[iName].export();
140
- const dOutputs = dev.outputs();
141
- for (const oName in dOutputs)
142
- result.outputs[oName] = dOutputs[oName].export();
143
- const dActions = dev.actions();
144
- for (const aName in dActions)
145
- result.actions[aName] = dActions[aName].export();
146
- const dMetrics = dev.metrics();
147
- for (const mName in dMetrics)
148
- result.metrics[mName] = dMetrics[mName].export();
149
- result.description = dev.description();
150
- }
151
- catch (error) {
152
- throw ErrorManager_1.default.make('DM_GET_INFO_EXCEPTION').setTrace(new Error).add(error);
153
- }
154
- return result;
155
- });
118
+ async getDeviceInfo(vendor, device) {
119
+ const di = [vendor, device].join('.');
120
+ const DeviceClass = await this.get(di);
121
+ const dev = new DeviceClass('1', di, this);
122
+ const result = { actions: {}, metrics: {}, inputs: {}, outputs: {}, options: {}, description: '' };
123
+ try {
124
+ const preOptions = dev.checkOptions();
125
+ for (const oName in preOptions)
126
+ result.options[oName] = preOptions[oName].export();
127
+ const dInputs = dev.inputs();
128
+ for (const iName in dInputs)
129
+ result.inputs[iName] = dInputs[iName].export();
130
+ const dOutputs = dev.outputs();
131
+ for (const oName in dOutputs)
132
+ result.outputs[oName] = dOutputs[oName].export();
133
+ const dActions = dev.actions();
134
+ for (const aName in dActions)
135
+ result.actions[aName] = dActions[aName].export();
136
+ const dMetrics = dev.metrics();
137
+ for (const mName in dMetrics)
138
+ result.metrics[mName] = dMetrics[mName].export();
139
+ result.description = dev.description();
140
+ }
141
+ catch (error) {
142
+ throw ErrorManager_1.default.make('DM_GET_INFO_EXCEPTION').setTrace(new Error).add(error);
143
+ }
144
+ return result;
156
145
  }
157
146
  /**
158
147
  * Method for updating the device list
@@ -226,23 +215,21 @@ class DeviceManager extends BootClass_1.default {
226
215
  * example "vrack.System" where "vrack" is vendor and "System" is device class
227
216
  * @param {string} device Device path string
228
217
  */
229
- get(device, updateList = true) {
230
- return __awaiter(this, void 0, void 0, function* () {
231
- const p = this.devices.get(device); // return device path or undefined
232
- if (typeof p === 'string') {
233
- const deviceClass = yield Promise.resolve(`${p}`).then(s => __importStar(require(s))); // try import
234
- if (deviceClass.default)
235
- return deviceClass.default;
236
- }
237
- // Устройство не найдено, но не исключено что если перестроить дерево
238
- // вендоров и устройств все будет так же
239
- // По умолчанию мы попытаемся обновить дерево
240
- if (updateList) {
241
- yield this.updateDeviceList();
242
- return yield this.get(device, false);
243
- }
244
- throw ErrorManager_1.default.make('DM_DEVICE_NOT_FOUND', { device });
245
- });
218
+ async get(device, updateList = true) {
219
+ const p = this.devices.get(device); // return device path or undefined
220
+ if (typeof p === 'string') {
221
+ const deviceClass = await Promise.resolve(`${p}`).then(s => __importStar(require(s))); // try import
222
+ if (deviceClass.default)
223
+ return deviceClass.default;
224
+ }
225
+ // Устройство не найдено, но не исключено что если перестроить дерево
226
+ // вендоров и устройств все будет так же
227
+ // По умолчанию мы попытаемся обновить дерево
228
+ if (updateList) {
229
+ await this.updateDeviceList();
230
+ return await this.get(device, false);
231
+ }
232
+ throw ErrorManager_1.default.make('DM_DEVICE_NOT_FOUND', { device });
246
233
  }
247
234
  /**
248
235
  * Find vendor by name
@@ -3,15 +3,6 @@
3
3
  * Copyright © 2024 Boris Bobylev. All rights reserved.
4
4
  * Licensed under the Apache License, Version 2.0
5
5
  */
6
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
7
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
8
- return new (P || (P = Promise))(function (resolve, reject) {
9
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
11
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
12
- step((generator = generator.apply(thisArg, _arguments || [])).next());
13
- });
14
- };
15
6
  var __importDefault = (this && this.__importDefault) || function (mod) {
16
7
  return (mod && mod.__esModule) ? mod : { "default": mod };
17
8
  };
@@ -59,33 +50,29 @@ class StructureStorage extends BootClass_1.default {
59
50
  *
60
51
  * @see StructureStorage.process
61
52
  */
62
- beforeLoadedUpdate() {
63
- return __awaiter(this, void 0, void 0, function* () {
64
- const fp = this.makeFilePath(this.Container.id);
65
- let structure = {};
66
- try {
67
- if ((0, fs_1.existsSync)(fp))
68
- structure = ImportManager_1.default.importJSON(fp);
69
- const cStruct = yield this.Container.getStructure();
70
- this.updateStructure(cStruct, structure, this.Container.id);
71
- }
72
- catch (error) {
73
- this.error(error);
74
- }
75
- });
53
+ async beforeLoadedUpdate() {
54
+ const fp = this.makeFilePath(this.Container.id);
55
+ let structure = {};
56
+ try {
57
+ if ((0, fs_1.existsSync)(fp))
58
+ structure = ImportManager_1.default.importJSON(fp);
59
+ const cStruct = await this.Container.getStructure();
60
+ this.updateStructure(cStruct, structure, this.Container.id);
61
+ }
62
+ catch (error) {
63
+ this.error(error);
64
+ }
76
65
  }
77
66
  /**
78
67
  * Returns structure by container identifier
79
68
  *
80
69
  * @param id Container ID
81
70
  */
82
- getById(id) {
83
- return __awaiter(this, void 0, void 0, function* () {
84
- const fp = this.makeFilePath(id);
85
- if (!(0, fs_1.existsSync)(fp))
86
- throw ErrorManager_1.default.make('SS_STRUCT_NOT_FOUND', { id });
87
- return ImportManager_1.default.importJSON(fp);
88
- });
71
+ async getById(id) {
72
+ const fp = this.makeFilePath(id);
73
+ if (!(0, fs_1.existsSync)(fp))
74
+ throw ErrorManager_1.default.make('SS_STRUCT_NOT_FOUND', { id });
75
+ return ImportManager_1.default.importJSON(fp);
89
76
  }
90
77
  /**
91
78
  * Updating the container structure
@@ -93,11 +80,9 @@ class StructureStorage extends BootClass_1.default {
93
80
  * @param id Container ID
94
81
  * @param structure updated container structure object
95
82
  */
96
- updateById(id, structure) {
97
- return __awaiter(this, void 0, void 0, function* () {
98
- const cStruct = yield this.getById(id);
99
- this.updateStructure(cStruct, structure, id);
100
- });
83
+ async updateById(id, structure) {
84
+ const cStruct = await this.getById(id);
85
+ this.updateStructure(cStruct, structure, id);
101
86
  }
102
87
  /**
103
88
  * Updates the display structure parameter from the file structure
@@ -33,7 +33,7 @@ class ErrorManager {
33
33
  const reg2 = this.getRegistered(short);
34
34
  if (reg1 !== null || reg2 !== null) {
35
35
  // Если уже есть идентичная запись - просто игнорим
36
- if ((reg1 === null || reg1 === void 0 ? void 0 : reg1.code) === (reg2 === null || reg2 === void 0 ? void 0 : reg2.code) && (reg1 === null || reg1 === void 0 ? void 0 : reg1.short) === (reg2 === null || reg2 === void 0 ? void 0 : reg2.short)) {
36
+ if (reg1?.code === reg2?.code && reg1?.short === reg2?.short) {
37
37
  return;
38
38
  }
39
39
  else {
@@ -40,6 +40,12 @@ export default class Device {
40
40
  * @example 'vrack.KeyManager'
41
41
  */
42
42
  type: string;
43
+ /**
44
+ * Флаг общей работы
45
+ * Если флаг === false = все порты перестают принимать или отправлять данные/события
46
+ *
47
+ */
48
+ works: boolean;
43
49
  /**
44
50
  * Allows access to port management.
45
51
  */
@@ -136,6 +142,11 @@ export default class Device {
136
142
  * @param data data for action
137
143
  */
138
144
  beforeAction(action: string, data: any): boolean;
145
+ /**
146
+ * Должен вызываться перед завершением сервиса
147
+ * Но может не вызываться (зависит от реализации)
148
+ */
149
+ beforeTerminate(): void;
139
150
  /**
140
151
  * Prepare options
141
152
  *
@@ -3,15 +3,6 @@
3
3
  * Copyright © 2022 Boris Bobylev. All rights reserved.
4
4
  * Licensed under the Apache License, Version 2.0
5
5
  */
6
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
7
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
8
- return new (P || (P = Promise))(function (resolve, reject) {
9
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
11
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
12
- step((generator = generator.apply(thisArg, _arguments || [])).next());
13
- });
14
- };
15
6
  var __importDefault = (this && this.__importDefault) || function (mod) {
16
7
  return (mod && mod.__esModule) ? mod : { "default": mod };
17
8
  };
@@ -58,6 +49,7 @@ class Device {
58
49
  this.id = id;
59
50
  this.type = type;
60
51
  this.Container = Container;
52
+ this.works = true;
61
53
  this.ports = {
62
54
  input: {},
63
55
  output: {}
@@ -118,6 +110,11 @@ class Device {
118
110
  * @param data data for action
119
111
  */
120
112
  beforeAction(action, data) { return true; }
113
+ /**
114
+ * Должен вызываться перед завершением сервиса
115
+ * Но может не вызываться (зависит от реализации)
116
+ */
117
+ beforeTerminate() { return; }
121
118
  /**
122
119
  * Prepare options
123
120
  *
@@ -189,9 +186,7 @@ class Device {
189
186
  * Used when there is a need to execute before starting the rack
190
187
  * and wait for asynchronous code to execute (initialization of some file databases, etc.)
191
188
  */
192
- processPromise() {
193
- return __awaiter(this, void 0, void 0, function* () { return; });
194
- }
189
+ async processPromise() { return; }
195
190
  /**
196
191
  * Myby todo?
197
192
  *
@@ -268,7 +263,8 @@ class Device {
268
263
  */
269
264
  addInputHandler(name, action) {
270
265
  name = ImportManager_1.default.camelize('input.' + name);
271
- this[name] = action;
266
+ const a = this;
267
+ a[name] = action;
272
268
  }
273
269
  /**
274
270
  * Adding a handle for the action
@@ -278,7 +274,8 @@ class Device {
278
274
  */
279
275
  addActionHandler(name, action) {
280
276
  name = ImportManager_1.default.camelize('action.' + name);
281
- this[name] = action;
277
+ const a = this;
278
+ a[name] = action;
282
279
  }
283
280
  /**
284
281
  * Informs the rack that the unit cannot continue to operate.
@@ -18,6 +18,7 @@ export default class DevicePort {
18
18
  required: boolean;
19
19
  /** Ссылка на устройсто владельца */
20
20
  Device: Device;
21
+ bind: ((data: any) => {}) | null;
21
22
  /**
22
23
  * Список слушателей порта
23
24
  * Используется для захвата порта. Если какие либо данные будут проброшены
@@ -18,6 +18,7 @@ class DevicePort {
18
18
  this.connected = false;
19
19
  /** Port connection list. One port can have multiple connections */
20
20
  this.connections = [];
21
+ this.bind = null;
21
22
  /**
22
23
  * Список слушателей порта
23
24
  * Используется для захвата порта. Если какие либо данные будут проброшены
@@ -40,6 +41,10 @@ class DevicePort {
40
41
  * Calling the incoming port when calling a connection
41
42
  */
42
43
  push(data) {
44
+ if (!this.Device.works)
45
+ return;
46
+ if (this.bind !== null)
47
+ return this.bind(data);
43
48
  // Если у нас есть слушатели порта
44
49
  // Передаем им данные и пересоздаем Map
45
50
  if (this.listens.size) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vrack2-core",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "Фреймворк для создания событийно-ориентированных сервисов на JavaScript/TypeScript",
5
5
  "main": "./lib/index",
6
6
  "scripts": {},
package/src/Container.ts CHANGED
@@ -107,6 +107,9 @@ ErrorManager.register('Container', 'XR1K10R0OOUC', 'CTR_INCOMPATIBLE_PORTS', 'In
107
107
  ErrorManager.register('Container', 'MmVoDOQwaYkx', 'CTR_INCORRECT_BOOSTRAP', 'The required DeviceManager class is not specified correctly', {})
108
108
  ErrorManager.register('Container', 'e090R0MLyb7y', 'CTR_CONF_EXTENDS_PROBLEM', 'Problem with extending service configuration.', {})
109
109
 
110
+ ErrorManager.register('Container', 'LYC0VA1AWYKU', 'CTR_IGNORE_SERVICE_AUTORELOAD', 'Error that ignores service restart flag', {})
111
+
112
+
110
113
 
111
114
  /**
112
115
  * Contains the structure of the service
@@ -474,7 +477,9 @@ export default class Container extends EventEmitter {
474
477
  this.structure[dconf.id].ports.push(
475
478
  Object.assign({ port: subkey, direct: 'input' }, pList[subkey])
476
479
  )
477
- if (handler in dev) ndp.push = dev[handler].bind(dev)
480
+
481
+ // биндимся а не заменяем push для контроля внутри push
482
+ if (handler in dev) ndp.bind = dev[handler].bind(dev)
478
483
  }
479
484
  }
480
485
 
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Простой реактивный ref-аналог (как в Vue 3), но только для объектов.
3
+ * Поддерживает глубокую реактивность вложенных plain-объектов.
4
+ * Массивы НЕ отслеживаются внутри — только при переприсвоении свойства целиком.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const state = new ReactiveRef({ user: { name: 'Alice' }, items: [1, 2] });
9
+ *
10
+ * state.watch(() => console.log('изменилось!'));
11
+ *
12
+ * state.value.user.name = 'Bob'; // вызовет callback
13
+ * state.value.items = [1, 2, 3]; // вызовет callback
14
+ * state.value.items.push(4); // НЕ вызовет (мутация массива)
15
+ * ```
16
+ */
17
+ export default class ReactiveRef<T extends object> {
18
+ /**
19
+ * Хранимое значение
20
+ */
21
+ private _value: T;
22
+
23
+ /**
24
+ * Калбек для обработки при измении значения
25
+ */
26
+ private watcher: () => void = () => {};
27
+
28
+ /**
29
+ * При создании иницируем объектом
30
+ */
31
+ constructor(initialValue: T) {
32
+ this._value = this.makeReactive(initialValue);
33
+ }
34
+
35
+ /**
36
+ * Getter value
37
+ */
38
+ get value(): T {
39
+ return this._value;
40
+ }
41
+
42
+ /**
43
+ * Назначает обработчик который будет вызван при изменении объекта
44
+ */
45
+ watch(callback: () => void) {
46
+ this.watcher = callback;
47
+ }
48
+
49
+ /**
50
+ * Возвращает true если это простой объект не являющийся массивом или null
51
+ * Null кстати тоже объект внутри JS из-за чего эта проверка очень актуальна
52
+ */
53
+ private isPlainObject(val: unknown): val is Record<string, unknown> {
54
+ return val !== null && typeof val === 'object' && !Array.isArray(val);
55
+ }
56
+
57
+ /**
58
+ * Делает переданный объект (если он объект) реактивным
59
+ * Причем делает свойства объекта тоже реактивными рекурсивно
60
+ */
61
+ private makeReactive<TObj extends object>(obj: TObj): TObj {
62
+ if ((obj as any).__isReactive) return obj;
63
+
64
+ const handler: ProxyHandler<TObj> = {
65
+ set: (target, key, value) => {
66
+ const oldValue = target[key as keyof TObj];
67
+ const isOwn = Object.prototype.hasOwnProperty.call(target, key);
68
+
69
+ // Рекурсивно реактивизируем только обычные объекты (не массивы!)
70
+ const nextValue = this.isPlainObject(value) ? this.makeReactive(value) : value;
71
+ target[key as keyof TObj] = nextValue;
72
+ // Уведомляем только при изменении значения
73
+ if (!isOwn || oldValue !== nextValue) this.watcher();
74
+ return true;
75
+ },
76
+ };
77
+
78
+ const proxy = new Proxy(obj, handler);
79
+ (proxy as any).__isReactive = true;
80
+ return proxy;
81
+ }
82
+
83
+ }
@@ -0,0 +1,127 @@
1
+ // UniversalWorker.ts
2
+ import { ChildProcess, fork } from 'child_process';
3
+ import { Worker as WorkerThread, parentPort, workerData, isMainThread } from 'worker_threads';
4
+
5
+
6
+ type UniversalWorkerOptions = {
7
+ isolated?: boolean;
8
+ scriptPath: string;
9
+ workerData?: any;
10
+ };
11
+
12
+ export default class UniversalWorker {
13
+
14
+ private impl: {
15
+ send: (msg: any) => void;
16
+ on: (event: 'message' | 'exit' | 'error', handler: (...args: any[]) => void) => void;
17
+ kill: () => void;
18
+ };
19
+
20
+ /**
21
+ * Создает новые воркер
22
+ *
23
+ * Если параметр isolated = true будет создан форк
24
+ * иначе будет использоваться WorkerThread
25
+ */
26
+ constructor(options: UniversalWorkerOptions) {
27
+ const { isolated = true, scriptPath, workerData } = options;
28
+
29
+ if (isolated) {
30
+ // child_process
31
+ const proc = fork(scriptPath, [], {
32
+ env: { ...process.env, VRACK2_WORKER_DATA: JSON.stringify(workerData) }
33
+ });
34
+ this.impl = {
35
+ send: (msg) => proc.send(msg),
36
+ on: (ev, handler) => proc.on(ev, handler),
37
+ kill: () => proc.kill()
38
+ };
39
+ } else {
40
+ // worker_threads
41
+ const wt = new WorkerThread(scriptPath, { workerData });
42
+ this.impl = {
43
+ send: (msg) => wt.postMessage(msg),
44
+ on: (ev, handler) => {
45
+ if (ev === 'message') wt.on('message', handler);
46
+ else if (ev === 'error') wt.on('error', handler);
47
+ else if (ev === 'exit') wt.on('exit', handler as (code: number) => void);
48
+ },
49
+ kill: () => wt.terminate()
50
+ };
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Отправляет внутрь воркера обхект способный к сериализации
56
+ */
57
+ send(msg: any): void {
58
+ this.impl.send(msg);
59
+ }
60
+
61
+ /**
62
+ * Подписывает на события воркера
63
+ *
64
+ * Доступно только message exit error
65
+ */
66
+ on(event: 'message' | 'exit' | 'error', handler: (...args: any[]) => void): void {
67
+ this.impl.on(event, handler);
68
+ }
69
+
70
+ /**
71
+ * Убивает процесс
72
+ */
73
+ kill() {
74
+ this.impl.kill();
75
+ }
76
+
77
+ /**
78
+ * @see kill
79
+ */
80
+ terminate() {
81
+ this.impl.kill();
82
+ }
83
+ }
84
+
85
+
86
+ export type WorkerMessageHandler = (message: any) => void;
87
+
88
+ /**
89
+ * Определяет - является ли процесс форкнутым
90
+ * Основанно на проверке метода process.send которая есть только у фокрнутого процесса
91
+ */
92
+ export const isForked = typeof process.send === 'function';
93
+
94
+ /**
95
+ * Определяет - является ли данный инстанс дочерним - не важно используя worker_threads или fork
96
+ * Проверяет !isMainThread || isForked
97
+ */
98
+ export const isChild = !isMainThread || isForked;
99
+
100
+ /**
101
+ * Получение данных сверху, которые были переданны при создании воркера
102
+ * В случае использования worker_threads - workerData
103
+ * Если же это форк - парсит устаноленнюу переменную окружения VRACK2_WORKER_DATA
104
+ */
105
+ export function getWorkerData(): any {
106
+ if (!isMainThread) return workerData;
107
+ if (isForked) return JSON.parse(process.env.VRACK2_WORKER_DATA || '{}');
108
+ throw new Error('getWorkerData() called in main process');
109
+ }
110
+
111
+ /**
112
+ * Отправка данных наверх в родительский процесс
113
+ */
114
+ export function sendMessage(message: any): void {
115
+ if (!isMainThread) return parentPort?.postMessage(message);
116
+ if (isForked) { process.send?.(message); return }
117
+ throw new Error('sendMessage() called in main process');
118
+ }
119
+
120
+ /**
121
+ * Позволяет подписаться на данные которые приходят сверху
122
+ */
123
+ export function onMessage(handler: WorkerMessageHandler): void {
124
+ if (!isMainThread) parentPort?.on('message', handler);
125
+ else if (isForked) process.on('message', handler);
126
+ else throw new Error('onMessage() called in main process');
127
+ }
@@ -54,6 +54,13 @@ export default class Device {
54
54
  */
55
55
  type: string;
56
56
 
57
+ /**
58
+ * Флаг общей работы
59
+ * Если флаг === false = все порты перестают принимать или отправлять данные/события
60
+ *
61
+ */
62
+ works: boolean
63
+
57
64
  /**
58
65
  * Allows access to port management.
59
66
  */
@@ -83,6 +90,7 @@ export default class Device {
83
90
  this.id = id
84
91
  this.type = type
85
92
  this.Container = Container
93
+ this.works = true
86
94
  this.ports = {
87
95
  input: {},
88
96
  output: {}
@@ -165,6 +173,12 @@ export default class Device {
165
173
  * @param data data for action
166
174
  */
167
175
  beforeAction(action: string, data: any) { return true }
176
+
177
+ /**
178
+ * Должен вызываться перед завершением сервиса
179
+ * Но может не вызываться (зависит от реализации)
180
+ */
181
+ beforeTerminate(){ return }
168
182
 
169
183
  /**
170
184
  * Prepare options
@@ -330,7 +344,8 @@ export default class Device {
330
344
  */
331
345
  addInputHandler(name: string, action: (data: any) => any) {
332
346
  name = ImportManager.camelize('input.' + name)
333
- this[name as keyof Device] = action
347
+ const a = this as any
348
+ a[name] = action
334
349
  }
335
350
 
336
351
  /**
@@ -341,7 +356,8 @@ export default class Device {
341
356
  */
342
357
  addActionHandler(name: string, action: (data: any) => any) {
343
358
  name = ImportManager.camelize('action.' + name)
344
- this[name as keyof Device] = action
359
+ const a = this as any
360
+ a[name] = action
345
361
  }
346
362
 
347
363
  /**
@@ -353,4 +369,5 @@ export default class Device {
353
369
  terminate(error: Error, action: string) {
354
370
  return this.makeEvent('device.terminate', action, error, [])
355
371
  }
372
+
356
373
  }
@@ -30,6 +30,8 @@ export default class DevicePort {
30
30
  /** Ссылка на устройсто владельца */
31
31
  Device: Device
32
32
 
33
+ bind: ((data: any) => {}) | null = null;
34
+
33
35
  /**
34
36
  * Список слушателей порта
35
37
  * Используется для захвата порта. Если какие либо данные будут проброшены
@@ -56,6 +58,9 @@ export default class DevicePort {
56
58
  * Calling the incoming port when calling a connection
57
59
  */
58
60
  push(data: any): any {
61
+ if (!this.Device.works) return
62
+ if (this.bind !== null) return this.bind(data)
63
+
59
64
  // Если у нас есть слушатели порта
60
65
  // Передаем им данные и пересоздаем Map
61
66
  if (this.listens.size) {