The Combi Library
/* @provengo summon combi */
The Combi library enables specification developers to define specification parameters, and the way they interact with each other and with the expected system behavior in general. A common use case would be to describe the traits of a specification entity, such as user, insurance policy, or inventory item.
Sample usage, describing options for a vehicle:
// @provengo summon combi (1)
// Defining the vehicle combi object.
const vehicle = Combi("vehicle");
// A vehicle can be of type "car", "truck", or "sports car".
const vehicleType = vehicle.field("type").isOneOf("car", "truck", "sports car");
// Seat count options in the vehicle
const seatCount = vehicle.field("seat count").isOneOf([2,4,6]); (2)
// A vehicle is either under warranty, or it isn't. Possible values are `Combi.YES` or `Combi.NO`.
const isUnderWarranty = vehicle.yesNoField("under warranty");
// A vehicle may have any combination of the following add-ons:
const addons = vehicle.field("add ons").isSomeOf("turbo", "hybrid motor", "dark windows");
///// Field inter-dependencies
// A sports car must have exactly two seats.
vehicleType.whenSetTo("sports car").field(seatCount).mustBe(2);
// A truck can't have 6 seats.
vehicleType.whenSetTo("truck").field(seatCount).cannotBe(6);
// Starting the vehicle properties selection process.
vehicle.doStart();
1 | Bring the Combi library into scope. |
2 | In this field definition, options are passed as an array. |
The above code generates 72 test cases. These can be described in the following graph (created using analyze
), or as a spreadsheet (by using combi
).
The Combi library defines two main objects: Combi
, which is the entry point to the library’s functionality, and CombiField
, which describes a single aspect of the described entity.
Event
Structure
All the events of the Combi library contain rich data field, which makes them easy to work with in a consistent manner. The fields are as follows:
combi
-
The name of the Combi object the event belongs to.
action
-
What this event means. Possible values are:
"start"
-
A combi or a field started its evaluation.
"set"
-
A combi field has gotten a value. Read the
value
andfield
fields to learn which field and what value. Note that this type of event can happen multiple times for anisSomeOf
field. "done"
-
A combi or a field has finished its evaluation - no more
set
events will be selected.
field
-
Name of the field being set. This entry does not exist in events that belong to the combi object itself, such as
combi.startEvent
, as these events do not refer to any specific field. value
-
The value chosen for a combi field. Present only on events whose
action
field is"set"
.
Example - combi explainer bthread
Below, the explainer
bthread waits for all combi events, and writes what they mean to the log.
// @provengo summon combi
const brt = new Combi("Burrito");
brt.field("main").isOneOf("tofu", "chicken", "beef");
brt.field("extras").isSomeOf("rice","beans","sour cream");
brt.field("tortilla").isOneOf("flour", "corn");
bthread("explainer", function(){
let evt = null;
while ( true ) {
evt = waitFor( brt.anyEvent );
switch( evt.data.action ) {
case "start":
if ( evt.data.field === undefined ) {
bp.log.info(`Starting to evaluate combi ${evt.data.combi}.`);
} else {
bp.log.info(`Starting to evaluate field ${evt.data.field} of combi ${evt.data.combi}.`);
}
break;
case "set":
bp.log.info(`Field ${evt.data.field} of combi ${evt.data.combi} set to ${evt.data.value}.`);
break;
case "done":
if ( evt.data.field === undefined ) {
bp.log.info(`Done evaluating combi ${evt.data.combi}.`);
} else {
bp.log.info(`Done evaluating field ${evt.data.field} of combi ${evt.data.combi}.`);
}
break;
}
evt = null;
}
});
brt.doStart();
Combi(name)
A function that creates combi objects, describing parametric variations of the model.
name
-
The name of the combi object.
combi.anyEvent
An event set holding all events related to combi
. Blocking this event set effectively pauses combi
's evaluation process.
combi.anyFieldSetEvent
An event set holding all events in which a field of combi
was set. Useful, e.g., when building a database query based on a sequence of events.
combi.field(name)
Returns a field named name
of combi
. If the field does not exist yet, it is created. Returns a CombiField
object for further defining the field options.
name
-
The name of the field being defined.
combi.yesNoField(name)
Defines a new field for combi
, whose value can only be Combi.YES
, Combi.NO
or null
. Returns a CombiField
object for further defining the field options. The options for the returned object are already populated with Combi.YES
and Combi.NO
.
name
-
The name of the field being defined.
combi.doStart()
Starts the process of setting the combi parameters. Emits the startEvent
for this specific combi, then multiple field set events, and finally the combi’s doneEvent
.
combi.doStop()
Requests that the combi evaluation stops. The combi’s doneEvent
will still be emitted.
CombiField
A CombiField
object describes all options for a single aspect of an entity. This can be, for example, the color of a car, side dish combinations, or amount of kids under an insurance policy. This object defines methods for listing possible values, as well as creating inter-dependencies with other fields.
Fields are evaluated in the order they are defined. Thus, if one field affects to content of another, the affecting field should be described first. |
fld.isOneOf(v1,v2…/arr)
Specifies that fld
can have exactly one of the passed values. The choice is announce by the fld.setTo(vx)
event.
v1, v2, …
-
A list of possible values.
arr
-
An array of possible values.
Returns: the field object.
fld.isSomeOf(v1,v2…/arr)
Specifies that fld
can contain zero or more items from the passed values. The choices are announced by the fld.setTo(vx)
event.
v1, v2, …
-
A list of possible values.
arr
-
An array of possible values.
Returns: the field object.
fld.whenSetTo(x).field(f2).mustBe(y)
A statement creating a conditional constraint over field f2
, based on the value of fld
. When field fld
is set to value x
, then field f2
must have the value y
.
fld
-
The field whose value will trigger the constraint.
x
-
The value that, when assigned to
fld
, will trigger the constraint. f2
-
The field whose value must be
y
, iffld
is set tox
. y
-
The value that must be assigned to
f2
, iffld
is set tox
.
fld.whenSetTo(x).field(f2).cannotBe(y)
A statement creating a conditional constraint over field f2
, based on the value of fld
. When field fld
is set to value x
, then field f2
cannot have the value y
.
fld
-
The field whose value will trigger the constraint.
x
-
The value that, when assigned to
fld
, will trigger the constraint. f2
-
The field whose value cannot be
y
, iffld
is set tox
. y
-
The value that cannot be assigned to
f2
, iffld
is set tox
.
fld.mustBe(aValue)
Constraints fld
to have the passed value (if at all - there’s also the option that the field value will not be set at all).
aValue
-
The value
fld
should have, once the model gets to a point wherefld
is assigned a value.
fld.cannotBe(aValue)
Prevents fld
from being assigned the value aValue
.
aValue
-
The value that
fld
cannot have.
fld.record()
Record the field value into the bp.store
object. The storage key is the field’s name.
The value will be available after fld.doneEvent was chosen.
|
The stored value type depends on the field type:
- Single valued field (created using
combi.isOneOf
) -
The selected value.
- Multi valued field (created using
combi.isSomeOf
) -
An array of the selected values. If no value was selected, the stored value will be an empty array (
[]
).
fld.recordAs( name, storageFn )
Process and store values assigned to fld
in bp.store
, under name
. Each time an event assigning a value to fld
is emitted, storageFn
is invoked with the newly selected value, and the existing one (if any). The value returned from storageFn
is then stored under name
, replacing the existing value if one exists.
The value will be available after fld.doneEvent was chosen.
|
name
-
The name under which values are stored in
bp.store
. storageFn
-
A function that gets two parameters - the newly selected value and the previously stored one, and returns a new value to store in
bp.store
.+ Signature:(newValue:Object, storedValue:Object)→Object
. Note thatstoredValue
may benull
.
fld.getAllSetEvents()
Returns an array containing a set event for each value that might be assigned to the field. This is useful for setting ensemble goals.