@ospin/fct-graph
Advanced tools
Comparing version 2.1.2 to 2.2.0
14
index.js
const FCTGraph = require('./src/fctGraph/FCTGraph') | ||
const DataStream = require('./src/dataStreams/DataStream') | ||
const functionalities = require('./src/functionalities') | ||
const slots = require('./src/slots') | ||
const { | ||
FCTGraphSeeder, | ||
functionalitySeeders, | ||
slotSeeders, | ||
} = require('./src/seeders') | ||
module.exports = { | ||
FCTGraph, | ||
functionalities, | ||
slots, | ||
FCTGraphSeeder, | ||
functionalitySeeders, | ||
slotSeeders, | ||
DataStream, | ||
} |
{ | ||
"name": "@ospin/fct-graph", | ||
"author": "danielseehausen", | ||
"version": "2.1.2", | ||
"version": "2.2.0", | ||
"description": "Graph data structure with conditional edges via 'slots' on nodes. Intended to represent physical and virtual functionalities on a device.", | ||
@@ -27,3 +27,3 @@ "main": "index.js", | ||
"test": "jest", | ||
"test-with-coverage": "jest -i --coverage", | ||
"test-with-coverage": "jest --coverage", | ||
"lint-diff": "LIST=$(git diff-index --name-only --diff-filter=d HEAD | grep .*\\.[jt]sx\\\\? | grep -v json); if [ \"$LIST\" ]; then eslint $LIST; fi", | ||
@@ -62,3 +62,4 @@ "lint-diff-and-fix": "LIST=$(git diff-index --name-only --diff-filter=d HEAD | grep .*\\.[jt]sx\\\\? | grep -v json); if [ \"$LIST\" ]; then eslint --fix $LIST; fi" | ||
"collectCoverageFrom": [ | ||
"src/**/*.js" | ||
"src/**/*.js", | ||
"!src/seeders/**" | ||
], | ||
@@ -65,0 +66,0 @@ "moduleNameMapper": { |
232
README.md
@@ -1,29 +0,229 @@ | ||
[![codecov](https://codecov.io/gh/ospin-web-dev/FCTGraph/branch/master/graph/badge.svg)](https://codecov.io/gh/ospin-web-dev/FCTGraph) | ||
[![Maintainability](https://api.codeclimate.com/v1/badges/ab083cc74a1fbb1d7319/maintainability)](https://codeclimate.com/repos/60ae147b04beeb018b015a77/maintainability) | ||
[![codecov](https://codecov.io/gh/ospin-web-dev/FCTGraph/branch/main/graph/badge.svg?token=RXXLX0HDAR)](https://codecov.io/gh/ospin-web-dev/FCTGraph) | ||
[![Maintainability](https://api.codeclimate.com/v1/badges/184840b3c795f19f837b/maintainability)](https://codeclimate.com/github/ospin-web-dev/FCTGraph/maintainability) | ||
## Overview | ||
This documentation is likely to remain sparse, as it is for internal use and under development! | ||
TODO! | ||
--- | ||
#### Use Example | ||
## Table of Contents | ||
TODO! | ||
- [Use Overview](#UseOverview) | ||
- [Instantiation](#Instantiation) | ||
- [Action](#Action) | ||
- [Inspection](#Inspection) | ||
- [From/To JSON](#FromAndToJson) | ||
- [Public Methods that Mutate](#PublicMethodsThatMutate) | ||
- [Class Structure and Hierarchies](#ClassStructureAndHierarchies) | ||
- [Factories](#Factories) | ||
- [Seeders](#Seeders) | ||
- [Contributing](#Contributing) | ||
- [Upcoming](#Upcoming) | ||
--- | ||
#### FAQ | ||
**Q:** TODO! | ||
**A:** TODO! | ||
## <a name="UseOverview"></a>Use Overview | ||
**Q:** I have further questions re: the implementation | ||
**A:** see the test coverage! | ||
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) | ||
#### <a name="Instantiation">Instantiation! | ||
```js | ||
const { FCTGraph, functionalities, slots } = require('@ospin/FCTGraph') // or import | ||
// first, let's set up some seed data. Functionalities (nodes) have many dataStreams (edges)... | ||
const tempOutSlotData = { name: 'temp out', type: 'OutSlot', ... } /* see OutSlot.SCHEMA */ | ||
const tempSensorData = { slots: [ tempOutSlotData ], ... } /* see TemperatureSensor.SCHEMA */ | ||
const tempInSlotData = { name: 'temp in', type: 'InSlot', ... } /* see InSlot.SCHEMA */ | ||
const pidControllerData = { slots: [ tempInSlotData ], ... } /* see PIDController.SCHEMA */ | ||
// ...and instantiate our FCTGraph | ||
const fctGraph = new FCTGraph({ | ||
functionalities: [ tempSensorData, pidControllerData ], | ||
..., | ||
}) /* see FCTGraph.SCHEMA */) | ||
// we can also add functionalities after the fact | ||
fctGraph.addFunctionality({ ...heaterActuatorData }) | ||
fctGraph.functionalities | ||
// -> a temperature sensor with a temperature outslot | ||
// -> a PID controller with a temperature in slot | ||
// -> a heater actuator | ||
``` | ||
#### <a name="Action">Action! | ||
```js | ||
// ...continuing from above. let's connect the temperature sensor to the PIDController | ||
const [ tempSensor, pidController, heaterActuator ] = fctGraph.functionalities | ||
const { slots: [ tempOutSlot, ... ] } = tempSensor | ||
const { slots: pidControllerSlots } = pidController | ||
// get those slots on the PIDController which are connectable to the temperature sensor | ||
const [ connectableSlot, ... ] = tempOutSlot.filterConnectableSlots(pidControllerSlots) | ||
// connect! | ||
const { dataStream } = tempOutSlot.addConnectionTo(connectableSlot) | ||
// -> dataStream { id, sourceSlotName: 'temp out', sinkSlotName: 'temp in', ... } | ||
``` | ||
#### <a name="Inspection">Inspection! | ||
```js | ||
// ...continuing from above | ||
tempSensor.isPossibleToConnectToFct(pidController) | ||
// -> true | ||
fctGraph.getConnectableFctsToTargetFct(pidController) | ||
// -> [ tempSensor, ... ] | ||
// Graph mutating public methods return a standard response object: | ||
// response object: { error: <bool>, errorMsg: <string>, ...relevantData } | ||
const goodFctData = /* valid data for a new functionality */ | ||
const { error, errorMsg, functionality } = fctGraph.addFunctionality(fctData) | ||
console.log(error) // -> false | ||
console.log(errorMsg) // -> null | ||
console.log(functionality instanceof Functionality) // -> null | ||
``` | ||
#### <a name="FromAndToJson">From JSON, all life flows, and returns | ||
```js | ||
fctGraph.serialize() | ||
// -> returns the nested data object (no instances) | ||
const fctGraphJSON = JSON.stringify(fctGraph) | ||
// -> returns valid JSON with no information lost | ||
const fctGraphClone = FCTGraph.new(JSON.parse(fctGraphJSON)) | ||
// -> works! | ||
``` | ||
#### <a name="PublicMethodsThatMutate">Public Methods that Mutate | ||
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. | ||
```js | ||
const failure = fctGraph.addFunctionality({ name: 123, ...validData }) | ||
// { | ||
// error: true, | ||
// errorMsg: 'Failed to add fct: <fct data>. Underlying error: name must be a string', | ||
// functionality: <{ ...the failed functionalities data }>, | ||
// } | ||
const success = fctGraph.addFunctionality({ name: 'Dr. Strangelove\'s Bunker Heater', ...validData }) | ||
// { | ||
// error: false, | ||
// errorMsg: null, | ||
// functionality: <the added functionality>, | ||
// } | ||
``` | ||
--- | ||
## For Developers | ||
TODO! | ||
## <a name="ClassStructureAndHierarchies"></a>Class Structure and Hierarchies | ||
## Commit Message Guidelines | ||
This repo is set up with semantic versioning https://semantic-release.gitbook.io/semantic-release/ to automatically keep track of the version number and the changelogs | ||
```js | ||
FCTGraph | ||
All merged pull request need to indicate the level of change (fix,feat,perf) | ||
// an FCTGraph has many Functionalities | ||
Functionality (virtual) | ||
├── Actuator (virtual) | ||
│ └── HeaterActuator | ||
├── Controller (virtual) | ||
│ └── PIDController | ||
├── InputNode (virtual) | ||
│ └── PushIn | ||
├── OutputNode (virtual) | ||
│ ├── PushOut | ||
│ └── IntervalOut | ||
└── Sensor (virtual) | ||
└── TemperatureSensor | ||
// a Functionality has many Slots | ||
Slot (virtual) | ||
├── InSlot | ||
└── OutSlot | ||
// a Slot has many DataStreams | ||
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 | ||
--- | ||
## <a name="Factories"></a>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](#ClassStructureAndHierarchies) | ||
--- | ||
## <a name="Seeders"></a>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: | ||
```js | ||
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](https://semantic-release.gitbook.io/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](https://semantic-release.gitbook.io/semantic-release/#commit-message-format). | ||
--- | ||
## 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 | ||
@@ -11,3 +11,5 @@ const Joi = require('joi') | ||
id: Joi.string().pattern(RegexUtils.UUIDV4).required(), | ||
sourceFctId: Joi.string().pattern(RegexUtils.UUIDV4).required(), | ||
sourceSlotName: Joi.string().required(), | ||
sinkFctId: Joi.string().pattern(RegexUtils.UUIDV4).required(), | ||
sinkSlotName: Joi.string().required(), | ||
@@ -18,5 +20,14 @@ averagingWindowSize: Joi.number().integer().strict(), | ||
constructor({ id, sourceSlotName, sinkSlotName, averagingWindowSize }) { | ||
constructor({ | ||
id, | ||
sourceFctId, | ||
sourceSlotName, | ||
sinkFctId, | ||
sinkSlotName, | ||
averagingWindowSize, | ||
}) { | ||
this.id = id | ||
this.sourceFctId = sourceFctId | ||
this.sourceSlotName = sourceSlotName | ||
this.sinkFctId = sinkFctId | ||
this.sinkSlotName = sinkSlotName | ||
@@ -29,3 +40,5 @@ this.averagingWindowSize = averagingWindowSize | ||
id: this.id, | ||
sourceFctId: this.sourceFctId, | ||
sourceSlotName: this.sourceSlotName, | ||
sinkFctId: this.sinkFctId, | ||
sinkSlotName: this.sinkSlotName, | ||
@@ -32,0 +45,0 @@ averagingWindowSize: this.averagingWindowSize, |
const Joi = require('joi') | ||
const ArrayUtils = require('@choux/array-utils') | ||
const { v4: uuidv4 } = require('uuid') | ||
const JOIous = require('mixins/instanceMixins/JOIous') | ||
const FunctionalityFactory = require('functionalities/factories/FunctionalityFactory') | ||
const RegexUtils = require('utils/RegexUtils') | ||
const FunctionalityFactory = require('functionalities/factories/FunctionalityFactory') | ||
const { publicSuccessRes, publicErrorRes } = require('utils/publicResponses') | ||
const AddFunctionalityError = require('./AddFunctionalityError') | ||
@@ -13,4 +16,2 @@ class FCTGraph { | ||
id: Joi.string().pattern(RegexUtils.UUIDV4).required(), | ||
deviceId: Joi.string().pattern(RegexUtils.UUIDV4).required(), | ||
deviceDefault: Joi.boolean().default(false), | ||
functionalities: Joi.array().items(Joi.alternatives().try( | ||
@@ -24,10 +25,6 @@ ...FunctionalityFactory.SUPPORTED_CLASSES_SCHEMAS, | ||
id, | ||
deviceId, | ||
deviceDefault, | ||
functionalities, | ||
}) { | ||
this.id = id | ||
this.deviceId = deviceId | ||
this.deviceDefault = deviceDefault | ||
// Avoids blowing up in the constructor. Joi gives better errors | ||
// Avoid blowing up in the constructor if non-array given for fcts. Joi will give a better error | ||
this.functionalities = Array.isArray(functionalities) | ||
@@ -41,4 +38,2 @@ ? functionalities.map(FunctionalityFactory.new) | ||
id: this.id, | ||
deviceId: this.deviceId, | ||
deviceDefault: this.deviceDefault, | ||
functionalities: this.functionalities.map(func => func.serialize()), | ||
@@ -51,2 +46,33 @@ } | ||
* **************************************************************** */ | ||
_addFunctionalityAndAssertStructure(fctData) { | ||
const newFct = FunctionalityFactory.new({ id: uuidv4(), ...fctData }) | ||
this.functionalities.push(newFct) | ||
this.assertStructure() | ||
return newFct | ||
} | ||
_addFunctionalityOrThrow(fctData) { | ||
const preLength = this.functionalities.length | ||
try { | ||
const newFct = this._addFunctionalityAndAssertStructure(fctData) | ||
return newFct | ||
} catch (e) { | ||
const postLength = this.functionalities.length | ||
if (postLength > preLength) { this.functionalities.pop() } | ||
throw new AddFunctionalityError(fctData, e.message) | ||
} | ||
} | ||
// safe - returns a public response | ||
addFunctionality(fctData) { | ||
try { | ||
const functionality = this._addFunctionalityOrThrow(fctData) | ||
return publicSuccessRes({ functionality }) | ||
} catch (e) { | ||
return publicErrorRes({ errorMsg: e.message, functionality: fctData }) | ||
} | ||
} | ||
getFctsDifference(fcts) { | ||
@@ -53,0 +79,0 @@ const matcher = (arr, el) => arr.some(({ id }) => id === el.id) |
@@ -15,15 +15,30 @@ const Joi = require('joi') | ||
)).required(), | ||
controllerName: Joi.string().allow(''), // this is used to support the old devices: https://github.com/ospin-web-dev/hambda/issues/913 | ||
}) | ||
} | ||
_addSlot(slotData) { | ||
const { id: functionalityId } = this | ||
const newSlot = SlotFactory.new({ ...slotData, functionalityId }) | ||
this.slots.push(newSlot) | ||
return newSlot | ||
} | ||
_addSlots(slotsData) { | ||
/* NOTE: slots should never be added or removed outside of initialization | ||
* a.k.a. if I could make this method more private I would */ | ||
slotsData.map(slotData => this._addSlot(slotData)) | ||
} | ||
constructor({ | ||
id, | ||
name, | ||
slots, | ||
slots: slotsData, | ||
}) { | ||
this.id = id | ||
this.name = name | ||
this.slots = Array.isArray(slots) | ||
? slots.map(SlotFactory.new) | ||
: [] | ||
this.slots = [] | ||
if (slotsData) this._addSlots(slotsData) | ||
} | ||
@@ -48,5 +63,5 @@ | ||
/* returns { | ||
* <this.slotIdA>: [ <targetFctSlotA>, <targetFctSlotB> ], | ||
* <this.slotIdB>: [ <targetFctSlotB>, <targetFctSlotC> ], | ||
* <this.slotIdC>: [], | ||
* <this.slotA.name>: [ <targetFctSlotA>, <targetFctSlotB> ], | ||
* <this.slotB.name>: [ <targetFctSlotB>, <targetFctSlotC> ], | ||
* <this.slotC.name>: [], | ||
* ..., | ||
@@ -58,3 +73,3 @@ * } | ||
return this.slots.reduce((mapping, slot) => ({ | ||
[slot.id]: slot.filterConnectableSlots(targetSlots), | ||
[slot.name]: slot.filterConnectableSlots(targetSlots), | ||
...mapping, | ||
@@ -61,0 +76,0 @@ }), {}) |
@@ -0,1 +1,2 @@ | ||
const Functionality = require('./Functionality') | ||
const TemperatureSensor = require('./TemperatureSensor') | ||
@@ -9,2 +10,3 @@ const HeaterActuator = require('./HeaterActuator') | ||
module.exports = { | ||
Functionality, | ||
TemperatureSensor, | ||
@@ -11,0 +13,0 @@ HeaterActuator, |
@@ -33,3 +33,3 @@ const util = require('util') | ||
static deepEquals(objA, objB) { | ||
const objDiff = this.diff(objA, objB) | ||
const objDiff = this.diff(objA.serialize(), objB.serialize()) | ||
return typeof objDiff === 'undefined' | ||
@@ -44,2 +44,11 @@ } | ||
serialize(data) { | ||
// This looks backwards because JOIous is composed in to classes | ||
// in this case it anonymously extends the base class it is composed in to | ||
if (!super.serialize) { | ||
throw new Error(`${this.constructor.name} requires a .serialize method`) | ||
} | ||
return super.serialize(data) | ||
} | ||
assertStructure() { | ||
@@ -60,5 +69,4 @@ Joi.attempt( | ||
// deeper print outs while running on Node | ||
toString() { | ||
return util.inspect(this.toJSON(), { compact: false, depth: 4 }) | ||
return util.inspect(this, { compact: false, depth: 6 }) | ||
} | ||
@@ -68,3 +76,3 @@ | ||
[util.inspect.custom]() { | ||
return util.inspect(this.toJSON(), { compact: false, depth: 4 }) | ||
return util.inspect(this.toJSON(), { compact: false, depth: 6 }) | ||
} | ||
@@ -71,0 +79,0 @@ |
@@ -71,12 +71,11 @@ const Joi = require('joi') | ||
is: InSlot.DATA_TYPES.INTEGER, | ||
then: Joi.number().integer().min(Joi.ref('min')).max(Joi.ref('max')) | ||
.required(), | ||
then: Joi.number().integer().min(Joi.ref('min')).max(Joi.ref('max')), | ||
}) | ||
.when('dataType', { | ||
is: InSlot.DATA_TYPES.FLOAT, | ||
then: Joi.number().min(Joi.ref('min')).max(Joi.ref('max')).required(), | ||
then: Joi.number().min(Joi.ref('min')).max(Joi.ref('max')), | ||
}) | ||
.when('dataType', { | ||
is: InSlot.DATA_TYPES.BOOLEAN, | ||
then: Joi.boolean().required(), | ||
then: Joi.boolean(), | ||
}) | ||
@@ -88,2 +87,3 @@ .when('dataType', { | ||
.allow(null) | ||
.default(null) | ||
.required(), | ||
@@ -90,0 +90,0 @@ selectOptions: Joi.array() |
@@ -19,2 +19,24 @@ const Joi = require('joi') | ||
/* ******************************************************************* | ||
* CALIBRATION | ||
* **************************************************************** */ | ||
static get SUPPORTS_CALIBRATION() { return true } | ||
static get OFFSET_SLOPE_CALIBRATON_TYPE() { return 'OFFSET_SLOPE' } | ||
static get SUPPORTED_CALIBRATION_TYPES() { | ||
return [OutSlot.OFFSET_SLOPE_CALIBRATON_TYPE] | ||
} | ||
static get CALIBRATIONS_SCHEMA() { | ||
return Joi.array().items(Joi.object({ | ||
type: Joi.string().allow(OutSlot.OFFSET_SLOPE_CALIBRATON_TYPE).required(), | ||
params: Joi.object({ | ||
offset: Joi.string().allow('float').required(), | ||
slope: Joi.string().allow('float').required(), | ||
}), | ||
})) | ||
} | ||
/* **************************************************************** */ | ||
static get SCHEMA() { | ||
@@ -24,2 +46,3 @@ return Joi.object({ | ||
dataType: Joi.string().allow(...Object.values(OutSlot.DATA_TYPES)).required(), | ||
calibrations: OutSlot.CALIBRATIONS_SCHEMA, | ||
}).concat(super.SCHEMA) | ||
@@ -26,0 +49,0 @@ } |
@@ -5,2 +5,4 @@ const Joi = require('joi') | ||
const DataStream = require('dataStreams/DataStream') | ||
const RegexUtils = require('utils/RegexUtils') | ||
const { publicSuccessRes, publicErrorRes } = require('utils/publicResponses') | ||
const SlotConnectionError = require('./SlotConnectionError') | ||
@@ -10,2 +12,4 @@ | ||
static get SUPPORTS_CALIBRATION() { return false } | ||
/* ******************************************************************* | ||
@@ -25,9 +29,11 @@ * UNITS | ||
return { | ||
[this.UNIT_TYPES.TEMPERATURE]: ['K', '°C', '°F'], | ||
[this.UNIT_TYPES.ROTATIONAL_SPEED]: ['rpm'], | ||
[this.UNIT_TYPES.PERCENTAGE]: ['%'], | ||
[this.UNIT_TYPES.UNITLESS]: ['-'], | ||
[Slot.UNIT_TYPES.TEMPERATURE]: ['K', '°C', '°F'], | ||
[Slot.UNIT_TYPES.ROTATIONAL_SPEED]: ['rpm'], | ||
[Slot.UNIT_TYPES.PERCENTAGE]: ['%'], | ||
[Slot.UNIT_TYPES.UNITLESS]: ['-'], | ||
} | ||
} | ||
static get UNITLESS_UNIT() { return Slot.UNIT_TYPE_UNIT_OPTIONS[Slot.UNIT_TYPES.UNITLESS][0] } | ||
static get ALL_UNIT_VALUES() { | ||
@@ -56,4 +62,5 @@ return Object.values(this.UNIT_TYPE_UNIT_OPTIONS) | ||
constructor({ name, displayType, dataStreams, unit }) { | ||
constructor({ name, functionalityId, displayType, dataStreams, unit }) { | ||
this.name = name | ||
this.functionalityId = functionalityId | ||
this.displayType = displayType | ||
@@ -68,3 +75,3 @@ this.dataStreams = dataStreams | ||
displayType: this.displayType, | ||
dataStreams: this.dataStreams, | ||
dataStreams: this.dataStreams.map(ds => ds.serialize()), | ||
unit: this.unit, | ||
@@ -79,14 +86,24 @@ } | ||
isUnitless() { return this.unit === Slot.UNITLESS_UNIT } | ||
/* ******************************************************************* | ||
* GRAPH ACTIONS: CONNECTING SLOTS | ||
* **************************************************************** */ | ||
_assertConnectionBetweenIsPossible(otherSlot) { | ||
_assertSlotDataTypeCompatible(otherSlot) { | ||
if (this.dataType !== otherSlot.dataType) { | ||
throw new SlotConnectionError(this, otherSlot, 'dataTypes must match between slots') | ||
} | ||
} | ||
if (this.unit !== otherSlot.unit) { | ||
_assertSlotUnitCompatible(otherSlot) { | ||
if ( | ||
this.unit !== otherSlot.unit | ||
&& !this.isUnitless() | ||
&& !otherSlot.isUnitless() | ||
) { | ||
throw new SlotConnectionError(this, otherSlot, 'units must match between slots') | ||
} | ||
} | ||
_assertSlotTypeCompatible(otherSlot) { | ||
if (this.type === 'OutSlot' && otherSlot.type !== 'InSlot') { | ||
@@ -101,5 +118,12 @@ throw new SlotConnectionError(this, otherSlot, 'must have complimentary types') | ||
_assertConnectionBetweenIsPossible(otherSlot) { | ||
this._assertSlotDataTypeCompatible(otherSlot) | ||
this._assertSlotUnitCompatible(otherSlot) | ||
this._assertSlotTypeCompatible(otherSlot) | ||
} | ||
_validateConnectionBetweenIsPossible(otherSlot) { | ||
try { | ||
this._assertConnectionBetweenIsPossible(this, otherSlot) | ||
this._assertConnectionBetweenIsPossible(otherSlot) | ||
return true | ||
@@ -112,3 +136,3 @@ } catch (e) { | ||
_forceAddConnection(dataStream) { | ||
_addDataStreamAndAssertStructure(dataStream) { | ||
this.dataStreams.push(dataStream) | ||
@@ -118,19 +142,55 @@ this.assertStructure() | ||
connect(otherSlot, opts) { | ||
this._assertConnectionBetweenIsPossible(this, otherSlot) | ||
_createDataStreamTo(otherSlot, dataStreamOpts) { | ||
const { | ||
name: sourceSlotName, | ||
functionalityId: sourceFctId, | ||
} = this.type === 'OutSlot' ? this : otherSlot | ||
const dataStream = new DataStream({ | ||
const { | ||
name: sinkSlotName, | ||
functionalityId: sinkFctId, | ||
} = this.type === 'InSlot' ? this : otherSlot | ||
return new DataStream({ | ||
id: uuidv4(), | ||
sourceSlotName: this.type === 'OutSlot' ? this.name : otherSlot.name, | ||
sinkSlotName: this.type === 'InSlot' ? this.name : otherSlot.name, | ||
averagingWindowSize: opts.averagingWindowSize, | ||
sourceFctId, | ||
sourceSlotName, | ||
sinkFctId, | ||
sinkSlotName, | ||
averagingWindowSize: dataStreamOpts.averagingWindowSize, | ||
}) | ||
} | ||
this._forceAddConnection(dataStream) | ||
otherSlot._forceAddConnection(dataStream) | ||
_connectToOrThrow(otherSlot, dataStream) { | ||
const thisDataStreamsLengthPre = this.dataStreams.length | ||
const otherDataStreamsLengthPre = otherSlot.dataStreams.length | ||
try { | ||
this._addDataStreamAndAssertStructure(dataStream) | ||
otherSlot._addDataStreamAndAssertStructure(dataStream) | ||
return dataStream | ||
} catch (e) { | ||
const thisDataStreamsLengthPost = this.dataStreams.length | ||
const otherDataStreamsLengthPost = otherSlot.dataStreams.length | ||
if (thisDataStreamsLengthPost > thisDataStreamsLengthPre) { this.dataStreams.pop() } | ||
if (otherDataStreamsLengthPost > otherDataStreamsLengthPre) { otherSlot.dataStreams.pop() } | ||
} | ||
} | ||
// safe - returns a public response | ||
addConnectionTo(otherSlot, dataStreamOpts = {}) { | ||
try { | ||
this._assertConnectionBetweenIsPossible(otherSlot) | ||
const dataStream = this._createDataStreamTo(otherSlot, dataStreamOpts) | ||
this._connectToOrThrow(otherSlot, dataStream) | ||
return publicSuccessRes({ thisSlot: this, otherSlot, dataStream }) | ||
} catch (e) { | ||
return publicErrorRes({ errorMsg: e.message, thisSlot: this, otherSlot, dataStream: null }) | ||
} | ||
} | ||
filterConnectableSlots(slots) { | ||
return slots.filter(slot => ( | ||
this._validateConnectionBetweenIsPossible(this, slot) | ||
this._validateConnectionBetweenIsPossible(slot) | ||
)) | ||
@@ -137,0 +197,0 @@ } |
@@ -8,7 +8,2 @@ class SlotConnectionError extends Error { | ||
// Maintains proper stack trace for where our error was thrown (only available on V8) | ||
if (Error.captureStackTrace) { | ||
Error.captureStackTrace(this, SlotConnectionError) | ||
} | ||
this.name = SlotConnectionError.NAME | ||
@@ -15,0 +10,0 @@ this.message = `${msg}:\n${slotA}\n${slotB}` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
58944
50
1546
230