dal-engine-core-js-lib-dev 0.0.4 → 0.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dal-engine-core-js-lib-dev",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "main": "dist/index.cjs",
5
5
  "module": "dist/index.esm.js",
6
6
  "type": "module",
package/src/Base.js CHANGED
@@ -1,15 +1,20 @@
1
- /**
2
- * Base class for all objects in design.
3
- */
4
1
  class Base {
5
2
  /**
6
- * Initialize the base class.
3
+ * Initialize the base class of all members of the design.
4
+ * The dal_engine_uid is a unique identifier for each instance.
5
+ * When the design is serialized, the dal_engine_uid is used
6
+ * to identify when the design is being loaded from file.
7
7
  * @param {String} name
8
8
  */
9
9
  constructor () {
10
- this.uid = crypto.randomUUID();
10
+ this.dal_engine_uid = crypto.randomUUID();
11
11
  }
12
12
 
13
+ /**
14
+ * Returns the type of object as specifed in TYPES.js.
15
+ * (e.g. participant, behavior, invariant, graph node, etc.)
16
+ * @returns {Number} The type of node.
17
+ */
13
18
  getType () {
14
19
  return this.type;
15
20
  }
@@ -1,37 +1,68 @@
1
1
  import Base from "../Base";
2
+ import BehaviorAlreadyExistsError from "../Errors/BehaviorAlreadyExistsError";
2
3
  import InvalidTransitionError from "../Errors/InvalidTransitionError";
4
+ import MissingAttributes from "../Errors/MissingAttributes";
3
5
  import UnknownBehaviorError from "../Errors/UnknownBehaviorError";
6
+ import isLoadedFromFile from "../helpers/isLoadedFromFile";
4
7
  import Behavior from "../Members/Behavior";
5
8
  import ENGINE_TYPES from "../TYPES";
6
9
  import GraphNode from "./GraphNode";
7
10
 
8
- /**
9
- * Class representing the behavioral control graph.
10
- */
11
+
11
12
  class BehavioralControlGraph extends Base {
12
13
  /**
13
- * Initialize the behavioral control graph.
14
- * @param {String} name
15
- * @param args
14
+ * Class representing the behavioral control graph. The behavioral control
15
+ * graph is a directed graph where nodes represent behaviors and edges
16
+ * represent valid transitions between behaviors.
17
+ *
18
+ * The graph can be used to execute the design by starting at the atomic
19
+ * node and transitioning to observed behaviors. As the design is executed,
20
+ * the values of the participants are set from the observed values and the
21
+ * invariants are checked at each transition to recognize if the design has
22
+ * entered a semantically invalid state.
23
+ *
24
+ * Currently, it only has to be initialized with a name and the remaining
25
+ * attributes can be added using the provided methods. If the graph is being
26
+ * loaded from a file, then the presence of a UID in the args will select
27
+ * the relevant method to load the graph from a JSON object.
28
+ *
29
+ * @param {Object} args The args to initialize the behavioral control graph.
16
30
  */
17
31
  constructor (args) {
18
32
  super();
19
33
  this.type = ENGINE_TYPES.BEHAVIORAL_CONTROL_GRAPH;
20
34
  this.nodes = [];
21
- if (typeof args === "object" && args !== null) {
22
- if (Object.hasOwn(args, "uid")) {
23
- this._loadGraphFromJSON(args);
24
- } else {
25
- this.name = args.name;
26
- }
35
+ (isLoadedFromFile(args) ? this._loadFromFile(args) : this._loadArgs(args));
36
+ }
37
+
38
+ /**
39
+ * Loads the provided arguments.
40
+ * @throws {MissingAttributes} Thrown when required attr is not present.
41
+ * @param {Object} args
42
+ */
43
+ _loadArgs (args) {
44
+ /**
45
+ * TODO: Move the attributes to private and use getters and setters
46
+ * for them. Repeat for all the other classes.
47
+ */
48
+ const expectedAttributes = ["name"];
49
+ if (typeof args !== "object" || args === null || Array.isArray(args)) {
50
+ // Not an object, so all attributes are missing.
51
+ throw new MissingAttributes("BehavioralControlGraph", expectedAttributes);
27
52
  }
53
+ expectedAttributes.forEach((attr) => {
54
+ if (!(attr in args)) {
55
+ throw new MissingAttributes("BehavioralControlGraph", attr);
56
+ }
57
+ this[attr] = args[attr];
58
+ });
28
59
  }
29
60
 
30
61
  /**
31
62
  * Loads the graph from a JSON object..
32
63
  * @param {Object} graphJson
33
64
  */
34
- _loadGraphFromJSON (graphJson) {
65
+ _loadFromFile (graphJson) {
35
66
  for (const [key, value] of Object.entries(graphJson)) {
36
67
  if (key === "nodes") {
37
68
  value.forEach(node => this.nodes.push(new GraphNode(node)));
@@ -42,17 +73,27 @@ class BehavioralControlGraph extends Base {
42
73
  }
43
74
 
44
75
  /**
45
- * Adds a node to the graph.
46
- * @param {Behavior} behaviorId
47
- * @param {Array} goToBehaviorsIds
48
- * @param {Boolean} isAtomic
49
- * @returns
76
+ * Adds a node to the graph with the provided arguments.
77
+ *
78
+ * @param {Behavior} behaviorId ID of the behavior represented by the node.
79
+ * @param {Array} goToBehaviorIds IDs of the behaviors that are valid
80
+ * transitions from this node.
81
+ * @param {Boolean} isAtomic Flag indicating if the behavior represented by
82
+ * the node is atomic.
83
+ * @param {Boolean} isDesignFork Flag indicating if the node is a
84
+ * design fork.
85
+ * @throws {BehaviorAlreadyExistsError} Raised when a node with the provided
86
+ * @returns {GraphNode} The created graph node.
50
87
  */
51
- _addNode (behaviorId, goToBehaviorsIds, isAtomic) {
88
+ addNode (behaviorId, goToBehaviorIds, isAtomic, isDesignFork) {
89
+ if (this.nodes.some((node) => node.getBehavior().name === behaviorId)) {
90
+ throw new BehaviorAlreadyExistsError(behaviorId);
91
+ }
52
92
  const node = new GraphNode({
53
93
  behavior: new Behavior({name: behaviorId}),
54
- goToBehaviorsIds: goToBehaviorsIds?goToBehaviorsIds:[],
94
+ goToBehaviorIds: goToBehaviorIds?goToBehaviorIds:[],
55
95
  isAtomic: isAtomic?isAtomic:false,
96
+ isDesignFork: isDesignFork?isDesignFork:false,
56
97
  });
57
98
  this.nodes.push(node);
58
99
  return node;
@@ -64,29 +105,25 @@ class BehavioralControlGraph extends Base {
64
105
  * @param {String} behaviorName
65
106
  * @throws {UnknownBehaviorError} Raised when the provided behavior
66
107
  * does not exist in the graph.
67
- * @returns
108
+ * @returns {GraphNode} The found graph node.
68
109
  */
69
- _findNode (behaviorName) {
70
- for (let i = 0; i < this.nodes.length; i++) {
71
- const behavior = this.nodes[i].getBehavior();
72
- if (behavior.name === behaviorName) {
73
- return this.nodes[i];
74
- }
110
+ findNode (behaviorName) {
111
+ const node = this.nodes.find(
112
+ (node) => node.getBehavior().name === behaviorName
113
+ );
114
+ if (!node) {
115
+ throw new UnknownBehaviorError(behaviorName);
75
116
  }
76
- throw new UnknownBehaviorError(behaviorName);
117
+ return node;
77
118
  }
78
119
 
79
120
  /**
80
121
  * Sets the active node given the behavior name.
81
- * The execution provides the next observed
82
- * behavior and the active node indicates if
83
- * if it is a valid transition.
84
- *
85
122
  *
86
123
  * @param {String} behaviorName
87
124
  */
88
- _setCurrentBehavior (behaviorName) {
89
- const node = this._findNode(behaviorName);
125
+ setCurrentBehavior (behaviorName) {
126
+ const node = this.findNode(behaviorName);
90
127
  /**
91
128
  * TODO: Ensure it is atomic because the execution
92
129
  * will only set a behavior when its the first one.
@@ -98,13 +135,15 @@ class BehavioralControlGraph extends Base {
98
135
  /**
99
136
  * Check if the observed behavior is a valid transition
100
137
  * given the current node.
101
- * @param {String} nextBehaviorName
138
+ *
139
+ * @param {String} nextBehaviorName Name of the next behavior to
140
+ * transition to.
102
141
  * @throws {InvalidTransitionError} Raised when the provided
103
142
  * behavior is not a valid transition.
104
143
  */
105
- _goToBehavior (nextBehaviorName) {
144
+ goToBehavior (nextBehaviorName) {
106
145
  if (this.currentNode.isValidTransition(nextBehaviorName)) {
107
- this.currentNode = this._findNode(nextBehaviorName);
146
+ this.currentNode = this.findNode(nextBehaviorName);
108
147
  } else {
109
148
  throw new InvalidTransitionError(this.currentNode.getBehavior().name, nextBehaviorName);
110
149
  }
@@ -1,14 +1,18 @@
1
1
  import Base from "../Base";
2
+ import MissingAttributes from "../Errors/MissingAttributes";
3
+ import TransitionAlreadyExistsError from "../Errors/TransitionAlreadyExistsError";
4
+ import isLoadedFromFile from "../helpers/isLoadedFromFile";
2
5
  import Behavior from "../Members/Behavior";
3
6
  import ENGINE_TYPES from "../TYPES";
4
- /**
5
- * Class representing a behavioral control graph node.
6
- */
7
+
7
8
  class GraphNode extends Base {
8
9
  /**
9
- * Initialize the node.
10
- * @param {String} name
11
- * @param args
10
+ * Class representing a behavioral control graph node. Each node has a
11
+ * behavior and a list of behaviors it can transition to. The node also
12
+ * has flags to indicate if the behavior is atomic or if the node is a
13
+ * design fork.
14
+ *
15
+ * @param {Object} args The args to initialize the graph node.
12
16
  */
13
17
  constructor (args) {
14
18
  super();
@@ -16,28 +20,41 @@ class GraphNode extends Base {
16
20
  this._behavior = null;
17
21
  this._goToBehaviorIds = [];
18
22
  this._isAtomic = false;
19
- if (typeof args === "object" && args !== null) {
20
- if (Object.hasOwn(args, "uid")) {
21
- this._loadNodeFromJSON(args);
22
- } else {
23
- this._behavior = args.behavior;
24
- this._goToBehaviorIds = args.goToBehaviorsIds;
25
- }
23
+ this._isDesignFork = false;
24
+ (isLoadedFromFile(args) ? this._loadFromFile(args) : this._loadArgs(args));
25
+ }
26
+
27
+ /**
28
+ * Loads the provided arguments.
29
+ * @param {Object} args Arguments to initialize the graph node with.
30
+ * @throws {MissingAttributes} Thrown when required attr is not present.
31
+ */
32
+ _loadArgs (args) {
33
+ const expectedAttributes = ["behavior", "goToBehaviorIds", "isAtomic", "isDesignFork"];
34
+ if (typeof args !== "object" || args === null || Array.isArray(args)) {
35
+ // Not an object, so all attributes are missing.
36
+ throw new MissingAttributes("GraphNode", expectedAttributes);
26
37
  }
38
+ expectedAttributes.forEach((attr) => {
39
+ if (!(attr in args)) {
40
+ throw new MissingAttributes("GraphNode", attr);
41
+ }
42
+ this["_" + attr] = args[attr];
43
+ });
27
44
  }
28
45
 
29
46
  /**
30
- * Loads the nodes from a JSON object.
31
- * @param {Object} nodesJSON
47
+ * Loads the node from file.
48
+ * @param {Object} nodeJSON The JSON object read from file.
32
49
  */
33
- _loadNodeFromJSON (nodesJSON) {
34
- for (const [key, value] of Object.entries(nodesJSON)) {
50
+ _loadFromFile (nodeJSON) {
51
+ for (const [key, value] of Object.entries(nodeJSON)) {
35
52
  if (key === "behavior") {
36
53
  this._behavior = new Behavior(value);
37
- } else if (key === "goToBehaviorsIds") {
54
+ } else if (key === "goToBehaviorIds") {
38
55
  value.forEach(behaviorId => this._goToBehaviorIds.push(behaviorId));
39
56
  } else {
40
- this[key] = nodesJSON[key];
57
+ this[key] = nodeJSON[key];
41
58
  }
42
59
  };
43
60
  }
@@ -52,24 +69,27 @@ class GraphNode extends Base {
52
69
 
53
70
  /**
54
71
  * Returns the list of behavior names that this node transitions to.
55
- * @returns {Array}
72
+ * @returns {Array} List of behavior names that this node transitions to.
56
73
  */
57
74
  getGoToBehaviors () {
58
75
  return this._goToBehaviorIds;
59
76
  }
60
77
 
61
78
  /**
62
- * Adds a behavior name to the list of behaviors that this
63
- * node transitions to.
79
+ * Adds a behavior to the transitions from this node.
64
80
  * @param {String} behaviorId ID of behavior.
81
+ * @throws {TransitionAlreadyExistsError} Raised when a transition to the
82
+ * provided behavior already exists.
65
83
  */
66
84
  addGoToBehavior (behaviorId) {
85
+ if (this._goToBehaviorIds.includes(behaviorId)) {
86
+ throw new TransitionAlreadyExistsError(this.behaviorName, behaviorId);
87
+ }
67
88
  this._goToBehaviorIds.push(behaviorId);
68
89
  }
69
90
 
70
91
  /**
71
- * Adds a behavior name to the list of behaviors that this
72
- * node transitions to.
92
+ * Adds list of behaviors to the transitions from this node.
73
93
  * @param {Array} behaviorIds IDs of behaviors.
74
94
  */
75
95
  addGoToBehaviors (behaviorIds) {
@@ -91,7 +111,7 @@ class GraphNode extends Base {
91
111
  * Raises a flag to indicate if the behavior is atomic or not.
92
112
  * @param {Boolean} isAtomic Flag indicates if the behavior is atomic.
93
113
  */
94
- setAtomic (isAtomic) {
114
+ setIsAtomic (isAtomic) {
95
115
  this._isAtomic = isAtomic;
96
116
  }
97
117
 
@@ -103,6 +123,22 @@ class GraphNode extends Base {
103
123
  return this._isAtomic;
104
124
  }
105
125
 
126
+ /**
127
+ * Raises a flag to indicate if this node is a fork in the design.
128
+ * @param {Boolean} forks Flag indicates if the node is a fork.
129
+ */
130
+ setIsDesignFork (forks) {
131
+ this._isDesignFork = forks;
132
+ }
133
+
134
+ /**
135
+ * Returns whether the node is a fork in the design or not.
136
+ * @returns {Boolean}
137
+ */
138
+ isDesignFork () {
139
+ return this._isDesignFork;
140
+ }
141
+
106
142
  /**
107
143
  * Checks if the provided behavior name is a valid
108
144
  * transition from this node.
@@ -1,35 +1,137 @@
1
+ import GraphWithNameExistsError from "../Errors/GraphWithNameExistsError";
2
+ import UnknownGraph from "../Errors/UnknownGraph";
1
3
  import BehavioralControlGraph from "./BehavioralControlGraph";
2
4
 
3
5
  /**
4
6
  * Class representing a collection of atomic graphs.
5
7
  *
6
- * These graphs together represent the design. I chose to separate
7
- * them into their graphs because as the design grows larger, it will
8
- * be easier to manage the design if it is separated into smaller graphs.
9
- * It is also easier to visualize in the UI in managable way.
8
+ * These graphs together represent the design. Within each graph, every node
9
+ * must be part of the same tree. There cannot be multiple disconnected trees,
10
+ * if there are, then it is a separate graph in this collection. Each graph
11
+ * is identified by a unique name and is selected as the active graph when the
12
+ * atomic behavior is observed. The atomic behavior is not transitioned to from
13
+ * any other behavior, it is the root of the tree.
10
14
  */
11
- export class Graphs {
12
-
15
+ class Graphs {
13
16
  constructor () {
14
17
  this._graphs = {};
18
+ this.addGraph("default graph");
15
19
  }
16
20
 
17
21
  /**
18
- * Adds a graph to the collection of graphs.
22
+ * Load the graphs from file.
23
+ *
24
+ * @param {String} jsonText JSON text representing the collection of graphs.
25
+ */
26
+ loadFromJson (jsonText) {
27
+ const parsed = JSON.parse(jsonText);
28
+ Object.keys(parsed._graphs).forEach(graphId => {
29
+ this._graphs[graphId] = new BehavioralControlGraph(parsed._graphs[graphId]);
30
+ });
31
+ this._activeGraph = this._graphs[Object.keys(this._graphs)[0]];
32
+ }
33
+
34
+ /**
35
+ * Given a graph ID, creates the graph and adds it to the collection.
36
+ *
19
37
  * @param {String} graphId ID of the graph.
20
38
  * @returns {BehavioralControlGraph} The graph that was added.
39
+ * @throws {GraphWithNameExistsError} Raised when a graph with the provided
40
+ * name already exists in the collection of graphs.
21
41
  */
22
42
  addGraph (graphId) {
23
- this._graphs[graphId] = new BehavioralControlGraph();
24
- return this._graphs[graphId];
43
+ if (graphId in this._graphs) {
44
+ throw new GraphWithNameExistsError(graphId);
45
+ }
46
+ this._graphs[graphId] = new BehavioralControlGraph({name: graphId});
47
+ this._activeGraph = this._graphs[graphId];
48
+ return this._activeGraph;
25
49
  }
26
50
 
27
51
  /**
28
- * Returns the graph with the given graphId.
52
+ * Returns the graph with the given graphID from the collection.
53
+ *
29
54
  * @param {String} graphId ID of the graph to return.
30
55
  * @returns {BehavioralControlGraph} The graph with the given graphId.
56
+ * @throws {UnknownGraph} Raised when a graph with the provided graphId does
57
+ * not exist in the collection of graphs.
31
58
  */
32
59
  getGraph (graphId) {
33
- return this._graphs[graphId];
60
+ if (graphId in this._graphs) {
61
+ return this._graphs[graphId];
62
+ } else {
63
+ throw new UnknownGraph(graphId);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Removes a graph with the given id from the collection of graphs. After
69
+ * the graph is removed, it selects the first graph in the collection of
70
+ * graphs. If there are no graphs left in the collection, it creates a new
71
+ * graph with the name "default graph" and selects it as the active graph.
72
+ *
73
+ * @param {String} graphId ID of the graph to remove.
74
+ * @throws {UnknownGraph} Raised when the provided graphId does not exist
75
+ * in the collection of graphs.
76
+ */
77
+ removeGraph (graphId) {
78
+ if (graphId in this._graphs) {
79
+ delete this._graphs[graphId];
80
+ } else {
81
+ throw new UnknownGraph(graphId);
82
+ }
83
+ // TODO: Is there a better policy for which graph to select
84
+ // after the current graph is removed?
85
+ if (Object.keys(this._graphs).length === 0) {
86
+ this.addGraph("default graph");
87
+ } else {
88
+ this._activeGraph = this._graphs[Object.keys(this._graphs)[0]];
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Finds the graph with the given graphId and sets it as the active graph.
94
+ *
95
+ * @param {String} graphId Id of the graph to set as active.
96
+ * @returns {BehavioralControlGraph} The currently active graph.
97
+ * @throws {UnknownGraph} Raised when the provided graphId does not exist
98
+ * in the collection of graphs.
99
+ */
100
+ setActiveGraph (graphId) {
101
+ if (graphId in this._graphs) {
102
+ this._activeGraph = this._graphs[graphId];
103
+ } else {
104
+ throw new UnknownGraph(graphId);
105
+ }
106
+ return this._activeGraph;
107
+ }
108
+
109
+ /**
110
+ * Returns the active graph.
111
+ *
112
+ * @returns {BehavioralControlGraph} The currently active graph.
113
+ */
114
+ getActiveGraph () {
115
+ return this._activeGraph;
116
+ }
117
+
118
+ /**
119
+ * Returns a collection of all the graphs in the design.
120
+ *
121
+ * @returns {Object} The collection of all graphs in the design.
122
+ */
123
+ getGraphs () {
124
+ return this._graphs;
125
+ }
126
+
127
+ /**
128
+ * Returns a list of all the graph names in the design.
129
+ *
130
+ * @returns {Array} A list of all graph names in the design.
131
+ */
132
+ getGraphNames () {
133
+ return Object.keys(this._graphs);
34
134
  }
35
135
  }
136
+
137
+ export default Graphs;