Repository navigation
docs: specify the type descriptor format - #154
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Important
A few of the spec's stated bounds are wrong in ways that make them unsound under its own rule 2: a bound must hold for every value. Engines built to follow the spec would refuse values the database accepts. I checked the Postgres ones against a Postgres 16 server.
Reviewed changes
I reviewed the whole new spec: the kinds, the rules, the per-database facts and the typed examples. I also ran the type check with spec/ included, and it passes.
spec/type-descriptor.ts: adds theTypeunion andStatementDescriptor. The rules are written as comments, and the examples for Postgres, MySQL, SQL Server, SQLite and Oracle are checked by the type check.tsconfig.json: addsspectoinclude, sonub run typechecknow checks the examples.AGENTS.md: adds one row for the new file to the table of what is where.
ℹ️ Rule 4 (one spelling per meaning) still allows two spellings
Rule 4 says each meaning has one spelling, but the spec still allows two in at least two places:
- Exclusive bounds. Rule 7 forbids exclusive bounds only on whole-number kinds, but every discrete kind has the same problem. On a
numeric(10,2)column,CHECK (x > 0)can be written asexclusive_min: "0"or asmin: "0.01". Adate, or a time kind with atick, can be written both ways too. - Union member order. Rule 4 orders a union's members by kind, but says nothing about members of the same kind. The
inetexample has two records, and so does the Oracle subtype example, and either order is allowed.
Two descriptors with the same meaning can therefore differ, which defeats comparing them for equality.
Technical details
# Canonical spelling gaps
## Affected sites
- spec/type-descriptor.ts:26 and :427 — the rule against exclusive bounds covers whole-number kinds only
- spec/type-descriptor.ts:421 — rule 4 orders union members by kind only
- spec/type-descriptor.ts:519-522 and :578-584 — unions whose members share a kind
## Required outcome
- Every discrete kind (a decimal with a fixed `max_scale`, `date`, and the time kinds with a `tick`) has one canonical bound spelling, or the rule states the exception explicitly.
- Union members of the same kind have a defined order, or the spec states that member order is not significant.claude-opus-5-5 | 𝕏
There was a problem hiding this comment.
🟡 Changes recommended
Finite bounds incorrectly exclude valid Postgres special values, and restricting schema sources to objects rejects valid boolean JSON Schemas already supported by the runtime.
2 open issues
What changed in this PR
This PR specifies a shared type descriptor format for Reylo’s database engines to follow. It defines the contract without changing engine behavior.
Changes:
- Defines statement descriptors, type kinds, validity rules, and database-specific semantics.
- Adds typed examples for five databases and includes them in TypeScript checking.
- Documents the specification’s location in the contributor guide.
| File | Description |
|---|---|
tsconfig.json |
Includes spec/ in TypeScript checking. |
spec/type-descriptor.ts |
Defines the descriptor format, rules, and typed examples. |
AGENTS.md |
Adds the specification to the repository guide. |
Review effort: Balanced
| comment?: string; | ||
| } | ||
|
|
||
| /** Bounds on an ordered kind's values, in its canonical text (`"-1.50"`, `"2026-10-23"`, and for a decimal an exponent where it is shorter, `"1e126"`), as JSON Schema's `minimum`, `maximum`, `exclusiveMinimum` and `exclusiveMaximum` are: each holds a value, so `CHECK (x > 0)` on a float is `exclusive_min: "0"`. A side takes one of its two fields, and a whole-number kind takes only `min` and `max` (`x > 0` is `min: "1"`). The infinities sort before and after every finite value, and NaN after infinity, as Postgres sorts them, so a `max` excludes NaN. */ |
There was a problem hiding this comment.
Fixed in 0b36bf7: bounds now constrain the finite values only, and whether infinity, -infinity and NaN occur is said by the infinity and nan flags alone, whatever the bounds. The timestamptz, day and total examples are right under that rule.
| export interface JsonType extends Base { | ||
| kind: "json"; | ||
| content?: Position; | ||
| schema?: { dialect: string; source: object }; |
There was a problem hiding this comment.
This is deliberate, and nothing is lost. A true schema admits every JSON value, so it is written by leaving schema out (rule 4 omits defaults). A false schema admits no value, which is a union with no members (UnionType). Keeping source an object gives each meaning one spelling.
There was a problem hiding this comment.
ℹ️ No new issues. Every bound corrected in e2a4540 is now right, and one thread from the first review is still open.
Reviewed changes
I reviewed e2a4540, which fixes five of the six threads from the first review. I resolved those five threads.
- Fixed Postgres's earliest date: it is now
-4713-11-24in thedatedoc, in rule 5, and in thetimestamptzanddayexamples. - Fixed the duration bounds' sign: each part's
maxis now the magnitude on the negative side (P2147483648M,P2147483648D,PT2562047788H54.775808S), and theDurationTypedoc explains why. The time bound checks out: 9,223,372,036,854.775808 s is 2,562,047,788 h plus 54.775808 s. - Fixed
money's bounds: they are now -922,337,203,685,477.5808 and 922,337,203,685,477.5807. - Listed the duration parts as widening fields: rule 1 now names
months,daysandtime. - Narrowed pgvector's element type: the
vector(3)element is now a float that holds neither infinity nor NaN, and the unusedfloat4constant is gone. A grep finds no leftover use of the old values.
Still open from the first review: the StatementDescriptor.kind thread on line 347 (a statement that parses but is none of the five kinds, such as CALL, VALUES, PRAGMA or DDL, has no value for kind), and the ℹ️ note in the review body about rule 4's two spellings.
claude-opus-5-5 | 𝕏
|
On the first review's rule-4 points, both settled in 0b36bf7: only a whole-number kind must write its bounds inclusive; any other kind writes a bound as the constraint states it, so |
There was a problem hiding this comment.
ℹ️ No critical issues. One minor suggestion inline: rule 2 still contradicts the new rule that bounds cover only finite values.
Reviewed changes
I reviewed 0b36bf7, which settles the open thread on kind, the bound spelling and union member order, and the Copilot thread on infinities beside bounds. I resolved the kind thread.
- Limited bounds to finite values:
Boundsnow says only theinfinityandnanflags decide whether the special values occur, whatever the bounds say. Thedate,datetimeandinstantflag docs no longer say a bound excludes the infinity beyond it. Under this rule thetimestamptz,dayandtotalexamples are consistent. - Kept each bound as the constraint writes it: a kind that is not a whole number keeps an exclusive bound exclusive (
x > 0onnumeric(10,2)isexclusive_min: "0", nevermin: "0.01"). Only whole-number kinds rewrite a bound as inclusive. - Added
kind: "other": a statement that parses but is none of the five kinds is nowother, andkindis absent only when the statement does not parse. - Ordered union members of one kind: members of the same kind now follow the order the database declares them in. The Oracle example now lists
R.SHAPE_TbeforeR.CIRCLE_T, and theinetexample already lists IPv4 first.
claude-opus-5-5 | 𝕏
There was a problem hiding this comment.
✅ No new issues found. Rule 2 and
Boundsnow agree that bounds cover only finite values.
Reviewed changes
I reviewed ff2f4f7, which fixes the one open thread from the last review. I resolved that thread.
- Limited rule 2's bounds to finite values: rule 2 now says a bound must hold for every finite value, and the
infinityandnanflags cover the rest. This matchesBounds, thedate,datetimeandinstantflag docs, and thetimestamptz,dayandtotalexamples, each of which has a finite bound besideinfinity: true.
claude-opus-5-5 | 𝕏



Adds
spec/type-descriptor.ts, the format for describing the types of a statement's parameters and columns across relational databases, and what introspecting a statement returns (StatementDescriptor). It is written as TypeScript types with the rules as comments, and the format itself is plain data (JSON, YAML or any serialization with objects, lists, strings, numbers and booleans).bits,unsigned), float (exponent and mantissa widths), decimal (precision and a scale range), text in three shapes by well-formedness, bytes, uuid, array, record, json, date, time, datetime, instant, duration, union, set, map, range, multirange, geometry, xml, and the opaque, any and unknown escape hatches.kind, its required fields and a closed list of widening fields, and may ignore the rest; a type states what the database guarantees; a value is what the type denotes, never its encoding; one spelling per meaning; values inside the format are strings; validity rules for what no shape can rule out.nub run typecheck, which now includesspec/.AGENTS.md lists the file. Nothing is implemented yet; the engines' types are to follow it.