Skip to content

This site describes solve-engine 2.44.0, the version npm installs.

Value

Defined in: packages/engine/src/vm/Value.ts:544

Universal runtime value for the solve-js VM.

Carries a ValueType discriminant, a polymorphic value payload, and an optional unit string (for UoM values). Treated as immutable after construction, the arena reuses objects internally via recycle(), but external code should never mutate Value fields.

A cached _cachedNumber avoids repeated toNumber() computation on hot paths (ADD/SUB/MUL in the VM dispatch loop).

new Value(
type,
value,
unit?
): Value;

Defined in: packages/engine/src/vm/Value.ts:791

ParameterType
typeValueType
value| string | number | bigint | boolean | SymbolicNode | MatrixData | RangeData | ColourData | SplitData | ChartData | IpCidrData
unit?string

Value

calendarName?: CalendarName;

Defined in: packages/engine/src/vm/Value.ts:765

For a String that is a weekday or month name drawn from a date (as weekday, as month), which name it is.

The text stays the engine’s English ("Tuesday"), so a comparison with "Tuesday", a join with other text and a lookup all read it as before; the formatter reads this to write the name in the reader’s language (Dienstag under de). Text built from the name ("on " + x) is new text and carries nothing. A variable holding the name keeps it, through clone. Cleared by recycle alongside the other sidecars.


datetimeSpan?: boolean;

Defined in: packages/engine/src/vm/Value.ts:710

That this quantity is the gap between two datetimes, rather than a duration someone wrote down.

Both are milliseconds and both are a Uom in "ms", so the unit cannot tell them apart, and the formatter used to render every one of them as a clock. That is right for 10:30 - 08:00, which reads 2:30, and wrong for 40ms + 120ms, which read 0:00 instead of 190 ms: a latency budget is a quantity and not a time of day. Set where two datetimes subtract, and read only by the formatter.

It survives the arithmetic that keeps a span a span: adding two spans, scaling one by a plain number, and totalling a column of them, so a timesheet still reads as a clock and half a shift is 0:30. It does not survive a conversion, because (10:30 - 08:00) in ms asked for milliseconds and should be given them, nor combining with a quantity somebody typed. Cleared by recycle alongside the other sidecars.


decimalPlaces?: number;

Defined in: packages/engine/src/vm/Value.ts:622

The number of decimal places this value should DISPLAY at, when it has been given an explicit precision.

A display sidecar, not a value one: value is unchanged, so every .value/toNumber() reader and all arithmetic behave exactly as before, and a value with no decimalPlaces formats the way it always did (the global two-place default with trailing zeros trimmed). It is set only by an explicit precision request, <x> to N dp and round(x, N), so that 3.14159 to 4 dp shows 3.1416 and 1.5 to 2 dp shows 1.50 rather than the value being rounded but then displayed at the default two places. It is NOT propagated through arithmetic (a later + 1 re-decides precision), which is why nothing that did not ask for a precision is affected. Cleared by recycle alongside the other sidecars.


divisionByZero?: boolean;

Defined in: packages/engine/src/vm/Value.ts:789

Set on an infinity that a division by zero gave (1/0, 40 is what % of 0), so a refusal can name that cause; an infinity reached because a number grew past the largest double (2^2000, 1e309) carries none. The double alone cannot tell the two apart. Set by the VM’s / (see vm/IndeterminateQuotient.ts’s zeroDivisorQuotient) and carried by +, -, * and ^ when an operand had it and the result is still infinite. Cleared by recycle.


exact?: DecimalData;

Defined in: packages/engine/src/vm/Value.ts:570

The exact base-ten value this Value stands for, when it has one.

A sidecar rather than a replacement for value: money and decimal-point literals set it to a DecimalData so that same-currency arithmetic and display can be exact (“$0.10 + $0.20” is “$0.30”, not “$0.30000000000000004”), while value stays the nearest double so every existing consumer that reads .value or toNumber() is unchanged. Plain numbers carry it through arithmetic as money does (see vm/ExactDecimals.ts), so “0.1 + 0.2” is exactly 0.3; the plain fast paths leave only for an operand that carries it, so two whole numbers never pay for it. Cleared by recycle so a reused arena Value never inherits a stale exact.


frozen?: FrozenMark;

Defined in: packages/engine/src/vm/Value.ts:738

That this value is a frozen answer: when it was frozen, and the key it is stored under.

Set only on what a frozen line answers with (see engine/FrozenValues.ts), and deliberately not carried by arithmetic: a value computed from a frozen one is new, and says so through sources, whose records carry the freeze date. Cleared by recycle.


grain?: DatetimeGrain;

Defined in: packages/engine/src/vm/Value.ts:646

What this instant anchors: a calendar day, a wall-clock reading on one, or a fixed point on the timeline.

The fifth sidecar, the same shape as exact, rational, uncertainty and decimalPlaces and for the same reason: a Datetime’s payload is epoch milliseconds and has to stay a plain number, because the arena union, the worker DTO, the snapshot’s Datetime variant and the Datetime SUB path all read it as one. The number cannot say which of three questions it is the answer to, and the difference is not recoverable from it: formatDatetime decides whether to print a time by testing whether the local hour, minute, second and millisecond are all zero, which is a guess that 2026-04-03T09:00:00+09:00 under TZ=UTC gets wrong, because the nine o’clock the reader typed IS UTC midnight.

Set only where the shape is known: a date literal is 'date', a wall-clock literal (2026-04-03T09:30, 6pm) is 'datetime', a literal carrying Z or an offset is 'instant', and so is now. Absent means not recorded, which every reader treats as an instant rather than inferring one. Nothing in 2.26.0 displays it: the formatter does not read it, so every rendered date is byte-identical to 2.25.0. Cleared by recycle alongside the other sidecars.


rational?: Rational;

Defined in: packages/engine/src/vm/Value.ts:589

The exact rational value this Value stands for, when it has one.

The second sidecar, the same shape as exact and for the same reason: a fraction has no exact base-ten form (1/3 is not any decimal), so exact fraction arithmetic needs a numerator/denominator pair rather than a coefficient and a scale. Integer division seeds it (“1/3” carries the Rational 1/3), and +, -, *, / between rational-bearing numbers keep it reduced, so “1/49 * 49” is exactly 1 and “5/6 - 1/6 - 1/6

  • 1/6 - 1/6 - 1/6” is exactly 0 rather than the 1.6e-16 the doubles drift to. value still holds the nearest double, recomputed from the exact rational so accumulation error never creeps in, which is why the default display and every .value/toNumber() reader are unchanged. A fraction written with ”/” carries it, and so does a division of decimals that never ends (“0.1 / 3” is 1/30); a decimal that ends carries exact instead, and a transcendental result (“sqrt(2)”) carries neither. Cleared by recycle alongside exact.

sources?: readonly ValueSource[];

Defined in: packages/engine/src/vm/Value.ts:727

Where the live figures behind this value came from: the provider, how the engine came by each one, and when.

A provenance sidecar, the same shape as the others and kept off the same paths: value is untouched, so every reader of the number is unchanged, and a value computed only from what the reader typed carries nothing. It is set where a live figure enters (a currency conversion reads the rate table’s record, createQueryResolver stamps what it fetched) and merged by arithmetic, so (10 USD in GBP) * 3 carries the rate’s record and a host can mark the line as rate-dependent. The ADD/SUB/MUL/DIV fast paths decline a value carrying it, exactly as they decline a rational or an uncertainty. The list is shared, never mutated, so a value copied by clone shares it safely. See vm/Provenance.ts. Cleared by recycle alongside the other sidecars.


timeAnchor?: number;

Defined in: packages/engine/src/vm/Value.ts:674

For a time of day (grain "time", #708), an instant on the day it is counted from, in epoch milliseconds: today’s midnight for a clock time, the value’s own instant for one written as time. The formatter shows the time alone and the days it has moved from this one beside it, read in the value’s zone (1:00:00 AM (+1 day) for 11pm + 2 hours; 8:00 AM (+1 day) for 11pm London in Tokyo on 10 March 2026, since Tokyo’s clock is on the day after the one the reader named), so the shift is fixed when the time is written rather than counted from whatever day it is displayed on. Carried through duration arithmetic with the grain. Cleared by recycle alongside the other sidecars.


timedOut?: boolean;

Defined in: packages/engine/src/vm/Value.ts:556

Set by async resolvers when a fetch timed out, the result is a fallback (typically 0).


timePrecision?: "minute";

Defined in: packages/engine/src/vm/Value.ts:680

For a time of day, that it is written to the minute (7:00 PM) rather than to the second, as every time-zone answer is (#757). Carried through duration arithmetic with the grain. Cleared by recycle.


type: ValueType;

Defined in: packages/engine/src/vm/Value.ts:552


uncertainty?: number;

Defined in: packages/engine/src/vm/Value.ts:606

The one-sigma uncertainty (standard error) this value carries, when it has one.

The third sidecar, the same shape as exact and rational and for the same reason: a measurement written 12.3 ± 0.5 is still the number 12.3 everywhere it is read as one, so the type stays ValueType.Number and value stays the center, while this non negative field carries the tolerance. The ± (or ASCII +/-) operator seeds it, and +, -, *, / propagate it in quadrature for independent errors (see vm/VMConversion.ts’s uncertainOp). Everything else (a comparison, a transcendental function, a unit conversion) reads the center through toNumber() and drops the tolerance, which is why a value with no uncertainty behaves exactly as a plain number always did. Cleared by recycle alongside the other two sidecars.


unit?: string;

Defined in: packages/engine/src/vm/Value.ts:554


unitLabel?: UnitLabel;

Defined in: packages/engine/src/vm/Value.ts:779

The name a quantity is shown under when the reader wrote a word for its unit that is not the unit’s own: a package’s alias (Meile for the mile) or a unit the document defined (sprints, two weeks each). The quantity itself stays in unit, so it converts and adds as that unit does; only the written answer changes, to value / per followed by name (#762).

Set on the quantity a literal (2 Meile) or a conversion (5 km in Meile, 84 days in sprints) produces. Arithmetic does not carry it, since a computed value is a new quantity; a variable holding a labelled value does, through clone. Cleared by recycle.


unspelledUnit?: string;

Defined in: packages/engine/src/vm/Value.ts:753

The unit this plain number is measured in when the engine cannot spell it yet: J·s for planck, C (the coulomb) for elementary charge.

Set only by the constants package (#648), on a physical constant whose unit needs a dimension the unit table does not have (charge, the mole). The value stays a Number, so planck * 2 and avogadro / 1000 are plain numbers as they always were; what the mark changes is a quantity meeting it. planck * 5e14 Hz read the constant as a bare number and answered in hertz, the other operand’s unit, and is refused by name instead. Arithmetic does not carry it, since a computed value is not the constant any more; a variable holding the constant does, through clone. Cleared by recycle alongside the other sidecars.


value:
| string
| number
| bigint
| boolean
| SymbolicNode
| MatrixData
| RangeData
| ColourData
| SplitData
| ChartData
| IpCidrData;

Defined in: packages/engine/src/vm/Value.ts:553


zone?: string;

Defined in: packages/engine/src/vm/Value.ts:661

The zone this instant should be read and displayed in, when the line named one.

A zone reference in the encoding calendar/IntlZone.ts documents: an IANA name ("Asia/Tokyo") or a fixed offset ("UTCOFFSET:540"). Set by <datetime> in <zone> and by an ISO literal carrying Z or an explicit offset, which names an offset rather than a zone and so records one. Absent means the value is read in the calendar backend’s own zone, which is what every Datetime meant before this sidecar existed. Like grain it is recorded and readable in 2.26.0 and read by the formatter from 3.0. Cleared by recycle alongside the other sidecars.


zoneDifference?: ZoneDifference;

Defined in: packages/engine/src/vm/Value.ts:691

For a duration that is the gap between two places’ clocks (time difference between London and Tokyo, #757), the two places, so the formatter writes the gap as a direction: Tokyo is 8 hours ahead of London. The quantity is a plain signed duration in hours, positive when the second place is ahead, so it converts (in hours), adds and compares as any duration does; a conversion or arithmetic gives a plain duration, since the answer is then a new quantity. A variable holding it keeps it, through clone. Cleared by recycle.

get errorCode(): string | undefined;

Defined in: packages/engine/src/vm/Value.ts:1008

The stable ErrorFactory code of an Error value, else undefined.

The code is the machine-branchable identifier (the message is the human-facing text, errorMessage). It reads from the same slot errorValue writes, so it is defined for exactly the values isError accepts and undefined for every other type.

string | undefined


get errorMessage(): string | undefined;

Defined in: packages/engine/src/vm/Value.ts:1016

The human-facing message of an Error value, else undefined. Paired with errorCode, which carries the branchable identifier.

string | undefined

clone(): Value;

Defined in: packages/engine/src/vm/Value.ts:872

A fresh Value carrying everything this one does: the payload, the unit, the cached number and every sidecar, present or future.

The inverse of recycle, and written as a whole-object copy rather than a list of fields on purpose. persistentValue() used to name the sidecars it copied, and the two it did not name (decimalPlaces, timedOut) were silently dropped by every viewport evaluation, so 3.14159 to 4 dp displayed as 3.14 there and as 3.1416 on a single line. A sidecar added later is carried here without this method knowing its name, which closes that class of bug rather than the one instance.

Shallow, as the old copy was: a matrix, a chart or a symbolic tree is shared with the original, which is fine because a Value’s payload is treated as immutable everywhere outside the arena’s own recycle().

Value

A new Value equal to this one in every field.


isBigInt(): this is Value & { value: bigint };

Defined in: packages/engine/src/vm/Value.ts:926

this is Value & { value: bigint }


isChart(): this is Value & { value: ChartData };

Defined in: packages/engine/src/vm/Value.ts:953

this is Value & { value: ChartData }


isColour(): this is Value & { value: ColourData };

Defined in: packages/engine/src/vm/Value.ts:949

this is Value & { value: ColourData }


isError(): boolean;

Defined in: packages/engine/src/vm/Value.ts:976

An Error value: a fault carrying a code and message, propagated through the DAG when a plugin or opcode cannot produce a quantity.

An Error reads as the number 0 through toNumber, so a numeric consumer that does not check this first cannot tell the fault apart from a real zero: exactly the silently-wrong result the engine guards against internally with faultedOperand. A host reading a live Value off a result should branch on this (or isFault) before trusting the number, and read errorCode/errorMessage for the detail.

boolean


isFault(): boolean;

Defined in: packages/engine/src/vm/Value.ts:996

Either fault, an Error or a Pending: the single predicate to check before trusting toNumber. It mirrors the internal faultedOperand rule that every opcode applies to its operands, so a host reading a result makes the same distinction the engine does rather than computing with a zero it cannot tell from a real one.

boolean


isHex(): this is Value & { value: number };

Defined in: packages/engine/src/vm/Value.ts:922

this is Value & { value: number }


isIpCidr(): this is Value & { value: IpCidrData };

Defined in: packages/engine/src/vm/Value.ts:957

this is Value & { value: IpCidrData }


isMatrix(): this is Value & { value: MatrixData };

Defined in: packages/engine/src/vm/Value.ts:934

this is Value & { value: MatrixData }


isNaN(): boolean;

Defined in: packages/engine/src/vm/Value.ts:1081

boolean


isNumber(): this is Value & { value: number };

Defined in: packages/engine/src/vm/Value.ts:918

this is Value & { value: number }


isPending(): boolean;

Defined in: packages/engine/src/vm/Value.ts:985

A Pending value: an async result that has not yet resolved, holding the query key it is waiting on. Reads as 0 through toNumber, the same caveat as isError: a settled value arrives on a later evaluation.

boolean


isRange(): this is Value & { value: RangeData };

Defined in: packages/engine/src/vm/Value.ts:945

this is Value & { value: RangeData }


isString(): this is Value & { value: string };

Defined in: packages/engine/src/vm/Value.ts:930

this is Value & { value: string }


isSymbolic(): this is Value & { value: SymbolicNode };

Defined in: packages/engine/src/vm/Value.ts:961

this is Value & { value: SymbolicNode }


isVectorShape(): boolean;

Defined in: packages/engine/src/vm/Value.ts:939

A Matrix shaped like a vector, 1×N (row) or N×1 (column).

boolean


recycle(
type,
value,
unit?
): void;

Defined in: packages/engine/src/vm/Value.ts:810

Phase 5.3: Reset all fields for arena reuse. Called by ValueArena.acquire(), zero allocation, just field assignment.

ParameterType
typeValueType
value| string | number | bigint | boolean | SymbolicNode | MatrixData | RangeData | ColourData | SplitData | ChartData | IpCidrData
unit?string

void


toJSON(): Record<string, unknown>;

Defined in: packages/engine/src/vm/Value.ts:893

The value as plain JSON, for a host that logs, stores or posts a result.

JSON.stringify of a Value used to throw “Do not know how to serialize a BigInt” as soon as the value carried an exact sidecar, which every decimal literal did and, since exact decimals and exact large integers, most computed answers do (#598). This writes the fields a host already reads (type, value, unit), then each sidecar that is set, with every bigint as its decimal string: exact as the decimal it stands for ("0.3") and rational as "n/d". The private cached number is left out. It is for reading, not a snapshot format; engine.toJSON() is the one that restores.

Record<string, unknown>

A JSON-safe object describing this value.


toNumber(): number;

Defined in: packages/engine/src/vm/Value.ts:1020

number