dal-engine-core-js-lib-dev 0.0.0 → 0.0.2
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/dist/index.cjs +246 -51
- package/dist/index.esm.js +246 -51
- package/package.json +1 -1
- package/src/BehavioralControlGraph/BehavioralControlGraph.js +18 -4
- package/src/BehavioralControlGraph/GraphNode.js +16 -7
- package/src/DALEngine.js +64 -12
- package/src/Errors/MissingAttributes.js +15 -0
- package/src/Members/Behavior.js +38 -15
- package/src/Members/Invariant.js +68 -9
- package/src/Members/Participant.js +38 -6
- package/tests/DALEngine.test.js +50 -33
- package/tests/Invariant.test.js +15 -7
- package/tests/SimpleDesign.test.js +31 -0
- package/tests/simple_design_temp.json +1 -0
package/dist/index.cjs
CHANGED
|
@@ -54,6 +54,18 @@ ENGINE_TYPES = Object.freeze(ENGINE_TYPES);
|
|
|
54
54
|
|
|
55
55
|
var ENGINE_TYPES$1 = ENGINE_TYPES;
|
|
56
56
|
|
|
57
|
+
class MissingAttributes extends DALEngineError {
|
|
58
|
+
constructor (type, attribute) {
|
|
59
|
+
let msg;
|
|
60
|
+
if (Array.isArray(attribute) && attribute.length > 1) {
|
|
61
|
+
msg = `"${type}" must be initialized with the attributes "${attribute}".`;
|
|
62
|
+
} else {
|
|
63
|
+
msg = `"${type}" must be initialized with the attribute "${attribute}".`;
|
|
64
|
+
}
|
|
65
|
+
super(msg);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
57
69
|
/**
|
|
58
70
|
* Class representing a Invariant in the design.
|
|
59
71
|
*/
|
|
@@ -67,16 +79,34 @@ class Invariant extends Base {
|
|
|
67
79
|
super();
|
|
68
80
|
this.type = ENGINE_TYPES$1.INVARIANT;
|
|
69
81
|
this.invariantViolated = false;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
82
|
+
this.invariantType = null;
|
|
83
|
+
this.traceId = null;
|
|
84
|
+
if (typeof args === "object" && Object.hasOwn(args, "uid")) {
|
|
85
|
+
this.loadInvariantFromJSON(args);
|
|
86
|
+
} else {
|
|
87
|
+
this.loadArgs(args);
|
|
77
88
|
}
|
|
78
89
|
}
|
|
79
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Loads the provided arguments.
|
|
93
|
+
* @throws {MissingAttributes} Thrown when required attr is not present.
|
|
94
|
+
* @param {Object} args
|
|
95
|
+
*/
|
|
96
|
+
loadArgs (args) {
|
|
97
|
+
const expectedAttributes = ["name", "rule"];
|
|
98
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
99
|
+
// Not an object, so all attributes are missing.
|
|
100
|
+
throw new MissingAttributes("Invariant", expectedAttributes);
|
|
101
|
+
}
|
|
102
|
+
expectedAttributes.forEach((attr) => {
|
|
103
|
+
if (!(attr in args)) {
|
|
104
|
+
throw new MissingAttributes("Invariant", attr);
|
|
105
|
+
}
|
|
106
|
+
this[attr] = args[attr];
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
80
110
|
/**
|
|
81
111
|
* Loads the participant from a JSON object.
|
|
82
112
|
* @param {Object} invariantJSON
|
|
@@ -109,13 +139,52 @@ class Invariant extends Base {
|
|
|
109
139
|
enforceMinLength (value) {
|
|
110
140
|
if ("keys" in this.rule) {
|
|
111
141
|
for (let i = 0; i < this.rule["keys"].length; i++) {
|
|
112
|
-
console.log(this.rule["keys"][i], value);
|
|
113
142
|
value = value[this.rule["keys"][i]];
|
|
114
143
|
}
|
|
115
144
|
} if (value === null || typeof value !== "string" || value.length < this.rule.value) {
|
|
116
145
|
this.invariantViolated = true;
|
|
117
146
|
}
|
|
118
147
|
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Sets the invariant type.
|
|
152
|
+
*
|
|
153
|
+
* The two types of invariants are: Intrinsic and Substrate
|
|
154
|
+
*
|
|
155
|
+
* Substrate invariants are learnt by the design from the
|
|
156
|
+
* environment.
|
|
157
|
+
*
|
|
158
|
+
* Intrinsic invariants are arrived at naturally from the
|
|
159
|
+
* designs control flow, data dependencies and semantic assumptions.
|
|
160
|
+
*
|
|
161
|
+
* @param {String} invariantType
|
|
162
|
+
*/
|
|
163
|
+
setInvariantType (invariantType) {
|
|
164
|
+
// TODO: Add validation for invariantType.
|
|
165
|
+
this.invariantType = invariantType;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* This function adds the trace id which can be used for
|
|
170
|
+
* automated testing.
|
|
171
|
+
*
|
|
172
|
+
* Substrate invariants correspond to a trace that represents
|
|
173
|
+
* an environment that reveals a limitation of the substrate,
|
|
174
|
+
* thus motivating the invariant. It is also the environment
|
|
175
|
+
* in which an implementation can prove that it respects this
|
|
176
|
+
* invariant.
|
|
177
|
+
*
|
|
178
|
+
* Intrisinc invariants correspond to a trace that represents
|
|
179
|
+
* a factory default environment that enables the implementation
|
|
180
|
+
* to prove that it respects the invariant.
|
|
181
|
+
*
|
|
182
|
+
* @param {String} traceId ID of trace used for automated testing.
|
|
183
|
+
*/
|
|
184
|
+
setTraceId (traceId) {
|
|
185
|
+
// TODO: Add validation for traceId.
|
|
186
|
+
this.traceId = traceId;
|
|
187
|
+
}
|
|
119
188
|
}
|
|
120
189
|
|
|
121
190
|
/**
|
|
@@ -131,16 +200,34 @@ class Participant extends Base {
|
|
|
131
200
|
super();
|
|
132
201
|
this.type = ENGINE_TYPES$1.INVARIANT;
|
|
133
202
|
this.invariants = [];
|
|
203
|
+
this.abstractionId = null;
|
|
134
204
|
this.invariantViolated = false;
|
|
135
|
-
if (typeof args === "object" && args
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this.name = args.name;
|
|
140
|
-
}
|
|
205
|
+
if (typeof args === "object" && Object.hasOwn(args, "uid")) {
|
|
206
|
+
this.loadParticipantFromJSON(args);
|
|
207
|
+
} else {
|
|
208
|
+
this.loadArgs(args);
|
|
141
209
|
}
|
|
142
210
|
}
|
|
143
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Loads the provided arguments.
|
|
214
|
+
* @throws {MissingAttributes} Thrown when required attr is not present.
|
|
215
|
+
* @param {Object} args
|
|
216
|
+
*/
|
|
217
|
+
loadArgs (args) {
|
|
218
|
+
const expectedAttributes = ["name"];
|
|
219
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
220
|
+
// Not an object, so all attributes are missing.
|
|
221
|
+
throw new MissingAttributes("Participant", expectedAttributes);
|
|
222
|
+
}
|
|
223
|
+
expectedAttributes.forEach((attr) => {
|
|
224
|
+
if (!(attr in args)) {
|
|
225
|
+
throw new MissingAttributes("Participant", attr);
|
|
226
|
+
}
|
|
227
|
+
this[attr] = args[attr];
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
144
231
|
/**
|
|
145
232
|
* Loads the participant from a JSON object.
|
|
146
233
|
* @param {Object} participantJSON
|
|
@@ -187,6 +274,18 @@ class Participant extends Base {
|
|
|
187
274
|
}
|
|
188
275
|
return this.invariantViolated;
|
|
189
276
|
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Map the abstraction ID from the execution to the participant.
|
|
280
|
+
*
|
|
281
|
+
* This abstraction id will be used to assign a value to the
|
|
282
|
+
* participant from the execution using the logged abstraction id.
|
|
283
|
+
*
|
|
284
|
+
* @param {String} abstractionId
|
|
285
|
+
*/
|
|
286
|
+
mapAbstraction (abstractionId) {
|
|
287
|
+
this.abstractionId = abstractionId;
|
|
288
|
+
}
|
|
190
289
|
}
|
|
191
290
|
|
|
192
291
|
/**
|
|
@@ -202,16 +301,34 @@ class Behavior extends Base {
|
|
|
202
301
|
super();
|
|
203
302
|
this.type = ENGINE_TYPES$1.BEHAVIOR;
|
|
204
303
|
this.participants = [];
|
|
304
|
+
this.abstractionIds = [];
|
|
205
305
|
this.invalidWorldState = false;
|
|
206
|
-
if (typeof args === "object" && args
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
this.name = args.name;
|
|
211
|
-
}
|
|
306
|
+
if (typeof args === "object" && Object.hasOwn(args, "uid")) {
|
|
307
|
+
this.loadBehaviorFromJSON(args);
|
|
308
|
+
} else {
|
|
309
|
+
this.loadArgs(args);
|
|
212
310
|
}
|
|
213
311
|
}
|
|
214
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Loads the provided arguments.
|
|
315
|
+
* @throws {MissingAttributes} Thrown when required attr is not present.
|
|
316
|
+
* @param {Object} args
|
|
317
|
+
*/
|
|
318
|
+
loadArgs (args) {
|
|
319
|
+
const expectedAttributes = ["name"];
|
|
320
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
321
|
+
// Not an object, so all attributes are missing.
|
|
322
|
+
throw new MissingAttributes("Behavior", expectedAttributes);
|
|
323
|
+
}
|
|
324
|
+
expectedAttributes.forEach((attr) => {
|
|
325
|
+
if (!(attr in args)) {
|
|
326
|
+
throw new MissingAttributes("Behavior", attr);
|
|
327
|
+
}
|
|
328
|
+
this[attr] = args[attr];
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
215
332
|
/**
|
|
216
333
|
* Loads the behavior from a JSON object.
|
|
217
334
|
* @param {Object} behaviorJSON
|
|
@@ -241,17 +358,21 @@ class Behavior extends Base {
|
|
|
241
358
|
* @param {*} value
|
|
242
359
|
*/
|
|
243
360
|
setParticipantValue (participantName, value) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (violation) {
|
|
250
|
-
this.invalidWorldState = true;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
361
|
+
const participant = this.participants.find(obj => obj.name === participantName);
|
|
362
|
+
participant.value = value;
|
|
363
|
+
const violation = participant.enforceInvariants();
|
|
364
|
+
if (violation) {
|
|
365
|
+
this.invalidWorldState = true;
|
|
253
366
|
}
|
|
254
367
|
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Maps the abstraction id from execution to the behavior.
|
|
371
|
+
* @param {String} abstractionId
|
|
372
|
+
*/
|
|
373
|
+
addMapping (abstractionId) {
|
|
374
|
+
this.abstractionIds.push(abstractionId);
|
|
375
|
+
}
|
|
255
376
|
}
|
|
256
377
|
|
|
257
378
|
/**
|
|
@@ -266,13 +387,13 @@ class GraphNode extends Base {
|
|
|
266
387
|
constructor (args) {
|
|
267
388
|
super();
|
|
268
389
|
this.type = ENGINE_TYPES$1.GRAPH_NODE;
|
|
269
|
-
this.
|
|
390
|
+
this.goToBehaviorsIds = [];
|
|
270
391
|
if (typeof args === "object" && args !== null) {
|
|
271
392
|
if (Object.hasOwn(args, "uid")) {
|
|
272
393
|
this.loadNodeFromJSON(args);
|
|
273
394
|
} else {
|
|
274
395
|
this.behavior = args.behavior;
|
|
275
|
-
this.
|
|
396
|
+
this.goToBehaviorsIds = args.goToBehaviorsIds;
|
|
276
397
|
}
|
|
277
398
|
}
|
|
278
399
|
}
|
|
@@ -285,13 +406,22 @@ class GraphNode extends Base {
|
|
|
285
406
|
for (const [key, value] of Object.entries(nodesJSON)) {
|
|
286
407
|
if (key === "behavior") {
|
|
287
408
|
this.behavior = new Behavior(value);
|
|
288
|
-
} else if (key === "
|
|
289
|
-
value.forEach(
|
|
409
|
+
} else if (key === "goToBehaviorsIds") {
|
|
410
|
+
value.forEach(behaviorId => this.goToBehaviorsIds.push(behaviorId));
|
|
290
411
|
} else {
|
|
291
412
|
this[key] = nodesJSON[key];
|
|
292
413
|
}
|
|
293
414
|
} }
|
|
294
415
|
|
|
416
|
+
/**
|
|
417
|
+
* Adds a behavior name to the list of behaviors that this
|
|
418
|
+
* node transitions to.
|
|
419
|
+
* @param {String} behaviorId ID of behavior.
|
|
420
|
+
*/
|
|
421
|
+
addGoToBehavior (behaviorId) {
|
|
422
|
+
this.goToBehaviorsIds.push(behaviorId);
|
|
423
|
+
}
|
|
424
|
+
|
|
295
425
|
/**
|
|
296
426
|
* Checks if the provided behavior name is a valid
|
|
297
427
|
* behavior that the control flow selects as a
|
|
@@ -301,9 +431,9 @@ class GraphNode extends Base {
|
|
|
301
431
|
* @returns {Boolean}
|
|
302
432
|
*/
|
|
303
433
|
isValidGoToBehavior (behaviorName) {
|
|
304
|
-
for (let i = 0; i < this.
|
|
305
|
-
const
|
|
306
|
-
if (
|
|
434
|
+
for (let i = 0; i < this.goToBehaviorsIds.length; i++) {
|
|
435
|
+
const behaviorId = this.goToBehaviorsIds[i];
|
|
436
|
+
if (behaviorId === behaviorName) {
|
|
307
437
|
return true;
|
|
308
438
|
}
|
|
309
439
|
}
|
|
@@ -349,13 +479,13 @@ class BehavioralControlGraph extends Base {
|
|
|
349
479
|
/**
|
|
350
480
|
* Adds a node to the graph.
|
|
351
481
|
* @param {Behavior} behavior
|
|
352
|
-
* @param {Array}
|
|
482
|
+
* @param {Array} goToBehaviorsIds
|
|
353
483
|
* @returns
|
|
354
484
|
*/
|
|
355
|
-
addNode (behavior,
|
|
485
|
+
addNode (behavior, goToBehaviorsIds) {
|
|
356
486
|
const node = new GraphNode({
|
|
357
487
|
behavior: behavior,
|
|
358
|
-
|
|
488
|
+
goToBehaviorsIds: goToBehaviorsIds,
|
|
359
489
|
});
|
|
360
490
|
this.nodes.push(node);
|
|
361
491
|
return node;
|
|
@@ -412,6 +542,20 @@ class BehavioralControlGraph extends Base {
|
|
|
412
542
|
throw new InvalidTransitionError(this.currentNode.behavior.name, nextBehaviorName);
|
|
413
543
|
}
|
|
414
544
|
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Exports the graph represented using mermaid syntax for visualization.
|
|
548
|
+
* @returns {String} Graph as mermaid diagram.
|
|
549
|
+
*/
|
|
550
|
+
exportAsMermaid () {
|
|
551
|
+
let mermaid = "flowchart TD\n";
|
|
552
|
+
this.nodes.forEach((node) => {
|
|
553
|
+
node.goToBehaviorsIds.forEach((behaviorId) => {
|
|
554
|
+
mermaid += ` ${node.behavior.name} --> ${behaviorId}\n`;
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
return mermaid;
|
|
558
|
+
}
|
|
415
559
|
}
|
|
416
560
|
|
|
417
561
|
/**
|
|
@@ -419,16 +563,37 @@ class BehavioralControlGraph extends Base {
|
|
|
419
563
|
* abstraction language. It exposes functions
|
|
420
564
|
* configure the engine through the DAL specification.
|
|
421
565
|
*
|
|
422
|
-
* The
|
|
423
|
-
*
|
|
424
|
-
*
|
|
566
|
+
* The design specified in this engine is mapped onto
|
|
567
|
+
* the implementation using abstraction ids. The
|
|
568
|
+
* implementation is then instrumented and the resulting
|
|
569
|
+
* execution trace is fed back into the engine and is
|
|
570
|
+
* automatically debugged by transforming the execution
|
|
571
|
+
* into the behavior of the design and enforcing the
|
|
572
|
+
* invariants.
|
|
425
573
|
*/
|
|
426
574
|
class DALEngine {
|
|
427
575
|
constructor (args) {
|
|
428
576
|
this.graph = new BehavioralControlGraph();
|
|
429
|
-
|
|
430
|
-
|
|
577
|
+
this.loadArgs(args);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Loads the provided arguments.
|
|
582
|
+
* @throws {MissingAttributes} Thrown when required attr is not present.
|
|
583
|
+
* @param {Object} args
|
|
584
|
+
*/
|
|
585
|
+
loadArgs (args) {
|
|
586
|
+
const expectedAttributes = ["name"];
|
|
587
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
588
|
+
// Not an object, so all attributes are missing.
|
|
589
|
+
throw new MissingAttributes("Engine", expectedAttributes);
|
|
431
590
|
}
|
|
591
|
+
expectedAttributes.forEach((attr) => {
|
|
592
|
+
if (!(attr in args)) {
|
|
593
|
+
throw new MissingAttributes("Engine", attr);
|
|
594
|
+
}
|
|
595
|
+
this[attr] = args[attr];
|
|
596
|
+
});
|
|
432
597
|
}
|
|
433
598
|
|
|
434
599
|
/**
|
|
@@ -454,9 +619,6 @@ class DALEngine {
|
|
|
454
619
|
* @returns {Participant}
|
|
455
620
|
*/
|
|
456
621
|
createParticipant (args) {
|
|
457
|
-
// TODO: Validate that the args have the necessary keys
|
|
458
|
-
// and raise custom error if they are missing.
|
|
459
|
-
// Perhaps it is better to do that in the class itself.
|
|
460
622
|
return new Participant(args);
|
|
461
623
|
}
|
|
462
624
|
|
|
@@ -466,8 +628,6 @@ class DALEngine {
|
|
|
466
628
|
* @returns {Behavior}
|
|
467
629
|
*/
|
|
468
630
|
createBehavior (args) {
|
|
469
|
-
// TODO: Validate that the args have the necessary keys
|
|
470
|
-
// and raise custom error if they are missing.
|
|
471
631
|
return new Behavior(args);
|
|
472
632
|
}
|
|
473
633
|
|
|
@@ -477,10 +637,45 @@ class DALEngine {
|
|
|
477
637
|
* @returns {Invariant}
|
|
478
638
|
*/
|
|
479
639
|
createInvariant (args) {
|
|
480
|
-
// TODO: Validate that the args have the necessary keys
|
|
481
|
-
// and raise custom error if they are missing.
|
|
482
640
|
return new Invariant(args);
|
|
483
641
|
}
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Returns the node in the graph with the given behavior name.
|
|
646
|
+
* @param {String} behaviorId
|
|
647
|
+
* @returns {GraphNode}
|
|
648
|
+
*/
|
|
649
|
+
getNode (behaviorId) {
|
|
650
|
+
return this.graph.findNode(behaviorId);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Adds a node to the graph with the given behaviorId and goToBehaviors.
|
|
655
|
+
* @param {String} behaviorId
|
|
656
|
+
* @param {Array} goToBehaviorsIds
|
|
657
|
+
* @returns {GraphNode}
|
|
658
|
+
*/
|
|
659
|
+
addNode (behaviorId, goToBehaviorsIds) {
|
|
660
|
+
const behavior = this.createBehavior({name: behaviorId});
|
|
661
|
+
return this.graph.addNode(behavior, goToBehaviorsIds);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Adds a goToBehavior to the node with the given behaviorId.
|
|
666
|
+
* @param {String} behaviorId
|
|
667
|
+
* @param {String|Array} goToBehaviorIds
|
|
668
|
+
* @returns {GraphNode}
|
|
669
|
+
*/
|
|
670
|
+
addGoToBehavior (behaviorId, goToBehaviorIds) {
|
|
671
|
+
const node = this.graph.findNode(behaviorId);
|
|
672
|
+
if (Array.isArray(goToBehaviorIds)) {
|
|
673
|
+
node.goToBehaviorsIds.push(...goToBehaviorIds);
|
|
674
|
+
} else {
|
|
675
|
+
node.goToBehaviorsIds.push(goToBehaviorIds);
|
|
676
|
+
}
|
|
677
|
+
return node;
|
|
678
|
+
}
|
|
484
679
|
}
|
|
485
680
|
|
|
486
681
|
exports.DALEngine = DALEngine;
|