@2112-lab/central-plant 0.3.58 โ†’ 0.3.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/bundle/index.js +1635 -32
  2. package/dist/cjs/src/core/centralPlant.js +80 -11
  3. package/dist/cjs/src/core/centralPlantInternals.js +4 -0
  4. package/dist/cjs/src/core/centralPlantValidator.js +10 -6
  5. package/dist/cjs/src/core/stateEngine.js +733 -0
  6. package/dist/cjs/src/core/stateEngineScene.js +97 -0
  7. package/dist/cjs/src/index.js +10 -0
  8. package/dist/cjs/src/managers/pathfinding/PathFlowManager.js +94 -13
  9. package/dist/cjs/src/managers/pathfinding/PathRenderingManager.js +15 -0
  10. package/dist/cjs/src/managers/scene/componentTooltipManager.js +7 -1
  11. package/dist/cjs/src/managers/scene/controlPanelManager.js +377 -0
  12. package/dist/cjs/src/managers/scene/sceneExportManager.js +10 -1
  13. package/dist/cjs/src/managers/state/EffectiveVisualManager.js +270 -0
  14. package/dist/esm/src/core/centralPlant.js +80 -11
  15. package/dist/esm/src/core/centralPlantInternals.js +4 -0
  16. package/dist/esm/src/core/centralPlantValidator.js +10 -6
  17. package/dist/esm/src/core/stateEngine.js +725 -0
  18. package/dist/esm/src/core/stateEngineScene.js +90 -0
  19. package/dist/esm/src/index.js +2 -0
  20. package/dist/esm/src/managers/pathfinding/PathFlowManager.js +94 -13
  21. package/dist/esm/src/managers/pathfinding/PathRenderingManager.js +15 -0
  22. package/dist/esm/src/managers/scene/componentTooltipManager.js +7 -1
  23. package/dist/esm/src/managers/scene/controlPanelManager.js +352 -0
  24. package/dist/esm/src/managers/scene/sceneExportManager.js +10 -1
  25. package/dist/esm/src/managers/state/EffectiveVisualManager.js +266 -0
  26. package/dist/index.d.ts +85 -0
  27. package/package.json +5 -2
@@ -2,6 +2,7 @@ import { createClass as _createClass, classCallCheck as _classCallCheck, toConsu
2
2
  import 'three';
3
3
  import { GLTFExporter } from '../../../node_modules/three/examples/jsm/exporters/GLTFExporter.js';
4
4
  import { getHardcodedUuid } from '../../utils/nameUtils.js';
5
+ import { applyDeclarationToExport } from '../../core/stateEngineScene.js';
5
6
 
6
7
  var SceneExportManager = /*#__PURE__*/function () {
7
8
  function SceneExportManager(sceneViewer) {
@@ -56,7 +57,8 @@ var SceneExportManager = /*#__PURE__*/function () {
56
57
  key: "exportSceneData",
57
58
  value: function exportSceneData() {
58
59
  var _this = this,
59
- _this$sceneViewer$cur2;
60
+ _this$sceneViewer$cur2,
61
+ _this$sceneViewer$cen;
60
62
  console.log('๐Ÿ“ค Starting scene export...');
61
63
  if (!this.sceneViewer.scene) {
62
64
  console.warn('โš ๏ธ No scene available for export');
@@ -289,6 +291,13 @@ var SceneExportManager = /*#__PURE__*/function () {
289
291
  if (sceneBehaviors && sceneBehaviors.length > 0) {
290
292
  exportData.behaviors = sceneBehaviors;
291
293
  }
294
+
295
+ // Persist plant / groups / chains / stateRegistry from the State Engine and
296
+ // bump the format to 3.0 (Framework ยง12; no-op if no engine present).
297
+ var stateEngine = (_this$sceneViewer$cen = this.sceneViewer.centralPlant) === null || _this$sceneViewer$cen === void 0 ? void 0 : _this$sceneViewer$cen.stateEngine;
298
+ if (stateEngine) {
299
+ applyDeclarationToExport(exportData, stateEngine);
300
+ }
292
301
  console.log('โœ… Scene export completed:', exportData);
293
302
  console.log("\uD83D\uDCCA Exported ".concat(sceneChildren.length, " components and ").concat(exportData.connections.length, " connections"));
294
303
  return exportData;
@@ -0,0 +1,266 @@
1
+ import { inherits as _inherits, createClass as _createClass, slicedToArray as _slicedToArray, createForOfIteratorHelper as _createForOfIteratorHelper, classCallCheck as _classCallCheck, callSuper as _callSuper } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
+ import { BaseDisposable } from '../../core/baseDisposable.js';
3
+ import { findObjectByHardcodedUuid } from '../../utils/nameUtils.js';
4
+
5
+ /** How far the albedo is pulled toward black for a gated-off mesh. */
6
+ var OFF_COLOR_SCALE = 0.2;
7
+ var EffectiveVisualManager = /*#__PURE__*/function (_BaseDisposable) {
8
+ /**
9
+ * @param {Object} sceneViewer - The central sceneViewer hub
10
+ */
11
+ function EffectiveVisualManager(sceneViewer) {
12
+ var _this;
13
+ _classCallCheck(this, EffectiveVisualManager);
14
+ _this = _callSuper(this, EffectiveVisualManager);
15
+ _this.sceneViewer = sceneViewer;
16
+
17
+ /**
18
+ * Materials this manager has gated (cloned + cached), for restore on dispose.
19
+ * @type {Set<THREE.Material>}
20
+ */
21
+ _this._gatedMaterials = new Set();
22
+
23
+ // Bound handlers so we can unsubscribe in _doDispose().
24
+ _this._onChange = _this._onChange.bind(_this);
25
+ _this._regateAll = _this._regateAll.bind(_this);
26
+ if (sceneViewer !== null && sceneViewer !== void 0 && sceneViewer.on) {
27
+ sceneViewer.on('engine-state-changed', _this._onChange);
28
+ sceneViewer.on('scene-loaded', _this._regateAll);
29
+ }
30
+ return _this;
31
+ }
32
+
33
+ // โ”€โ”€ Engine change handling โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
34
+
35
+ /**
36
+ * React to one committed registry change. Only 2-part object / pipe addresses
37
+ * are gated; everything else is ignored.
38
+ * @param {{ address: string, desired: *, effective: * }} change
39
+ * @private
40
+ */
41
+ _inherits(EffectiveVisualManager, _BaseDisposable);
42
+ return _createClass(EffectiveVisualManager, [{
43
+ key: "_onChange",
44
+ value: function _onChange(change) {
45
+ if (!change || typeof change.address !== 'string') return;
46
+ var parts = change.address.split('::');
47
+ if (parts.length !== 2) return; // 3-part I/O is desired-driven; skip
48
+
49
+ var _parts = _slicedToArray(parts, 2),
50
+ scope = _parts[0],
51
+ attr = _parts[1];
52
+ if (scope === 'plant' || scope.startsWith('group:')) return; // members self-report
53
+
54
+ if (attr === 'power') {
55
+ this._gateEquipment(scope, change.effective);
56
+ } else if (attr === 'flow') {
57
+ this._gatePipe(scope, change.effective);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Re-gate the whole scene from current effective state. Used after scene load,
63
+ * where StateEngine.load() populates the registry without emitting per-address
64
+ * events (and the meshes have just been (re)built).
65
+ * @private
66
+ */
67
+ }, {
68
+ key: "_regateAll",
69
+ value: function _regateAll() {
70
+ var _this$sceneViewer;
71
+ var engine = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.centralPlant) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.stateEngine;
72
+ if (!engine) return;
73
+ var registry = engine.toRegistry();
74
+ for (var _i = 0, _Object$keys = Object.keys(registry); _i < _Object$keys.length; _i++) {
75
+ var address = _Object$keys[_i];
76
+ var parts = address.split('::');
77
+ if (parts.length !== 2) continue;
78
+ var _parts2 = _slicedToArray(parts, 2),
79
+ scope = _parts2[0],
80
+ attr = _parts2[1];
81
+ if (scope === 'plant' || scope.startsWith('group:')) continue;
82
+ if (attr === 'power') {
83
+ this._gateEquipment(scope, engine.getEffective(address));
84
+ } else if (attr === 'flow') {
85
+ this._gatePipe(scope, engine.getEffective(address));
86
+ }
87
+ }
88
+ }
89
+
90
+ // โ”€โ”€ Equipment gating โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
91
+
92
+ /**
93
+ * Darken (off) or restore (on) every mesh of an equipment object.
94
+ * @param {string} objectId - Hardcoded object id (address scope)
95
+ * @param {boolean} on - Effective power
96
+ * @private
97
+ */
98
+ }, {
99
+ key: "_gateEquipment",
100
+ value: function _gateEquipment(objectId, on) {
101
+ var _this$sceneViewer2,
102
+ _this2 = this;
103
+ var scene = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.scene;
104
+ if (!scene) return;
105
+ var obj = findObjectByHardcodedUuid(scene, objectId);
106
+ if (!obj || typeof obj.traverse !== 'function') return;
107
+ obj.traverse(function (mesh) {
108
+ if (!mesh.isMesh && !mesh.material) return;
109
+ // Leave attached I/O control panels (switches) lit and operable even when
110
+ // their host is powered off โ€” you must be able to see and click the switch
111
+ // that turns it back on. (traverse can't prune, so guard per mesh.)
112
+ if (_this2._isUnderIoDevice(mesh, obj)) return;
113
+ var materials = _this2._prepareMeshMaterials(mesh);
114
+ var _iterator = _createForOfIteratorHelper(materials),
115
+ _step;
116
+ try {
117
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
118
+ var material = _step.value;
119
+ _this2._applyGate(material, on);
120
+ }
121
+ } catch (err) {
122
+ _iterator.e(err);
123
+ } finally {
124
+ _iterator.f();
125
+ }
126
+ });
127
+ }
128
+
129
+ /**
130
+ * True if `mesh` is an io-device or nested under one, walking up to (but not
131
+ * past) the equipment root `stopAt`.
132
+ * @param {THREE.Object3D} mesh
133
+ * @param {THREE.Object3D} stopAt - equipment root to stop the upward walk at
134
+ * @returns {boolean}
135
+ * @private
136
+ */
137
+ }, {
138
+ key: "_isUnderIoDevice",
139
+ value: function _isUnderIoDevice(mesh, stopAt) {
140
+ var node = mesh;
141
+ while (node) {
142
+ var _node$userData;
143
+ if (((_node$userData = node.userData) === null || _node$userData === void 0 ? void 0 : _node$userData.objectType) === 'io-device') return true;
144
+ if (node === stopAt) break;
145
+ node = node.parent;
146
+ }
147
+ return false;
148
+ }
149
+
150
+ /**
151
+ * Ensure a mesh's material(s) are cloned-on-write and have their baseline
152
+ * cached, then return the material list to gate.
153
+ * @param {THREE.Mesh} mesh
154
+ * @returns {THREE.Material[]}
155
+ * @private
156
+ */
157
+ }, {
158
+ key: "_prepareMeshMaterials",
159
+ value: function _prepareMeshMaterials(mesh) {
160
+ if (!mesh.material) return [];
161
+
162
+ // Clone-on-write once per mesh so shared library materials aren't shared.
163
+ if (!mesh.userData._effectiveGateCloned) {
164
+ if (Array.isArray(mesh.material)) {
165
+ mesh.material = mesh.material.map(function (m) {
166
+ return m !== null && m !== void 0 && m.clone ? m.clone() : m;
167
+ });
168
+ } else if (mesh.material.clone) {
169
+ mesh.material = mesh.material.clone();
170
+ }
171
+ mesh.userData._effectiveGateCloned = true;
172
+ }
173
+ var materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
174
+ var _iterator2 = _createForOfIteratorHelper(materials),
175
+ _step2;
176
+ try {
177
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
178
+ var _material$userData, _material$color$clone, _material$color, _material$color$clone2, _material$emissive$cl, _material$emissive, _material$emissive$cl2;
179
+ var material = _step2.value;
180
+ if (!material || (_material$userData = material.userData) !== null && _material$userData !== void 0 && _material$userData._effectiveGate) continue;
181
+ material.userData = material.userData || {};
182
+ material.userData._effectiveGate = {
183
+ color: (_material$color$clone = (_material$color = material.color) === null || _material$color === void 0 || (_material$color$clone2 = _material$color.clone) === null || _material$color$clone2 === void 0 ? void 0 : _material$color$clone2.call(_material$color)) !== null && _material$color$clone !== void 0 ? _material$color$clone : null,
184
+ emissive: (_material$emissive$cl = (_material$emissive = material.emissive) === null || _material$emissive === void 0 || (_material$emissive$cl2 = _material$emissive.clone) === null || _material$emissive$cl2 === void 0 ? void 0 : _material$emissive$cl2.call(_material$emissive)) !== null && _material$emissive$cl !== void 0 ? _material$emissive$cl : null,
185
+ emissiveIntensity: typeof material.emissiveIntensity === 'number' ? material.emissiveIntensity : null
186
+ };
187
+ this._gatedMaterials.add(material);
188
+ }
189
+ } catch (err) {
190
+ _iterator2.e(err);
191
+ } finally {
192
+ _iterator2.f();
193
+ }
194
+ return materials.filter(Boolean);
195
+ }
196
+
197
+ /**
198
+ * Darken or restore a single material from its cached baseline.
199
+ * @param {THREE.Material} material
200
+ * @param {boolean} on
201
+ * @private
202
+ */
203
+ }, {
204
+ key: "_applyGate",
205
+ value: function _applyGate(material, on) {
206
+ var _material$userData2;
207
+ var base = (_material$userData2 = material.userData) === null || _material$userData2 === void 0 ? void 0 : _material$userData2._effectiveGate;
208
+ if (!base) return;
209
+ if (on) {
210
+ if (base.color && material.color) material.color.copy(base.color);
211
+ if (base.emissive && material.emissive) material.emissive.copy(base.emissive);
212
+ if (base.emissiveIntensity !== null) material.emissiveIntensity = base.emissiveIntensity;
213
+ } else {
214
+ if (base.color && material.color) material.color.copy(base.color).multiplyScalar(OFF_COLOR_SCALE);
215
+ if (material.emissive) material.emissive.setScalar(0);
216
+ if (typeof material.emissiveIntensity === 'number') material.emissiveIntensity = 0;
217
+ }
218
+ material.needsUpdate = true;
219
+ }
220
+
221
+ // โ”€โ”€ Pipe flow gating โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
222
+
223
+ /**
224
+ * Delegate pipe-flow gating to PathFlowManager (grays the shared per-path
225
+ * material when off, restores the temperature colour when on).
226
+ * @param {string} pathId - `${from}-->${to}`
227
+ * @param {boolean} on - Effective flow
228
+ * @private
229
+ */
230
+ }, {
231
+ key: "_gatePipe",
232
+ value: function _gatePipe(pathId, on) {
233
+ var _this$sceneViewer3, _pathFlowManager$setF;
234
+ var pathFlowManager = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.managers) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.pathFlowManager;
235
+ pathFlowManager === null || pathFlowManager === void 0 || (_pathFlowManager$setF = pathFlowManager.setFlowEnabled) === null || _pathFlowManager$setF === void 0 || _pathFlowManager$setF.call(pathFlowManager, pathId, on);
236
+ }
237
+
238
+ // โ”€โ”€ Lifecycle โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
239
+ }, {
240
+ key: "_doDispose",
241
+ value: function _doDispose() {
242
+ var _this$sceneViewer4;
243
+ if ((_this$sceneViewer4 = this.sceneViewer) !== null && _this$sceneViewer4 !== void 0 && _this$sceneViewer4.off) {
244
+ this.sceneViewer.off('engine-state-changed', this._onChange);
245
+ this.sceneViewer.off('scene-loaded', this._regateAll);
246
+ }
247
+ // Restore any still-gated materials to their cached baseline.
248
+ var _iterator3 = _createForOfIteratorHelper(this._gatedMaterials),
249
+ _step3;
250
+ try {
251
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
252
+ var material = _step3.value;
253
+ this._applyGate(material, true);
254
+ }
255
+ } catch (err) {
256
+ _iterator3.e(err);
257
+ } finally {
258
+ _iterator3.f();
259
+ }
260
+ this._gatedMaterials.clear();
261
+ this.sceneViewer = null;
262
+ }
263
+ }]);
264
+ }(BaseDisposable);
265
+
266
+ export { EffectiveVisualManager };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,91 @@
5
5
  * organized by their functional categories.
6
6
  */
7
7
 
8
+ // โ”€โ”€โ”€ State Engine โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
9
+
10
+ export function makeAddress(scope: string, attribute: string): string
11
+ export function valuesEqual(a: unknown, b: unknown): boolean
12
+ export function offValue(desired: unknown): unknown
13
+
14
+ export interface StateChange {
15
+ address: string
16
+ desired: unknown
17
+ effective: unknown
18
+ }
19
+
20
+ /** One endpoint of a declared chain (scope + attribute). */
21
+ export interface ChainEndpoint {
22
+ /** Object/scope id, e.g. "pump-001" or "pump-001::start-switch". */
23
+ address: string
24
+ /** Attribute name, e.g. "power", "mode", "flow". */
25
+ attribute: string
26
+ }
27
+
28
+ /** The chain trigger; `source` selects which layer of the trigger to read. */
29
+ export interface ChainWhen extends ChainEndpoint {
30
+ /** Read the trigger's desired (default) or effective value. */
31
+ source?: 'desired' | 'effective'
32
+ }
33
+
34
+ /** A declared chain: when the trigger changes, copy its value to the target. */
35
+ export interface Chain {
36
+ /** Unique id within the scene. */
37
+ id?: string
38
+ /** Only 'copy' is supported today. */
39
+ action?: 'copy'
40
+ when: ChainWhen
41
+ set: ChainEndpoint
42
+ }
43
+
44
+ export interface StateEngineOptions {
45
+ /** Ripple hop limit per initiating change (Framework ยง11.2). Default 8. */
46
+ maxChainDepth?: number
47
+ /** Operational attributes gated to a safe-off value when power is off (ยง6/ยง10). */
48
+ gatedOperationalAttributes?: string[]
49
+ }
50
+
51
+ export declare class StateEngine {
52
+ constructor(options?: StateEngineOptions)
53
+ maxChainDepth: number
54
+ load(declaration?: Record<string, unknown>): void
55
+ reset(): void
56
+ getDesired(address: string): unknown
57
+ getEffective(address: string): unknown
58
+ getGroupsForObject(objectId: string): string[]
59
+ setDesired(address: string, value: unknown): boolean
60
+ /** Register a pipe/path so its `<pipeId>::flow` state exists (tied to lifecycle). */
61
+ registerPipe(pipeId: string, options?: { defaultFlow?: boolean }): string
62
+ /** Remove a pipe and all of its registry keys; returns the number removed. */
63
+ unregisterPipe(pipeId: string): number
64
+ subscribe(listener: (change: StateChange) => void): () => void
65
+ toRegistry(): Record<string, unknown>
66
+ /** Reconstruct the ยง17 scene declaration (round-trips with load). */
67
+ toDeclaration(): {
68
+ version: string
69
+ plant: Record<string, unknown>
70
+ groups: Record<string, unknown>
71
+ chains: Chain[]
72
+ stateRegistry: Record<string, unknown>
73
+ }
74
+ }
75
+
76
+ // โ”€โ”€โ”€ State Engine โ‡„ scene glue (stateEngineScene.js) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
77
+
78
+ export function ioAddress(attachmentId: string, stateId: string, parentUuid?: string): string
79
+ export function parseIoAddress(
80
+ address: string
81
+ ): { parentUuid: string; attachmentId: string; stateId: string; scopedKey: string } | null
82
+ export function extractDeclaration(sceneData?: Record<string, unknown>): {
83
+ plant: Record<string, unknown>
84
+ groups: Record<string, unknown>
85
+ chains: Chain[]
86
+ stateRegistry: Record<string, unknown>
87
+ }
88
+ export function applyDeclarationToExport<T extends Record<string, unknown>>(
89
+ exportData: T,
90
+ engine: { toDeclaration: () => Record<string, unknown> }
91
+ ): T
92
+
8
93
  // โ”€โ”€โ”€ Connector flow types โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
9
94
 
10
95
  /** The flow direction of a connector port. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.3.58",
3
+ "version": "0.3.59",
4
4
  "description": "Utility modules for the Central Plant Application",
5
5
  "main": "dist/bundle/index.js",
6
6
  "module": "dist/esm/src/index.js",
@@ -18,6 +18,8 @@
18
18
  "build": "rollup -c && npm run build:types",
19
19
  "build:types": "node -e \"require('fs').copyFileSync('src/index.d.ts', 'dist/index.d.ts')\"",
20
20
  "dev": "rollup -c -w",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
21
23
  "prepublishOnly": "npm run build",
22
24
  "docs": "jsdoc -c jsdoc.json",
23
25
  "release": "npm install @2112-lab/pathfinder@latest && npm version patch && npm publish"
@@ -46,6 +48,7 @@
46
48
  "regenerator-runtime": "^0.14.1",
47
49
  "rollup": "^2.79.1",
48
50
  "rollup-plugin-copy": "^3.5.0",
49
- "rollup-plugin-terser": "^7.0.2"
51
+ "rollup-plugin-terser": "^7.0.2",
52
+ "vitest": "^2.1.9"
50
53
  }
51
54
  }