Typed constructors

A constructor is a name followed immediately by a parenthesised payload. The payload is opaque text, not a nested value, so a constructor never contains another constructor — a ( while scanning a payload is ERR_NESTED_CONSTRUCTOR.

STF
{
  price:    DECIMAL(19.99),
  id:       BIGINT(9007199254740993),
  created:  DATE(2026-01-15),
  at:       TIMESTAMP(2026-01-15T10:30:00Z),
  blob:     BINARY(SGVsbG8=),
  place:    Geometry("Point", [80.27, 13.08]),
  opens:    Time("09:30"),
  lasts:    Duration("PT45M"),
}

STF defines eight constructors — the five above plus Geometry (GEOMETRY alias), Time (TIME), and Duration (DURATION). The canonical spelling for the three new ones is Geometry/Time/Duration; upper-case aliases are accepted. Uppercase-initial names are a reserved namespace: an unrecognised one, such as DATETIME(...), is ERR_UNKNOWN_CONSTRUCTOR rather than a generic syntax error, so a document written against a future version fails with a code that says why.

DECIMAL — exact decimal

Plain notation, with an optional sign and an optional fractional part.

STF
DECIMAL(1.5)     DECIMAL(1.50)     DECIMAL(15)     DECIMAL(-0.001)

Scale is data. The scale is the number of digits after the point — DECIMAL(1.5) has scale 1, DECIMAL(1.50) has scale 2 — and they are distinct values that must not compare equal and must not be normalised. This is what makes a price representable: 199.00 stays two decimal places through a round trip.

Significant digits are the payload with the sign, point, and leading zeros removed; trailing zeros count. They may not exceed 34, and scale may not exceed 6143, both being decimal128 limits; exceeding either is ERR_DECIMAL_OVERFLOW.

Rejected with ERR_INVALID_CONSTRUCTOR_PAYLOAD: exponent notation (1.5e3), a leading +, leading zeros (01.5), a trailing point (1.), NaN, Infinity, hexadecimal, underscores, whitespace, and an empty payload.

BIGINT — arbitrary-precision integer

STF
BIGINT(9007199254740993)     BIGINT(-123456789012345678901234567890)     BIGINT(0)

There is no magnitude limit. Every integer has exactly one spelling: leading zeros (007) and negative zero (-0) are rejected, and a parser must not silently rewrite them. Fractional parts, exponents, a leading +, and an empty payload are rejected.

This is the answer to the identifier that JSON forces through a string because JSON.parse would round it: it stays an integer, and it stays exact.

DATE — wall date

STF
DATE(2026-01-15)     DATE(2024-02-29)   # 2024 is a leap year

Exactly YYYY-MM-DD, zero-padded, in the proleptic Gregorian calendar, with full calendar validation: DATE(2026-02-31), DATE(2026-13-01), and DATE(2025-02-29) are all rejected, not merely parsed. DATE(2026-1-5) is rejected for the missing padding. Any time, offset, or T component is rejected — a date is a date.

TIMESTAMP — instant

STF
TIMESTAMP(2026-01-15T10:30:00Z)
TIMESTAMP(2026-01-15T10:30:00.123456789+05:30)
  • The separator is an uppercase T; a space is rejected.

  • The UTC offset is mandatoryZ, or ±HH:MM. An offset-less timestamp is ambiguous and is rejected.

  • An optional fractional part carries 1–9 digits. Its trailing zeros are preserved, as is the offset spelling: .100.1, and +00:00Z.

  • Leap seconds are not supported. A seconds field of 60 is rejected, because most host time libraries cannot represent it and admitting it would make round-tripping implementation-dependent.

Geometry — generic coordinate geometry

Generic, not GIS — GeoJSON/WKT/WKB are interoperability outputs, not the definition. Geometry represents points/shapes in an arbitrary [x,y] space (geo, screen/UI, CAD, game, graphics).

STF
Geometry("Point", [120, 80])
Geometry("LineString", [[0,0],[10,5],[20,15]])
Geometry("Polygon", [[[0,0],[100,0],[100,80],[0,80],[0,0]]])
Geometry("MultiPoint", [[10,20],[30,40]])
Geometry("MultiLineString", [[[0,0],[10,10]], [[20,0],[30,10]]])
Geometry("MultiPolygon", [[[[0,0],[10,0],[10,10],[0,0]]], [[[20,20],[30,20],[30,30],[20,20]]]])

Payload is a quoted type plus JSON coordinates: Geometry("Point", [x,y]). Types are Point|LineString|Polygon|MultiPoint|MultiLineString|MultiPolygon exactly; GeometryCollection is reserved. Coordinates are generic [x, y] — no CRS baked in ([120,80] is not implicitly lon/lat). GEOMETRY(...) is accepted as an alias. Validation is primitive-level: finite numbers, correct nesting, LineString ≥2, Polygon ring ≥4 and closed first==last, Multi* non-empty. No winding-direction assumption.

toJSON() / toGeoJSON() / toGeo() emits GeoJSON {"type":"Point","coordinates":[…]} for Geometry; there is no inference — plain {type,coordinates} objects remain plain Objects. Call toGeo(geometry) to produce GeoJSON for consumption.

Time — time of day

STF
Time("09:30")
Time("09:30:00")
Time("09:30:00.123456789")

Quoted HH:mm with optional :ss and .<1-9 fraction>, or bare HH:mm for ergonomics; canonical is quoted. 00-23:00-59[:00-59], no zone — timezone-aware values belong to TIMESTAMP. Bare TIME(...) is the upper-case alias. Invalid: Time("24:00"), Time("12:60"), Time("abc").

Duration — elapsed time

STF
Duration("PT30S")
Duration("PT45M")
Duration("PT2H30M")
Duration("P1D")
Duration("P1Y")

Quoted ISO-8601 duration P[nY][nM][nW][nD][T[nH][nM][nS]] (bare P… also accepted; canonical quotes). At least one component is required (P/PT alone rejected); Y/M/W/D integers, S may be decimal; H/S only after T. Calendar durations P1Y/P1M are not reduced to seconds. Invalid: Duration("invalid"), Duration("P1DT"). TIME vs Duration vs Timestamptime of day vs how long vs when.

BINARY — octet sequence

Base64 with the standard RFC 4648 §4 alphabet.

STF
BINARY(SGVsbG8=)     BINARY(SGVsbG9X)     BINARY()   # the empty octet sequence

The encoding must be canonical, so each octet sequence has exactly one legal spelling:

  • Length is a multiple of 4, padded with =. Padding appears only when the octet count is not a multiple of 3 — so BINARY(SGVsbG9X) correctly carries none.

  • = appears only as the final one or two characters.

  • Unused trailing bits must be zero: BINARY(Zh==) is rejected.

  • Internal whitespace and line breaks are rejected. The URL-safe alphabet (-, _) is rejected.

The empty payload is valid. Encoding is not data: the value is the decoded octets, and base64 is only how they are written down.

Normative text

This page explains; it does not define. Where the two differ the specification wins.

Last updated on
Edit this page