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).

combi vehicle

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 and field fields to learn which field and what value. Note that this type of event can happen multiple times for an isSomeOf 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.YES static

A value of a yesNoField, meaning the described field is positive/true.

Combi.NO static

A value of a yesNoField, meaning the described field is negative/false.

combi.name

The name of the combi, passed to Combi() at the object creation. Read-only.

combi.startEvent

The event emitted when the evaluation for combi begins.

combi.doneEvent

The event emitted when the evaluation for combi ended.

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.

combi.setAllowOverride(isAllow)

Normally, re-defining a combi field (i.e. calling combi.field("doubleDef") more than a single time with the same field name) causes an error. To allow the (rare) case where a spec needs to re-define a field, first call combi.setAllowOverride(true).

combi.setNext(anotherCombi)

Allow chaining Combi objects. After combi is done, anotherCombi will be started automatically.

anotherCombi

A combi object to be started once this Combi object finished its evaluation.

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, if fld is set to x.

y

The value that must be assigned to f2, if fld is set to x.

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, if fld is set to x.

y

The value that cannot be assigned to f2, if fld is set to x.

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 where fld 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 that storedValue may be null.

fld.name

The name of the field.

fld.combiName

The name of the Combi object to which fld belongs.

fld.getPossibleValues()

Returns all possible values that may be assigned to the field.

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.

fld.getFieldType()

Returns a string describing the field type. This can be: Combi.fieldType.Single, Combi.fieldType.Multi or Combi.fieldType.YesNo.

Events and Event Sets

fld.anySetEvent

Contains all the events where fld is assigned a value.

fld.setToEvent(v)

An event where fld is assigned the value of v.

fld.doneEvent

An event emitted when fld finished its value assignment.

fld.startEvent

An event emitted when fld begins its value assignment.