Current version : 0.21.0
Made in 🇫🇷 ◌ GitHubNPM

💡 Sept. Tip => everything has a meaning

Or how to define the semantics of a data type's values.

During this month of September 2025, we'll highlight various and interesting aspects of libmodulor.

In libmodulor, each value is represented with a data-type.

Let's say we are working on a product to manage the machines/systems running in a factory. We want to visualize each one with their status. The possible statuses are : down, running and unstable.

In the GUI (or TUI) targets, we want to display these values with a badge with specific colors like so :

+----+----------------+----------------+
| ID |  Machine Name  |     Status     |
+----+----------------+----------------+
| 01 | Server-Alpha   | [ RUNNING  ]   |
| 02 | Server-Beta    | [   DOWN   ]   |
| 03 | Server-Gamma   | [ UNSTABLE ]   |
| 04 | Server-Delta   | [ RUNNING  ]   |
+----+----------------+----------------+

The fact that down means "bad", running means "good" and unstable means "something is wrong" is part of the spec and should never be defined in the UI layer.

That's why libmodulor defines the notion of SemanticsVariant that can be attached to any data type. This is typically how we would design the TSystemStatus data type :

export type SystemStatus = 'down' | 'running' | 'unstable';

export class TSystemStatus extends TString<SystemStatus> {
    public static readonly OPTIONS: SystemStatus[] = [
        'down',
        'running',
        'unstable',
    ];

    constructor() {
        super();

        this.setOptions(
            TSystemStatus.OPTIONS.map((v) => ({
                label: v,
                value: v,
            })),
            { shouldTranslateLabels: true },
        );
        this.setSemanticsMapping({
            down: { variant: 'danger' },
            running: { variant: 'success' },
            unstable: { variant: 'warning' },
        });
    }
}

Now it's up to the UI layer to define that danger is "red", success is "green" and warning is "orange". Or not.

But what if the possible values of the data type are not discrete ?

For example, following up with our initial scenario, we now want to display the uptime of each machine/system.

+----+----------------+----------------+------------+
| ID |  Machine Name  |     Status     |   Uptime   |
+----+----------------+----------------+------------+
| 01 | Server-Alpha   | [ RUNNING  ]   | [  99.8% ] |
| 02 | Server-Beta    | [   DOWN   ]   | [  72.4% ] |
| 03 | Server-Gamma   | [ UNSTABLE ]   | [  88.1% ] |
| 04 | Server-Delta   | [ RUNNING  ]   | [  95.6% ] |
+----+----------------+----------------+------------+

Easy : we define a new data type named TSystemUptime that extends the TPercentage data type like so :

export type SystemUptime = Percentage;

export class TSystemUptime extends TPercentage {
    constructor() {
        super();

        this.setSemanticsPredicate((v) => {
            if (v < 0.95) {
                return { variant: 'danger' };
            }
            if (v < 0.98) {
                return { variant: 'warning' };
            }

            return { variant: 'success' };
        });
    }
}

With setSemanticsMapping and setSemanticsPredicate we can handle all the possible cases.

All in all, the output type of our ListSystemsUCD would look like this :

export interface ListSystemsOPI0 extends UCOPIBase {
    name: FreeTextShort;
    status: SystemStatus;
    uptime: SystemUptime;
}

Voilà c'est tout.