@yarkivaev/scada 1.4.0 → 2.3.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +196 -33
  2. package/db/migrations/C0001__central_drop_metrics.sql +1 -0
  3. package/db/migrations/C0002__central_revoke_delete.sql +12 -0
  4. package/db/migrations/C0003__central_operators.sql +7 -0
  5. package/db/migrations/C0004__central_operations_grants.sql +2 -0
  6. package/db/migrations/C0005__central_operators_grants.sql +1 -0
  7. package/db/migrations/C0006__central_operators_registration.sql +16 -0
  8. package/db/migrations/E0001__edge_metrics.sql +8 -0
  9. package/db/migrations/E0002__edge_retention_delete.sql +8 -0
  10. package/db/migrations/E0003__edge_operations_grants.sql +2 -0
  11. package/db/migrations/V0001__baseline.sql +25 -0
  12. package/db/migrations/V0002__sink_extension_tables.sql +6 -0
  13. package/db/migrations/V0003__operations.sql +10 -0
  14. package/db/migrations/V0004__ingest_checkpoints.sql +6 -0
  15. package/db/migrations/V0005__user_decisions_operator.sql +2 -0
  16. package/index.js +75 -45
  17. package/package.json +28 -8
  18. package/src/application/bindSilentStreams.js +20 -0
  19. package/src/application/edgeOperatorCatalog.js +45 -0
  20. package/src/application/export/exportJob.js +118 -0
  21. package/src/application/export/exportQuery.js +32 -0
  22. package/src/application/export/exportSink.js +25 -0
  23. package/src/application/export/exportStream.js +33 -0
  24. package/src/application/machineInPlant.js +20 -0
  25. package/src/application/metricsPlant.js +35 -0
  26. package/src/application/plantApi.js +49 -0
  27. package/src/application/plantOperations.js +19 -0
  28. package/src/application/plantServer.js +107 -0
  29. package/src/application/shopWithTimeline.js +112 -0
  30. package/src/application/siteOperatorCatalog.js +78 -0
  31. package/src/application/siteServer.js +191 -0
  32. package/src/application/supervisorSink.js +97 -0
  33. package/src/application/timelineOperatorFromEnv.js +19 -0
  34. package/src/bin/supervisor-sink.js +28 -0
  35. package/src/{alert.js → domain/alerting/alert.js} +2 -2
  36. package/src/{alerts.js → domain/alerting/alerts.js} +3 -3
  37. package/src/domain/alerting/ingest.js +18 -0
  38. package/src/domain/ingest/ingestCursor.js +26 -0
  39. package/src/domain/operation/operation.js +23 -0
  40. package/src/domain/operation/operations.js +81 -0
  41. package/src/domain/operator/operator.js +23 -0
  42. package/src/domain/plant/machine.js +32 -0
  43. package/src/domain/plant/plant.js +20 -0
  44. package/src/domain/plant/shop.js +24 -0
  45. package/src/domain/segment/dispatch.js +31 -0
  46. package/src/domain/segment/normalize.js +31 -0
  47. package/src/domain/segment/silenceBudget.js +16 -0
  48. package/src/{initialized.js → domain/shared/initialized.js} +3 -4
  49. package/src/{pubsub.js → domain/shared/pubsub.js} +2 -3
  50. package/src/domain/timeline/timeline.js +25 -0
  51. package/src/infrastructure/catalog/tag-catalog.json +27 -0
  52. package/src/infrastructure/catalog/tagCatalog.js +65 -0
  53. package/src/infrastructure/client/index.js +20 -0
  54. package/src/infrastructure/client/machineClient.js +159 -0
  55. package/src/infrastructure/client/machineOperationsClient.js +159 -0
  56. package/src/infrastructure/client/scadaClient.js +67 -0
  57. package/src/infrastructure/client/sseConnection.js +42 -0
  58. package/src/infrastructure/http/edge/edgeApi.js +54 -0
  59. package/src/infrastructure/http/edge/httpMetricsRead.js +34 -0
  60. package/src/infrastructure/http/edge/metricsSensor.js +15 -0
  61. package/src/infrastructure/http/edge/parseStateHttpTimeoutMs.js +20 -0
  62. package/src/infrastructure/http/edge/routes/checkpoint/checkpointRoutes.js +79 -0
  63. package/src/infrastructure/http/edge/routes/metrics/metricsBatch.js +59 -0
  64. package/src/infrastructure/http/edge/routes/metrics/metricsCurrent.js +29 -0
  65. package/src/infrastructure/http/edge/routes/metrics/metricsPoll.js +25 -0
  66. package/src/infrastructure/http/edge/routes/metrics/metricsRange.js +26 -0
  67. package/src/infrastructure/http/edge/routes/metrics/metricsRoutes.js +21 -0
  68. package/src/infrastructure/http/edge/routes/retention/retentionRoutes.js +41 -0
  69. package/src/infrastructure/http/edge/startTestEdgeApi.js +39 -0
  70. package/src/infrastructure/http/edge/stateAccess.js +13 -0
  71. package/src/infrastructure/http/edge/stateHttpClient.js +106 -0
  72. package/src/infrastructure/http/edge/stateHttpTimeoutError.js +13 -0
  73. package/src/infrastructure/http/plant/json/decisionJson.js +44 -0
  74. package/src/infrastructure/http/plant/json/operationJson.js +28 -0
  75. package/src/infrastructure/http/plant/json/operatorJson.js +22 -0
  76. package/src/infrastructure/http/plant/json/segmentJson.js +27 -0
  77. package/src/infrastructure/http/plant/operationAudit.js +56 -0
  78. package/src/infrastructure/http/plant/routes/alertRoute.js +93 -0
  79. package/src/infrastructure/http/plant/routes/catalogRoute.js +21 -0
  80. package/src/infrastructure/http/plant/routes/decisionRoute.js +60 -0
  81. package/src/infrastructure/http/plant/routes/machineRoute.js +35 -0
  82. package/src/infrastructure/http/plant/routes/measurementRoute.js +60 -0
  83. package/src/infrastructure/http/plant/routes/operationDrafts.js +79 -0
  84. package/src/infrastructure/http/plant/routes/operationRoute.js +80 -0
  85. package/src/infrastructure/http/plant/routes/operationWrites.js +186 -0
  86. package/src/infrastructure/http/plant/routes/operatorRoute.js +143 -0
  87. package/src/infrastructure/http/plant/routes/simulationRoute.js +61 -0
  88. package/src/infrastructure/http/plant/routes/timelineRoute.js +112 -0
  89. package/src/infrastructure/http/plant/streams/alertStream.js +68 -0
  90. package/src/infrastructure/http/plant/streams/heartbeatStream.js +34 -0
  91. package/src/infrastructure/http/plant/streams/measurementStream.js +90 -0
  92. package/src/infrastructure/http/plant/streams/operationStream.js +42 -0
  93. package/src/infrastructure/http/plant/streams/timelineStream.js +97 -0
  94. package/src/infrastructure/http/plant/timelineOperator.js +87 -0
  95. package/src/infrastructure/ingest/activity/activityTracking.js +74 -0
  96. package/src/infrastructure/ingest/activity/activityTransformer.js +45 -0
  97. package/src/infrastructure/ingest/codecs/alertCodec.js +56 -0
  98. package/src/infrastructure/ingest/codecs/segmentCodec.js +30 -0
  99. package/src/infrastructure/ingest/codecs/userDecisionCodec.js +78 -0
  100. package/src/infrastructure/ingest/cooldown.js +24 -0
  101. package/src/infrastructure/ingest/db/migrate.js +78 -0
  102. package/src/infrastructure/ingest/db/migrationProfile.js +35 -0
  103. package/src/infrastructure/ingest/db/runRetention.js +58 -0
  104. package/src/infrastructure/ingest/ingestCheckpoint.js +71 -0
  105. package/src/infrastructure/ingest/modbus/mx210Tcp.js +81 -0
  106. package/src/infrastructure/ingest/modbus/silentStreams.js +152 -0
  107. package/src/infrastructure/ingest/mqtt/metricsTransformer.js +54 -0
  108. package/src/infrastructure/ingest/mqtt/modbusDeviceSpec.js +112 -0
  109. package/src/infrastructure/ingest/mqtt/modbusMqtt.js +204 -0
  110. package/src/infrastructure/ingest/mqtt/mqttMetrics.js +78 -0
  111. package/src/infrastructure/ingest/parseRequestTimeoutMs.js +20 -0
  112. package/src/infrastructure/ingest/pipelines/alertPipeline.js +48 -0
  113. package/src/infrastructure/ingest/pipelines/decisionPipeline.js +45 -0
  114. package/src/infrastructure/ingest/pipelines/segmentPipeline.js +60 -0
  115. package/src/infrastructure/ingest/processingErrorLog.js +25 -0
  116. package/src/infrastructure/ingest/silentOpenWatch.js +50 -0
  117. package/src/infrastructure/ingest/sinks/alertSink.js +43 -0
  118. package/src/infrastructure/ingest/sinks/closeOrphanOpen.js +32 -0
  119. package/src/infrastructure/ingest/sinks/closeSilentOpen.js +39 -0
  120. package/src/infrastructure/ingest/sinks/retagSink.js +43 -0
  121. package/src/infrastructure/ingest/sinks/userDecisionSink.js +41 -0
  122. package/src/infrastructure/ingest/telemetry/amqpMetricsIngest.js +94 -0
  123. package/src/infrastructure/ingest/telemetry/amqpMqttRelay.js +71 -0
  124. package/src/infrastructure/ingest/telemetry/deliverToMqttRecord.js +19 -0
  125. package/src/infrastructure/ingest/telemetry/streamNameFromTopic.js +21 -0
  126. package/src/infrastructure/messaging/ownership/httpOperations.js +96 -0
  127. package/src/infrastructure/messaging/ownership/httpTimeline.js +99 -0
  128. package/src/infrastructure/messaging/ownership/machineOwners.js +39 -0
  129. package/src/infrastructure/messaging/ownership/ownerTimeline.js +28 -0
  130. package/src/infrastructure/messaging/stomp/alerts/stompAlerts.js +21 -0
  131. package/src/infrastructure/messaging/stomp/alerts/stompAlertsCollection.js +51 -0
  132. package/src/infrastructure/messaging/stomp/alerts/stompAlertsInit.js +24 -0
  133. package/src/infrastructure/messaging/stomp/alerts/stompAlertsLogic.js +51 -0
  134. package/src/infrastructure/messaging/stomp/stompTimelineSegments.js +80 -0
  135. package/src/infrastructure/messaging/stomp/timeline.js +21 -0
  136. package/src/infrastructure/messaging/stomp/userDecisionBody.js +25 -0
  137. package/src/infrastructure/messaging/stomp/userDecisions.js +42 -0
  138. package/src/infrastructure/operators/centralOperators.js +64 -0
  139. package/src/infrastructure/operators/edgeOperators.js +39 -0
  140. package/src/infrastructure/operators/operatorById.js +33 -0
  141. package/src/infrastructure/operators/operatorExtras.js +41 -0
  142. package/src/infrastructure/operators/operators.js +32 -0
  143. package/src/infrastructure/operators/operatorsFromSeed.js +18 -0
  144. package/src/infrastructure/operators/operatorsSync.js +57 -0
  145. package/src/infrastructure/persistence/clickhouse/connection.js +58 -0
  146. package/src/infrastructure/persistence/clickhouse/pollTopicCursors.js +48 -0
  147. package/src/{clickhouseSensor.js → infrastructure/persistence/clickhouse/sensor.js} +9 -27
  148. package/src/infrastructure/persistence/clickhouse/streamHub.js +165 -0
  149. package/src/infrastructure/persistence/memory/alerts.js +21 -0
  150. package/src/infrastructure/persistence/memory/checkpoints.js +98 -0
  151. package/src/infrastructure/persistence/memory/metrics.js +69 -0
  152. package/src/infrastructure/persistence/memory/metricsDisabled.js +19 -0
  153. package/src/infrastructure/persistence/memory/operations.js +87 -0
  154. package/src/infrastructure/persistence/memory/segments.js +83 -0
  155. package/src/infrastructure/persistence/memory/timeline.js +56 -0
  156. package/src/infrastructure/persistence/metricsSensor.js +112 -0
  157. package/src/infrastructure/persistence/pg/alerts.js +25 -0
  158. package/src/infrastructure/persistence/pg/checkpoints.js +115 -0
  159. package/src/infrastructure/persistence/pg/metrics.js +73 -0
  160. package/src/infrastructure/persistence/pg/operations.js +95 -0
  161. package/src/infrastructure/persistence/pg/operators.js +118 -0
  162. package/src/infrastructure/persistence/pg/segments.js +48 -0
  163. package/src/infrastructure/persistence/pg/timeline.js +53 -0
  164. package/src/infrastructure/persistence/pg/userDecisions.js +72 -0
  165. package/src/infrastructure/persistence/postgresPool.js +24 -0
  166. package/src/infrastructure/persistence/stateDataFromMemory.js +28 -0
  167. package/src/infrastructure/persistence/stateDataFromPool.js +18 -0
  168. package/src/infrastructure/sync/operationCodec.js +75 -0
  169. package/src/infrastructure/sync/operationSyncIngest.js +82 -0
  170. package/src/infrastructure/sync/operationSyncSink.js +40 -0
  171. package/src/activeMelting.js +0 -54
  172. package/src/completedMelting.js +0 -42
  173. package/src/event.js +0 -24
  174. package/src/events.js +0 -51
  175. package/src/interval.js +0 -18
  176. package/src/machineChronology.js +0 -79
  177. package/src/meltingChronology.js +0 -37
  178. package/src/meltingMachine.js +0 -53
  179. package/src/meltingRuleEngine.js +0 -37
  180. package/src/meltingShop.js +0 -32
  181. package/src/meltings.js +0 -97
  182. package/src/monitoredMeltingMachine.js +0 -33
  183. package/src/plant.js +0 -23
  184. package/src/requests.js +0 -44
  185. package/src/rule.js +0 -33
  186. package/src/rules.js +0 -23
  187. package/src/scyllaSensor.js +0 -54
  188. package/src/segments.js +0 -64
  189. package/src/sqliteSensor.js +0 -106
@@ -1,53 +0,0 @@
1
- import machineChronology from './machineChronology.js';
2
-
3
- /**
4
- * Melting machine that tracks metal weight history and sensor measurements.
5
- * Supports loading and dispensing metal during melting operations.
6
- * Weight history is tracked for historical queries.
7
- *
8
- * @param {string} name - unique machine identifier
9
- * @param {object} sensors - object containing sensor instances
10
- * @param {object} alerts - centralized alerts collection
11
- * @param {number} initial - initial weight (defaults to 0)
12
- * @returns {object} machine with name, sensors, alerts, chronology, load, dispense
13
- *
14
- * @example
15
- * const machine = meltingMachine('icht1', { voltage: voltageSensor() }, alerts());
16
- * machine.load(500);
17
- * machine.chronology().get().weight; // 500
18
- * machine.chronology().get(pastDate).weight; // weight at pastDate
19
- */
20
- export default function meltingMachine(name, sensors, alerts, initial) {
21
- const start = initial === undefined ? 0 : initial;
22
- const history = [{ timestamp: new Date(), weight: start }];
23
- let current = start;
24
- return {
25
- name() {
26
- return name;
27
- },
28
- sensors,
29
- alerts() {
30
- return alerts.all((item) => {
31
- return item.object === name;
32
- });
33
- },
34
- chronology() {
35
- return machineChronology(start, history, sensors);
36
- },
37
- load(w) {
38
- current += w;
39
- history.push({ timestamp: new Date(), weight: current });
40
- },
41
- dispense(w) {
42
- current -= w;
43
- history.push({ timestamp: new Date(), weight: current });
44
- },
45
- reset(w) {
46
- current = w;
47
- history.push({ timestamp: new Date(), weight: current });
48
- },
49
- init() {
50
- return this;
51
- }
52
- };
53
- }
@@ -1,37 +0,0 @@
1
- /**
2
- * Expert system that evaluates measurements and issues alerts.
3
- * Checks voltage and power factor thresholds.
4
- *
5
- * @param {function} issue - callback to issue alerts with message and timestamp
6
- * @returns {object} rule engine with evaluate method
7
- *
8
- * @example
9
- * const engine = meltingRuleEngine(function(msg, ts) { console.log(msg); });
10
- * engine.evaluate({ voltage: { value: 340 }, cosphi: { value: 0.9 } });
11
- */
12
- export default function meltingRuleEngine(issue) {
13
- return {
14
- evaluate(snapshot) {
15
- const timestamp = new Date();
16
- const {voltage} = snapshot;
17
- const {cosphi} = snapshot;
18
- if (voltage && voltage.value < 350) {
19
- issue(`Critical low voltage: ${ voltage.value.toFixed(1) }V`, timestamp);
20
- } else if (voltage && voltage.value < 360) {
21
- issue(`Low voltage: ${ voltage.value.toFixed(1) }V`, timestamp);
22
- } else if (voltage && voltage.value > 410) {
23
- issue(`Critical high voltage: ${ voltage.value.toFixed(1) }V`, timestamp);
24
- } else if (voltage && voltage.value > 400) {
25
- issue(`High voltage: ${ voltage.value.toFixed(1) }V`, timestamp);
26
- }
27
- if (cosphi && cosphi.value < 0.7) {
28
- issue(`Critical low power factor: ${ cosphi.value.toFixed(2)}`, timestamp);
29
- } else if (cosphi && cosphi.value < 0.8) {
30
- issue(`Low power factor: ${ cosphi.value.toFixed(2)}`, timestamp);
31
- }
32
- if (voltage && cosphi && voltage.value < 370 && cosphi.value < 0.8) {
33
- issue('Power quality issue detected', timestamp);
34
- }
35
- }
36
- };
37
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * Melting shop containing melting machines and their meltings.
3
- * Provides initialization for all contained machines.
4
- *
5
- * @param {string} name - unique identifier for the shop
6
- * @param {object} meltingMachines - initialized wrapper of melting machines
7
- * @param {object} meltings - collection managing melting sessions
8
- * @param {object} alerts - alerts collection for the shop
9
- * @param {object} events - events collection shared from plant
10
- * @returns {object} shop with name, machines, meltings, alerts, events properties and init method
11
- *
12
- * @example
13
- * const shop = meltingShop('shop1', initialized({ m1: machine }, Object.values), meltings(), alerts(), events());
14
- * shop.name(); // 'shop1'
15
- * shop.machines.init().m1; // access machine by key
16
- * shop.events.create(new Date(), {}, ['label']);
17
- * shop.init();
18
- */
19
- export default function meltingShop(name, meltingMachines, meltings, alerts, events) {
20
- return {
21
- name() {
22
- return name;
23
- },
24
- machines: meltingMachines,
25
- meltings,
26
- alerts,
27
- events,
28
- init() {
29
- meltingMachines.init();
30
- },
31
- };
32
- }
package/src/meltings.js DELETED
@@ -1,97 +0,0 @@
1
- /* eslint-disable max-lines-per-function, max-statements */
2
- import pubsub from './pubsub.js';
3
- import meltingChronology from './meltingChronology.js';
4
- import activeMelting from './activeMelting.js';
5
- import completedMelting from './completedMelting.js';
6
-
7
- /**
8
- * Collection of melting sessions associated with machines.
9
- * Uses add() for creating both active and completed meltings.
10
- * Uses query() for filtering and streaming meltings.
11
- *
12
- * @returns {object} collection with add, query methods
13
- *
14
- * @example
15
- * const list = meltings();
16
- * const active = list.add(machine, {}); // creates active melting
17
- * const completed = list.add(machine, { start, end }); // creates completed melting
18
- * list.query(); // returns all completed meltings
19
- * list.query({ machine }); // returns meltings for machine
20
- * list.query({ id: 'm1' }); // returns melting by id
21
- * list.query({ stream: callback }); // subscribe to events
22
- */
23
- export default function meltings() {
24
- const items = [];
25
- const bus = pubsub();
26
- let counter = 0;
27
- function onUpdate(id, updated) {
28
- const item = items.find((i) => {
29
- return i.melting.id() === id;
30
- });
31
- if (item) {
32
- item.melting = updated;
33
- bus.emit({ type: 'updated', melting: updated });
34
- }
35
- }
36
- return {
37
- add(machine, data) {
38
- const opts = data === undefined ? {} : data;
39
- if (opts.end === undefined) {
40
- const existing = items.find((i) => {
41
- const chron = i.melting.chronology().get();
42
- return i.machine === machine && chron.end === undefined;
43
- });
44
- if (existing) {
45
- return existing.melting;
46
- }
47
- }
48
- counter += 1;
49
- const id = `m${counter}`;
50
- if (opts.end !== undefined) {
51
- const chron = meltingChronology(machine, new Date(opts.start), new Date(opts.end));
52
- const completed = completedMelting(id, machine, chron, (updated) => {
53
- onUpdate(id, updated);
54
- });
55
- items.push({ machine, melting: completed });
56
- bus.emit({ type: 'completed', melting: completed });
57
- return completed;
58
- }
59
- const start = opts.start === undefined ? new Date() : new Date(opts.start);
60
- const item = { machine, melting: undefined };
61
- items.push(item);
62
- const active = activeMelting(id, machine, start, (completed) => {
63
- item.melting = completed;
64
- bus.emit({ type: 'completed', melting: completed });
65
- }, (updated) => {
66
- onUpdate(id, updated);
67
- });
68
- item.melting = active;
69
- bus.emit({ type: 'started', melting: active });
70
- return active;
71
- },
72
- query(options) {
73
- const opts = options === undefined ? {} : options;
74
- if (opts.stream !== undefined) {
75
- return bus.stream(opts.stream);
76
- }
77
- if (opts.id !== undefined) {
78
- const item = items.find((i) => {
79
- return i.melting.id() === opts.id;
80
- });
81
- return item === undefined ? undefined : item.melting;
82
- }
83
- if (opts.machine !== undefined) {
84
- return items.filter((i) => {
85
- return i.machine === opts.machine;
86
- }).map((i) => {
87
- return i.melting;
88
- });
89
- }
90
- return items.filter((i) => {
91
- return i.melting.chronology().get().end !== undefined;
92
- }).map((i) => {
93
- return i.melting;
94
- });
95
- }
96
- };
97
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * Melting machine wrapper that monitors measurements and generates alerts.
3
- * Periodically evaluates sensor readings using a rule engine via chronology.
4
- *
5
- * @param {object} machine - the melting machine to monitor
6
- * @param {object} ruleEngine - engine that evaluates measurements and triggers alerts
7
- * @param {function} interval - factory to create periodic intervals
8
- * @returns {object} monitored machine with name, sensors, alerts, chronology, init methods
9
- *
10
- * @example
11
- * const monitored = monitoredMeltingMachine(machine, ruleEngine(), interval);
12
- * monitored.init(); // starts periodic monitoring
13
- */
14
- export default function monitoredMeltingMachine(machine, ruleEngine, interval) {
15
- return {
16
- name() {
17
- return machine.name();
18
- },
19
- sensors: machine.sensors,
20
- alerts() {
21
- return machine.alerts();
22
- },
23
- chronology() {
24
- return machine.chronology();
25
- },
26
- init() {
27
- interval(1000, async () => {
28
- const snapshot = await machine.chronology().get({ type: 'current' });
29
- ruleEngine.evaluate(snapshot);
30
- }).start();
31
- }
32
- };
33
- }
package/src/plant.js DELETED
@@ -1,23 +0,0 @@
1
- /**
2
- * Top-level plant structure containing melting shops.
3
- * Provides initialization for all contained shops.
4
- *
5
- * @param {object} shops - initialized list of melting shops
6
- * @param {object} events - events collection shared by all shops
7
- * @returns {object} plant with shops, events properties and init method
8
- *
9
- * @example
10
- * const p = plant(initializedList(shop1, shop2), events(event, rules));
11
- * p.shops.list(); // [shop1, shop2]
12
- * p.events.create(new Date(), {}, ['label']);
13
- * p.init();
14
- */
15
- export default function plant(shops, events) {
16
- return {
17
- shops,
18
- events,
19
- init() {
20
- shops.init();
21
- },
22
- };
23
- }
package/src/requests.js DELETED
@@ -1,44 +0,0 @@
1
- import pubsub from './pubsub.js';
2
-
3
- /**
4
- * Per-machine in-memory label request collection.
5
- * Stores requests for segment labeling with options for the user to choose from.
6
- * Supports responding to requests and streaming events via pubsub.
7
- *
8
- * @returns {object} collection with add, respond, query, stream methods
9
- *
10
- * @example
11
- * const reqs = requests();
12
- * reqs.add({ id: 'req-0', name: 'unknown', startTime: new Date(), endTime: new Date(), duration: 60, options: ['on', 'off'] });
13
- * reqs.respond('req-0', { label: 'on' }); // marks resolved
14
- * reqs.query(); // returns unresolved requests
15
- * const sub = reqs.stream((e) => console.log(e));
16
- * sub.cancel();
17
- */
18
- export default function requests() {
19
- const items = [];
20
- const bus = pubsub();
21
- return {
22
- add(request) {
23
- items.push({ ...request, resolved: false });
24
- bus.emit({ type: 'created', request });
25
- },
26
- respond(id, body) {
27
- const found = items.find((r) => {
28
- return r.id === id && !r.resolved;
29
- });
30
- if (!found) {
31
- return undefined;
32
- }
33
- found.resolved = true;
34
- bus.emit({ type: 'resolved', request: found });
35
- return { id, ...body };
36
- },
37
- query() {
38
- return items.filter((r) => {
39
- return !r.resolved;
40
- });
41
- },
42
- stream: bus.stream
43
- };
44
- }
package/src/rule.js DELETED
@@ -1,33 +0,0 @@
1
- /**
2
- * Unified trigger-action mapping for event processing.
3
- * Evaluates context and executes action when trigger matches.
4
- *
5
- * @param {function} trigger - predicate that receives context and returns boolean
6
- * @param {function} action - callback executed when trigger returns true
7
- * @returns {object} rule with evaluate method
8
- *
9
- * @example
10
- * // Sensor rule - executes on voltage threshold
11
- * const r1 = rule(
12
- * (ctx) => ctx.sensor && ctx.sensor.voltage < 350,
13
- * (ctx) => alerts.trigger('Low voltage')
14
- * );
15
- *
16
- * // Event rule - handles labeled events
17
- * const r2 = rule(
18
- * (ctx) => ctx.event && ctx.event.labels().includes('melting-start'),
19
- * (ctx) => meltings.add(ctx.event.properties().machine)
20
- * );
21
- *
22
- * r1.evaluate({sensor: {voltage: 340}}); // triggers action
23
- * r2.evaluate({event: e}); // triggers action if labels match
24
- */
25
- export default function rule(trigger, action) {
26
- return {
27
- evaluate(context) {
28
- if (trigger(context)) {
29
- action(context);
30
- }
31
- }
32
- };
33
- }
package/src/rules.js DELETED
@@ -1,23 +0,0 @@
1
- /**
2
- * Collection of rules with unified evaluation.
3
- * Evaluates all rules against provided context.
4
- *
5
- * @param {object[]} list - array of rule objects with evaluate method
6
- * @returns {object} rules collection with evaluate and all methods
7
- *
8
- * @example
9
- * const rs = rules([rule1, rule2, rule3]);
10
- * rs.evaluate({sensor: {voltage: 340}}); // evaluates all rules
11
- * rs.evaluate({event: e}); // evaluates all rules against event
12
- * rs.all(); // returns array of all rules
13
- */
14
- export default function rules(list) {
15
- return {
16
- evaluate(context) {
17
- list.forEach((item) => {
18
- item.evaluate(context);
19
- });
20
- },
21
- all: () => {return [...list]}
22
- };
23
- }
@@ -1,54 +0,0 @@
1
- /**
2
- * Sensor backed by ScyllaDB metrics table.
3
- *
4
- * Reads sensor measurements from scada.metrics table
5
- * and provides real-time streaming via polling.
6
- *
7
- * @param {object} connection - ScyllaDB connection with query method
8
- * @param {string} topic - Metric topic in format '{machine}/{sensor}'
9
- * @param {string} displayName - Human-readable sensor name
10
- * @param {string} unit - Measurement unit (e.g., 'V', 'cos(φ)')
11
- * @returns {object} sensor with name, measurements and stream methods
12
- *
13
- * @example
14
- * const sensor = scyllaSensor(conn, 'icht1/voltage', 'Voltage', 'V');
15
- * sensor.name(); // 'Voltage'
16
- * await sensor.measurements({ start, end }); // array of readings
17
- * sensor.stream(since, 1000, callback); // live stream
18
- */
19
- export default function scyllaSensor(connection, topic, displayName, unit) {
20
- return {
21
- name() {
22
- return displayName;
23
- },
24
- async measurements(range, step) {
25
- void step;
26
- const rows = await connection.query(
27
- 'SELECT ts, value FROM scada.metrics WHERE topic = ? AND ts >= ? AND ts <= ?',
28
- [topic, range.start, range.end]
29
- );
30
- return rows.map((row) => {
31
- return { timestamp: row.ts, value: row.value, unit };
32
- });
33
- },
34
- stream(since, step, callback, clock) {
35
- const time = clock || (() => { return new Date(); });
36
- let lastTs = since;
37
- const timer = setInterval(async () => {
38
- const rows = await connection.query(
39
- 'SELECT ts, value FROM scada.metrics WHERE topic = ? AND ts > ? AND ts <= ? LIMIT 100',
40
- [topic, lastTs, time()]
41
- );
42
- rows.forEach((row) => {
43
- callback({ timestamp: row.ts, value: row.value, unit });
44
- lastTs = row.ts;
45
- });
46
- }, step);
47
- return {
48
- cancel() {
49
- clearInterval(timer);
50
- }
51
- };
52
- }
53
- };
54
- }
package/src/segments.js DELETED
@@ -1,64 +0,0 @@
1
- import pubsub from './pubsub.js';
2
-
3
- /**
4
- * Per-machine in-memory timeline segment collection.
5
- * Stores segments with name, startTime, endTime, duration.
6
- * Supports resolving by startTime and streaming events via pubsub.
7
- *
8
- * @returns {object} collection with add, resolve, query, stream methods
9
- *
10
- * @example
11
- * const segs = segments();
12
- * segs.add({ name: 'on', startTime: new Date(), endTime: new Date(), duration: 60 });
13
- * segs.resolve(startTime, ['heating'], {});
14
- * segs.query(); // all segments
15
- * segs.query({ from: '2024-01-01', to: '2024-01-02' }); // filtered
16
- * const sub = segs.stream((e) => console.log(e));
17
- * sub.cancel();
18
- */
19
- export default function segments() {
20
- const items = [];
21
- const bus = pubsub();
22
- return {
23
- add(segment) {
24
- items.push(segment);
25
- bus.emit({ type: 'created', segment });
26
- },
27
- resolve(start, tags, properties) {
28
- const found = items.find((item) => {
29
- return item.startTime.getTime() === start.getTime();
30
- });
31
- if (found) {
32
- found.tags = tags;
33
- found.properties = properties;
34
- bus.emit({ type: 'resolved', segment: found });
35
- }
36
- },
37
- retag(start, tags, properties) {
38
- const found = items.find((item) => {
39
- return item.startTime.getTime() === start.getTime();
40
- });
41
- if (found) {
42
- found.tags = tags;
43
- found.properties = properties;
44
- delete found.options;
45
- bus.emit({ type: 'resolved', segment: found });
46
- }
47
- },
48
- query(options) {
49
- if (!options) {
50
- return items.slice();
51
- }
52
- return items.filter((item) => {
53
- if (options.from && item.endTime < new Date(options.from)) {
54
- return false;
55
- }
56
- if (options.to && item.startTime > new Date(options.to)) {
57
- return false;
58
- }
59
- return true;
60
- });
61
- },
62
- stream: bus.stream
63
- };
64
- }
@@ -1,106 +0,0 @@
1
- /**
2
- * Sensor backed by SQLite metrics table with downsampling.
3
- *
4
- * Reads sensor measurements from a SQLite metrics table
5
- * and provides real-time streaming via polling.
6
- * Supports time-based downsampling using window functions.
7
- * Timestamps are stored as epoch milliseconds (REAL).
8
- *
9
- * @param {object} connection - SQLite connection with query(sql, params) method
10
- * @param {string} topic - Metric topic in format '{machine}/{sensor}'
11
- * @param {string} displayName - Human-readable sensor name
12
- * @param {string} unit - Measurement unit (e.g., 'V', 'cos(φ)')
13
- * @returns {object} sensor with name, current, measurements and stream methods
14
- *
15
- * @example
16
- * const sensor = sqliteSensor(conn, 'icht1/voltage', 'Voltage', 'V');
17
- * sensor.name(); // 'Voltage'
18
- * await sensor.current(); // { found: true, timestamp, value, unit } or { found: false }
19
- * await sensor.measurements({ start, end }, 60000); // downsampled to 1-minute intervals
20
- * sensor.stream(since, 1000, callback); // live stream
21
- */
22
- // eslint-disable-next-line max-lines-per-function
23
- export default function sqliteSensor(connection, topic, displayName, unit) {
24
- return {
25
- /**
26
- * Returns the human-readable sensor name.
27
- *
28
- * @returns {string} Display name
29
- */
30
- name() {
31
- return displayName;
32
- },
33
- /**
34
- * Returns the most recent measurement.
35
- *
36
- * @returns {Promise<object>} Object with found flag and optional timestamp, value, unit
37
- */
38
- async current() {
39
- const rows = await connection.query(
40
- 'SELECT ts, value FROM metrics WHERE topic = ? ORDER BY ts DESC LIMIT 1',
41
- [topic]
42
- );
43
- if (rows.length === 0) {
44
- return { found: false };
45
- }
46
- return { found: true, timestamp: new Date(rows[0].ts), value: rows[0].value, unit };
47
- },
48
- /**
49
- * Returns downsampled measurements within a time range.
50
- *
51
- * @param {object} range - Object with start and end Date properties
52
- * @param {number} step - Downsampling bucket size in milliseconds
53
- * @returns {Promise<Array>} Array of {timestamp, value, unit} objects
54
- */
55
- async measurements(range, step) {
56
- const millis = Math.max(1000, step);
57
- const rows = await connection.query(
58
- `SELECT bucket * ? as ts, value FROM (
59
- SELECT CAST(ts / ? AS INTEGER) as bucket, value,
60
- ROW_NUMBER() OVER (PARTITION BY CAST(ts / ? AS INTEGER) ORDER BY ts DESC) as rn
61
- FROM metrics WHERE topic = ? AND ts >= ? AND ts <= ?
62
- ) WHERE rn = 1 ORDER BY ts`,
63
- [millis, millis, millis, topic, range.start.getTime(), range.end.getTime()]
64
- );
65
- return rows.map((row) => {
66
- return { timestamp: new Date(row.ts), value: row.value, unit };
67
- });
68
- },
69
- /**
70
- * Polls for new measurements and delivers them via callback.
71
- *
72
- * @param {Date} since - Start timestamp for polling
73
- * @param {number} step - Polling interval in milliseconds
74
- * @param {Function} callback - Called with each {timestamp, value, unit}
75
- * @param {Function} [clock] - Optional time provider returning current Date
76
- * @returns {object} Object with cancel() method to stop polling
77
- */
78
- stream(since, step, callback, clock) {
79
- const time = clock || (() => { return new Date(); });
80
- let lastTs = since;
81
- const timer = setInterval(async () => {
82
- try {
83
- const rows = await connection.query(
84
- 'SELECT ts, value FROM metrics WHERE topic = ? AND ts > ? AND ts <= ? ORDER BY ts LIMIT 100',
85
- [topic, lastTs.getTime(), time().getTime()]
86
- );
87
- rows.forEach((row) => {
88
- const timestamp = new Date(row.ts);
89
- callback({ timestamp, value: row.value, unit });
90
- lastTs = timestamp;
91
- });
92
- } catch {
93
- // Connection errors are non-fatal; next poll retries
94
- }
95
- }, step);
96
- return {
97
- /**
98
- * Stops the polling timer.
99
- */
100
- cancel() {
101
- clearInterval(timer);
102
- }
103
- };
104
- }
105
- };
106
- }