Replaces the prior stub/partial wiki with a Home + Reference-{Architecture,
Contracts,Examples,Limitations} + _Sidebar structure. Topic-contract and
data-model sections wrapped in AUTOGEN markers for the future wiki-gen tool.
Source-vs-spec contradictions surfaced and flagged inline (not silently
fixed). Pending-review notes mark sections that need a full node review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 KiB
Reference — Limitations
Note
What
generalFunctionsdoes not do, current rough edges, stability/versioning rules, and open questions. For an intuitive overview, return to Home.
When NOT to depend on this library
- Passive HTTP gateway nodes (e.g.
dashboardAPI) may skipBaseDomainandBaseNodeAdapterentirely if they hold no domain state. A plain Node-RED node with HTTP endpoints needs onlylogger,outputUtils, andconfigManager. See thedashboardAPIwiki for the rationale. - External scripts or standalone tools that need only unit conversion can import just
const { convert } = require('generalFunctions')without pulling in the full domain stack. - Nodes at a different S88 level that inherit from a third-party base class must not import from
src/domain/orsrc/nodered/internal paths — they may only use root-level exports.
Known limitations
1. loadCurve is deprecated
loadCurve(modelId) is kept as a thin shim over assetResolver.resolve('curves', modelId) so legacy consumers don't have to change in one go. New code should use assetResolver directly. Replacement loadModel exists but not every node has migrated.
- Tracked in:
OPEN_QUESTIONS.md— Phase 8.5 cleanup.
2. outlierDetection (DynamicClusterDeviation) prints to console.log
The dynamic-cluster outlier detector emits diagnostic lines via console.log directly, bypassing the structured logger. This means its output cannot be silenced per-node and doesn't honour logLevel. Fix is routing through logger like the rest of the library.
- Tracked in: Code review backlog.
3. configUtils.initConfig silently strips unknown keys
When the user config carries a key that isn't in the schema, configUtils.initConfig (via validationUtils.validateSchema) silently drops it. This means a typo in an editor field name or a missed schema entry results in the default value being used — with no error, no warning, no log line.
Workaround: the schema must include every key the domain reads, with a sensible default. The 2026-05-11 monster schema fix was a direct consequence of this gotcha.
- Tracked in:
OPEN_QUESTIONS.md— e.g. monster schema fix.
4. state (FSM) and predict are not yet integrated with BaseDomain lifecycle
The state machine and the prediction class are exported but not lifecycle-managed by BaseDomain. Consumer nodes wire them manually in configure() — constructor, event subscriptions, teardown. A second wave of refactor work will move them under the BaseDomain umbrella so subclasses get them for free.
- Tracked in: Architecture backlog.
5. menuUtils / MenuManager bypass the Node.js import path
These are served as browser JavaScript via the admin endpointUtils and run in the Node-RED editor's iframe. Deep changes require testing in both environments (Node-side schema validation, browser-side editor form rendering). There is no automated test harness for the browser side.
- Tracked in:
endpointUtils.jscomments.
6. CascadePIDController has no dedicated test suite
PIDController is unit-tested; the cascade variant is not. Adding tests is on the backlog.
- Tracked in: Test backlog.
7. Wiki autogen is hand-maintained
The API surface section is hand-maintained between the <!-- BEGIN/END AUTOGEN: api-surface --> markers in CONTRACT.md. There is no npm run wiki:all script (yet); when an export is added or changed, the table must be edited by hand. Mitigation: the source-of-truth is the barrel (index.js); when in doubt, trust the barrel.
- Tracked in: Phase 9 follow-up.
8. Single-side pressure handling lives in consumers
Consumer-node concerns like single-side pressure degradation, residue handling, and sequence-abort semantics are NOT centralised in this library — each consumer (rotatingMachine, valveGroupControl, …) implements its own variant. Cross-node consistency is by convention, not by enforcement. A future BaseDomain extension could pull common pressure-routing patterns up.
- Tracked in: Internal architecture notes.
9. Asset registry backends are not fully symmetric
FileBackend is the production default (sync, in-process JSON). HttpBackend is provided for remote-resolver scenarios but has fewer call sites and less test coverage. If you switch to HttpBackend in production, expect to find edge-case differences.
- Tracked in: Internal — not yet ticketed.
10. No editor form
generalFunctions is never placed in a flow. It has no Node-RED type registration, no .html, no admin endpoint of its own. Consumer nodes expose their own editor forms; each form field writes into a config key that configManager.buildConfig validates against the node's schema in src/configs/<nodeName>.json. This is a deliberate design choice, not a limitation — documented here for visitors searching for "where's the editor form".
Stability + versioning
Source of truth: .claude/rules/general-functions.md in the superproject.
| Category | Rule |
|---|---|
| Safe to add | New named exports. New optional methods on existing classes. New config keys with defaults in the schema. |
| Requires decision-gate interview | Removing or renaming any export. Changing a method signature. Changing the output key format of MeasurementContainer.getFlattenedOutput(). Changing the formatMsg delta-compression behaviour. |
| Forbidden without migration | Breaking the 4-segment key shape (type.variant.position.childId). Changing Port 0/1/2 payload envelope. Changing the CONTRACTS.md §1–§9 shapes. |
Cross-node impact
generalFunctions is a git submodule shared by all 12 node repos. Any change here can break any node. Before modifying any module:
# Identify all consumers of the symbol you're touching.
grep -r "require('generalFunctions')" nodes/*/
# Or for a specific export:
grep -rn "BaseDomain\|UnitPolicy\|MeasurementContainer" nodes/*/src/
After changes, run the test suites of every affected consumer node, not just generalFunctions/test/.
Canonical units
MeasurementContainer and all internal processing assume canonical units:
| Quantity | Canonical |
|---|---|
| Pressure | Pa |
| Flow | m3/s |
| Power | W |
| Temperature | K |
Unit conversion happens at system boundaries (input via CommandRegistry.units normalisation, output via UnitPolicy.output rendering) — never in core logic. Code that assumes anything else is a bug.
Deprecations
| Symbol | Status | Replacement | Plan |
|---|---|---|---|
loadCurve(modelId) |
deprecated | assetResolver.resolve('curves', modelId) |
Remove after every consumer migrates. Tracked in Phase 8.5. |
When a symbol is marked deprecated:
- The row in
CONTRACT.mdflips todeprecatedand gains a "removed-in" line. - Consumers in
nodes/*are updated to the replacement. - Each touched node's submodule pin is bumped in the superproject.
- After one release on
developmentwith no consumers, the export and its row are removed.
Open questions (tracked)
| Question | Where it lives |
|---|---|
Phase 8.5: complete loadCurve → assetResolver migration |
Internal |
Route DynamicClusterDeviation log lines through logger |
Code review backlog |
Surface a warning when configUtils.initConfig strips a key not in schema |
OPEN_QUESTIONS.md |
Move state (FSM) and predict under BaseDomain lifecycle |
Architecture backlog |
Browser-side test harness for menuUtils |
endpointUtils.js |
Test suite for CascadePIDController |
Test backlog |
Wiki autogen script (npm run wiki:all) for the API surface section |
Phase 9 follow-up |
HttpBackend test coverage parity with FileBackend |
Internal |
Centralised single-side-pressure handling pattern in BaseDomain |
Internal architecture notes |
Migration notes
Pre-refactor: per-node registerChild switch
The ChildRouter replaces hand-written registerChild(child) methods. The mechanical migration:
// Before:
registerChild(child) {
switch (child.softwareType) {
case 'measurement':
if (child.config.asset.type === 'pressure' && child.positionVsParent === 'upstream') {
this._onUpstream(child);
} else if (child.config.asset.type === 'flow') {
this._onFlow(child);
}
break;
case 'machinegroup':
this._onMgcChild(child);
break;
}
}
// After (in configure()):
this.router
.onMeasurement('measurement', { type: 'pressure', position: 'upstream' }, (data, child) => this._onUpstream(child))
.onMeasurement('measurement', { type: 'flow' }, (data, child) => this._onFlow(child))
.onRegister('machinegroup', (child) => this._onMgcChild(child));
Behaviour is identical (the underlying childRegistrationUtils calls are unchanged); the wiring is just declarative.
Pre-refactor: per-node getStatusBadge duplication
The statusBadge pure-function helpers replaced 12 copies of slightly different status-text formatters. New domains should use statusBadge.compose(parts, opts), statusBadge.error(msg), statusBadge.idle(label) instead of building {fill, shape, text} by hand. Text is clipped to 60 chars to fit the Node-RED editor.
Pre-AssetResolver: loadCurve shim
Old code:
const { loadCurve } = require('generalFunctions');
const curve = loadCurve('SomeModel');
New code (preferred):
const { assetResolver } = require('generalFunctions');
const curve = assetResolver.resolve('curves', 'SomeModel');
The shim still works, but the next API-surface review may remove it. Migrate when next touching the file.
Related pages
| Page | Why |
|---|---|
| Home | Intuitive overview |
| Reference — Contracts | Full public API surface, per-export stability tags |
| Reference — Architecture | Three-tier rule, src/ layout, consumer responsibilities |
| Reference — Examples | Usage patterns: extending base classes, registering commands, declaring child routes |
| Platform CONTRACTS.md | The authoritative platform base-class + protocol spec |
.claude/rules/general-functions.md |
Stability + change-impact rules |