@yarkivaev/scada 1.5.0 → 2.3.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +196 -33
- package/db/migrations/C0001__central_drop_metrics.sql +1 -0
- package/db/migrations/C0002__central_revoke_delete.sql +12 -0
- package/db/migrations/C0003__central_operators.sql +7 -0
- package/db/migrations/C0004__central_operations_grants.sql +2 -0
- package/db/migrations/C0005__central_operators_grants.sql +1 -0
- package/db/migrations/C0006__central_operators_registration.sql +16 -0
- package/db/migrations/E0001__edge_metrics.sql +8 -0
- package/db/migrations/E0002__edge_retention_delete.sql +8 -0
- package/db/migrations/E0003__edge_operations_grants.sql +2 -0
- package/db/migrations/V0001__baseline.sql +25 -0
- package/db/migrations/V0002__sink_extension_tables.sql +6 -0
- package/db/migrations/V0003__operations.sql +10 -0
- package/db/migrations/V0004__ingest_checkpoints.sql +6 -0
- package/db/migrations/V0005__user_decisions_operator.sql +2 -0
- package/index.js +75 -46
- package/package.json +28 -9
- package/src/application/bindSilentStreams.js +20 -0
- package/src/application/edgeOperatorCatalog.js +45 -0
- package/src/application/export/exportJob.js +118 -0
- package/src/application/export/exportQuery.js +32 -0
- package/src/application/export/exportSink.js +25 -0
- package/src/application/export/exportStream.js +33 -0
- package/src/application/machineInPlant.js +20 -0
- package/src/application/metricsPlant.js +35 -0
- package/src/application/plantApi.js +49 -0
- package/src/application/plantOperations.js +19 -0
- package/src/application/plantServer.js +107 -0
- package/src/application/shopWithTimeline.js +112 -0
- package/src/application/siteOperatorCatalog.js +79 -0
- package/src/application/siteServer.js +191 -0
- package/src/application/supervisorSink.js +97 -0
- package/src/application/timelineOperatorFromEnv.js +19 -0
- package/src/bin/supervisor-sink.js +28 -0
- package/src/{alert.js → domain/alerting/alert.js} +2 -2
- package/src/{alerts.js → domain/alerting/alerts.js} +3 -3
- package/src/domain/alerting/ingest.js +18 -0
- package/src/domain/ingest/ingestCursor.js +26 -0
- package/src/domain/operation/operation.js +23 -0
- package/src/domain/operation/operations.js +81 -0
- package/src/domain/operator/operator.js +23 -0
- package/src/domain/plant/machine.js +32 -0
- package/src/domain/plant/plant.js +20 -0
- package/src/domain/plant/shop.js +24 -0
- package/src/domain/segment/dispatch.js +31 -0
- package/src/domain/segment/normalize.js +31 -0
- package/src/domain/segment/silenceBudget.js +16 -0
- package/src/{initialized.js → domain/shared/initialized.js} +3 -4
- package/src/{pubsub.js → domain/shared/pubsub.js} +2 -3
- package/src/domain/timeline/timeline.js +25 -0
- package/src/infrastructure/catalog/tag-catalog.json +27 -0
- package/src/infrastructure/catalog/tagCatalog.js +65 -0
- package/src/infrastructure/client/index.js +20 -0
- package/src/infrastructure/client/machineClient.js +159 -0
- package/src/infrastructure/client/machineOperationsClient.js +159 -0
- package/src/infrastructure/client/scadaClient.js +67 -0
- package/src/infrastructure/client/sseConnection.js +42 -0
- package/src/infrastructure/http/edge/edgeApi.js +54 -0
- package/src/infrastructure/http/edge/httpMetricsRead.js +34 -0
- package/src/infrastructure/http/edge/metricsSensor.js +15 -0
- package/src/infrastructure/http/edge/parseStateHttpTimeoutMs.js +20 -0
- package/src/infrastructure/http/edge/routes/checkpoint/checkpointRoutes.js +79 -0
- package/src/infrastructure/http/edge/routes/metrics/metricsBatch.js +59 -0
- package/src/infrastructure/http/edge/routes/metrics/metricsCurrent.js +29 -0
- package/src/infrastructure/http/edge/routes/metrics/metricsPoll.js +25 -0
- package/src/infrastructure/http/edge/routes/metrics/metricsRange.js +26 -0
- package/src/infrastructure/http/edge/routes/metrics/metricsRoutes.js +21 -0
- package/src/infrastructure/http/edge/routes/retention/retentionRoutes.js +41 -0
- package/src/infrastructure/http/edge/startTestEdgeApi.js +39 -0
- package/src/infrastructure/http/edge/stateAccess.js +13 -0
- package/src/infrastructure/http/edge/stateHttpClient.js +106 -0
- package/src/infrastructure/http/edge/stateHttpTimeoutError.js +13 -0
- package/src/infrastructure/http/plant/json/decisionJson.js +44 -0
- package/src/infrastructure/http/plant/json/operationJson.js +28 -0
- package/src/infrastructure/http/plant/json/operatorJson.js +22 -0
- package/src/infrastructure/http/plant/json/segmentJson.js +27 -0
- package/src/infrastructure/http/plant/operationAudit.js +56 -0
- package/src/infrastructure/http/plant/routes/alertRoute.js +93 -0
- package/src/infrastructure/http/plant/routes/catalogRoute.js +21 -0
- package/src/infrastructure/http/plant/routes/decisionRoute.js +60 -0
- package/src/infrastructure/http/plant/routes/machineRoute.js +35 -0
- package/src/infrastructure/http/plant/routes/measurementRoute.js +60 -0
- package/src/infrastructure/http/plant/routes/operationDrafts.js +79 -0
- package/src/infrastructure/http/plant/routes/operationRoute.js +80 -0
- package/src/infrastructure/http/plant/routes/operationWrites.js +186 -0
- package/src/infrastructure/http/plant/routes/operatorRoute.js +143 -0
- package/src/infrastructure/http/plant/routes/simulationRoute.js +61 -0
- package/src/infrastructure/http/plant/routes/timelineRoute.js +112 -0
- package/src/infrastructure/http/plant/streams/alertStream.js +68 -0
- package/src/infrastructure/http/plant/streams/heartbeatStream.js +34 -0
- package/src/infrastructure/http/plant/streams/measurementStream.js +90 -0
- package/src/infrastructure/http/plant/streams/operationStream.js +42 -0
- package/src/infrastructure/http/plant/streams/timelineStream.js +97 -0
- package/src/infrastructure/http/plant/timelineOperator.js +87 -0
- package/src/infrastructure/ingest/activity/activityTracking.js +74 -0
- package/src/infrastructure/ingest/activity/activityTransformer.js +45 -0
- package/src/infrastructure/ingest/codecs/alertCodec.js +56 -0
- package/src/infrastructure/ingest/codecs/segmentCodec.js +30 -0
- package/src/infrastructure/ingest/codecs/userDecisionCodec.js +78 -0
- package/src/infrastructure/ingest/cooldown.js +24 -0
- package/src/infrastructure/ingest/db/migrate.js +78 -0
- package/src/infrastructure/ingest/db/migrationProfile.js +35 -0
- package/src/infrastructure/ingest/db/runRetention.js +58 -0
- package/src/infrastructure/ingest/ingestCheckpoint.js +71 -0
- package/src/infrastructure/ingest/modbus/mx210Tcp.js +81 -0
- package/src/infrastructure/ingest/modbus/silentStreams.js +152 -0
- package/src/infrastructure/ingest/mqtt/metricsTransformer.js +54 -0
- package/src/infrastructure/ingest/mqtt/modbusDeviceSpec.js +112 -0
- package/src/infrastructure/ingest/mqtt/modbusMqtt.js +204 -0
- package/src/infrastructure/ingest/mqtt/mqttMetrics.js +78 -0
- package/src/infrastructure/ingest/parseRequestTimeoutMs.js +20 -0
- package/src/infrastructure/ingest/pipelines/alertPipeline.js +48 -0
- package/src/infrastructure/ingest/pipelines/decisionPipeline.js +45 -0
- package/src/infrastructure/ingest/pipelines/segmentPipeline.js +60 -0
- package/src/infrastructure/ingest/processingErrorLog.js +25 -0
- package/src/infrastructure/ingest/silentOpenWatch.js +50 -0
- package/src/infrastructure/ingest/sinks/alertSink.js +43 -0
- package/src/infrastructure/ingest/sinks/closeOrphanOpen.js +32 -0
- package/src/infrastructure/ingest/sinks/closeSilentOpen.js +39 -0
- package/src/infrastructure/ingest/sinks/retagSink.js +43 -0
- package/src/infrastructure/ingest/sinks/userDecisionSink.js +41 -0
- package/src/infrastructure/ingest/telemetry/amqpMetricsIngest.js +94 -0
- package/src/infrastructure/ingest/telemetry/amqpMqttRelay.js +71 -0
- package/src/infrastructure/ingest/telemetry/deliverToMqttRecord.js +19 -0
- package/src/infrastructure/ingest/telemetry/streamNameFromTopic.js +21 -0
- package/src/infrastructure/messaging/ownership/httpOperations.js +96 -0
- package/src/infrastructure/messaging/ownership/httpTimeline.js +99 -0
- package/src/infrastructure/messaging/ownership/machineOwners.js +39 -0
- package/src/infrastructure/messaging/ownership/ownerTimeline.js +28 -0
- package/src/infrastructure/messaging/stomp/alerts/stompAlerts.js +21 -0
- package/src/infrastructure/messaging/stomp/alerts/stompAlertsCollection.js +51 -0
- package/src/infrastructure/messaging/stomp/alerts/stompAlertsInit.js +24 -0
- package/src/infrastructure/messaging/stomp/alerts/stompAlertsLogic.js +51 -0
- package/src/infrastructure/messaging/stomp/stompTimelineSegments.js +80 -0
- package/src/infrastructure/messaging/stomp/timeline.js +21 -0
- package/src/infrastructure/messaging/stomp/userDecisionBody.js +25 -0
- package/src/infrastructure/messaging/stomp/userDecisions.js +42 -0
- package/src/infrastructure/operators/centralOperators.js +64 -0
- package/src/infrastructure/operators/edgeOperators.js +39 -0
- package/src/infrastructure/operators/operatorById.js +33 -0
- package/src/infrastructure/operators/operatorExtras.js +41 -0
- package/src/infrastructure/operators/operators.js +32 -0
- package/src/infrastructure/operators/operatorsFromSeed.js +18 -0
- package/src/infrastructure/operators/operatorsSync.js +57 -0
- package/src/infrastructure/persistence/clickhouse/connection.js +58 -0
- package/src/infrastructure/persistence/clickhouse/pollTopicCursors.js +48 -0
- package/src/{clickhouseSensor.js → infrastructure/persistence/clickhouse/sensor.js} +9 -27
- package/src/infrastructure/persistence/clickhouse/streamHub.js +165 -0
- package/src/infrastructure/persistence/memory/alerts.js +21 -0
- package/src/infrastructure/persistence/memory/checkpoints.js +98 -0
- package/src/infrastructure/persistence/memory/metrics.js +69 -0
- package/src/infrastructure/persistence/memory/metricsDisabled.js +19 -0
- package/src/infrastructure/persistence/memory/operations.js +87 -0
- package/src/infrastructure/persistence/memory/segments.js +83 -0
- package/src/infrastructure/persistence/memory/timeline.js +56 -0
- package/src/infrastructure/persistence/metricsSensor.js +112 -0
- package/src/infrastructure/persistence/pg/alerts.js +25 -0
- package/src/infrastructure/persistence/pg/checkpoints.js +115 -0
- package/src/infrastructure/persistence/pg/metrics.js +73 -0
- package/src/infrastructure/persistence/pg/operations.js +95 -0
- package/src/infrastructure/persistence/pg/operators.js +118 -0
- package/src/infrastructure/persistence/pg/segments.js +48 -0
- package/src/infrastructure/persistence/pg/timeline.js +53 -0
- package/src/infrastructure/persistence/pg/userDecisions.js +72 -0
- package/src/infrastructure/persistence/postgresPool.js +24 -0
- package/src/infrastructure/persistence/stateDataFromMemory.js +28 -0
- package/src/infrastructure/persistence/stateDataFromPool.js +18 -0
- package/src/infrastructure/sync/operationCodec.js +75 -0
- package/src/infrastructure/sync/operationSyncIngest.js +82 -0
- package/src/infrastructure/sync/operationSyncSink.js +40 -0
- package/src/activeMelting.js +0 -54
- package/src/completedMelting.js +0 -42
- package/src/event.js +0 -24
- package/src/events.js +0 -51
- package/src/interval.js +0 -18
- package/src/machineChronology.js +0 -79
- package/src/meltingChronology.js +0 -37
- package/src/meltingMachine.js +0 -53
- package/src/meltingRuleEngine.js +0 -37
- package/src/meltingShop.js +0 -32
- package/src/meltings.js +0 -97
- package/src/monitoredMeltingMachine.js +0 -33
- package/src/plant.js +0 -23
- package/src/postgresSensor.js +0 -105
- package/src/requests.js +0 -44
- package/src/rule.js +0 -33
- package/src/rules.js +0 -23
- package/src/scyllaSensor.js +0 -54
- package/src/segments.js +0 -64
- package/src/sqliteSensor.js +0 -106
package/README.md
CHANGED
|
@@ -1,53 +1,216 @@
|
|
|
1
1
|
# scada
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Generic industrial monitoring domain, state persistence, and HTTP APIs (`@yarkivaev/scada`).
|
|
4
|
+
|
|
5
|
+
This package is plant-agnostic: it defines `plant` → `shop` → `machine` hierarchy, timeline and alerts, sensor read ports, STOMP ingest pipelines, and PostgreSQL/ClickHouse adapters. Plant-specific routes, sensors, HMI strings, and tag taxonomies live in downstream plant packages.
|
|
6
|
+
|
|
7
|
+
Repository: `https://github.com/yarkivaev/scada`
|
|
4
8
|
|
|
5
9
|
## Install
|
|
6
10
|
|
|
11
|
+
```bash
|
|
12
|
+
npm install @yarkivaev/scada
|
|
7
13
|
```
|
|
8
|
-
|
|
14
|
+
|
|
15
|
+
Node.js 22+, ES modules.
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
import { plantApi, metricsPlant, clickhouseConnection } from '@yarkivaev/scada';
|
|
21
|
+
|
|
22
|
+
const p = await metricsPlant(
|
|
23
|
+
[{ device: 'device-1', machine: 'm1' }],
|
|
24
|
+
{
|
|
25
|
+
connection: () => clickhouseConnection(process.env.CLICKHOUSE_HOST),
|
|
26
|
+
buildSensors: (conn, entry) => ({ /* sensor factories */ }),
|
|
27
|
+
shopFactory: (entries, ctx) => { /* return shop */ }
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const api = plantApi('/api/v1', p);
|
|
9
32
|
```
|
|
10
33
|
|
|
11
|
-
|
|
34
|
+
For a full deployable process, use `siteServer` or the `supervisor-sink` binary (see below). Plant packages wrap these with site-specific routes and sensors.
|
|
35
|
+
|
|
36
|
+
## Layout
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
scada/
|
|
40
|
+
src/
|
|
41
|
+
domain/ business objects and ingest policies (no I/O)
|
|
42
|
+
application/ composition roots (plantApi, supervisorSink, siteServer)
|
|
43
|
+
infrastructure/ adapters (http, persistence, messaging, ingest, client)
|
|
44
|
+
bin/ process entry points
|
|
45
|
+
test/ mirrors src/ (domain/, infrastructure/, helpers/)
|
|
46
|
+
db/migrations/ PostgreSQL schema (profile-specific prefixes)
|
|
47
|
+
index.js public API facade
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Three layers
|
|
51
|
+
|
|
52
|
+
| Layer | Role | Examples |
|
|
53
|
+
|-------|------|----------|
|
|
54
|
+
| **domain/** | Pure business logic, no imports from infrastructure | `plant`, `shop`, `machine`, `timeline`, `alerts`, `segment/dispatch` |
|
|
55
|
+
| **application/** | Wires domain + infrastructure into runnable units | `plantApi`, `metricsPlant`, `supervisorSink`, `siteServer` |
|
|
56
|
+
| **infrastructure/** | I/O adapters behind narrow ports | `persistence/pg`, `http/plant`, `http/edge`, `messaging/stomp`, `ingest` |
|
|
57
|
+
|
|
58
|
+
Dependency rules:
|
|
59
|
+
|
|
60
|
+
- `domain/` imports `domain/` only
|
|
61
|
+
- `application/` composes `domain/` + `infrastructure/`
|
|
62
|
+
- HTTP routes receive injected `plant` objects; storage backends are swappable behind the same port shape
|
|
63
|
+
|
|
64
|
+
## Data flow
|
|
65
|
+
|
|
66
|
+
**Central plant API** (read path):
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
ClickHouse ← mqttMetrics / amqpMetricsIngest
|
|
70
|
+
PostgreSQL ← STOMP alerts (via plantServer)
|
|
71
|
+
plantApi → REST + SSE → frontend / @yarkivaev/scada/client
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Supervisor-sink** (write path):
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
STOMP (`/queue/scada.segments.ingest`, `/exchange/scada.alerts`, `/exchange/scada.user_decisions`)
|
|
78
|
+
→ ingest/codecs → domain/segment → ingest/sinks → PostgreSQL
|
|
79
|
+
edgeApi (:8081) ← metrics batch, checkpoints, retention (edge profile)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Edge site** (`siteServer`): runs supervisor-sink HTTP, plant API, and optional MQTT ingest in one process. Metrics land in PostgreSQL on edge; on central they go to ClickHouse.
|
|
83
|
+
|
|
84
|
+
## Public exports
|
|
85
|
+
|
|
86
|
+
Main entry (`import { … } from '@yarkivaev/scada'`):
|
|
87
|
+
|
|
88
|
+
| Export | Purpose |
|
|
89
|
+
|--------|---------|
|
|
90
|
+
| `plant`, `shop`, `machine` | Domain hierarchy |
|
|
91
|
+
| `timeline`, `alerts`, `alert`, `acknowledgedAlert` | Timeline and alerting |
|
|
92
|
+
| `plantApi`, `plantServer`, `siteServer` | HTTP composition |
|
|
93
|
+
| `exportQuery`, `exportStream`, `exportSink`, `exportJob` | Generic export ports over plantApi / `@yarkivaev/scada/client` |
|
|
94
|
+
| `metricsPlant`, `shopWithTimeline`, `machineInPlant` | Plant wiring helpers |
|
|
95
|
+
| `supervisorSink`, `readDeploymentConfig` | STOMP ingest + PG persistence |
|
|
96
|
+
| `edgeApi`, `stateHttpClient`, `metricsSensor` | Edge HTTP read/write |
|
|
97
|
+
| `clickhouseConnection`, `clickhouseSensor`, `pgMetricsSensor` | Metric storage |
|
|
98
|
+
| `pgTimeline`, `pgAlerts`, `stateDataFromPool`, `stateDataFromMemory` | State ports |
|
|
99
|
+
| `stompTimeline`, `stompAlerts`, `userDecisions` | STOMP messaging |
|
|
100
|
+
| `machineOwners`, `ownerTimeline`, `httpTimeline`, `httpOperations` | Owner-routed timeline and operations write (local or edge HTTP) |
|
|
101
|
+
| `mqttMetrics`, `modbusMqtt`, `activityTracking`, `amqpMetricsIngest` | Ingest pipelines |
|
|
102
|
+
|
|
103
|
+
Subpath exports:
|
|
104
|
+
|
|
105
|
+
- `@yarkivaev/scada/client` — browser/Node HTTP+SSE client (`scadaClient`, `machineClient`) plus re-exported export ports
|
|
106
|
+
- `@yarkivaev/scada/stateDataFake` — in-memory state backends for tests
|
|
107
|
+
- `@yarkivaev/scada/startTestEdgeApi` — ephemeral edge API for integration tests
|
|
108
|
+
|
|
109
|
+
## Export ports
|
|
110
|
+
|
|
111
|
+
Generic read/stream/write contract for outbound jobs. Read side uses existing `plantApi` / `@yarkivaev/scada/client` only (no direct DB). Destination adapters and plant maps live in plant packages, not here. Do not put export into `edgeApi`.
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
import { exportQuery, exportStream, exportSink, exportJob } from '@yarkivaev/scada';
|
|
115
|
+
import { scadaClient } from '@yarkivaev/scada/client';
|
|
116
|
+
|
|
117
|
+
const client = scadaClient(baseUrl, fetch, EventSource);
|
|
118
|
+
const query = exportQuery(client);
|
|
119
|
+
const stream = exportStream(client);
|
|
120
|
+
const sink = exportSink({
|
|
121
|
+
write(records) { return destination.write(records); },
|
|
122
|
+
send(artifact) { return destination.send(artifact); }
|
|
123
|
+
});
|
|
124
|
+
const job = exportJob({ query, transform: (rows) => rows, sink });
|
|
125
|
+
await job.run({ kind: 'segments', machine: 'furnace-α', from, to });
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Downstream plant packages should pin a released tag (e.g. `#v2.3.46`).
|
|
129
|
+
|
|
130
|
+
Alert and HMI copy are inject-only: pass `translations` into `siteServer` / `plantServer` / `alertPipeline` config, and override `TAG_CATALOG_PATH` or `plantApi({ tagCatalog })` for site taxonomy. Defaults ship empty or English stubs.
|
|
131
|
+
|
|
132
|
+
## Binaries
|
|
133
|
+
|
|
134
|
+
| Binary | Role |
|
|
135
|
+
|--------|------|
|
|
136
|
+
| `supervisor-sink` | STOMP → PostgreSQL ingest + edge HTTP API (`STATE_HTTP_PORT`, default 8081) |
|
|
137
|
+
|
|
138
|
+
Full plant API with site-specific routes: plant packages call `siteServer` from this package.
|
|
139
|
+
|
|
140
|
+
## Sensor port
|
|
141
|
+
|
|
142
|
+
All sensor implementations expose the same interface:
|
|
12
143
|
|
|
13
144
|
```javascript
|
|
14
|
-
|
|
145
|
+
{ name(), current(), measurements(range, step), stream(since, step, callback, clock?) }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Implementations: `clickhouseSensor` (central; live `stream()` batches many topics into one ClickHouse query per tick), `pgMetricsSensor` / `metricsSensor` (edge HTTP or PG).
|
|
149
|
+
|
|
150
|
+
## Database migrations
|
|
151
|
+
|
|
152
|
+
SQL files in `db/migrations/` use profile prefixes:
|
|
153
|
+
|
|
154
|
+
| Prefix | Applies to |
|
|
155
|
+
|--------|------------|
|
|
156
|
+
| `V*` | Both central and edge |
|
|
157
|
+
| `E*` | Edge only (metrics tables, retention) |
|
|
158
|
+
| `C*` | Central only (revoke delete on metrics) |
|
|
159
|
+
|
|
160
|
+
Set `SINK_DB_PROFILE=central|edge`. Migrations run at supervisor-sink startup via `POSTGRES_ADMIN_URL`.
|
|
15
161
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
162
|
+
## Environment (supervisor-sink / siteServer)
|
|
163
|
+
|
|
164
|
+
| Variable | Purpose |
|
|
165
|
+
|----------|---------|
|
|
166
|
+
| `STOMP_URL` | RabbitMQ STOMP broker |
|
|
167
|
+
| `POSTGRES_URL` | Application DB user (`supervisor_sink`) |
|
|
168
|
+
| `POSTGRES_ADMIN_URL` | Admin user for migrations |
|
|
169
|
+
| `SINK_DB_PROFILE` | `central` or `edge` |
|
|
170
|
+
| `STATE_HTTP_PORT` / `STATE_HTTP_TOKEN` | Edge internal API |
|
|
171
|
+
| `MQTT_URL`, `MQTT_TOPICS` | Optional metrics ingest |
|
|
172
|
+
| `CLICKHOUSE_URL` | Central metrics sink |
|
|
173
|
+
| `PORT` | Plant API port (siteServer, default 3000) |
|
|
174
|
+
|
|
175
|
+
## Testing
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npm test # all Mocha unit tests
|
|
179
|
+
npm test -- --grep "pattern" # filter by name
|
|
180
|
+
npm run test:deep # domain + ingest (integration needs Docker)
|
|
181
|
+
npm run coverage
|
|
182
|
+
npm run lint
|
|
183
|
+
npm run lint:eo # Elegant Objects naming audit
|
|
19
184
|
```
|
|
20
185
|
|
|
21
|
-
|
|
186
|
+
Tests mirror `src/` under `test/`. Use fakes instead of mocks; one assertion per test. Integration tests under `test/infrastructure/**/integration/` use testcontainers.
|
|
187
|
+
|
|
188
|
+
For HTTP/state tests without PostgreSQL:
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
import stateDataFake from '@yarkivaev/scada/stateDataFake';
|
|
192
|
+
```
|
|
22
193
|
|
|
23
|
-
|
|
24
|
-
- `plant`
|
|
25
|
-
- `meltingShop`
|
|
26
|
-
- `meltingMachine`
|
|
27
|
-
- `machineChronology`
|
|
28
|
-
- `monitoredMeltingMachine`
|
|
194
|
+
## Design conventions
|
|
29
195
|
|
|
30
|
-
|
|
31
|
-
- `
|
|
32
|
-
-
|
|
33
|
-
- `
|
|
34
|
-
- `meltingChronology`
|
|
196
|
+
- Factory functions returning frozen objects — no classes, no inheritance
|
|
197
|
+
- ESLint limits: `max-lines: 200`, `max-lines-per-function: 50`, `no-console`, `no-inline-comments`
|
|
198
|
+
- Docblocks on every module; method bodies without blank lines
|
|
199
|
+
- See `CLAUDE.md` for layer rules and ingest sandwich diagram
|
|
35
200
|
|
|
36
|
-
|
|
37
|
-
- `alert`
|
|
38
|
-
- `acknowledgedAlert`
|
|
39
|
-
- `alerts`
|
|
40
|
-
- `meltingRuleEngine`
|
|
201
|
+
## Package boundary
|
|
41
202
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
203
|
+
| This package (`@yarkivaev/scada`) | Downstream plant package |
|
|
204
|
+
|------------------------|--------------------------|
|
|
205
|
+
| Generic plant, timeline, alerts, ingest | Site domain, sensors, HMI strings, tag taxonomy |
|
|
206
|
+
| Empty/injectable alert `translations` | Localized rule messages |
|
|
207
|
+
| Opaque operation `kind` strings | Site-specific kinds (`sample`, `load`, …) |
|
|
208
|
+
| `plantApi`, `siteServer`, `supervisorSink` | Site bins and routes |
|
|
209
|
+
| Export ports `Query` / `Stream` / `Sink` / `Job` | Export jobs + destination adapters |
|
|
210
|
+
| ClickHouse + PG adapters | Plant wiring (`metricsPlant`, shops, machines) |
|
|
45
211
|
|
|
46
|
-
|
|
47
|
-
- `interval`
|
|
48
|
-
- `initialized`
|
|
49
|
-
- `events`
|
|
212
|
+
Depend on a pinned version (`"@yarkivaev/scada": "…#v2.3.46"`), not a local path, in downstream packages.
|
|
50
213
|
|
|
51
|
-
##
|
|
214
|
+
## CI/CD
|
|
52
215
|
|
|
53
|
-
|
|
216
|
+
GitHub Actions via `yarkivaev/npm-workflows` (lint, test, version check). This repo publishes an npm package only; container images are built in downstream deploy repos.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DROP TABLE IF EXISTS metrics;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
REVOKE ALL ON SCHEMA public FROM supervisor_sink;
|
|
2
|
+
GRANT USAGE ON SCHEMA public TO supervisor_sink;
|
|
3
|
+
|
|
4
|
+
GRANT SELECT, INSERT, UPDATE ON segments TO supervisor_sink;
|
|
5
|
+
GRANT SELECT, INSERT, UPDATE ON alerts TO supervisor_sink;
|
|
6
|
+
GRANT SELECT, INSERT ON user_decisions TO supervisor_sink;
|
|
7
|
+
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO supervisor_sink;
|
|
8
|
+
|
|
9
|
+
REVOKE CREATE ON SCHEMA public FROM supervisor_sink;
|
|
10
|
+
REVOKE DELETE ON segments FROM supervisor_sink;
|
|
11
|
+
REVOKE DELETE ON alerts FROM supervisor_sink;
|
|
12
|
+
REVOKE DELETE ON user_decisions FROM supervisor_sink;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
GRANT SELECT ON operators TO supervisor_sink;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- Singleton flag: HMI may register operators only while enabled is true.
|
|
2
|
+
-- CHECK keeps exactly one row (singleton = 1); default disabled until console turns registration on.
|
|
3
|
+
CREATE TABLE IF NOT EXISTS operators_registration (
|
|
4
|
+
singleton SMALLINT PRIMARY KEY DEFAULT 1 CHECK (singleton = 1),
|
|
5
|
+
enabled BOOLEAN NOT NULL DEFAULT FALSE
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
-- Seed the singleton on first apply; keep an existing enabled value on re-apply.
|
|
9
|
+
INSERT INTO operators_registration (singleton, enabled)
|
|
10
|
+
VALUES (1, FALSE)
|
|
11
|
+
ON CONFLICT (singleton) DO NOTHING;
|
|
12
|
+
|
|
13
|
+
-- Plant API role: create operators and read/update the registration flag.
|
|
14
|
+
GRANT INSERT ON operators TO supervisor_sink;
|
|
15
|
+
GRANT USAGE, SELECT ON SEQUENCE operators_id_seq TO supervisor_sink;
|
|
16
|
+
GRANT SELECT, UPDATE ON operators_registration TO supervisor_sink;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
ALTER TABLE IF EXISTS metrics OWNER TO supervisor_sink;
|
|
2
|
+
|
|
3
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON metrics TO supervisor_sink;
|
|
4
|
+
GRANT DELETE ON segments TO supervisor_sink;
|
|
5
|
+
GRANT DELETE ON alerts TO supervisor_sink;
|
|
6
|
+
GRANT DELETE ON user_decisions TO supervisor_sink;
|
|
7
|
+
|
|
8
|
+
ALTER TABLE IF EXISTS user_decisions OWNER TO supervisor_sink;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS segments (
|
|
2
|
+
machine TEXT,
|
|
3
|
+
name TEXT,
|
|
4
|
+
start_time TIMESTAMPTZ,
|
|
5
|
+
end_time TIMESTAMPTZ,
|
|
6
|
+
duration FLOAT,
|
|
7
|
+
options TEXT,
|
|
8
|
+
tags TEXT,
|
|
9
|
+
properties TEXT,
|
|
10
|
+
resolved BOOLEAN DEFAULT TRUE,
|
|
11
|
+
consumed BOOLEAN DEFAULT TRUE
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_segments_machine_start
|
|
15
|
+
ON segments (machine, start_time);
|
|
16
|
+
|
|
17
|
+
CREATE TABLE IF NOT EXISTS alerts (
|
|
18
|
+
id SERIAL PRIMARY KEY,
|
|
19
|
+
name TEXT,
|
|
20
|
+
message TEXT,
|
|
21
|
+
machine TEXT,
|
|
22
|
+
severity TEXT,
|
|
23
|
+
timestamp TIMESTAMPTZ,
|
|
24
|
+
acknowledged BOOLEAN DEFAULT FALSE
|
|
25
|
+
);
|
package/index.js
CHANGED
|
@@ -1,51 +1,80 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Provides immutable objects for modeling melting operations,
|
|
4
|
-
* alerting systems, events, and plant hierarchy.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* import { plant, meltingShop, meltingMachine, meltings, alerts, initialized } from 'scada';
|
|
8
|
-
* const shop = meltingShop('shop1', initialized({ m1: machine }, Object.values), meltings());
|
|
9
|
-
* const p = plant(initialized({ shop1: shop }, Object.values));
|
|
2
|
+
* scada v2 — generic industrial monitoring domain and HTTP API.
|
|
10
3
|
*/
|
|
11
4
|
|
|
12
|
-
|
|
13
|
-
export { default as
|
|
14
|
-
export { default as
|
|
15
|
-
export { default as
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
5
|
+
export { default as plant } from './src/domain/plant/plant.js';
|
|
6
|
+
export { default as shop } from './src/domain/plant/shop.js';
|
|
7
|
+
export { default as machine } from './src/domain/plant/machine.js';
|
|
8
|
+
export { default as timeline } from './src/domain/timeline/timeline.js';
|
|
9
|
+
export { alert, acknowledgedAlert } from './src/domain/alerting/alert.js';
|
|
10
|
+
export { operation } from './src/domain/operation/operation.js';
|
|
11
|
+
export { ingestCursorAt, ingestCursorEmpty } from './src/domain/ingest/ingestCursor.js';
|
|
12
|
+
export { default as alerts } from './src/domain/alerting/alerts.js';
|
|
13
|
+
export { default as initialized } from './src/domain/shared/initialized.js';
|
|
14
|
+
export { default as pubsub } from './src/domain/shared/pubsub.js';
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
export { default as
|
|
21
|
-
export { default as
|
|
22
|
-
export { default as
|
|
23
|
-
export { default as
|
|
16
|
+
export { default as plantApi } from './src/application/plantApi.js';
|
|
17
|
+
export { default as tagCatalog } from './src/infrastructure/catalog/tagCatalog.js';
|
|
18
|
+
export { default as machineInPlant } from './src/application/machineInPlant.js';
|
|
19
|
+
export { default as shopWithTimeline } from './src/application/shopWithTimeline.js';
|
|
20
|
+
export { default as plantOperations } from './src/application/plantOperations.js';
|
|
21
|
+
export { default as metricsPlant } from './src/application/metricsPlant.js';
|
|
22
|
+
export { default as plantServer } from './src/application/plantServer.js';
|
|
23
|
+
export { default as siteServer } from './src/application/siteServer.js';
|
|
24
|
+
export {
|
|
25
|
+
default as siteOperatorCatalog,
|
|
26
|
+
buildSiteOperatorCatalog
|
|
27
|
+
} from './src/application/siteOperatorCatalog.js';
|
|
28
|
+
export { default as edgeOperatorCatalog } from './src/application/edgeOperatorCatalog.js';
|
|
29
|
+
export { default as operator } from './src/domain/operator/operator.js';
|
|
30
|
+
export { default as operatorsFromPg } from './src/infrastructure/persistence/pg/operators.js';
|
|
31
|
+
export { default as userDecisionsFromPg } from './src/infrastructure/persistence/pg/userDecisions.js';
|
|
32
|
+
export { default as operatorRoute } from './src/infrastructure/http/plant/routes/operatorRoute.js';
|
|
33
|
+
export { default as decisionRoute } from './src/infrastructure/http/plant/routes/decisionRoute.js';
|
|
34
|
+
export { default as operatorJson } from './src/infrastructure/http/plant/json/operatorJson.js';
|
|
35
|
+
export { default as operators } from './src/infrastructure/operators/operators.js';
|
|
36
|
+
export { default as centralOperators } from './src/infrastructure/operators/centralOperators.js';
|
|
37
|
+
export { default as edgeOperators } from './src/infrastructure/operators/edgeOperators.js';
|
|
38
|
+
export { default as operatorsSync } from './src/infrastructure/operators/operatorsSync.js';
|
|
39
|
+
export { default as operatorExtras } from './src/infrastructure/operators/operatorExtras.js';
|
|
24
40
|
|
|
25
|
-
|
|
26
|
-
export { default as
|
|
27
|
-
export { default as
|
|
28
|
-
export { default as
|
|
29
|
-
export { default as
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export {
|
|
36
|
-
export { default as
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
export { default as
|
|
40
|
-
export { default as
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export { default as
|
|
44
|
-
export { default as
|
|
45
|
-
export { default as
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
export { default as
|
|
49
|
-
export { default as
|
|
50
|
-
export { default as
|
|
51
|
-
export { default as
|
|
41
|
+
export { default as pgTimeline } from './src/infrastructure/persistence/pg/timeline.js';
|
|
42
|
+
export { default as pgAlerts } from './src/infrastructure/persistence/pg/alerts.js';
|
|
43
|
+
export { default as pgOperations } from './src/infrastructure/persistence/pg/operations.js';
|
|
44
|
+
export { default as clickhouseConnection } from './src/infrastructure/persistence/clickhouse/connection.js';
|
|
45
|
+
export { default as clickhouseSensor } from './src/infrastructure/persistence/clickhouse/sensor.js';
|
|
46
|
+
export { default as postgresPool } from './src/infrastructure/persistence/postgresPool.js';
|
|
47
|
+
export { metricsSinkFromPool as pgMetricsSink } from './src/infrastructure/persistence/pg/metrics.js';
|
|
48
|
+
export { pgMetricsSensor } from './src/infrastructure/persistence/metricsSensor.js';
|
|
49
|
+
export { default as stompTimeline } from './src/infrastructure/messaging/stomp/timeline.js';
|
|
50
|
+
export { default as stompTimelineSegments } from './src/infrastructure/messaging/stomp/stompTimelineSegments.js';
|
|
51
|
+
export { default as machineOwners } from './src/infrastructure/messaging/ownership/machineOwners.js';
|
|
52
|
+
export { default as httpTimeline } from './src/infrastructure/messaging/ownership/httpTimeline.js';
|
|
53
|
+
export { default as httpOperations } from './src/infrastructure/messaging/ownership/httpOperations.js';
|
|
54
|
+
export { default as ownerTimeline } from './src/infrastructure/messaging/ownership/ownerTimeline.js';
|
|
55
|
+
export { default as stateDataFromPool } from './src/infrastructure/persistence/stateDataFromPool.js';
|
|
56
|
+
export { default as stateDataFromMemory } from './src/infrastructure/persistence/stateDataFromMemory.js';
|
|
57
|
+
export { default as stateDataFake } from './src/infrastructure/persistence/stateDataFromMemory.js';
|
|
58
|
+
export { default as supervisorSink, readDeploymentConfig } from './src/application/supervisorSink.js';
|
|
59
|
+
export { default as runRetention } from './src/infrastructure/ingest/db/runRetention.js';
|
|
60
|
+
export { createEdgeApi, default as edgeApi } from './src/infrastructure/http/edge/edgeApi.js';
|
|
61
|
+
export { default as startTestEdgeApi } from './src/infrastructure/http/edge/startTestEdgeApi.js';
|
|
62
|
+
export { default as stateHttpClient } from './src/infrastructure/http/edge/stateHttpClient.js';
|
|
63
|
+
export { default as metricsSensor } from './src/infrastructure/http/edge/metricsSensor.js';
|
|
64
|
+
export { default as userDecisions } from './src/infrastructure/messaging/stomp/userDecisions.js';
|
|
65
|
+
export { default as stompAlerts } from './src/infrastructure/messaging/stomp/alerts/stompAlerts.js';
|
|
66
|
+
export { default as stompAlertsInit } from './src/infrastructure/messaging/stomp/alerts/stompAlertsInit.js';
|
|
67
|
+
export { default as mqttMetrics } from './src/infrastructure/ingest/mqtt/mqttMetrics.js';
|
|
68
|
+
export { default as modbusMqtt } from './src/infrastructure/ingest/mqtt/modbusMqtt.js';
|
|
69
|
+
export { default as mx210Tcp } from './src/infrastructure/ingest/modbus/mx210Tcp.js';
|
|
70
|
+
export { default as silentStreams } from './src/infrastructure/ingest/modbus/silentStreams.js';
|
|
71
|
+
export { default as activityTracking } from './src/infrastructure/ingest/activity/activityTracking.js';
|
|
72
|
+
export { default as amqpMetricsIngest } from './src/infrastructure/ingest/telemetry/amqpMetricsIngest.js';
|
|
73
|
+
export { default as amqpMqttRelay } from './src/infrastructure/ingest/telemetry/amqpMqttRelay.js';
|
|
74
|
+
export { default as operationSyncIngest } from './src/infrastructure/sync/operationSyncIngest.js';
|
|
75
|
+
export { default as ingestCheckpointMemory, ingestCheckpointPg } from './src/infrastructure/ingest/ingestCheckpoint.js';
|
|
76
|
+
export { default as bindSilentStreams } from './src/application/bindSilentStreams.js';
|
|
77
|
+
export { default as exportQuery } from './src/application/export/exportQuery.js';
|
|
78
|
+
export { default as exportStream } from './src/application/export/exportStream.js';
|
|
79
|
+
export { default as exportSink } from './src/application/export/exportSink.js';
|
|
80
|
+
export { default as exportJob } from './src/application/export/exportJob.js';
|
package/package.json
CHANGED
|
@@ -1,33 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yarkivaev/scada",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "SCADA domain objects
|
|
3
|
+
"version": "2.3.46",
|
|
4
|
+
"description": "SCADA domain objects, state persistence, and plant monitoring",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/yarkivaev/scada"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./index.js",
|
|
13
|
+
"./client": "./src/infrastructure/client/index.js",
|
|
14
|
+
"./stateDataFake": "./src/infrastructure/persistence/stateDataFromMemory.js",
|
|
15
|
+
"./startTestEdgeApi": "./src/infrastructure/http/edge/startTestEdgeApi.js",
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"supervisor-sink": "./src/bin/supervisor-sink.js"
|
|
20
|
+
},
|
|
11
21
|
"scripts": {
|
|
12
|
-
"test": "mocha --exit",
|
|
22
|
+
"test": "mocha --exit 'test/**/test*.js'",
|
|
23
|
+
"test:deep": "mocha --exit 'test/domain/**/test*.js' 'test/infrastructure/ingest/**/test*.js'",
|
|
13
24
|
"coverage": "c8 --all --src src npm test",
|
|
14
|
-
"lint": "eslint .",
|
|
15
|
-
"lint:fix": "eslint . --fix"
|
|
25
|
+
"lint": "eslint . && node scripts/check-eo-names.mjs",
|
|
26
|
+
"lint:fix": "eslint . --fix",
|
|
27
|
+
"lint:eo": "node scripts/check-eo-names.mjs"
|
|
16
28
|
},
|
|
17
|
-
"
|
|
29
|
+
"dependencies": {
|
|
18
30
|
"@clickhouse/client": "^1.0.0",
|
|
31
|
+
"@yarkivaev/simple-server": "^1.0.0",
|
|
32
|
+
"@yarkivaev/source-to-sink": "1.3.2",
|
|
33
|
+
"amqplib": "^0.10.9",
|
|
34
|
+
"pg": "^8.0.0",
|
|
35
|
+
"stompit": "^1.0.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
19
38
|
"@eslint/js": "^9.39.2",
|
|
39
|
+
"better-sqlite3": "^11.0.0",
|
|
20
40
|
"c8": "^10.1.3",
|
|
21
41
|
"cassandra-driver": "^4.7.2",
|
|
22
42
|
"eslint": "^9.39.2",
|
|
23
43
|
"globals": "^17.0.0",
|
|
24
|
-
"mocha": "^10.2
|
|
25
|
-
"better-sqlite3": "^11.0.0",
|
|
26
|
-
"pg": "^8.0.0",
|
|
44
|
+
"mocha": "^10.8.2",
|
|
27
45
|
"testcontainers": "^10.13.0"
|
|
28
46
|
},
|
|
29
47
|
"files": [
|
|
30
48
|
"index.js",
|
|
49
|
+
"db/",
|
|
31
50
|
"src/"
|
|
32
51
|
]
|
|
33
52
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binds silent Modbus streams to a metrics sink and returns an onSeen callback.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* const onSeen = bindSilentStreams(streams, clickhouseSink);
|
|
6
|
+
* amqpMetricsIngest(url, queue, sink, { onSeen });
|
|
7
|
+
*
|
|
8
|
+
* @param {object|undefined} streams - silentStreams gate or omitted
|
|
9
|
+
* @param {object} sink - metrics sink with write(records)
|
|
10
|
+
* @returns {Function|undefined} onSeen(name) or undefined
|
|
11
|
+
*/
|
|
12
|
+
export default function bindSilentStreams(streams, sink) {
|
|
13
|
+
if (!streams) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
streams.bind(sink);
|
|
17
|
+
return (name) => {
|
|
18
|
+
streams.seen(name);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import operators from '../infrastructure/operators/operators.js';
|
|
2
|
+
import centralOperators from '../infrastructure/operators/centralOperators.js';
|
|
3
|
+
import operatorsSync from '../infrastructure/operators/operatorsSync.js';
|
|
4
|
+
import edgeOperators from '../infrastructure/operators/edgeOperators.js';
|
|
5
|
+
import stateHttpClient from '../infrastructure/http/edge/stateHttpClient.js';
|
|
6
|
+
import operatorRoute from '../infrastructure/http/plant/routes/operatorRoute.js';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_SYNC_INTERVAL_SEC = 30;
|
|
9
|
+
|
|
10
|
+
function syncIntervalMs(env) {
|
|
11
|
+
const sec = parseInt(env.OPERATORS_SYNC_INTERVAL_SEC || String(DEFAULT_SYNC_INTERVAL_SEC), 10);
|
|
12
|
+
if (!Number.isFinite(sec) || sec <= 0) {
|
|
13
|
+
return DEFAULT_SYNC_INTERVAL_SEC * 1000;
|
|
14
|
+
}
|
|
15
|
+
return sec * 1000;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Edge operator catalog: in-memory cache, periodic central sync, plant routes.
|
|
20
|
+
* POST create proxies to central and refreshes cache; registration flag is read from cache.
|
|
21
|
+
* Without CENTRAL_PLANT_URL create fails fast; first boot yields an empty list until sync.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} basePath - plant API base path
|
|
24
|
+
* @param {object} env - process environment
|
|
25
|
+
* @returns {object} routes array and optional sync job
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* edgeOperatorCatalog('/api/v1', process.env);
|
|
29
|
+
*/
|
|
30
|
+
export default function edgeOperatorCatalog(basePath, env) {
|
|
31
|
+
const cache = operators();
|
|
32
|
+
const centralUrl = env.CENTRAL_PLANT_URL;
|
|
33
|
+
if (!centralUrl) {
|
|
34
|
+
const provider = edgeOperators(cache, undefined);
|
|
35
|
+
return { routes: operatorRoute(basePath, provider), sync: undefined, provider };
|
|
36
|
+
}
|
|
37
|
+
const client = stateHttpClient({
|
|
38
|
+
baseUrl: centralUrl,
|
|
39
|
+
token: env.CENTRAL_PLANT_TOKEN
|
|
40
|
+
});
|
|
41
|
+
const source = centralOperators(client, basePath);
|
|
42
|
+
const provider = edgeOperators(cache, source);
|
|
43
|
+
const sync = operatorsSync(source, cache, { intervalMs: syncIntervalMs(env) });
|
|
44
|
+
return { routes: operatorRoute(basePath, provider), sync, provider };
|
|
45
|
+
}
|