@yarkivaev/scada 2.3.81 → 2.3.83

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 CHANGED
@@ -89,7 +89,7 @@ One `off` phase can be replaced by contiguous pieces. There is no parent row and
89
89
 
90
90
  A later edit sends the whole family again. `timeline.respond` / `PATCH /segments` take `{ splits: [{ start, end, tag, open }] }` addressed by the original start. The supervisor publishes one STOMP body, `type: split-family`, with `origin` in epoch millis and the pieces. The sink deletes family starts the new cut does not use, then upserts the pieces, in one transaction.
91
91
 
92
- The replacement must keep the original start and, for a closed off, the latest end of the stored family. Removing every piece but one therefore restores those bounds. `splits: []` is refused before any delete, and the stored family stays. A closed piece is at least 60 seconds. Only the tail of a still-open off may have `duration = 0`. A duration-0 heartbeat does not extend a closed piece that already has `split_of`.
92
+ The replacement must keep the original start and, for a closed off, the latest end of the stored family. Removing every piece but one therefore restores those bounds. `splits: []` is refused before any delete, and the stored family stays. A piece may be any positive length. Only the tail of a still-open off may have `duration = 0`. A duration-0 heartbeat does not extend a closed piece that already has `split_of`.
93
93
 
94
94
  `C0007` grants `DELETE` on `segments` to `supervisor_sink` on the privileged admin path. The sink role does not run that grant itself. Central previously revoked this privilege (`C0002`).
95
95
 
@@ -194,6 +194,7 @@ Set `SINK_DB_PROFILE=central|edge`. Migrations run at supervisor-sink startup vi
194
194
  | `POSTGRES_URL` | Application DB user (`supervisor_sink`) |
195
195
  | `POSTGRES_ADMIN_URL` | Admin user for migrations |
196
196
  | `SINK_DB_PROFILE` | `central` or `edge` |
197
+ | `SINK_PIPELINES` | `false` skips STOMP segment, decision, and alert consumers. The site container sets this and a `supervisor-sink` container keeps the default |
197
198
  | `STATE_HTTP_PORT` / `STATE_HTTP_TOKEN` | Edge internal API |
198
199
  | `MQTT_URL`, `MQTT_TOPICS` | Optional metrics ingest |
199
200
  | `CLICKHOUSE_URL` | Central metrics sink |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yarkivaev/scada",
3
- "version": "2.3.81",
3
+ "version": "2.3.83",
4
4
  "description": "SCADA domain objects, state persistence, and plant monitoring",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,6 +15,8 @@ const appRoot = path.dirname(fileURLToPath(import.meta.url));
15
15
 
16
16
  /**
17
17
  * Reads deployment environment into a normalized config object.
18
+ * `SINK_PIPELINES=false` leaves Postgres and the state HTTP API in this process
19
+ * and leaves the STOMP consumers to another container.
18
20
  *
19
21
  * @param {NodeJS.ProcessEnv} env - process environment
20
22
  * @returns {object} deployment configuration
@@ -35,6 +37,7 @@ export function readDeploymentConfig(env) {
35
37
  retentionDays: parseInt(env.RETENTION_DAYS || '30', 10),
36
38
  migrationsDir: path.join(root, 'db', 'migrations'),
37
39
  requestTimeoutMs: parseRequestTimeoutMs(env.REQUEST_TIMEOUT_MS),
40
+ pipelines: env.SINK_PIPELINES !== 'false',
38
41
  pipeline: {
39
42
  size: parseInt(env.BATCH_SIZE || '50', 10),
40
43
  interval: parseInt(env.FLUSH_INTERVAL || '2', 10),
@@ -87,9 +90,11 @@ export default function supervisorSink(env = process.env) {
87
90
  } finally {
88
91
  await adminPool.end();
89
92
  }
90
- await segments.start();
91
- decisions.start();
92
- alerts.start();
93
+ if (cfg.pipelines) {
94
+ await segments.start();
95
+ decisions.start();
96
+ alerts.start();
97
+ }
93
98
  if (httpServer) {
94
99
  httpServer.start();
95
100
  }
@@ -1,5 +1,3 @@
1
- const MIN_MS = 60000;
2
-
3
1
  function dated(piece) {
4
2
  return {
5
3
  start: new Date(piece.start),
@@ -29,11 +27,8 @@ function refuseGap(ordered, piece, index) {
29
27
 
30
28
  function refuseSpan(piece) {
31
29
  const span = piece.end.getTime() - piece.start.getTime();
32
- if (!piece.open && span < MIN_MS) {
33
- throw new RangeError('piece is shorter than 60 seconds');
34
- }
35
- if (piece.open && span < 0) {
36
- throw new RangeError('open tail ends before it starts');
30
+ if (span < 0) {
31
+ throw new RangeError('piece ends before it starts');
37
32
  }
38
33
  }
39
34