Scalars and conversion
Temporal
Section titled “Temporal”re-export
Re-exported from temporal-polyfill, so that an application names the same copy the framework converts into.
registerConverter()
Section titled “registerConverter()”function
declare function registerConverter<Wire, Live>(type: string, converter: Converter<Wire, Live>): void;Registers a converter for the given GraphQL named type.
Any named type can have one – scalars are the common case (a Timestamp is an
ISO-8601 string on the wire and a Temporal.Instant in the application), but object
types work just as well, which is how the QueryDocument
Registering a second converter for a type replaces the first, so applications can override the converters QLive brings along.
What QLive registers for itself, and what an application overrides by registering the same type again:
| Type | On the wire | In the application |
|---|---|---|
Timestamp |
ISO-8601 string | Temporal.Instant |
Date |
ISO-8601 string | Temporal.PlainDate |
Long, BigInteger |
decimal string | bigint |
FooDocument and friends |
plain JSON object | QueryDocument instance |
Do it from startup()’s init hook – see
Register a converter.
Parameters
type |
GraphQL type name, e.g. “Timestamp” |
converter |
converter for that type |
getConverter()
Section titled “getConverter()”function
declare function getConverter(type: string): Converter | null;Returns the converter registered for the given GraphQL type name, if any.
Parameters
type |
GraphQL type name |
Returns converter or null
formatValue()
Section titled “formatValue()”function
declare function formatValue(value: unknown, type: string): string;The text a user reads for a live value of the given GraphQL type: nothing for null or undefined, what the type’s converter formats it as where it has a format(), String(value) otherwise.
Parameters
value |
live value, as in a query result |
type |
GraphQL type name of the value, e.g. “Timestamp” |
Returns display text
inputText()
Section titled “inputText()”function
declare function inputText(value: unknown, type: string): string;Turns a live value into the text an input edits: its converter’s toInput() where it has one, the built-in conversion of String, ID, Boolean, Byte, Short, Int, Number, Float and Currency otherwise. Null and undefined are empty text.
Parameters
value |
live value, as in a query result or a draft |
type |
GraphQL type name of the value, e.g. “Date” |
Returns input text
Throws Error for a type without an input: BigDecimal until a decimal type is chosen, and an application’s own scalar until its converter has toInput() and fromInput()
parseInput()
Section titled “parseInput()”function
declare function parseInput(text: string, type: string): unknown;Turns what the user typed into a live value of the type, the reverse of inputText(). Blank text is null, except for a String or an ID, whose text is the value as it is.
Parameters
text |
input text |
type |
GraphQL type name of the value, e.g. “Date” |
Returns live value
Throws Error with a message for the user where the text is no value of the type, and Error for a type without an input, see inputText()
Converter
Section titled “Converter”type
type Converter<Wire = any, Live = any> = { /** * Converts the JSON value received from the server into a live value. Used on * query results, which arrive along a conversion map. */ fromServer: ConversionFn<Wire, Live>; /** * Converts a live value into the JSON format the server expects. Used on the * variables of a query, which are converted along the schema. * * Optional: a type that only ever appears in results needs none, and values of * it are passed through unchanged on the way out. */ toServer?: ConversionFn<Live, Wire>; /** * Turns a live value into the text a user reads, e.g. in a grid cell. Never * called with null or undefined, which display as nothing. * * Optional: a value of a type without one displays as String(value). */ format?: (value: Live, type: string) => string; /** * Orders two live values: negative if the first comes first, positive if the * second does, 0 for the same value. Never called with null or undefined. What * a condition evaluated in the browser (`lt`, `between`, ...) and a local sort * order values of the type by, see scalarCompare(). * * Optional: numbers, strings and Booleans order without one. A type whose live * values are objects needs one to be ordered at all. */ compare?: (a: Live, b: Live, type: string) => number; /** * Turns a live value into the text an input edits, e.g. `2026-10-19` for a * date input. Not the same as format(): what a user reads and what an input * takes differ for a Timestamp, and a Currency amount reads as `1.15` either * way but is held as `11500`. Never called with null or undefined, which edit * as an empty input. * * Optional along with fromInput(): a type without either has no input, see * inputText(). */ toInput?: (value: Live, type: string) => string; /** * Turns what the user typed back into a live value. Called with the text * trimmed and never with blank text, which is null. Throws an Error whose * message the user can read where the text is no value of the type. */ fromInput?: (text: string, type: string) => Live;};Conversion of one GraphQL named type between its wire format and its live JavaScript representation.
The two directions are separate members instead of two positional arguments so that a converter reads as what it is at the registration site and can grow further members without breaking existing registrations.
ConversionFn
Section titled “ConversionFn”type
type ConversionFn<I, O> = (value: I, type: string) => O;Converts a value between its wire format and its live JavaScript form. Never called with null or undefined: a null converts to null, and an input value the schema declares non-null is rejected before any conversion, so a converter only ever deals with actual values.
Parameters
value |
value of the GraphQL type the converter is registered for |
type |
GraphQL type name the converter was invoked for. Handed in so that one function can serve a family of types, e.g. all the types derived from QueryDocument |
convertToServer()
Section titled “convertToServer()”function
declare function convertToServer(value: any, type: string): any;Converts a live value into the JSON format the server expects, walking it along its GraphQL type.
Top-down, the reverse of the way results are converted: the converter of the type turns the live value into its wire shape first, then that shape’s fields are converted.
Parameters
value |
live value |
type |
GraphQL type name of that value, e.g. “FooInput” |
Returns wire value
convertVariablesToServer()
Section titled “convertVariablesToServer()”function
declare function convertVariablesToServer(variables: { [name: string]: any;}, map: QueryConversionMap): { [name: string]: any;};Converts the variables of a query into their wire format. Only the variables the query declares are converted, along their declared type – everything else is passed on as it is.
Parameters
variables |
variable values |
map |
conversion map of the query |
Returns variables in wire format
convertResultFromServer()
Section titled “convertResultFromServer()”function
declare function convertResultFromServer<T = any>(result: any, map: QueryConversionMap): T;Converts a complete GraphQL result, whose keys are the top-level selections of the query. Result keys the map does not mention are passed through unconverted.
Parameters
result |
data of the GraphQL response |
map |
conversion map of the query |
Returns converted result
convertSelectionFromServer()
Section titled “convertSelectionFromServer()”function
declare function convertSelectionFromServer<T = any>(value: any, node: SelectionNode): T;Converts the value of a single selection, which is what an injection delivers.
Parameters
value |
wire value of the selection |
node |
selection node for it |
Returns converted value
SelectionNode
Section titled “SelectionNode”type
type SelectionNode = { /** * GraphQL type name of the value at this position, modifiers stripped */ type: string; /** * Nodes of the selection below this one, keyed by result key. Absent for leaves, * which includes scalars whose wire format is an object graph of its own. */ fields?: { [key: string]: SelectionNode; };};One selection of a conversion map: the type of the value at one position of a query result, plus the nodes of whatever was selected below it.
The types are resolved when the map is generated, so converting along a map needs neither the schema nor the query. That is what takes aliases out of the picture: the child nodes are keyed by the key the value actually appears under in the result, and each one names its type outright, so a field selected as “desc: description” converts as the Timestamp, String, … it is.
List modifiers are not represented. A list is recognized by the value being an array and its elements convert against the same node, which covers nested lists without the map having to spell out a depth.
QueryConversionMap
Section titled “QueryConversionMap”type
type QueryConversionMap = { /** * Nodes of the top-level selections, keyed by result key. An injected query has * exactly one, a query executed at runtime may have several. */ selections: { [key: string]: SelectionNode; }; /** * GraphQL type name per variable of the query, e.g. { config: "QueryConfig" }. * Converting the variables needs nothing but the type: input values have neither * aliases nor a selection, so the schema describes them completely. */ variables?: { [name: string]: string; };};Conversion map of one query, as built from its source by buildConversionMap().
scalarEqual()
Section titled “scalarEqual()”function
declare function scalarEqual(a: unknown, b: unknown): boolean;Whether two values of the same scalar type are the same value.
A converted value is an object rather than a primitive – a Timestamp is a Temporal.Instant – and two of them holding the same instant are not the same object. What knows they are equal is the value itself, so an equals() is asked wherever there is one.
A scalar that converts to a plain object or an array has neither identity nor an equals(), and comes back false here. That is the safe direction for what this is used for – a value the user typed stays a change rather than being dropped as one – and the cost is a write and a version for a value that did not move.
Parameters
a |
one value, in the live form of its type |
b |
the other |
scalarCompare()
Section titled “scalarCompare()”function
declare function scalarCompare(type: string, a: unknown, b: unknown): number;Orders two values of a scalar type: negative if the first comes first, positive if the second does, 0 for the same value. What a condition evaluated in the browser compares by, and a local sort orders by.
- A type whose converter has a
compare()is ordered by it, see Converter. QLive registers one for Timestamp. - Numbers of the number types compare by value across their forms, see isNumericType().
StringandIDvalues follow the user’s locale, so"a" < "B". A database orders by its collation, which the browser doesn’t know; the locale is the nearest guess, and where they differ a server sort and a local one differ too. Text of other types, aDateamong them, compares by character code, which orders ISO dates right.- Booleans order
falsefirst.
Nulls aren’t values here; a caller decides where they go.
Parameters
type |
GraphQL scalar type name the values are of |
a |
one value, in the live form of its type |
b |
the other |
Throws Error for values that type has no order for
genericScalarEqual()
Section titled “genericScalarEqual()”function
declare function genericScalarEqual(a: GenericScalar | null, b: GenericScalar | null): boolean;Whether two scalars are the same type carrying the same value.
The type names have to agree: “Int” 1 and “Float” 1 are two values a domain distinguishes, and what the server coerces the value along is the name. Two nulls are the same value, one null is not.
Parameters
a |
one scalar, or null |
b |
the other |
GenericScalar
Section titled “GenericScalar”type
type GenericScalar = { /** * Name of the scalar type the value is of, e.g. "Timestamp". A list of them is written the way GraphQL * writes it, e.g. "[String]". */ type: string; /** * The value, in the live form of its type -- a Timestamp is a Temporal.Instant here and an ISO-8601 * string on the wire, the same as everywhere else in QLive. */ value: any;};A value of any scalar type the domain has, traveling as the name of that type plus a value of it.
That is what lets one GraphQL field accept every scalar the application has: the server coerces the value along the named type, so a mutation taking a GenericScalar needs no input type per domain type. QLive’s merge mutation is the reason it exists here – a field change is a field name and one of these.
The type name is open on purpose. Which scalars there are is the domain’s decision, so “String”, “Int” and whatever the application registered are as valid here as the ones below, and a closed union would exclude most of what is actually sent. The aliases below narrow this for the types QLive knows, and an application that knows what it is holding can name one of them.
GenericBigDecimal
Section titled “GenericBigDecimal”type
type GenericBigDecimal = { type: "BigDecimal"; /** the decimal as the string it travels as, no decimal type being chosen for it yet */ value: string;};GenericByte
Section titled “GenericByte”type
type GenericByte = { type: "Byte"; value: number;};GenericComputedValue
Section titled “GenericComputedValue”type
type GenericComputedValue = { type: "ComputedValue"; value: { name: string; args: [Scalar]; };};GenericCondition
Section titled “GenericCondition”type
type GenericCondition = { type: "Condition"; value: ConditionNode;};GenericDate
Section titled “GenericDate”type
type GenericDate = { type: "Date"; value: Temporal.PlainDate;};GenericDomainObject
Section titled “GenericDomainObject”type
type GenericDomainObject = { type: "DomainObject"; value: object;};GenericFieldExpression
Section titled “GenericFieldExpression”type
type GenericFieldExpression = { type: "FieldExpression"; value: string | FieldNode;};GenericJSONB
Section titled “GenericJSONB”type
type GenericJSONB = { type: "JSONB"; value: RawValue;};GenericLong
Section titled “GenericLong”type
type GenericLong = { type: "Long"; value: bigint;};GenericTimestamp
Section titled “GenericTimestamp”type
type GenericTimestamp = { type: "Timestamp"; value: Temporal.Instant;};