Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
350babc
TML-3709: Plan the convenience date and time types project and draft …
wmadden-electric Oct 22, 2026
0d41c69
TML-3709: ADR 279 names the explicit types of the new codecs and reco…
wmadden-electric Oct 22, 2026
9e6ca0f
TML-3709: ADR 279 names the exact type the Prisma 7 reader writes for…
wmadden-electric Oct 22, 2026
0273369
TML-3709: Record the 2026-10-22 decisions: ISO string codecs keep the…
wmadden-electric Oct 22, 2026
e7e2354
TML-3709: Slice specs for the ISO string codecs, the Prisma 7 now() m…
wmadden-electric Oct 22, 2026
797de78
TML-3709: ADR 279 limits UTC conversion to timestamptz; timetz keeps …
wmadden-electric Oct 22, 2026
85f1a0a
TML-3709: ADR 279 states that IntervalString reads every IntervalStyl…
wmadden-electric Oct 22, 2026
202419d
TML-3709: Slice 5 also updates slice 2's upgrade instruction
wmadden-electric Oct 22, 2026
ab403be
TML-3709: Slice 2 spec: re-emit only, no db sign, since the marker ho…
wmadden-electric Oct 22, 2026
576ab6c
Temporal fields take their types from temporal-spec
wmadden-electric Oct 22, 2026
35a6cb9
Rename the Temporal date and time types and fix the IntervalParts out…
wmadden-electric Oct 22, 2026
ae4345a
Add the timestamp Date, timestamp Instant and interval string codecs
wmadden-electric Oct 22, 2026
aa2221b
Column helpers for the timestamp Date, timestamp Instant and interval…
wmadden-electric Oct 22, 2026
27a8242
Pin the new explicit types in the scalar, output-type and print checks
wmadden-electric Oct 22, 2026
d939a74
Regenerate contract.d.ts fixtures for the new codecs' aggregate rows
wmadden-electric Oct 22, 2026
592d07c
Upgrade instructions for the explicit date and time type renames
wmadden-electric Oct 22, 2026
67dbe7d
Document the timestamp Date, timestamp Instant and interval string types
wmadden-electric Oct 22, 2026
e7bcbd9
Declare temporal-spec where the published postgres types name it
wmadden-electric Oct 22, 2026
c4df676
Remove the unused branded Timestamp, Timestamptz and Time types
wmadden-electric Oct 22, 2026
ac0b729
Say how a database now() default reads through the UTC timestamp codecs
wmadden-electric Oct 22, 2026
da14f20
Pin the UTC timestamp codecs under a non-UTC host time zone
wmadden-electric Oct 22, 2026
9090661
Key the Supabase timestamp alias by the name contract infer writes
wmadden-electric Oct 22, 2026
56752f3
Use the renamed type names in the remaining docs and comment
wmadden-electric Oct 22, 2026
18a43c7
Type-test the timestamp Instant, timestamp Date and interval string f…
wmadden-electric Oct 22, 2026
1d23c71
Test the explicit date and time upgrade detection patterns
wmadden-electric Oct 22, 2026
24fb2a4
Keep the published package name out of the detection test's import text
wmadden-electric Oct 22, 2026
203352a
Drop the removed branded type imports from the last telemetry snapshot
wmadden-electric Oct 22, 2026
2f1984f
contract print writes the explicit Temporal type names
wmadden-electric Oct 22, 2026
51806ec
Final review fixes for the explicit date and time types
wmadden-electric Oct 22, 2026
a082295
TML-3709: Convenience types are type choices that name their explicit…
wmadden-electric Oct 22, 2026
aa9ff21
Bring in the current convenience type design and slice spec
wmadden-electric Oct 22, 2026
13f1672
Detect a Temporal type test even when temporal-spec is imported under…
wmadden-electric Oct 22, 2026
d3a658d
Add the type choice, a type whose arguments choose a type constructor
wmadden-electric Oct 22, 2026
02cfe3f
PSL readers resolve a type choice to the constructor its call chooses
wmadden-electric Oct 22, 2026
1352d64
type.* helpers for type choices, typed by the row their arguments choose
wmadden-electric Oct 22, 2026
7fb8b21
PostgreSQL DateTime, Date, Time and Duration are convenience types
wmadden-electric Oct 22, 2026
8f7bbd6
Prisma 8 Postgres schemas write the explicit Temporal type names
wmadden-electric Oct 22, 2026
139dac4
The Prisma 7 reader resolves a type its binding maps to a type choice
wmadden-electric Oct 22, 2026
bcf34d4
A misplaced option argument names the values the option takes
wmadden-electric Oct 22, 2026
54ee6f5
Postgres tests write the explicit Temporal names, and test the conven…
wmadden-electric Oct 22, 2026
48e82df
Journey: the convenience date and time types work with no global Temp…
wmadden-electric Oct 22, 2026
c3606ea
Upgrade instruction: DateTime, Date and Time are convenience types on…
wmadden-electric Oct 22, 2026
3aad953
Test that type.* and PSL give the same column for each convenience type
wmadden-electric Oct 22, 2026
0117077
Hover shows a type choice's choices and the explicit type each becomes
wmadden-electric Oct 22, 2026
494234b
ADR 279 is accepted; ADR 254, 257 and 275 note what it changes
wmadden-electric Oct 22, 2026
235a744
Docs describe the two layers of PostgreSQL date and time types
wmadden-electric Oct 22, 2026
8b2235a
Say an option argument takes bare names when a value is written in it…
wmadden-electric Oct 22, 2026
75dcd25
Keep SQL vocabulary out of the language server's hover helper name
wmadden-electric Oct 22, 2026
ac2581a
TML-3709: Slice 5 rebuilds the temporal.* aliases on DateTime
wmadden-electric Oct 22, 2026
0dfbc7c
Merge remote-tracking branch 'bot/main' into tml-3709-plan-datetime-c…
wmadden-electric Oct 22, 2026
b5ede4a
Say how the date and time upgrade relates to the convenience types
wmadden-electric Oct 22, 2026
5fcac29
Merge the shaping branch, with main's package moves, into the explici…
wmadden-electric Oct 22, 2026
a479f37
Regenerate the on-update-only fixture for the new codecs' aggregate rows
wmadden-electric Oct 22, 2026
6b9e74d
Merge slice 3 (explicit date and time types, with main merged in) int…
wmadden-electric Oct 22, 2026
151761f
A convenience type's parameter error names its own argument
wmadden-electric Oct 22, 2026
fcb2fe4
Type-test every PostgreSQL convenience row against its explicit helper
wmadden-electric Oct 22, 2026
332e05e
ADR 279's explicit type example matches the code, and its status says…
wmadden-electric Oct 22, 2026
929cb7e
The upgrade instruction says a SQLite or MongoDB match needs no change
wmadden-electric Oct 22, 2026
a061fba
A number written where an option goes is told to name its argument
wmadden-electric Oct 22, 2026
4ad3803
Journey asserts the exact text the string codecs read back
wmadden-electric Oct 22, 2026
bad0273
TML-3709: Note the journey values slices 1 and 4 must reconcile
wmadden-electric Oct 22, 2026
3a70f19
Test both messages of validateAuthoringTypeChoiceParams
wmadden-electric Oct 22, 2026
ce17206
Name MySQL's DateTime where the upgrade detection and ADR 279 list ot…
wmadden-electric Oct 22, 2026
c26d619
Assembly refuses two rows for one combination and a default the chose…
wmadden-electric Oct 22, 2026
e78c7af
A type choice call is read one way, for the column, its attributes an…
wmadden-electric Oct 22, 2026
3e2f496
The Prisma 7 reader names the field and binding when a type choice re…
wmadden-electric Oct 22, 2026
d8ab918
Glossary: explicit type, convenience type and type choice
wmadden-electric Oct 22, 2026
9b01e9c
The upgrade instruction gives the verified steps for moving to the ne…
wmadden-electric Oct 22, 2026
b4d163f
TML-3709: Slice 1: the target declares DateStyle=ISO and the driver e…
wmadden-electric Oct 23, 2026
3101f04
TML-3709: Slice 5 spec: infer takes the default path where it can
wmadden-electric Oct 23, 2026
db8e4d6
ADR 279 rewritten for a reader without context: grounding example fir…
wmadden-electric Oct 23, 2026
a82aacf
TML-3709: ADR 279 rewritten for a reader without context
wmadden-electric Oct 23, 2026
55d7024
ADR 279: fixes from a cold read
wmadden-electric Oct 23, 2026
49fce62
TML-3709: ADR 279 fixes from a cold read; slice 5 does not edit the ADR
wmadden-electric Oct 23, 2026
6e7c26b
TML-3709: ADR 279 says a timestamptz written with another offset read…
wmadden-electric Oct 23, 2026
f4bcf93
ADR 279 says a timestamptz written with another offset reads back in UTC
wmadden-electric Oct 23, 2026
3c6b36c
TML-3709: ADR 279 describes convenience types as a general mechanism,…
wmadden-electric Oct 23, 2026
d0b12db
TML-3709: The spec says the driver ensures DateStyle=ISO on supplied …
wmadden-electric Oct 23, 2026
8dd9a2c
TML-3709: Handover brief for the convenience date and time types project
wmadden-electric Oct 23, 2026
739fd31
TML-3709: Handover records the state of the as/storage rename
wmadden-electric Oct 23, 2026
5149dfb
TML-3711: Every explicit PostgreSQL date and time type names the valu…
wmadden-electric Oct 23, 2026
9fc86d9
TML-3719: An adopted Prisma 7 schema fills @default(now()) on create …
wmadden-electric Oct 23, 2026
86b11b6
Convenience type options are as: and storage:, and their TypeScript h…
wmadden-electric Oct 23, 2026
cd49fcf
TML-3418: PostgreSQL's string date and time types read ISO 8601 on ev…
wmadden-electric Oct 23, 2026
52c325d
Merge main into the design branch
wmadden-electric Oct 23, 2026
0397f66
Merge the design branch, with the ISO strings, Prisma 7 now() and mai…
wmadden-electric Oct 23, 2026
9850ad9
Links to ADR 279 use its new file name
wmadden-electric Oct 23, 2026
f4e5c18
The codec authoring guide says a type choice's TypeScript helper take…
wmadden-electric Oct 23, 2026
0b7a09b
The convenience types journey writes and reads ISO 8601 text
wmadden-electric Oct 23, 2026
510d1a0
TML-3713: contract infer, contract print, the Prisma 7 reader and orm…
wmadden-electric Oct 23, 2026
a10af60
Merge main into the convenience types
wmadden-electric Oct 23, 2026
a26b835
Format the Prisma 7 dates journey test
wmadden-electric Oct 23, 2026
3bb4ef5
now() on a Temporal timestamp column rounds down to the column precision
wmadden-electric Oct 23, 2026
900c1d0
Detect the temporal.timestamptzJsDate preset in the now-precision upg…
wmadden-electric Oct 23, 2026
e0cb736
Name the arguments a convenience type takes when a PSL call writes on…
wmadden-electric Oct 23, 2026
d026159
Try every type choice that names a constructor when print and infer w…
wmadden-electric Oct 23, 2026
12d2cdb
Show how a TypeScript convenience type takes its options from a variable
wmadden-electric Oct 23, 2026
70e3c03
Use ifDefined for the conditional properties the type choice code adds
wmadden-electric Oct 23, 2026
3574b98
Split the hover, completion and contributed-type binder tests by topic
wmadden-electric Oct 23, 2026
8bc20aa
Ask the column codec whether contract infer can print a default exactly
wmadden-electric Oct 23, 2026
1c8b383
Write a Prisma 7 default the codec refuses with the adapter's literal…
wmadden-electric Oct 23, 2026
9b69c6b
Say in the codec authoring guide that print and infer write a type ch…
wmadden-electric Oct 23, 2026
a0d1045
Test that each date and time type infer writes is its convenience def…
wmadden-electric Oct 23, 2026
4d3db71
Remove the import cycle between the authoring module and the type cho…
wmadden-electric Oct 23, 2026
365048e
Refuse a type choice whose row names another component's type constru…
wmadden-electric Oct 23, 2026
03df270
Label an undocumented type choice "Convenience type" in completion
wmadden-electric Oct 23, 2026
0977c23
Say in the glossary that convenience and explicit types exist in Type…
wmadden-electric Oct 23, 2026
4611823
Let the type constructor lookup read a PSL namespace that holds field…
wmadden-electric Oct 23, 2026
ecb621c
The project spec and plan describe the type choice, the one-object Ty…
wmadden-electric Oct 23, 2026
b632adc
contract infer picks a type that reads the column's own default
wmadden-electric Oct 27, 2026
4828a44
ADR 279 says infer picks a type that reads the column's own default
wmadden-electric Oct 27, 2026
f0c57a9
Assembly refuses a type choice row that leaves a required argument wi…
wmadden-electric Oct 27, 2026
35a602a
Merge main: field presets removed (#30845)
wmadden-electric Oct 27, 2026
a4ccc3a
Merge main: release 8.0.0-rc.21 (#30864)
wmadden-electric Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion apps/telemetry-backend/src/prisma/contract.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

model TelemetryEvent {
id BigInt @id @default(autoincrement())
ingestedAt DateTime @default(now())
ingestedAt TimestamptzTemporal @default(now())
installationId String
version String
command String
Expand Down
2 changes: 1 addition & 1 deletion docs/Serverless Deployment Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ This goes in `.env`, not `.dev.vars`. `.dev.vars` is for runtime worker secrets;

A serverless client at module scope; a connection from `connect` in each request; SQL, ORM and transactions on that connection; cursor streaming. The full file is `examples/prisma-8-cloudflare-worker/src/worker.ts`.

The samples use extensionless relative imports and `./contract.d`, which need `moduleResolution: "bundler"` in `tsconfig.json`; Worker projects built with wrangler use that setting, and so does the example. On a runtime without a built-in `Temporal`, which today includes Cloudflare Workers at the example's compatibility date and Node.js 24, reading a `DateTime` column needs a Temporal polyfill: add `temporal-polyfill` and import `temporal-polyfill/full/global` before the first query, as the example does at the top of `worker.ts` and `seed.ts`.
The samples use extensionless relative imports and `./contract.d`, which need `moduleResolution: "bundler"` in `tsconfig.json`; Worker projects built with wrangler use that setting, and so does the example. On a runtime without a built-in `Temporal`, which today includes Cloudflare Workers at the example's compatibility date and Node.js 24, reading a column whose type reads `Temporal` values, such as `TimestamptzTemporal` or `DateTime(as: temporal)`, needs a Temporal polyfill: add `temporal-polyfill` and import `temporal-polyfill/full/global` before the first query, as the example does at the top of `worker.ts` and `seed.ts`.

#### Module scope

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR 246 — Option arguments and select templates for authoring helpers

**Status:** Superseded 2026-10-22 by [ADR 275](ADR%20275%20-%20Client%20generators%20are%20field%20attributes%2C%20and%20a%20type%20alias%20is%20expanded%20into%20each%20column.md), except its two omission rules. Field presets, the `option` argument kind and the `select` template node were removed. The two omission rules, no empty `typeParams` and no empty `executionDefaults`, stand as canonicalization rules in [Data Contract](../subsystems/1.%20Data%20Contract.md#deterministic-emission). The content below is preserved as a historical record.
**Status:** Superseded 2026-10-22 by [ADR 275](ADR%20275%20-%20Client%20generators%20are%20field%20attributes%2C%20and%20a%20type%20alias%20is%20expanded%20into%20each%20column.md), except its two omission rules. Field presets, the `option` argument kind and the `select` template node were removed; [ADR 279](ADR%20279%20-%20Convenience%20types%20choose%20an%20explicit%20type%20from%20their%20arguments.md) gives a type choice its own `option` arguments, which only a type choice takes. The two omission rules, no empty `typeParams` and no empty `executionDefaults`, stand as canonicalization rules in [Data Contract](../subsystems/1.%20Data%20Contract.md#deterministic-emission). The content below is preserved as a historical record.

## At a glance

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ flowchart LR

**Rules of the language live in the family authoring package.** `@internal/sql-contract-prisma7` holds everything that is true of the Prisma 7 language for the SQL family: blocks and attributes, relation pairing, junction tables, defaults, and the diagnostics. It knows nothing about a particular database and depends on no Prisma 7 package.

**Facts about the database come from the target through one binding.** `Prisma7TargetBinding` is the complete list of what a target must answer: the target pack and namespace factory, the datasource providers it accepts, the type map of what the earlier version creates for each scalar and native-type attribute, the native enum entity kind, the index types, the identifier byte limit, the junction relation field names, the "now" generator for each column codec, and how a literal default is read for a column whose codec does not take the written value. The Postgres target exports one instance.
**Facts about the database come from the target through one binding.** `Prisma7TargetBinding` is the complete list of what a target must answer: the target pack and namespace factory, the datasource providers it accepts, the type map of what the earlier version creates for each scalar and native-type attribute, the native enum entity kind, the index types, the identifier byte limit, the junction relation field names, the "now" generator for each column codec, how a literal default is read for a column whose codec does not take the written value, and the SQL expression of a default whose value the column's type holds but its codec cannot hold exactly. The Postgres target exports one instance.

**The facade publishes one function** that wires the two together:

Expand Down
10 changes: 5 additions & 5 deletions docs/architecture docs/adrs/ADR 254 - Data types and casts.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR 254 — Data types and casts

Status: **Accepted.** Implemented for the Postgres, SQLite and MongoDB targets and their extensions. Amended 2026-10-20: a type-constructor argument that is a value names a data type and is admitted by the cast rule (see "Columns and type constructors"). Amended 2026-10-22: MongoDB values are written and cast as SQL values are, and enum members and discriminator values are read by one framework function in both families (see "Enum members and discriminator values").
Status: **Accepted.** Implemented for the Postgres, SQLite and MongoDB targets and their extensions. Amended 2026-10-20: a type-constructor argument that is a value names a data type and is admitted by the cast rule (see "Columns and type constructors"). Amended 2026-10-22: MongoDB values are written and cast as SQL values are, and enum members and discriminator values are read by one framework function in both families (see "Enum members and discriminator values"). Amended by [ADR 279](ADR%20279%20-%20Convenience%20types%20choose%20an%20explicit%20type%20from%20their%20arguments.md) on 2026-10-22: a type constructor still names one codec, and a second kind of type declaration, a type choice, resolves to a type constructor from its arguments, so a column always stores the codec and parameters of a type constructor.

## At a glance

Expand Down Expand Up @@ -103,7 +103,7 @@ A codec has four methods:

A codec is built with its descriptor, its data type and the column's parameters of that type, and `toDataTypeValue` ends with `dataTypeValueOf(json)`, which calls `DataType.valueFrom` with those parameters. A Mongo codec is the same: its `toDataTypeValue` returns a `DataTypeValue`. The wire value is what the driver exchanges with the database, and it is not always text: a Postgres `bytea` codec writes a `Uint8Array`, a SQLite integer codec reads a number, a Mongo codec writes a BSON value. `fromWire` and `toWire` take the per-call context of [ADR 207](ADR%20207%20-%20Codec%20call%20context%20per-query%20AbortSignal%20and%20column%20metadata.md) and are asynchronous as [ADR 204](ADR%20204%20-%20Single-Path%20Async%20Codec%20Runtime.md) describes. A codec never reads `contract.json` and never sees PSL text.

Codecs of one type differ only in the application value. `pg/int8@1` holds a `bigint` and `pg/int8number@1` a `number`; `pg/timestamptz-date@1`, `pg/timestamptz-temporal@1` and `pg/timestamptz-string@1` hold a `Date`, a `Temporal.Instant` and text. A codec refuses in `fromDataTypeValue` a value its application value cannot hold exactly: `pg/int8number@1` refuses digit text past 2^53, and the `Temporal` and `Date` codecs refuse `infinity`, which only the text codecs hold. A limit one target's storage has belongs to that target's codec: SQLite cannot store `NaN`, so `sqlite/real@1`, and `sql/float@1` as SQLite adapts it, refuse it in `toWire` and in both value methods, and the SQLite driver refuses a `NaN` parameter no codec encoded, such as one in raw SQL.
Codecs of one type differ only in the application value. `pg/int8@1` holds a `bigint` and `pg/int8number@1` a `number`; `pg/timestamptz-date@1`, `pg/timestamptz-temporal@1` and `pg/timestamptz-string@1` hold a `Date`, a `Temporal.Instant` and text. A codec refuses in `fromDataTypeValue` a value its application value cannot hold exactly: `pg/int8number@1` refuses digit text past 2^53, and the `Temporal` and `Date` codecs refuse `infinity`, which only the text codecs hold. The `Date` codecs also refuse digits below a millisecond, which the text and `Temporal` codecs hold. A limit one target's storage has belongs to that target's codec: SQLite cannot store `NaN`, so `sqlite/real@1`, and `sql/float@1` as SQLite adapts it, refuse it in `toWire` and in both value methods, and the SQLite driver refuses a `NaN` parameter no codec encoded, such as one in raw SQL.

A limit of the type itself is the type's, as "Values" describes. A codec checks only parameters it keeps for itself: `arktype/json@1` validates the document against its schema in `fromDataTypeValue` and `fromWire`.

Expand Down Expand Up @@ -229,9 +229,9 @@ Every consumer of a stored default reads it through the column's data type and c
- **Loading a contract.** When the CLI loads a contract, the SQL family's `deserializeContract` reads every literal default through its column's type and refuses a value the type does not hold, with `CONTRACT.VALIDATION_FAILED` naming the column. No later step meets an invalid stored value.
- **Writing a default into DDL.** The planner hands the column's value to its codec, `fromDataTypeValue` then `toWire`, and the adapter writes the wire value as a SQL literal of the column's type. The `DEFAULT` clause therefore holds the text the codec writes for every row: on SQLite a default and a row the application wrote for the same instant are one text, and on Postgres a year before 1 is written with ` BC`. The codec's methods are asynchronous, so this runs where DDL is lowered, which is asynchronous too.
- **Reading a default the database reports.** Postgres reports a default as a literal with a cast, `'2024-01-01 00:00:00+00'::timestamp with time zone`; SQLite reports the literal its `DEFAULT` clause holds. The target takes the literal's body out of that text, which is what its driver would hand `fromWire` for a row of the column, and introspection reads it with the column's codec, `fromWire` then `toDataTypeValue`. Introspection is asynchronous and receives the contract, so this runs there, and the tree it builds holds the value ([ADR 235](ADR%20235%20-%20The%20schema%20differ%20walks%20two%20derived%20schema%20IRs.md): each side of a diff is built in the form it is compared in). A default the database holds as an expression, such as `now()`, is compared as expression text. A contract default written as a `sql` expression that holds only a literal, such as `'hello'::bytea`, has its literal read the same way, so the two sides compare as values; text is compared only where no codec can read a literal, and then case and spaces inside quotes count. A literal the codec cannot read, such as a value written outside Prisma that the application value cannot hold, is reported as a difference naming the column and the codec's message.
- **Comparing defaults.** `db verify` and the planner compare two values of the column's type for equality. A `Date` default is a value of its column's type like any other: the database's default is read through the column's codec, and the comparison never goes through `toISOString()`. When no database is in reach, both sides come from contracts and no codec is needed.
- **Comparing defaults.** `db verify` and the planner compare two values of the column's type for equality. A `Date` default is a value of its column's type like any other: the database's default is read through the column's codec, and the comparison never goes through `toISOString()`. On a column whose codec reads a `Date`, both sides are read as the application reads them, so a difference below a millisecond is not reported. When no database is in reach, both sides come from contracts and no codec is needed.
- **Rows the database returns as JSON.** An `include` or an aggregated child row set comes back as JSON. Each column enters it through its codec's JSON projection ([codec authoring guide](../../reference/codec-authoring-guide.md#rows-the-database-returns-as-json)), and the runtime reads it with `fromWire`, the same method that reads an ordinary row. A projection puts into the JSON a value `fromWire` reads exactly, giving the same application value as an ordinary row. On Postgres a type whose value would enter the JSON in another form than its row's text is cast to text, `CAST(x AS text)`, including `json`, `jsonb` and vectors: a document embedded as JSON would come back already parsed, so `fromWire` would parse the string document `"42"` a second time, and a vector widened to double precision would read `0.10000000149011612` where a row reads `0.1`. A type whose JSON value `fromWire` already reads, such as a number, a boolean, or text like a uuid or an enum label, enters as the column itself, and its `fromWire` reads both forms; `pg/char` enters with its padding removed. The runtime has one way to read what the database returns, and the stored form never has to match the database's spelling.
- **Inferring a contract.** `contract infer` has no contract, so it reads a reported default with the codec of the type constructor it prints for the column, `fromWire` of the literal's body then `toDataTypeValue`, and an enum column's through `pg/text@1`. It prints the value as "Printing" describes.
- **Inferring a contract.** `contract infer` has no contract, so it reads a reported default with the codec of the type constructor it prints for the column, `fromWire` of the literal's body then `toDataTypeValue`, and an enum column's through `pg/text@1`. It prints a reported literal only when the codec holds it exactly, and otherwise prints the raw expression. Where the column's data type reads the body's text, infer asks the codec's `fromDataTypeValue`, as `contract emit` asks of a written default, so a `Date` codec's refusal of digits below a millisecond reaches infer from the codec. A JSON number keeps its own check, because the JSON codec parses the document's text into JavaScript numbers when it reads the body, so a number such as `12345678901234567890` is rounded before any value reaches `fromDataTypeValue`. It prints the value as "Printing" describes.

## How a data type names its database type

Expand Down Expand Up @@ -300,7 +300,7 @@ SQLite stores text, integers, reals and blobs, so its target declares `sqlite/te

A type is stored as its own database type only when the database can record it. Declaring a SQLite column `DATETIME` or `JSON` would let introspection tell the types apart, but `JSON` gives the column numeric affinity, and SQLite would then store the document `"42"` as the integer 42.

**Comparing column types.** Two columns have the same type when they are stored as the same data type, with equal parameters in its normal form, and are both lists or both not. The rule is the same whether one column comes from the database or both come from contracts, as when the migration planner compares two contracts or the contract validator checks a many-to-many join table; without the stack's data types, the validator compares parameters as written. The database side's type is the one that claims what the database reports: by its texts, or for an enum by the kind of the type, read from the catalog with its schema and name, so the result does not depend on the search path. A column whose reported type no data type in the stack claims never equals another, and its mismatch names the reported type: `The database column is "point", which no data type in the configured stack declares.`, or for a SQLite column declared with no type, `The database column has no declared type.` After `ALTER COLUMN TYPE`, the Postgres planner checks the new type the same way: a type with texts by comparing `format_type` with its catalog text, an enum by identity, `atttypid = to_regtype(...)`, or the element type's `typarray` for a list. A column in the unbound namespace whose enum type lives outside `public` does not compare equal; this is a known limit.
**Comparing column types.** Two columns have the same type when they are stored as the same data type, with equal parameters in its normal form, and are both lists or both not. The rule is the same whether one column comes from the database or both come from contracts, as when the migration planner compares two contracts or the contract validator checks a many-to-many join table; without the stack's data types, the validator compares parameters as written. The database side's type is the one that claims what the database reports: by its texts, or for an enum by the kind of the type, read from the catalog with its schema and name, so the result does not depend on the search path. A column whose reported type no data type in the stack claims never equals another, and its mismatch names the reported type: `The database column is "point", which no data type in the configured stack declares.`, or for a SQLite column declared with no type, `The database column has no declared type.` After `ALTER COLUMN TYPE`, the Postgres planner checks the new type the same way: a type with texts by comparing `format_type` with its catalog text for the type as written (a bare `timestamptz` is `timestamp with time zone`, though its normal form counts the omitted precision as 6), an enum by identity, `atttypid = to_regtype(...)`, or the element type's `typarray` for a list. A column in the unbound namespace whose enum type lives outside `public` does not compare equal; this is a known limit.

## Columns and type constructors

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Status: **Accepted**

**Amended by [ADR 279](ADR%20279%20-%20Convenience%20types%20choose%20an%20explicit%20type%20from%20their%20arguments.md) (2026-10-22):** where a stored type has several codecs, every one of them carries a suffix naming its kind of value, not only the second (`TimestamptzTemporal`, `TimestamptzJsDate`, `TimestamptzString`). The unsuffixed name of the concept, such as `DateTime`, `Date`, `Time` and `Duration` on PostgreSQL, belongs to a convenience type, a type choice that becomes one of the suffixed types.

## Decision

A scalar type's name says what the database stores, in the database's own vocabulary. Each type has one token, written exactly as its codec id writes it (`objectId` keeps its inner capital), and the three surfaces a user writes derive from it: the codec id is `<target>/<token>@<version>`, the PSL name is the token with its first letter uppercased, and the TypeScript builder helper is `field.<token>()`. Where a codec id already carries a name, the codec id is the source of the token, and no codec id is renamed to fit a surface name.
Expand Down
Loading
Loading