@yarkivaev/scada 2.3.47 → 2.3.48
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/db/migrations/V0006__operations_machine_kind_time.sql +2 -0
- package/package.json +1 -1
- package/src/application/plantOperations.js +2 -2
- package/src/domain/operation/operations.js +7 -2
- package/src/infrastructure/persistence/memory/operations.js +38 -0
- package/src/infrastructure/persistence/pg/operations.js +32 -1
package/package.json
CHANGED
|
@@ -6,9 +6,9 @@ import pubsub from '../domain/shared/pubsub.js';
|
|
|
6
6
|
*
|
|
7
7
|
* Optional kindSources inject non-PG kinds into listForMachine merges.
|
|
8
8
|
*
|
|
9
|
-
* @param {object} persistence - operations store with upsert, get, remove, listForMachine
|
|
9
|
+
* @param {object} persistence - operations store with upsert, get, remove, listForMachine, latestForMachine
|
|
10
10
|
* @param {object} [kindSources] - map of kind to { list(machineId, range) }
|
|
11
|
-
* @returns {object} operations with listForMachine, upsert, get, remove, and stream
|
|
11
|
+
* @returns {object} operations with listForMachine, latestForMachine, upsert, get, remove, and stream
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
14
14
|
* const ops = plantOperations(dataAccess.operations, { temp: temperaturePort });
|
|
@@ -32,15 +32,17 @@ function resolveKinds(kinds, sources) {
|
|
|
32
32
|
*
|
|
33
33
|
* listForMachine merges requested kinds from injectable sources and Postgres,
|
|
34
34
|
* sorted by occurred_at. Omitted kinds default to injectable source keys.
|
|
35
|
+
* latestForMachine reads only from persistence (single kind).
|
|
35
36
|
*
|
|
36
|
-
* @param {object} persistence - store with upsert, get, remove, listForMachine
|
|
37
|
+
* @param {object} persistence - store with upsert, get, remove, listForMachine, latestForMachine
|
|
37
38
|
* @param {object} bus - pubsub instance with stream and emit methods
|
|
38
39
|
* @param {object} [kindSources] - map of kind to { list(machineId, range) }
|
|
39
|
-
* @returns {object} operations with listForMachine, upsert, get, remove, stream
|
|
40
|
+
* @returns {object} operations with listForMachine, latestForMachine, upsert, get, remove, stream
|
|
40
41
|
*
|
|
41
42
|
* @example
|
|
42
43
|
* const ops = operations(store, bus, { temp: temperaturePort });
|
|
43
44
|
* await ops.listForMachine('m1', ['chem', 'temp'], { from: new Date() });
|
|
45
|
+
* await ops.latestForMachine('m1', 'chem', { to: new Date() });
|
|
44
46
|
* await ops.remove('m1', 'nb-1');
|
|
45
47
|
*/
|
|
46
48
|
export default function operations(persistence, bus, kindSources) {
|
|
@@ -59,6 +61,9 @@ export default function operations(persistence, bus, kindSources) {
|
|
|
59
61
|
});
|
|
60
62
|
});
|
|
61
63
|
},
|
|
64
|
+
latestForMachine(machineId, kind, bound) {
|
|
65
|
+
return persistence.latestForMachine(machineId, kind, bound);
|
|
66
|
+
},
|
|
62
67
|
upsert(item) {
|
|
63
68
|
const updatedAt = new Date();
|
|
64
69
|
const row = stampRow(item, updatedAt);
|
|
@@ -37,6 +37,41 @@ function filterList(store, machineId, kind, range) {
|
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Picks the latest matching row for a machine and kind at a time bound.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} store - shared mutable store with operations array
|
|
44
|
+
* @param {string} machineId - machine identifier
|
|
45
|
+
* @param {string} kind - operation kind
|
|
46
|
+
* @param {{ to?: Date, before?: Date }} bound - inclusive to or exclusive before
|
|
47
|
+
* @returns {object|null} latest row or null
|
|
48
|
+
*/
|
|
49
|
+
function filterLatest(store, machineId, kind, bound) {
|
|
50
|
+
const range = bound || {};
|
|
51
|
+
const before = range.before ? new Date(range.before).getTime() : null;
|
|
52
|
+
const to = range.to ? new Date(range.to).getTime() : null;
|
|
53
|
+
const matches = store.operations.filter((row) => {
|
|
54
|
+
if (row.machine !== machineId || row.kind !== kind) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const at = new Date(row.occurred_at).getTime();
|
|
58
|
+
if (before !== null && at >= before) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (to !== null && at > to) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}).sort((left, right) => {
|
|
66
|
+
const delta = new Date(right.occurred_at) - new Date(left.occurred_at);
|
|
67
|
+
if (delta !== 0) {
|
|
68
|
+
return delta;
|
|
69
|
+
}
|
|
70
|
+
return String(right.key).localeCompare(String(left.key));
|
|
71
|
+
});
|
|
72
|
+
return matches[0] || null;
|
|
73
|
+
}
|
|
74
|
+
|
|
40
75
|
/**
|
|
41
76
|
* In-memory operations state port for tests and local runs.
|
|
42
77
|
*
|
|
@@ -82,6 +117,9 @@ export default function operationStateMemory(store) {
|
|
|
82
117
|
},
|
|
83
118
|
listForMachine(machineId, kind, range) {
|
|
84
119
|
return Promise.resolve(filterList(store, machineId, kind, range));
|
|
120
|
+
},
|
|
121
|
+
latestForMachine(machineId, kind, bound) {
|
|
122
|
+
return Promise.resolve(filterLatest(store, machineId, kind, bound));
|
|
85
123
|
}
|
|
86
124
|
};
|
|
87
125
|
}
|
|
@@ -38,6 +38,33 @@ function listForMachine(pool, machineId, kind, range) {
|
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Loads the latest operation for a machine and kind at or before a bound.
|
|
43
|
+
*
|
|
44
|
+
* @param {object} pool - pg pool
|
|
45
|
+
* @param {string} machineId - machine identifier
|
|
46
|
+
* @param {string} kind - operation kind
|
|
47
|
+
* @param {{ to?: Date, before?: Date }} bound - inclusive to or exclusive before
|
|
48
|
+
* @returns {Promise<object|null>} latest row or null
|
|
49
|
+
*/
|
|
50
|
+
function latestForMachine(pool, machineId, kind, bound) {
|
|
51
|
+
const range = bound || {};
|
|
52
|
+
let sql = `SELECT machine, occurred_at, kind, key, payload
|
|
53
|
+
FROM operations WHERE machine = $1 AND kind = $2`;
|
|
54
|
+
const prm = [machineId, kind];
|
|
55
|
+
if (range.before) {
|
|
56
|
+
prm.push(range.before);
|
|
57
|
+
sql += ` AND occurred_at < $${prm.length}`;
|
|
58
|
+
} else if (range.to) {
|
|
59
|
+
prm.push(range.to);
|
|
60
|
+
sql += ` AND occurred_at <= $${prm.length}`;
|
|
61
|
+
}
|
|
62
|
+
sql += ' ORDER BY occurred_at DESC, key DESC LIMIT 1';
|
|
63
|
+
return pool.query(sql, prm).then((result) => {
|
|
64
|
+
return result.rows[0] || null;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
41
68
|
function missing(machineId, key) {
|
|
42
69
|
return new Error(`operation '${key}' not found for machine '${machineId}'`);
|
|
43
70
|
}
|
|
@@ -70,11 +97,12 @@ async function removeOperation(pool, machineId, key) {
|
|
|
70
97
|
* PostgreSQL operations persistence port for generic machine operations.
|
|
71
98
|
*
|
|
72
99
|
* @param {object} pool - pg pool
|
|
73
|
-
* @returns {object} operations port with upsert, get, remove,
|
|
100
|
+
* @returns {object} operations port with upsert, get, remove, listForMachine, latestForMachine
|
|
74
101
|
*
|
|
75
102
|
* @example
|
|
76
103
|
* const store = operationStatePg(pool);
|
|
77
104
|
* await store.upsert({ machine: 'm1', key: 'nb-1', kind: 'chem', ... });
|
|
105
|
+
* await store.latestForMachine('m1', 'chem', { to: new Date() });
|
|
78
106
|
* await store.remove('m1', 'nb-1');
|
|
79
107
|
*/
|
|
80
108
|
export default function operationStatePg(pool) {
|
|
@@ -90,6 +118,9 @@ export default function operationStatePg(pool) {
|
|
|
90
118
|
},
|
|
91
119
|
listForMachine(machineId, kind, range) {
|
|
92
120
|
return listForMachine(pool, machineId, kind, range);
|
|
121
|
+
},
|
|
122
|
+
latestForMachine(machineId, kind, bound) {
|
|
123
|
+
return latestForMachine(pool, machineId, kind, bound);
|
|
93
124
|
}
|
|
94
125
|
};
|
|
95
126
|
}
|