This documentation is likely to remain sparse, as it is for internal use and under development!
Table of Contents
Use Overview
The FCTGraph functions like a traditional graph with several features on top. Most importantly, all functionalities (nodes) have many slots, which hold data about themselves. Slots connect to other slots (if they are compatible) via dataStreams (edges).
The following is a selected showcase of the public functions on the various base objects of:
- FCTGraph (graph)
- Functionality (node)
- Slot (connection rule object)
- DataStream (edge)
const { FCTGraph, functionalities, slots } = require('@ospin/FCTGraph')
const tempOutSlotData = { name: 'temp out', type: 'OutSlot', ... }
const tempSensorData = { slots: [ tempOutSlotData ], ... }
const tempInSlotData = { name: 'temp in', type: 'InSlot', ... }
const pidControllerData = { slots: [ tempInSlotData ], ... }
const fctGraph = new FCTGraph({
functionalities: [ tempSensorData, pidControllerData ],
...,
}) )
fctGraph.addFunctionality({ ...heaterActuatorData })
fctGraph.functionalities
const [ tempSensor, pidController, heaterActuator ] = fctGraph.functionalities
const { slots: [ tempOutSlot, ... ] } = tempSensor
const { slots: pidControllerSlots } = pidController
const [ connectableSlot, ... ] = tempOutSlot.filterConnectableSlots(pidControllerSlots)
const { dataStream } = tempOutSlot.addConnectionTo(connectableSlot)
tempSensor.isPossibleToConnectToFct(pidController)
fctGraph.getConnectableFctsToTargetFct(pidController)
const goodFctData =
const { error, errorMsg, functionality } = fctGraph.addFunctionality(fctData)
console.log(error)
console.log(errorMsg)
console.log(functionality instanceof Functionality)
fctGraph.serialize()
const fctGraphJSON = JSON.stringify(fctGraph)
const fctGraphClone = FCTGraph.new(JSON.parse(fctGraphJSON))
Where appropriate (and hopefully whenever this package is extended) public methods which mutate instances in a major way (e.g. adding functionalities to the graph, connecting slots, etc.) return a response object. Response objects are intended to be useful in cases where a caller attempts to mutate the FCTGraph (or a portion of it) in a way that would ultimately fail data validation. The response objects will return actionable information for the caller.
const failure = fctGraph.addFunctionality({ name: 123, ...validData })
const success = fctGraph.addFunctionality({ name: 'Dr. Strangelove\'s Bunker Heater', ...validData })
Class Structure and Hierarchies
FCTGraph
Functionality (virtual)
├── Actuator (virtual)
│ └── HeaterActuator
├── Controller (virtual)
│ └── PIDController
├── InputNode (virtual)
│ └── PushIn
├── OutputNode (virtual)
│ ├── PushOut
│ └── IntervalOut
└── Sensor (virtual)
└── TemperatureSensor
Slot (virtual)
├── InSlot
└── OutSlot
DataStream
All non-virtual classes (e.g. HeaterActuator, InSlot, etc.) compose the JOIous module, which provides the following:
- post .constructor - asserts the instance's data against the JOI SCHEMA (which provides nested data validation) as a final step
- .serialize (virtual) - blows up - informing the user that the class that composed JOIous needs a
.serialize
method - .sortAndSerialize - uses .serialize returns the (deeply) sorted object
- .toJSON - uses .sortAndSerialize
- .toString - inspects deeply for richer print outs
- [util.inspect.custom] - inspects deeply for richer print outs in Node
Factories
Functionalities and Slots need somewhat intelligent instantiation as they are meant to be serialized to and from JSON. For this reason, Factories exist for instantiating both Functionalities and Slots. Instantiating an FCTGraph from parsed JSON will automatically delegate to the factories as it builds the hierarchy.
-
FCTGraph delegates to the FunctionalityFactory when functionalities are added. The factory will attempt to find the appropriate functionality sub-class via the type
and subType
key values and blow up if it can not find one.
-
Functionality delegates to the SlotFactory when slots are added. The factory will attempt to find the appropriate slot sub-class via the type
key value and blow up if it can not find one.
Functionalities and Slots can also be created directly calling the constructors on their non-virtual classes. See Class Structure and Hierarchies
Seeders
NOTE: Seeders are meant for testing purposes ONLY. While they carry a high test coverage %, they (likely) don't have the 100% Green™ coverage that the rest does.
The package comes with seeders which have the same class hierarchy as the primary models:
FCTGraphSeeder
FunctionalitySeeder (virtual)
├── ActuatorSeeder (virtual)
│ └── HeaterActuatorSeeder
├── ControllerSeeder (virtual)
│ └── PIDControllerSeeder
├── InputNodeSeeder (virtual)
│ └── PushInSeeder
├── OutputNodeSeeder (virtual)
│ ├── PushOutSeeder
│ └── IntervalOutSeeder
└── SensorSeeder (virtual)
└── TemperatureSensorSeeder
SlotSeeder (virtual)
├── InSlotSeeder
└── OutSlotSeeder
All virtual seeders (e.g. HeaterActuatorSeeder, InSlotSeeder etc.) compose the FactorySeeder module, which provides the following static methods (which extend to their children):
- static get SEED_METHOD (virtual) - blows up - informing the user that the class that composed FactorySeeder needs a static
.SEED_METHOD
getter. This method should used to call a constructor/factory's creation method - static generate (virtual) - blows up - informing the user that the class that composed FactorySeeder needs a static
.generate
method. This method should be used to create fake data which matches the class SCHEMA - .seedOne - expects a data object. delegates to
generate
and SEED_METHOD
- .seedMany - expects an array of data objects
- .seedN - expects an object and a count
Contributing
This repo employs the github action semantic-release, which, on approved PRs to main
, sniffs the PR title/commit message to automatically bump the semantic versioning and publish the package to NPM.
All PRs to the main
branch should indicate the semantic version change via the following rules.
Upcoming:
- reject setting properties on the core classes that should not change throughout the lifetime of an object (
type
, subType
, name
, etc.) - disconnect connections between slots
- dataStreams reference slots instead of just the slot name (works with instantiating from slot name) and still serialize to contain the slot names