@sakibcoolz/core 0.1.1 → 0.2.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.
- package/README.md +67 -7
- package/dist/acquisition.d.ts +29 -0
- package/dist/acquisition.d.ts.map +1 -0
- package/dist/acquisition.js +30 -0
- package/dist/acquisition.js.map +1 -0
- package/dist/assembly.d.ts +5 -2
- package/dist/assembly.d.ts.map +1 -1
- package/dist/assembly.js +7 -4
- package/dist/assembly.js.map +1 -1
- package/dist/attach.d.ts +4 -2
- package/dist/attach.d.ts.map +1 -1
- package/dist/attach.js +5 -3
- package/dist/attach.js.map +1 -1
- package/dist/clipboard.d.ts +6 -4
- package/dist/clipboard.d.ts.map +1 -1
- package/dist/clipboard.js +25 -4
- package/dist/clipboard.js.map +1 -1
- package/dist/collision.d.ts +2 -2
- package/dist/collision.js.map +1 -1
- package/dist/digest.d.ts +9 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +68 -0
- package/dist/digest.js.map +1 -0
- package/dist/flow.d.ts +3 -2
- package/dist/flow.d.ts.map +1 -1
- package/dist/flow.js.map +1 -1
- package/dist/graph.d.ts +67 -7
- package/dist/graph.d.ts.map +1 -1
- package/dist/graph.js +195 -10
- package/dist/graph.js.map +1 -1
- package/dist/index.d.ts +13 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/observation.d.ts +1 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +4 -1
- package/dist/observation.js.map +1 -1
- package/dist/routing.d.ts +6 -3
- package/dist/routing.d.ts.map +1 -1
- package/dist/routing.js +12 -2
- package/dist/routing.js.map +1 -1
- package/dist/scene.d.ts +245 -5
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +94 -3
- package/dist/scene.js.map +1 -1
- package/dist/serialization.d.ts +7 -7
- package/dist/serialization.d.ts.map +1 -1
- package/dist/serialization.js +120 -8
- package/dist/serialization.js.map +1 -1
- package/dist/snapping.d.ts +2 -2
- package/dist/snapping.d.ts.map +1 -1
- package/dist/snapping.js +10 -7
- package/dist/snapping.js.map +1 -1
- package/dist/status.d.ts +56 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +114 -0
- package/dist/status.js.map +1 -0
- package/dist/tags.d.ts +52 -0
- package/dist/tags.d.ts.map +1 -0
- package/dist/tags.js +103 -0
- package/dist/tags.js.map +1 -0
- package/dist/telemetry.d.ts +42 -4
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +84 -8
- package/dist/telemetry.js.map +1 -1
- package/dist/touching.d.ts +2 -2
- package/dist/touching.d.ts.map +1 -1
- package/dist/touching.js +7 -4
- package/dist/touching.js.map +1 -1
- package/dist/units.js +1 -1
- package/dist/units.js.map +1 -1
- package/dist/upgrade.d.ts +2 -2
- package/dist/upgrade.js.map +1 -1
- package/dist/validation.d.ts +33 -6
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +806 -39
- package/dist/validation.js.map +1 -1
- package/package.json +4 -2
- package/schema/fixtures/invalid/association-without-relationship.scene.json +69 -0
- package/schema/fixtures/invalid/binding-kind.scene.json +14 -0
- package/schema/fixtures/invalid/command-zero-step.scene.json +105 -0
- package/schema/fixtures/invalid/empty-name.scene.json +30 -0
- package/schema/fixtures/invalid/expected.json +70 -0
- package/schema/fixtures/invalid/extension-not-namespaced.scene.json +10 -0
- package/schema/fixtures/invalid/extension-prototype-key.scene.json +12 -0
- package/schema/fixtures/invalid/missing-document-type.scene.json +6 -0
- package/schema/fixtures/invalid/non-string-label.scene.json +32 -0
- package/schema/fixtures/invalid/underlay-opacity.scene.json +17 -0
- package/schema/fixtures/invalid/unknown-component-kind.scene.json +29 -0
- package/schema/fixtures/invalid/unknown-relationship.scene.json +63 -0
- package/schema/fixtures/invalid/unknown-root-field.scene.json +8 -0
- package/schema/fixtures/invalid/unsupported-version.scene.json +7 -0
- package/schema/fixtures/invalid/view-rotation.scene.json +139 -0
- package/schema/fixtures/invalid/writable-string.scene.json +93 -0
- package/schema/fixtures/invalid/writable-without-limits.scene.json +100 -0
- package/schema/fixtures/invalid/zero-scale.scene.json +29 -0
- package/schema/fixtures/migration/README.md +18 -0
- package/schema/fixtures/migration/v1-demo.scene.json +160 -0
- package/schema/fixtures/migration/v1-demo.v4.json +198 -0
- package/schema/fixtures/migration/v2-demo.scene.json +244 -0
- package/schema/fixtures/migration/v2-demo.v4.json +245 -0
- package/schema/fixtures/migration/v2-generated-12.scene.json +473 -0
- package/schema/fixtures/migration/v2-generated-12.v4.json +474 -0
- package/schema/fixtures/migration/v3-demo.scene.json +244 -0
- package/schema/fixtures/migration/v3-demo.v4.json +245 -0
- package/schema/fixtures/migration/v3-generated-12.scene.json +473 -0
- package/schema/fixtures/migration/v3-generated-12.v4.json +474 -0
- package/schema/fixtures/migration/v3-mostly-connected-plant.scene.json +386 -0
- package/schema/fixtures/migration/v3-mostly-connected-plant.v4.json +387 -0
- package/schema/fixtures/migration/v3-open-joints-plant.scene.json +288 -0
- package/schema/fixtures/migration/v3-open-joints-plant.v4.json +289 -0
- package/schema/fixtures/migration/v3-unconnected-plant.scene.json +14 -0
- package/schema/fixtures/migration/v3-unconnected-plant.v4.json +276 -0
- package/schema/fixtures/projection/acquisition.plan.json +50 -0
- package/schema/fixtures/projection/control-line.plan.json +86 -0
- package/schema/fixtures/valid/acquisition.scene.json +117 -0
- package/schema/fixtures/valid/associations.scene.json +79 -0
- package/schema/fixtures/valid/commands.scene.json +155 -0
- package/schema/fixtures/valid/control-line.scene.json +210 -0
- package/schema/fixtures/valid/empty.scene.json +7 -0
- package/schema/fixtures/valid/identity-and-extensions.scene.json +96 -0
- package/schema/fixtures/valid/tank-valve-line.scene.json +89 -0
- package/schema/fixtures/valid/views.scene.json +139 -0
- package/schema/fixtures/validator-only/association-id-reuses-connection.scene.json +70 -0
- package/schema/fixtures/validator-only/association-unknown-component.scene.json +70 -0
- package/schema/fixtures/validator-only/command-max-below-min.scene.json +104 -0
- package/schema/fixtures/validator-only/command-out-of-type-range.scene.json +97 -0
- package/schema/fixtures/validator-only/connection-domain-mismatch.scene.json +63 -0
- package/schema/fixtures/validator-only/credential-in-connection.scene.json +66 -0
- package/schema/fixtures/validator-only/credential-in-extension.scene.json +14 -0
- package/schema/fixtures/validator-only/credential-label.scene.json +32 -0
- package/schema/fixtures/validator-only/duplicate-component-id.scene.json +50 -0
- package/schema/fixtures/validator-only/endpoint-with-credentials.scene.json +74 -0
- package/schema/fixtures/validator-only/expected.json +66 -0
- package/schema/fixtures/validator-only/modbus-coil-float.scene.json +100 -0
- package/schema/fixtures/validator-only/non-unit-rotation.scene.json +29 -0
- package/schema/fixtures/validator-only/unknown-data-source.scene.json +100 -0
- package/schema/fixtures/validator-only/unknown-port.scene.json +62 -0
- package/schema/fixtures/validator-only/unregistered-host-type.scene.json +29 -0
- package/schema/fixtures/validator-only/view-unknown-component.scene.json +143 -0
- package/schema/industrial-scene.v4.schema.json +365 -0
package/dist/digest.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { serializeScene } from './serialization.js';
|
|
2
|
+
// Document digests (ADR-0028): SHA-256 (FIPS 180-4) of a scene's canonical serialization, so a runtime
|
|
3
|
+
// can check it shows exactly the revision a host approved. Synchronous and dependency-free: `crypto.subtle`
|
|
4
|
+
// exists only on secure origins, and plant LANs often serve plain HTTP. Integrity, not authenticity:
|
|
5
|
+
// signing who approved a revision is the host's job.
|
|
6
|
+
const K = new Uint32Array([
|
|
7
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
|
8
|
+
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
9
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
|
10
|
+
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
11
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
|
12
|
+
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
13
|
+
]);
|
|
14
|
+
const rotr = (x, n) => (x >>> n) | (x << (32 - n));
|
|
15
|
+
/** SHA-256 of a string's UTF-8 bytes, as 64 lower-case hex digits. */
|
|
16
|
+
export function sha256Hex(text) {
|
|
17
|
+
const data = new TextEncoder().encode(text);
|
|
18
|
+
// Message, a 1 bit, zero padding, and the bit length as a 64-bit big-endian number.
|
|
19
|
+
const blocks = Math.ceil((data.length + 9) / 64);
|
|
20
|
+
const bytes = new Uint8Array(blocks * 64);
|
|
21
|
+
bytes.set(data);
|
|
22
|
+
bytes[data.length] = 0x80;
|
|
23
|
+
const view = new DataView(bytes.buffer);
|
|
24
|
+
const bits = data.length * 8;
|
|
25
|
+
view.setUint32(bytes.length - 8, Math.floor(bits / 0x1_0000_0000));
|
|
26
|
+
view.setUint32(bytes.length - 4, bits >>> 0);
|
|
27
|
+
const h = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
|
|
28
|
+
const w = new Uint32Array(64);
|
|
29
|
+
for (let offset = 0; offset < bytes.length; offset += 64) {
|
|
30
|
+
for (let i = 0; i < 16; i++)
|
|
31
|
+
w[i] = view.getUint32(offset + i * 4);
|
|
32
|
+
for (let i = 16; i < 64; i++) {
|
|
33
|
+
const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >>> 3);
|
|
34
|
+
const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >>> 10);
|
|
35
|
+
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
|
|
36
|
+
}
|
|
37
|
+
let [a, b, c, d, e, f, g, hh] = h;
|
|
38
|
+
for (let i = 0; i < 64; i++) {
|
|
39
|
+
const t1 = (hh + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + ((e & f) ^ (~e & g)) + K[i] + w[i]) >>> 0;
|
|
40
|
+
const t2 = ((rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0;
|
|
41
|
+
hh = g;
|
|
42
|
+
g = f;
|
|
43
|
+
f = e;
|
|
44
|
+
e = (d + t1) >>> 0;
|
|
45
|
+
d = c;
|
|
46
|
+
c = b;
|
|
47
|
+
b = a;
|
|
48
|
+
a = (t1 + t2) >>> 0;
|
|
49
|
+
}
|
|
50
|
+
h[0] += a;
|
|
51
|
+
h[1] += b;
|
|
52
|
+
h[2] += c;
|
|
53
|
+
h[3] += d;
|
|
54
|
+
h[4] += e;
|
|
55
|
+
h[5] += f;
|
|
56
|
+
h[6] += g;
|
|
57
|
+
h[7] += hh;
|
|
58
|
+
}
|
|
59
|
+
return Array.from(h, (x) => x.toString(16).padStart(8, '0')).join('');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The digest a host records when it approves a revision: SHA-256 of `serializeScene(scene)`, so
|
|
63
|
+
* whitespace and key order in the stored file do not matter. `ScadaRuntime` compares it before drawing.
|
|
64
|
+
*/
|
|
65
|
+
export function sceneDigest(scene) {
|
|
66
|
+
return sha256Hex(serializeScene(scene));
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=digest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ScadaScene } from './scene.js';\nimport { serializeScene } from './serialization.js';\n\n// Document digests (ADR-0028): SHA-256 (FIPS 180-4) of a scene's canonical serialization, so a runtime\n// can check it shows exactly the revision a host approved. Synchronous and dependency-free: `crypto.subtle`\n// exists only on secure origins, and plant LANs often serve plain HTTP. Integrity, not authenticity:\n// signing who approved a revision is the host's job.\n\nconst K = new Uint32Array([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,\n 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,\n 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,\n 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,\n]);\n\nconst rotr = (x: number, n: number) => (x >>> n) | (x << (32 - n));\n\n/** SHA-256 of a string's UTF-8 bytes, as 64 lower-case hex digits. */\nexport function sha256Hex(text: string): string {\n const data = new TextEncoder().encode(text);\n // Message, a 1 bit, zero padding, and the bit length as a 64-bit big-endian number.\n const blocks = Math.ceil((data.length + 9) / 64);\n const bytes = new Uint8Array(blocks * 64);\n bytes.set(data);\n bytes[data.length] = 0x80;\n const view = new DataView(bytes.buffer);\n const bits = data.length * 8;\n view.setUint32(bytes.length - 8, Math.floor(bits / 0x1_0000_0000));\n view.setUint32(bytes.length - 4, bits >>> 0);\n\n const h = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);\n const w = new Uint32Array(64);\n for (let offset = 0; offset < bytes.length; offset += 64) {\n for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4);\n for (let i = 16; i < 64; i++) {\n const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >>> 3);\n const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >>> 10);\n w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;\n }\n let [a, b, c, d, e, f, g, hh] = h;\n for (let i = 0; i < 64; i++) {\n const t1 = (hh + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + ((e & f) ^ (~e & g)) + K[i] + w[i]) >>> 0;\n const t2 = ((rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0;\n hh = g;\n g = f;\n f = e;\n e = (d + t1) >>> 0;\n d = c;\n c = b;\n b = a;\n a = (t1 + t2) >>> 0;\n }\n h[0] += a; h[1] += b; h[2] += c; h[3] += d; h[4] += e; h[5] += f; h[6] += g; h[7] += hh;\n }\n return Array.from(h, (x) => x.toString(16).padStart(8, '0')).join('');\n}\n\n/**\n * The digest a host records when it approves a revision: SHA-256 of `serializeScene(scene)`, so\n * whitespace and key order in the stored file do not matter. `ScadaRuntime` compares it before drawing.\n */\nexport function sceneDigest(scene: ScadaScene): string {\n return sha256Hex(serializeScene(scene));\n}\n"]}
|
package/dist/flow.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ScadaScene } from './scene.js';
|
|
2
|
-
|
|
2
|
+
/** `DISCONNECTED`: the link to the source is down, as distinct from a bad reading (ADR-0024). */
|
|
3
|
+
export type Quality = 'GOOD' | 'BAD' | 'UNCERTAIN' | 'STALE' | 'DISCONNECTED';
|
|
3
4
|
export type ValueSource = 'MEASURED' | 'SIMULATED';
|
|
4
5
|
/** One flow observation supplied by the host. The SDK never fetches readings itself. */
|
|
5
6
|
export interface FlowReading {
|
|
@@ -12,7 +13,7 @@ export interface FlowReading {
|
|
|
12
13
|
sourceTimestamp: string;
|
|
13
14
|
}
|
|
14
15
|
export type FlowDirection = 'forward' | 'reverse' | 'stopped' | 'unknown';
|
|
15
|
-
export type FlowUnknownReason = 'NO_READING' | 'BAD_QUALITY' | 'STALE' | 'INVALID_TIMESTAMP' | 'INVALID_VALUE';
|
|
16
|
+
export type FlowUnknownReason = 'NO_READING' | 'BAD_QUALITY' | 'DISCONNECTED' | 'STALE' | 'INVALID_TIMESTAMP' | 'INVALID_VALUE';
|
|
16
17
|
export type FlowState = {
|
|
17
18
|
connectionId: string;
|
|
18
19
|
direction: Exclude<FlowDirection, 'unknown'>;
|
package/dist/flow.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../src/flow.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,WAAW,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../src/flow.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,iGAAiG;AACjG,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,WAAW,GAAG,OAAO,GAAG,cAAc,CAAC;AAC9E,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,WAAW,CAAC;AAEnD,wFAAwF;AACxF,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,yGAAyG;IACzG,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,0DAA0D;IAC1D,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAC1E,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,GAAG,mBAAmB,GAAG,eAAe,CAAC;AAEhI,MAAM,MAAM,SAAS,GACjB;IACE,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,uFAAuF;IACvF,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,GACD;IACE,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,yGAAyG;IACzG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB,CAAC;AAEN,MAAM,WAAW,WAAW;IAC1B,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,8FAA8F;IAC9F,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,sFAAsF;IACtF,sBAAsB,EAAE,MAAM,EAAE,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,WAAW,EAAE,EAAE,OAAO,EAAE,WAAW,GAAG,cAAc,CAkDrH"}
|
package/dist/flow.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flow.js","sourceRoot":"","sources":["../src/flow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"flow.js","sourceRoot":"","sources":["../src/flow.ts"],"names":[],"mappings":"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","sourcesContent":["import { assessObservation, isNewer, qualities } from './observation.js';\nimport type { ScadaScene } from './scene.js';\n\n/** `DISCONNECTED`: the link to the source is down, as distinct from a bad reading (ADR-0024). */\nexport type Quality = 'GOOD' | 'BAD' | 'UNCERTAIN' | 'STALE' | 'DISCONNECTED';\nexport type ValueSource = 'MEASURED' | 'SIMULATED';\n\n/** One flow observation supplied by the host. The SDK never fetches readings itself. */\nexport interface FlowReading {\n connectionId: string;\n /** Volumetric flow in m³/s, signed: positive runs from the connection's `from` port to its `to` port. */\n rate: number;\n quality: Quality;\n source: ValueSource;\n /** ISO 8601 time the value was observed at its source. */\n sourceTimestamp: string;\n}\n\nexport type FlowDirection = 'forward' | 'reverse' | 'stopped' | 'unknown';\nexport type FlowUnknownReason = 'NO_READING' | 'BAD_QUALITY' | 'DISCONNECTED' | 'STALE' | 'INVALID_TIMESTAMP' | 'INVALID_VALUE';\n\nexport type FlowState =\n | {\n connectionId: string;\n direction: Exclude<FlowDirection, 'unknown'>;\n rate: number;\n quality: Quality;\n source: ValueSource;\n sourceTimestamp: string;\n /** The reading's quality is UNCERTAIN: show the direction, but mark it as doubtful. */\n uncertain: boolean;\n reason?: undefined;\n }\n | {\n connectionId: string;\n direction: 'unknown';\n reason: FlowUnknownReason;\n /** Present when a reading existed but could not be trusted, so the viewer can say why and since when. */\n quality?: Quality;\n source?: ValueSource;\n sourceTimestamp?: string;\n rate?: undefined;\n };\n\nexport interface FlowOptions {\n /** Current time in epoch milliseconds. Passed in so results are deterministic. */\n now: number;\n /** Which readings to use. Measured and simulated values are never mixed. Default MEASURED. */\n source?: ValueSource;\n /** Readings older than this are STALE. Default 10,000 ms. */\n staleAfterMs?: number;\n /** Timestamps further than this in the future are rejected. Default 5,000 ms. */\n maxClockSkewMs?: number;\n /** |rate| at or below this (m³/s) counts as stopped. Default 1e-6. */\n zeroThreshold?: number;\n}\n\nexport interface FlowResolution {\n /** One state per scene connection, in connection order. */\n states: FlowState[];\n /** Connection ids that had readings but are not in the scene, in first-seen order. */\n unmatchedConnectionIds: string[];\n}\n\n/**\n * Decides the flow direction of every connection from host-supplied readings only. Topology and\n * valve state are never used to infer flow: no trustworthy reading means `unknown`, and a known\n * flow into a tee says nothing about its branches.\n */\nexport function resolveFlow(scene: ScadaScene, readings: readonly FlowReading[], options: FlowOptions): FlowResolution {\n const source = options.source ?? 'MEASURED';\n const staleAfterMs = options.staleAfterMs ?? 10_000;\n const maxClockSkewMs = options.maxClockSkewMs ?? 5_000;\n const zeroThreshold = options.zeroThreshold ?? 1e-6;\n\n const known = new Set(scene.connections.map((c) => c.id));\n const latest = new Map<string, FlowReading>();\n const unmatched = new Set<string>();\n for (const r of readings) {\n if (!known.has(r.connectionId)) {\n unmatched.add(r.connectionId);\n continue;\n }\n if (r.source !== source) continue;\n if (isNewer(r, latest.get(r.connectionId))) latest.set(r.connectionId, r);\n }\n\n const states = scene.connections.map((connection): FlowState => {\n const connectionId = connection.id;\n const r = latest.get(connectionId);\n if (!r) return { connectionId, direction: 'unknown', reason: 'NO_READING' };\n\n const validQuality = qualities.includes(r.quality);\n const unknown = (reason: FlowUnknownReason): FlowState => ({\n connectionId,\n direction: 'unknown',\n reason,\n ...(validQuality ? { quality: r.quality } : {}),\n source: r.source,\n sourceTimestamp: r.sourceTimestamp,\n });\n\n if (typeof r.rate !== 'number' || !Number.isFinite(r.rate)) return unknown('INVALID_VALUE');\n const problem = assessObservation(r, { now: options.now, staleAfterMs, maxClockSkewMs });\n if (problem) return unknown(problem);\n\n const direction = Math.abs(r.rate) <= zeroThreshold ? 'stopped' : r.rate > 0 ? 'forward' : 'reverse';\n return {\n connectionId,\n direction,\n rate: r.rate,\n quality: r.quality,\n source: r.source,\n sourceTimestamp: r.sourceTimestamp,\n uncertain: r.quality === 'UNCERTAIN',\n };\n });\n\n return { states, unmatchedConnectionIds: [...unmatched] };\n}\n"]}
|
package/dist/graph.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentInstance, type ComponentType, type ConnectionPort, type PortKind, type Relationship, type ScadaScene, type SceneAssociation, type SceneConnection } from './scene.js';
|
|
2
2
|
import { type SceneValidationErrorCode, type SceneValidationOptions } from './validation.js';
|
|
3
|
-
export type GraphErrorCode = SceneValidationErrorCode | 'UNKNOWN_CONNECTION';
|
|
3
|
+
export type GraphErrorCode = SceneValidationErrorCode | 'UNKNOWN_CONNECTION' | 'UNKNOWN_ASSOCIATION';
|
|
4
4
|
export interface GraphError {
|
|
5
5
|
code: GraphErrorCode;
|
|
6
6
|
message: string;
|
|
@@ -12,21 +12,81 @@ export type GraphResult = {
|
|
|
12
12
|
ok: false;
|
|
13
13
|
error: GraphError;
|
|
14
14
|
};
|
|
15
|
-
export type GraphOptions = Pick<SceneValidationOptions, 'resolvePorts' | 'resolveBindable' | 'validateProperties' | 'limits'>;
|
|
15
|
+
export type GraphOptions = Pick<SceneValidationOptions, 'resolvePorts' | 'resolveType' | 'resolveBindable' | 'validateProperties' | 'limits'>;
|
|
16
16
|
/**
|
|
17
17
|
* Adds one edge. The candidate scene goes through `validateScene`, so the same rules apply as on
|
|
18
18
|
* load (known ports, compatibility, occupancy, no duplicates). The input scene must already be valid.
|
|
19
19
|
*/
|
|
20
20
|
export declare function connect(scene: ScadaScene, connection: SceneConnection, options: GraphOptions): GraphResult;
|
|
21
21
|
export declare function disconnect(scene: ScadaScene, connectionId: string): GraphResult;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Adds one association (ADR-0023), such as "PLC-01 controls VFD-101". Validated like `connect`:
|
|
24
|
+
* both components exist and differ, the id is unused, and the same fact is not recorded twice.
|
|
25
|
+
*/
|
|
26
|
+
export declare function addAssociation(scene: ScadaScene, association: SceneAssociation, options: GraphOptions): GraphResult;
|
|
27
|
+
/** Removes one association. The list is dropped when it becomes empty, so the file is unchanged by an add and remove. */
|
|
28
|
+
export declare function removeAssociation(scene: ScadaScene, associationId: string): GraphResult;
|
|
29
|
+
/**
|
|
30
|
+
* Removes a component with every connection and association that touches it, so no edge is left
|
|
31
|
+
* dangling; its children are freed, data sources and variables keep their description but lose the
|
|
32
|
+
* link, and its symbol and edge routes leave every view (ADR-0026).
|
|
33
|
+
*/
|
|
23
34
|
export declare function removeComponent(scene: ScadaScene, instanceId: string): GraphResult;
|
|
24
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Which edges a graph query follows (ADR-0023). Everything is optional; an empty filter follows
|
|
37
|
+
* every connection and association in both directions.
|
|
38
|
+
*/
|
|
39
|
+
export interface GraphFilter {
|
|
40
|
+
/** Connection domains to follow. A connection's domain is its `domain` field, else its from-port's kind via `resolvePorts`. Associations have no domain and are skipped when this is set. */
|
|
41
|
+
domains?: readonly PortKind[];
|
|
42
|
+
/** Relationships to follow; edges without a relationship are skipped when this is set. */
|
|
43
|
+
relationships?: readonly Relationship[];
|
|
44
|
+
/** Follow connections. Default true. */
|
|
45
|
+
connections?: boolean;
|
|
46
|
+
/** Follow associations. Default true for `neighbours`, `relatedComponents` and `pathBetween`. */
|
|
47
|
+
associations?: boolean;
|
|
48
|
+
/** `out` follows edges from→to, `in` to→from, `both` (default) either way. */
|
|
49
|
+
direction?: 'out' | 'in' | 'both';
|
|
50
|
+
/** Resolves a connection's domain when it has no `domain` field. */
|
|
51
|
+
resolvePorts?: (type: ComponentType, properties?: ComponentInstance['properties']) => readonly ConnectionPort[] | undefined;
|
|
52
|
+
}
|
|
53
|
+
/** One edge walked by a query, from one component to the next. */
|
|
54
|
+
export interface GraphStep {
|
|
55
|
+
edge: 'connection' | 'association';
|
|
56
|
+
/** The connection or association id. */
|
|
57
|
+
id: string;
|
|
58
|
+
/** Component walked from and to. */
|
|
59
|
+
from: string;
|
|
60
|
+
to: string;
|
|
61
|
+
/** True when walked in the edge's own from→to direction. */
|
|
62
|
+
forward: boolean;
|
|
63
|
+
domain?: PortKind;
|
|
64
|
+
relationship?: Relationship;
|
|
65
|
+
}
|
|
66
|
+
/** Components one followed edge away from `instanceId`, each once, in edge order. Empty for an unknown id. */
|
|
67
|
+
export declare function neighbours(scene: ScadaScene, instanceId: string, filter?: GraphFilter): string[];
|
|
68
|
+
/**
|
|
69
|
+
* Every component reachable from `instanceId` through followed edges, breadth first, excluding
|
|
70
|
+
* `instanceId` itself. For example, with `{ relationships: ['controls', 'drives'], direction: 'out' }`
|
|
71
|
+
* from a PLC: the drives it controls and the motors they drive. Derived only from explicit edges.
|
|
72
|
+
*/
|
|
73
|
+
export declare function relatedComponents(scene: ScadaScene, instanceId: string, filter?: GraphFilter): string[];
|
|
74
|
+
/**
|
|
75
|
+
* The shortest chain of followed edges from `fromId` to `toId` (fewest edges; ties go to scene
|
|
76
|
+
* order), or undefined when there is none. Empty when both ids are the same known component.
|
|
77
|
+
*/
|
|
78
|
+
export declare function pathBetween(scene: ScadaScene, fromId: string, toId: string, filter?: GraphFilter): GraphStep[] | undefined;
|
|
79
|
+
/** Connection filter for the older helpers: optional domains, resolved like `GraphFilter`. Associations are never followed. */
|
|
80
|
+
export type ConnectionFilter = Pick<GraphFilter, 'domains' | 'resolvePorts'>;
|
|
81
|
+
/** Domains whose ports are joined physically (fluid, mechanical and the deprecated `electrical`, as `matesPhysically`): the filter for assemblies, which move parts that are bolted together. */
|
|
82
|
+
export declare const physicalDomains: readonly PortKind[];
|
|
83
|
+
/** Connections touching `instanceId`, optionally only in some domains. Associations are separate (`scene.associations`). */
|
|
84
|
+
export declare function connectionsOf(scene: ScadaScene, instanceId: string, filter?: ConnectionFilter): SceneConnection[];
|
|
25
85
|
/**
|
|
26
86
|
* Component ids connected to `instanceId` through any path, ignoring edge direction, in
|
|
27
87
|
* breadth-first order following scene connection order. Empty for an unknown id.
|
|
28
88
|
*/
|
|
29
|
-
export declare function reachableComponents(scene: ScadaScene, instanceId: string): string[];
|
|
89
|
+
export declare function reachableComponents(scene: ScadaScene, instanceId: string, filter?: ConnectionFilter): string[];
|
|
30
90
|
/** Disconnected networks, each in breadth-first order, ordered by their first component in the scene. */
|
|
31
|
-
export declare function connectedGroups(scene: ScadaScene): string[][];
|
|
91
|
+
export declare function connectedGroups(scene: ScadaScene, filter?: ConnectionFilter): string[][];
|
|
32
92
|
//# sourceMappingURL=graph.d.ts.map
|
package/dist/graph.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"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"}
|
package/dist/graph.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { viewKinds } from './scene.js';
|
|
1
2
|
import { validateScene } from './validation.js';
|
|
2
3
|
/**
|
|
3
4
|
* Adds one edge. The candidate scene goes through `validateScene`, so the same rules apply as on
|
|
@@ -17,9 +18,38 @@ export function disconnect(scene, connectionId) {
|
|
|
17
18
|
return { ok: false, error: { code: 'UNKNOWN_CONNECTION', message: `No connection "${connectionId}".` } };
|
|
18
19
|
}
|
|
19
20
|
const next = { ...scene, connections: scene.connections.filter((c) => c.id !== connectionId) };
|
|
20
|
-
return { ok: true, scene: withoutDanglingBindings(next) };
|
|
21
|
+
return { ok: true, scene: withoutDanglingViews(withoutDanglingBindings(next)) };
|
|
21
22
|
}
|
|
22
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Adds one association (ADR-0023), such as "PLC-01 controls VFD-101". Validated like `connect`:
|
|
25
|
+
* both components exist and differ, the id is unused, and the same fact is not recorded twice.
|
|
26
|
+
*/
|
|
27
|
+
export function addAssociation(scene, association, options) {
|
|
28
|
+
const existing = scene.associations ?? [];
|
|
29
|
+
const index = existing.length;
|
|
30
|
+
const result = validateScene({ ...scene, associations: [...existing, association] }, options);
|
|
31
|
+
if (result.ok)
|
|
32
|
+
return result;
|
|
33
|
+
const error = result.errors.find((e) => e.path.startsWith(`associations[${index}]`)) ?? result.errors[0];
|
|
34
|
+
return { ok: false, error: { code: error.code, message: error.message } };
|
|
35
|
+
}
|
|
36
|
+
/** Removes one association. The list is dropped when it becomes empty, so the file is unchanged by an add and remove. */
|
|
37
|
+
export function removeAssociation(scene, associationId) {
|
|
38
|
+
if (!scene.associations?.some((a) => a.id === associationId)) {
|
|
39
|
+
return { ok: false, error: { code: 'UNKNOWN_ASSOCIATION', message: `No association "${associationId}".` } };
|
|
40
|
+
}
|
|
41
|
+
return { ok: true, scene: withoutDanglingViews(withAssociations(scene, scene.associations.filter((a) => a.id !== associationId))) };
|
|
42
|
+
}
|
|
43
|
+
/** The scene with `associations`, leaving the field out when the list is empty. */
|
|
44
|
+
function withAssociations(scene, associations) {
|
|
45
|
+
const { associations: _, ...rest } = scene;
|
|
46
|
+
return associations.length > 0 ? { ...rest, associations } : rest;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Removes a component with every connection and association that touches it, so no edge is left
|
|
50
|
+
* dangling; its children are freed, data sources and variables keep their description but lose the
|
|
51
|
+
* link, and its symbol and edge routes leave every view (ADR-0026).
|
|
52
|
+
*/
|
|
23
53
|
export function removeComponent(scene, instanceId) {
|
|
24
54
|
if (!scene.components.some((c) => c.id === instanceId)) {
|
|
25
55
|
return { ok: false, error: { code: 'UNKNOWN_INSTANCE', message: `No component "${instanceId}".` } };
|
|
@@ -36,7 +66,27 @@ export function removeComponent(scene, instanceId) {
|
|
|
36
66
|
}),
|
|
37
67
|
connections: scene.connections.filter((c) => c.from.instanceId !== instanceId && c.to.instanceId !== instanceId),
|
|
38
68
|
};
|
|
39
|
-
|
|
69
|
+
const kept = scene.associations ? withAssociations(next, scene.associations.filter((a) => a.from !== instanceId && a.to !== instanceId)) : next;
|
|
70
|
+
return { ok: true, scene: withoutDanglingViews(withoutDanglingBindings(unlinkAcquisition(kept, instanceId))) };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Clears a removed component's links from data sources (`deviceId`) and runtime variables
|
|
74
|
+
* (`componentId`), keeping the acquisition description itself (ADR-0024).
|
|
75
|
+
*/
|
|
76
|
+
function unlinkAcquisition(scene, instanceId) {
|
|
77
|
+
const dataSources = scene.dataSources?.map((s) => {
|
|
78
|
+
if (s.deviceId !== instanceId)
|
|
79
|
+
return s;
|
|
80
|
+
const { deviceId: _, ...rest } = s;
|
|
81
|
+
return rest;
|
|
82
|
+
});
|
|
83
|
+
const runtimeVariables = scene.runtimeVariables?.map((v) => {
|
|
84
|
+
if (v.componentId !== instanceId)
|
|
85
|
+
return v;
|
|
86
|
+
const { componentId: _, ...rest } = v;
|
|
87
|
+
return rest;
|
|
88
|
+
});
|
|
89
|
+
return { ...scene, ...(dataSources ? { dataSources } : {}), ...(runtimeVariables ? { runtimeVariables } : {}) };
|
|
40
90
|
}
|
|
41
91
|
/** Drops tag bindings whose component or connection no longer exists. Absent bindings stay absent. */
|
|
42
92
|
function withoutDanglingBindings(scene) {
|
|
@@ -47,12 +97,147 @@ function withoutDanglingBindings(scene) {
|
|
|
47
97
|
const tagBindings = scene.tagBindings.filter((b) => (b.kind === 'property' ? components.has(b.instanceId) : connections.has(b.connectionId)));
|
|
48
98
|
return { ...scene, tagBindings };
|
|
49
99
|
}
|
|
50
|
-
|
|
51
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Drops view symbols and routes whose component or edge no longer exists (ADR-0026). Layouts, views
|
|
102
|
+
* and the `views` field that become empty go too; a scene with nothing to drop is returned as is.
|
|
103
|
+
*/
|
|
104
|
+
function withoutDanglingViews(scene) {
|
|
105
|
+
if (!scene.views)
|
|
106
|
+
return scene;
|
|
107
|
+
const components = new Set(scene.components.map((c) => c.id));
|
|
108
|
+
const edges = new Set([...scene.connections.map((c) => c.id), ...(scene.associations ?? []).map((a) => a.id)]);
|
|
109
|
+
const keep = (record, known) => record && Object.fromEntries(Object.entries(record).filter(([id]) => known.has(id)));
|
|
110
|
+
let dropped = false;
|
|
111
|
+
const views = {};
|
|
112
|
+
for (const kind of viewKinds) {
|
|
113
|
+
const layout = scene.views[kind];
|
|
114
|
+
if (!layout)
|
|
115
|
+
continue;
|
|
116
|
+
const symbols = keep(layout.symbols, components), routes = keep(layout.routes, edges);
|
|
117
|
+
dropped ||= Object.keys(symbols ?? {}).length !== Object.keys(layout.symbols ?? {}).length || Object.keys(routes ?? {}).length !== Object.keys(layout.routes ?? {}).length;
|
|
118
|
+
const next = { ...(symbols && Object.keys(symbols).length > 0 ? { symbols } : {}), ...(routes && Object.keys(routes).length > 0 ? { routes } : {}) };
|
|
119
|
+
if (next.symbols || next.routes)
|
|
120
|
+
views[kind] = next;
|
|
121
|
+
}
|
|
122
|
+
if (!dropped)
|
|
123
|
+
return scene;
|
|
124
|
+
const { views: _, ...rest } = scene;
|
|
125
|
+
return Object.keys(views).length > 0 ? { ...rest, views } : rest;
|
|
126
|
+
}
|
|
127
|
+
/** A connection's domain: authored, else the from-port's kind; undefined when it cannot be resolved. */
|
|
128
|
+
function domainOf(c, resolvePorts, byId) {
|
|
129
|
+
if (c.domain !== undefined)
|
|
130
|
+
return c.domain;
|
|
131
|
+
const owner = byId.get(c.from.instanceId);
|
|
132
|
+
return owner && resolvePorts?.(owner.type, owner.properties)?.find((p) => p.id === c.from.portId)?.kind;
|
|
133
|
+
}
|
|
134
|
+
/** Steps leaving each component under `filter`, in scene order: connections first, then associations. */
|
|
135
|
+
function steps(scene, filter, associationsByDefault) {
|
|
136
|
+
const out = new Map(scene.components.map((c) => [c.id, []]));
|
|
137
|
+
const byId = new Map(scene.components.map((c) => [c.id, c]));
|
|
138
|
+
const direction = filter.direction ?? 'both';
|
|
139
|
+
const add = (step, a, b) => {
|
|
140
|
+
if (direction !== 'in')
|
|
141
|
+
out.get(a)?.push({ ...step, from: a, to: b, forward: true });
|
|
142
|
+
if (direction !== 'out')
|
|
143
|
+
out.get(b)?.push({ ...step, from: b, to: a, forward: false });
|
|
144
|
+
};
|
|
145
|
+
const relationshipOk = (r) => !filter.relationships || (r !== undefined && filter.relationships.includes(r));
|
|
146
|
+
if (filter.connections ?? true) {
|
|
147
|
+
for (const c of scene.connections) {
|
|
148
|
+
const domain = domainOf(c, filter.resolvePorts, byId);
|
|
149
|
+
if (filter.domains && (domain === undefined || !filter.domains.includes(domain)))
|
|
150
|
+
continue;
|
|
151
|
+
if (!relationshipOk(c.relationship))
|
|
152
|
+
continue;
|
|
153
|
+
add({ edge: 'connection', id: c.id, ...(domain ? { domain } : {}), ...(c.relationship ? { relationship: c.relationship } : {}) }, c.from.instanceId, c.to.instanceId);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if ((filter.associations ?? associationsByDefault) && !filter.domains) {
|
|
157
|
+
for (const a of scene.associations ?? []) {
|
|
158
|
+
if (!relationshipOk(a.relationship))
|
|
159
|
+
continue;
|
|
160
|
+
add({ edge: 'association', id: a.id, relationship: a.relationship }, a.from, a.to);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
/** Components one followed edge away from `instanceId`, each once, in edge order. Empty for an unknown id. */
|
|
166
|
+
export function neighbours(scene, instanceId, filter = {}) {
|
|
167
|
+
const seen = new Set();
|
|
168
|
+
for (const step of steps(scene, filter, true).get(instanceId) ?? [])
|
|
169
|
+
if (step.to !== instanceId)
|
|
170
|
+
seen.add(step.to);
|
|
171
|
+
return [...seen];
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Every component reachable from `instanceId` through followed edges, breadth first, excluding
|
|
175
|
+
* `instanceId` itself. For example, with `{ relationships: ['controls', 'drives'], direction: 'out' }`
|
|
176
|
+
* from a PLC: the drives it controls and the motors they drive. Derived only from explicit edges.
|
|
177
|
+
*/
|
|
178
|
+
export function relatedComponents(scene, instanceId, filter = {}) {
|
|
179
|
+
const graph = steps(scene, filter, true);
|
|
180
|
+
if (!graph.has(instanceId))
|
|
181
|
+
return [];
|
|
182
|
+
return walkSteps(graph, instanceId).order.slice(1);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The shortest chain of followed edges from `fromId` to `toId` (fewest edges; ties go to scene
|
|
186
|
+
* order), or undefined when there is none. Empty when both ids are the same known component.
|
|
187
|
+
*/
|
|
188
|
+
export function pathBetween(scene, fromId, toId, filter = {}) {
|
|
189
|
+
const graph = steps(scene, filter, true);
|
|
190
|
+
if (!graph.has(fromId) || !graph.has(toId))
|
|
191
|
+
return undefined;
|
|
192
|
+
const { via } = walkSteps(graph, fromId);
|
|
193
|
+
if (fromId !== toId && !via.has(toId))
|
|
194
|
+
return undefined;
|
|
195
|
+
const path = [];
|
|
196
|
+
for (let at = toId; at !== fromId;) {
|
|
197
|
+
const step = via.get(at);
|
|
198
|
+
path.unshift(step);
|
|
199
|
+
at = step.from;
|
|
200
|
+
}
|
|
201
|
+
return path;
|
|
202
|
+
}
|
|
203
|
+
/** Breadth-first walk over steps: visit order and the step that first reached each component. */
|
|
204
|
+
function walkSteps(graph, start) {
|
|
205
|
+
const order = [start];
|
|
206
|
+
const via = new Map();
|
|
207
|
+
const visited = new Set([start]);
|
|
208
|
+
for (let i = 0; i < order.length; i++) {
|
|
209
|
+
for (const step of graph.get(order[i]) ?? []) {
|
|
210
|
+
if (visited.has(step.to))
|
|
211
|
+
continue;
|
|
212
|
+
visited.add(step.to);
|
|
213
|
+
via.set(step.to, step);
|
|
214
|
+
order.push(step.to);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return { order, via };
|
|
218
|
+
}
|
|
219
|
+
/** Domains whose ports are joined physically (fluid, mechanical and the deprecated `electrical`, as `matesPhysically`): the filter for assemblies, which move parts that are bolted together. */
|
|
220
|
+
export const physicalDomains = ['fluid', 'mechanical', 'electrical'];
|
|
221
|
+
/** Connections touching `instanceId`, optionally only in some domains. Associations are separate (`scene.associations`). */
|
|
222
|
+
export function connectionsOf(scene, instanceId, filter = {}) {
|
|
223
|
+
const keep = connectionMatcher(scene, filter);
|
|
224
|
+
return scene.connections.filter((c) => (c.from.instanceId === instanceId || c.to.instanceId === instanceId) && keep(c));
|
|
225
|
+
}
|
|
226
|
+
function connectionMatcher(scene, filter) {
|
|
227
|
+
if (!filter.domains)
|
|
228
|
+
return () => true;
|
|
229
|
+
const byId = new Map(scene.components.map((c) => [c.id, c]));
|
|
230
|
+
return (c) => {
|
|
231
|
+
const domain = domainOf(c, filter.resolvePorts, byId);
|
|
232
|
+
return domain !== undefined && filter.domains.includes(domain);
|
|
233
|
+
};
|
|
52
234
|
}
|
|
53
|
-
function adjacency(scene) {
|
|
235
|
+
function adjacency(scene, filter = {}) {
|
|
54
236
|
const neighbours = new Map(scene.components.map((c) => [c.id, []]));
|
|
237
|
+
const keep = connectionMatcher(scene, filter);
|
|
55
238
|
for (const c of scene.connections) {
|
|
239
|
+
if (!keep(c))
|
|
240
|
+
continue;
|
|
56
241
|
neighbours.get(c.from.instanceId)?.push(c.to.instanceId);
|
|
57
242
|
neighbours.get(c.to.instanceId)?.push(c.from.instanceId);
|
|
58
243
|
}
|
|
@@ -76,13 +261,13 @@ function walk(neighbours, start, visited) {
|
|
|
76
261
|
* Component ids connected to `instanceId` through any path, ignoring edge direction, in
|
|
77
262
|
* breadth-first order following scene connection order. Empty for an unknown id.
|
|
78
263
|
*/
|
|
79
|
-
export function reachableComponents(scene, instanceId) {
|
|
80
|
-
const neighbours = adjacency(scene);
|
|
264
|
+
export function reachableComponents(scene, instanceId, filter = {}) {
|
|
265
|
+
const neighbours = adjacency(scene, filter);
|
|
81
266
|
return neighbours.has(instanceId) ? walk(neighbours, instanceId, new Set()) : [];
|
|
82
267
|
}
|
|
83
268
|
/** Disconnected networks, each in breadth-first order, ordered by their first component in the scene. */
|
|
84
|
-
export function connectedGroups(scene) {
|
|
85
|
-
const neighbours = adjacency(scene);
|
|
269
|
+
export function connectedGroups(scene, filter = {}) {
|
|
270
|
+
const neighbours = adjacency(scene, filter);
|
|
86
271
|
const visited = new Set();
|
|
87
272
|
const groups = [];
|
|
88
273
|
for (const c of scene.components) {
|
package/dist/graph.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ScadaScene, SceneConnection } from './scene.js';\nimport { validateScene, type SceneValidationErrorCode, type SceneValidationOptions } from './validation.js';\n\nexport type GraphErrorCode = SceneValidationErrorCode | 'UNKNOWN_CONNECTION';\n\nexport interface GraphError {\n code: GraphErrorCode;\n message: string;\n}\n\nexport type GraphResult = { ok: true; scene: ScadaScene } | { ok: false; error: GraphError };\n\nexport type GraphOptions = Pick<SceneValidationOptions, 'resolvePorts' | 'resolveBindable' | 'validateProperties' | 'limits'>;\n\n/**\n * Adds one edge. The candidate scene goes through `validateScene`, so the same rules apply as on\n * load (known ports, compatibility, occupancy, no duplicates). The input scene must already be valid.\n */\nexport function connect(scene: ScadaScene, connection: SceneConnection, options: GraphOptions): GraphResult {\n const index = scene.connections.length;\n const result = validateScene({ ...scene, connections: [...scene.connections, connection] }, options);\n if (result.ok) return result;\n // Prefer an error about the new edge; anything else means the input scene was already invalid.\n const error = result.errors.find((e) => e.path.startsWith(`connections[${index}]`)) ?? result.errors[0];\n return { ok: false, error: { code: error.code, message: error.message } };\n}\n\nexport function disconnect(scene: ScadaScene, connectionId: string): GraphResult {\n if (!scene.connections.some((c) => c.id === connectionId)) {\n return { ok: false, error: { code: 'UNKNOWN_CONNECTION', message: `No connection \"${connectionId}\".` } };\n }\n const next = { ...scene, connections: scene.connections.filter((c) => c.id !== connectionId) };\n return { ok: true, scene: withoutDanglingBindings(next) };\n}\n\n/** Removes a component with every edge that touches it, so no edge is left dangling; its children are freed. */\nexport function removeComponent(scene: ScadaScene, instanceId: string): GraphResult {\n if (!scene.components.some((c) => c.id === instanceId)) {\n return { ok: false, error: { code: 'UNKNOWN_INSTANCE', message: `No component \"${instanceId}\".` } };\n }\n const next = {\n ...scene,\n components: scene.components\n .filter((c) => c.id !== instanceId)\n .map((c) => {\n if (c.parentId !== instanceId) return c;\n const { parentId: _, ...free } = c;\n return free;\n }),\n connections: scene.connections.filter((c) => c.from.instanceId !== instanceId && c.to.instanceId !== instanceId),\n };\n return { ok: true, scene: withoutDanglingBindings(next) };\n}\n\n/** Drops tag bindings whose component or connection no longer exists. Absent bindings stay absent. */\nfunction withoutDanglingBindings(scene: ScadaScene): ScadaScene {\n if (!scene.tagBindings) return scene;\n const components = new Set(scene.components.map((c) => c.id));\n const connections = new Set(scene.connections.map((c) => c.id));\n const tagBindings = scene.tagBindings.filter((b) => (b.kind === 'property' ? components.has(b.instanceId) : connections.has(b.connectionId)));\n return { ...scene, tagBindings };\n}\n\nexport function connectionsOf(scene: ScadaScene, instanceId: string): SceneConnection[] {\n return scene.connections.filter((c) => c.from.instanceId === instanceId || c.to.instanceId === instanceId);\n}\n\nfunction adjacency(scene: ScadaScene): Map<string, string[]> {\n const neighbours = new Map<string, string[]>(scene.components.map((c) => [c.id, []]));\n for (const c of scene.connections) {\n neighbours.get(c.from.instanceId)?.push(c.to.instanceId);\n neighbours.get(c.to.instanceId)?.push(c.from.instanceId);\n }\n return neighbours;\n}\n\nfunction walk(neighbours: Map<string, string[]>, start: string, visited: Set<string>): string[] {\n // Iterative breadth-first search: cycles stop at visited nodes and long chains cannot overflow the stack.\n const order = [start];\n visited.add(start);\n for (let i = 0; i < order.length; i++) {\n for (const next of neighbours.get(order[i]) ?? []) {\n if (!visited.has(next)) {\n visited.add(next);\n order.push(next);\n }\n }\n }\n return order;\n}\n\n/**\n * Component ids connected to `instanceId` through any path, ignoring edge direction, in\n * breadth-first order following scene connection order. Empty for an unknown id.\n */\nexport function reachableComponents(scene: ScadaScene, instanceId: string): string[] {\n const neighbours = adjacency(scene);\n return neighbours.has(instanceId) ? walk(neighbours, instanceId, new Set()) : [];\n}\n\n/** Disconnected networks, each in breadth-first order, ordered by their first component in the scene. */\nexport function connectedGroups(scene: ScadaScene): string[][] {\n const neighbours = adjacency(scene);\n const visited = new Set<string>();\n const groups: string[][] = [];\n for (const c of scene.components) {\n if (!visited.has(c.id)) groups.push(walk(neighbours, c.id, visited));\n }\n return groups;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"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","sourcesContent":["import { viewKinds, type ComponentInstance, type ComponentType, type ConnectionPort, type PortKind, type Relationship, type ScadaScene, type SceneAssociation, type SceneConnection, type SceneViews, type ViewLayout } from './scene.js';\nimport { validateScene, type SceneValidationErrorCode, type SceneValidationOptions } from './validation.js';\n\nexport type GraphErrorCode = SceneValidationErrorCode | 'UNKNOWN_CONNECTION' | 'UNKNOWN_ASSOCIATION';\n\nexport interface GraphError {\n code: GraphErrorCode;\n message: string;\n}\n\nexport type GraphResult = { ok: true; scene: ScadaScene } | { ok: false; error: GraphError };\n\nexport type GraphOptions = Pick<SceneValidationOptions, 'resolvePorts' | 'resolveType' | 'resolveBindable' | 'validateProperties' | 'limits'>;\n\n/**\n * Adds one edge. The candidate scene goes through `validateScene`, so the same rules apply as on\n * load (known ports, compatibility, occupancy, no duplicates). The input scene must already be valid.\n */\nexport function connect(scene: ScadaScene, connection: SceneConnection, options: GraphOptions): GraphResult {\n const index = scene.connections.length;\n const result = validateScene({ ...scene, connections: [...scene.connections, connection] }, options);\n if (result.ok) return result;\n // Prefer an error about the new edge; anything else means the input scene was already invalid.\n const error = result.errors.find((e) => e.path.startsWith(`connections[${index}]`)) ?? result.errors[0];\n return { ok: false, error: { code: error.code, message: error.message } };\n}\n\nexport function disconnect(scene: ScadaScene, connectionId: string): GraphResult {\n if (!scene.connections.some((c) => c.id === connectionId)) {\n return { ok: false, error: { code: 'UNKNOWN_CONNECTION', message: `No connection \"${connectionId}\".` } };\n }\n const next = { ...scene, connections: scene.connections.filter((c) => c.id !== connectionId) };\n return { ok: true, scene: withoutDanglingViews(withoutDanglingBindings(next)) };\n}\n\n/**\n * Adds one association (ADR-0023), such as \"PLC-01 controls VFD-101\". Validated like `connect`:\n * both components exist and differ, the id is unused, and the same fact is not recorded twice.\n */\nexport function addAssociation(scene: ScadaScene, association: SceneAssociation, options: GraphOptions): GraphResult {\n const existing = scene.associations ?? [];\n const index = existing.length;\n const result = validateScene({ ...scene, associations: [...existing, association] }, options);\n if (result.ok) return result;\n const error = result.errors.find((e) => e.path.startsWith(`associations[${index}]`)) ?? result.errors[0];\n return { ok: false, error: { code: error.code, message: error.message } };\n}\n\n/** Removes one association. The list is dropped when it becomes empty, so the file is unchanged by an add and remove. */\nexport function removeAssociation(scene: ScadaScene, associationId: string): GraphResult {\n if (!scene.associations?.some((a) => a.id === associationId)) {\n return { ok: false, error: { code: 'UNKNOWN_ASSOCIATION', message: `No association \"${associationId}\".` } };\n }\n return { ok: true, scene: withoutDanglingViews(withAssociations(scene, scene.associations.filter((a) => a.id !== associationId))) };\n}\n\n/** The scene with `associations`, leaving the field out when the list is empty. */\nfunction withAssociations(scene: ScadaScene, associations: SceneAssociation[]): ScadaScene {\n const { associations: _, ...rest } = scene;\n return associations.length > 0 ? { ...rest, associations } : rest;\n}\n\n/**\n * Removes a component with every connection and association that touches it, so no edge is left\n * dangling; its children are freed, data sources and variables keep their description but lose the\n * link, and its symbol and edge routes leave every view (ADR-0026).\n */\nexport function removeComponent(scene: ScadaScene, instanceId: string): GraphResult {\n if (!scene.components.some((c) => c.id === instanceId)) {\n return { ok: false, error: { code: 'UNKNOWN_INSTANCE', message: `No component \"${instanceId}\".` } };\n }\n const next = {\n ...scene,\n components: scene.components\n .filter((c) => c.id !== instanceId)\n .map((c) => {\n if (c.parentId !== instanceId) return c;\n const { parentId: _, ...free } = c;\n return free;\n }),\n connections: scene.connections.filter((c) => c.from.instanceId !== instanceId && c.to.instanceId !== instanceId),\n };\n const kept = scene.associations ? withAssociations(next, scene.associations.filter((a) => a.from !== instanceId && a.to !== instanceId)) : next;\n return { ok: true, scene: withoutDanglingViews(withoutDanglingBindings(unlinkAcquisition(kept, instanceId))) };\n}\n\n/**\n * Clears a removed component's links from data sources (`deviceId`) and runtime variables\n * (`componentId`), keeping the acquisition description itself (ADR-0024).\n */\nfunction unlinkAcquisition(scene: ScadaScene, instanceId: string): ScadaScene {\n const dataSources = scene.dataSources?.map((s) => {\n if (s.deviceId !== instanceId) return s;\n const { deviceId: _, ...rest } = s;\n return rest;\n });\n const runtimeVariables = scene.runtimeVariables?.map((v) => {\n if (v.componentId !== instanceId) return v;\n const { componentId: _, ...rest } = v;\n return rest;\n });\n return { ...scene, ...(dataSources ? { dataSources } : {}), ...(runtimeVariables ? { runtimeVariables } : {}) };\n}\n\n/** Drops tag bindings whose component or connection no longer exists. Absent bindings stay absent. */\nfunction withoutDanglingBindings(scene: ScadaScene): ScadaScene {\n if (!scene.tagBindings) return scene;\n const components = new Set(scene.components.map((c) => c.id));\n const connections = new Set(scene.connections.map((c) => c.id));\n const tagBindings = scene.tagBindings.filter((b) => (b.kind === 'property' ? components.has(b.instanceId) : connections.has(b.connectionId)));\n return { ...scene, tagBindings };\n}\n\n/**\n * Drops view symbols and routes whose component or edge no longer exists (ADR-0026). Layouts, views\n * and the `views` field that become empty go too; a scene with nothing to drop is returned as is.\n */\nfunction withoutDanglingViews(scene: ScadaScene): ScadaScene {\n if (!scene.views) return scene;\n const components = new Set(scene.components.map((c) => c.id));\n const edges = new Set([...scene.connections.map((c) => c.id), ...(scene.associations ?? []).map((a) => a.id)]);\n const keep = <T>(record: Record<string, T> | undefined, known: Set<string>) =>\n record && Object.fromEntries(Object.entries(record).filter(([id]) => known.has(id)));\n let dropped = false;\n const views: SceneViews = {};\n for (const kind of viewKinds) {\n const layout = scene.views[kind];\n if (!layout) continue;\n const symbols = keep(layout.symbols, components), routes = keep(layout.routes, edges);\n dropped ||= Object.keys(symbols ?? {}).length !== Object.keys(layout.symbols ?? {}).length || Object.keys(routes ?? {}).length !== Object.keys(layout.routes ?? {}).length;\n const next: ViewLayout = { ...(symbols && Object.keys(symbols).length > 0 ? { symbols } : {}), ...(routes && Object.keys(routes).length > 0 ? { routes } : {}) };\n if (next.symbols || next.routes) views[kind] = next;\n }\n if (!dropped) return scene;\n const { views: _, ...rest } = scene;\n return Object.keys(views).length > 0 ? { ...rest, views } : rest;\n}\n\n/**\n * Which edges a graph query follows (ADR-0023). Everything is optional; an empty filter follows\n * every connection and association in both directions.\n */\nexport interface GraphFilter {\n /** Connection domains to follow. A connection's domain is its `domain` field, else its from-port's kind via `resolvePorts`. Associations have no domain and are skipped when this is set. */\n domains?: readonly PortKind[];\n /** Relationships to follow; edges without a relationship are skipped when this is set. */\n relationships?: readonly Relationship[];\n /** Follow connections. Default true. */\n connections?: boolean;\n /** Follow associations. Default true for `neighbours`, `relatedComponents` and `pathBetween`. */\n associations?: boolean;\n /** `out` follows edges from→to, `in` to→from, `both` (default) either way. */\n direction?: 'out' | 'in' | 'both';\n /** Resolves a connection's domain when it has no `domain` field. */\n resolvePorts?: (type: ComponentType, properties?: ComponentInstance['properties']) => readonly ConnectionPort[] | undefined;\n}\n\n/** One edge walked by a query, from one component to the next. */\nexport interface GraphStep {\n edge: 'connection' | 'association';\n /** The connection or association id. */\n id: string;\n /** Component walked from and to. */\n from: string;\n to: string;\n /** True when walked in the edge's own from→to direction. */\n forward: boolean;\n domain?: PortKind;\n relationship?: Relationship;\n}\n\n/** A connection's domain: authored, else the from-port's kind; undefined when it cannot be resolved. */\nfunction domainOf(c: SceneConnection, resolvePorts: GraphFilter['resolvePorts'], byId: Map<string, ComponentInstance>): PortKind | undefined {\n if (c.domain !== undefined) return c.domain;\n const owner = byId.get(c.from.instanceId);\n return owner && resolvePorts?.(owner.type, owner.properties)?.find((p) => p.id === c.from.portId)?.kind;\n}\n\n/** Steps leaving each component under `filter`, in scene order: connections first, then associations. */\nfunction steps(scene: ScadaScene, filter: GraphFilter, associationsByDefault: boolean): Map<string, GraphStep[]> {\n const out = new Map<string, GraphStep[]>(scene.components.map((c) => [c.id, []]));\n const byId = new Map(scene.components.map((c) => [c.id, c]));\n const direction = filter.direction ?? 'both';\n const add = (step: Omit<GraphStep, 'from' | 'to' | 'forward'>, a: string, b: string) => {\n if (direction !== 'in') out.get(a)?.push({ ...step, from: a, to: b, forward: true });\n if (direction !== 'out') out.get(b)?.push({ ...step, from: b, to: a, forward: false });\n };\n const relationshipOk = (r: Relationship | undefined) => !filter.relationships || (r !== undefined && filter.relationships.includes(r));\n if (filter.connections ?? true) {\n for (const c of scene.connections) {\n const domain = domainOf(c, filter.resolvePorts, byId);\n if (filter.domains && (domain === undefined || !filter.domains.includes(domain))) continue;\n if (!relationshipOk(c.relationship)) continue;\n add({ edge: 'connection', id: c.id, ...(domain ? { domain } : {}), ...(c.relationship ? { relationship: c.relationship } : {}) }, c.from.instanceId, c.to.instanceId);\n }\n }\n if ((filter.associations ?? associationsByDefault) && !filter.domains) {\n for (const a of scene.associations ?? []) {\n if (!relationshipOk(a.relationship)) continue;\n add({ edge: 'association', id: a.id, relationship: a.relationship }, a.from, a.to);\n }\n }\n return out;\n}\n\n/** Components one followed edge away from `instanceId`, each once, in edge order. Empty for an unknown id. */\nexport function neighbours(scene: ScadaScene, instanceId: string, filter: GraphFilter = {}): string[] {\n const seen = new Set<string>();\n for (const step of steps(scene, filter, true).get(instanceId) ?? []) if (step.to !== instanceId) seen.add(step.to);\n return [...seen];\n}\n\n/**\n * Every component reachable from `instanceId` through followed edges, breadth first, excluding\n * `instanceId` itself. For example, with `{ relationships: ['controls', 'drives'], direction: 'out' }`\n * from a PLC: the drives it controls and the motors they drive. Derived only from explicit edges.\n */\nexport function relatedComponents(scene: ScadaScene, instanceId: string, filter: GraphFilter = {}): string[] {\n const graph = steps(scene, filter, true);\n if (!graph.has(instanceId)) return [];\n return walkSteps(graph, instanceId).order.slice(1);\n}\n\n/**\n * The shortest chain of followed edges from `fromId` to `toId` (fewest edges; ties go to scene\n * order), or undefined when there is none. Empty when both ids are the same known component.\n */\nexport function pathBetween(scene: ScadaScene, fromId: string, toId: string, filter: GraphFilter = {}): GraphStep[] | undefined {\n const graph = steps(scene, filter, true);\n if (!graph.has(fromId) || !graph.has(toId)) return undefined;\n const { via } = walkSteps(graph, fromId);\n if (fromId !== toId && !via.has(toId)) return undefined;\n const path: GraphStep[] = [];\n for (let at = toId; at !== fromId; ) {\n const step = via.get(at)!;\n path.unshift(step);\n at = step.from;\n }\n return path;\n}\n\n/** Breadth-first walk over steps: visit order and the step that first reached each component. */\nfunction walkSteps(graph: Map<string, GraphStep[]>, start: string): { order: string[]; via: Map<string, GraphStep> } {\n const order = [start];\n const via = new Map<string, GraphStep>();\n const visited = new Set([start]);\n for (let i = 0; i < order.length; i++) {\n for (const step of graph.get(order[i]) ?? []) {\n if (visited.has(step.to)) continue;\n visited.add(step.to);\n via.set(step.to, step);\n order.push(step.to);\n }\n }\n return { order, via };\n}\n\n/** Connection filter for the older helpers: optional domains, resolved like `GraphFilter`. Associations are never followed. */\nexport type ConnectionFilter = Pick<GraphFilter, 'domains' | 'resolvePorts'>;\n\n/** Domains whose ports are joined physically (fluid, mechanical and the deprecated `electrical`, as `matesPhysically`): the filter for assemblies, which move parts that are bolted together. */\nexport const physicalDomains: readonly PortKind[] = ['fluid', 'mechanical', 'electrical'];\n\n/** Connections touching `instanceId`, optionally only in some domains. Associations are separate (`scene.associations`). */\nexport function connectionsOf(scene: ScadaScene, instanceId: string, filter: ConnectionFilter = {}): SceneConnection[] {\n const keep = connectionMatcher(scene, filter);\n return scene.connections.filter((c) => (c.from.instanceId === instanceId || c.to.instanceId === instanceId) && keep(c));\n}\n\nfunction connectionMatcher(scene: ScadaScene, filter: ConnectionFilter): (c: SceneConnection) => boolean {\n if (!filter.domains) return () => true;\n const byId = new Map(scene.components.map((c) => [c.id, c]));\n return (c) => {\n const domain = domainOf(c, filter.resolvePorts, byId);\n return domain !== undefined && filter.domains!.includes(domain);\n };\n}\n\nfunction adjacency(scene: ScadaScene, filter: ConnectionFilter = {}): Map<string, string[]> {\n const neighbours = new Map<string, string[]>(scene.components.map((c) => [c.id, []]));\n const keep = connectionMatcher(scene, filter);\n for (const c of scene.connections) {\n if (!keep(c)) continue;\n neighbours.get(c.from.instanceId)?.push(c.to.instanceId);\n neighbours.get(c.to.instanceId)?.push(c.from.instanceId);\n }\n return neighbours;\n}\n\nfunction walk(neighbours: Map<string, string[]>, start: string, visited: Set<string>): string[] {\n // Iterative breadth-first search: cycles stop at visited nodes and long chains cannot overflow the stack.\n const order = [start];\n visited.add(start);\n for (let i = 0; i < order.length; i++) {\n for (const next of neighbours.get(order[i]) ?? []) {\n if (!visited.has(next)) {\n visited.add(next);\n order.push(next);\n }\n }\n }\n return order;\n}\n\n/**\n * Component ids connected to `instanceId` through any path, ignoring edge direction, in\n * breadth-first order following scene connection order. Empty for an unknown id.\n */\nexport function reachableComponents(scene: ScadaScene, instanceId: string, filter: ConnectionFilter = {}): string[] {\n const neighbours = adjacency(scene, filter);\n return neighbours.has(instanceId) ? walk(neighbours, instanceId, new Set()) : [];\n}\n\n/** Disconnected networks, each in breadth-first order, ordered by their first component in the scene. */\nexport function connectedGroups(scene: ScadaScene, filter: ConnectionFilter = {}): string[][] {\n const neighbours = adjacency(scene, filter);\n const visited = new Set<string>();\n const groups: string[][] = [];\n for (const c of scene.components) {\n if (!visited.has(c.id)) groups.push(walk(neighbours, c.id, visited));\n }\n return groups;\n}\n"]}
|