@prisma/orm-mongo 8.0.0-rc.4-dev.17 → 8.0.0-rc.5-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -10
- package/skills/prisma-8/SKILL.md +84 -0
- package/skills/prisma-8/references/build.md +142 -0
- package/skills/prisma-8/references/contract.md +417 -0
- package/skills/prisma-8/references/debug.md +141 -0
- package/skills/prisma-8/references/feedback.md +251 -0
- package/skills/prisma-8/references/migration-review.md +224 -0
- package/skills/prisma-8/references/migrations.md +519 -0
- package/skills/prisma-8/references/queries-mongo.md +236 -0
- package/skills/prisma-8/references/queries-postgres.md +415 -0
- package/skills/prisma-8/references/queries.md +168 -0
- package/skills/prisma-8/references/quickstart.md +326 -0
- package/skills/prisma-8/references/runtime.md +344 -0
- package/skills/prisma-8/references/supabase.md +244 -0
- package/skills/prisma-8/references/upgrade-app.md +101 -0
- package/skills/prisma-8/references/upgrade-extension.md +105 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.10-to-0.11/instructions.md +56 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/instructions.md +381 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-closed-mongo-contracts.ts +202 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-domain-namespaced-contracts.ts +201 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/re-emit-postgres-public-default.ts +198 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +340 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/instructions.md +339 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.12-to-0.13/re-emit-mti-variant-link-columns.ts +229 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/instructions.md +543 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +290 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.13-to-0.14/uuid-preset-rename.ts +43 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.14-to-0.15/instructions.md +359 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.15-to-0.16/instructions.md +173 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/instructions.md +805 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +382 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.17-to-8.0.0-rc.1/instructions.md +72 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.7-to-0.8/instructions.md +8 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/instructions.md +36 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.8-to-0.9/strip-inline-contracts.ts +226 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/instructions.md +86 -0
- package/skills/prisma-8/upgrading/app/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +360 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +588 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md +5 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md +158 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md +42 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.10-to-0.11/instructions.md +276 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/instructions.md +738 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/migrate-contract-testing-imports.ts +97 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/regenerate-extension-public-baseline.ts +223 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.11-to-0.12/strip-migration-labels-hints.ts +340 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.12-to-0.13/instructions.md +266 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/instructions.md +522 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts +290 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/uuid-preset-rename.ts +43 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.14-to-0.15/instructions.md +803 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.15-to-0.16/instructions.md +219 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/instructions.md +731 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.16-to-0.17/strip-sha256-hash-prefixes.ts +382 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.17-to-8.0.0-rc.1/instructions.md +194 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.7-to-0.8/instructions.md +8 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/instructions.md +57 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.8-to-0.9/strip-inline-contracts.ts +226 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/instructions.md +150 -0
- package/skills/prisma-8/upgrading/extension/upgrades/0.9-to-0.10/stamp-storage-types-kind.ts +360 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md +746 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.2-to-8.0.0-rc.3/instructions.md +5 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.3-to-8.0.0-rc.4/instructions.md +137 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.4-to-8.0.0-rc.5/instructions.md +129 -0
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
---
|
|
2
|
+
from: "0.16"
|
|
3
|
+
to: "0.17"
|
|
4
|
+
changes:
|
|
5
|
+
- id: one-prisma-package-per-application
|
|
6
|
+
summary: |
|
|
7
|
+
Do this first — nothing else in this upgrade can run until the project installs. From
|
|
8
|
+
0.17 the `@prisma-next/*` scope is retired: nothing publishes under it again, so a
|
|
9
|
+
manifest that still names it fails at install. The public surface is the `@prisma`
|
|
10
|
+
scope, and an application depends on exactly ONE database facade —
|
|
11
|
+
`@prisma/orm-postgres`, `@prisma/orm-sqlite`, or `@prisma/orm-mongo` — plus any
|
|
12
|
+
extension packs it uses, which remain direct dependencies. Everything else that used
|
|
13
|
+
to be a separate dependency (framework, family, target, adapter, driver, CLI) arrives
|
|
14
|
+
transitively as the facade's exact-pinned dependencies. In `package.json`: delete
|
|
15
|
+
EVERY `@prisma-next/*` entry across dependencies/devDependencies/peerDependencies,
|
|
16
|
+
add the one facade for your database, and keep your extension packs — renamed
|
|
17
|
+
(`@prisma-next/extension-<x>` → `@prisma/orm-extension-<x>`; the middleware cache is
|
|
18
|
+
`@prisma/orm-extension-middleware-cache`). Drop the `prisma-next` devDependency if you
|
|
19
|
+
have one — the facade provides the `prisma-next` bin; the standalone `prisma-next`
|
|
20
|
+
package remains only as the bootstrap path for projects with no Prisma dependencies
|
|
21
|
+
yet. Reinstall, then regenerate your contract artefacts (`prisma-next contract emit`):
|
|
22
|
+
generated files now import facade entrypoints (e.g. `@prisma/orm-postgres/components`)
|
|
23
|
+
instead of old-scope package names. The `contractHash` is unchanged by regeneration —
|
|
24
|
+
`prisma-next db verify` passes with no database work. Finally rewrite hand-written
|
|
25
|
+
imports: same-package entrypoints keep their subpath under the facade
|
|
26
|
+
(`@prisma-next/postgres/config` → `@prisma/orm-postgres/config`, likewise `/runtime`,
|
|
27
|
+
`/target`, `/family`, `/migration`, `/control`, …); programmatic tooling imports
|
|
28
|
+
(`@prisma-next/cli/*`, `@prisma-next/config-loader`, `@prisma-next/migration-tools/*`,
|
|
29
|
+
`@prisma-next/emitter`) move to the matching `@prisma/orm-toolchain/*` subpath
|
|
30
|
+
(`@prisma/orm-toolchain/cli/config-types`, `/cli/control-api`, `/config-loader`,
|
|
31
|
+
`/migration-tools/<subpath>`, `/emitter`). The rule for every rewrite: import only
|
|
32
|
+
from packages your manifest names directly — the facade, your extension packs, and
|
|
33
|
+
(for tooling authors) `@prisma/orm-toolchain`.
|
|
34
|
+
detection:
|
|
35
|
+
glob: "**/*.{json,ts,tsx,mts,cts,js,mjs,cjs}"
|
|
36
|
+
contains:
|
|
37
|
+
- '@prisma-next/'
|
|
38
|
+
anyMatch: true
|
|
39
|
+
- id: strip-sha256-hash-prefixes
|
|
40
|
+
summary: |
|
|
41
|
+
Content hashes are bare lowercase hex from 0.17 — the `sha256:` prefix is gone from every
|
|
42
|
+
surface (emitted `contract.json` / `contract.d.ts`, migration manifests, refs, CLI output,
|
|
43
|
+
and the database marker/ledger), and loaders reject the legacy prefixed form. Contract hash
|
|
44
|
+
VALUES are unchanged (only the prefix drops; `pnpm emit` regenerates live artefacts), but
|
|
45
|
+
`migrationHash` VALUES change because the hashed manifest bytes embed the now-bare
|
|
46
|
+
`from`/`to` strings. Run the colocated codemod over your checked-in `migrations/` trees
|
|
47
|
+
FIRST, before the snapshot-layout migrator in the entries below — the 0.17 layout migrator
|
|
48
|
+
accepts only bare-hex trees. The codemod handles both layouts: it strips the prefix from
|
|
49
|
+
every hash literal (manifests, `ops.json`, pre-store sibling contract snapshots, store
|
|
50
|
+
entries under `migrations/snapshots/`, `.d.ts` branded literals), maps the empty-tree
|
|
51
|
+
sentinel `sha256:empty` to `empty`, recomputes each `migrationHash`, and repoints
|
|
52
|
+
`refs/*.json`. Store directory names are the hash's hex, which does not change. Databases
|
|
53
|
+
whose marker/ledger still hold prefixed values report a hash mismatch on verify — there is
|
|
54
|
+
no compatibility shim; re-sign against the regenerated contract (`prisma-next db sign`).
|
|
55
|
+
detection:
|
|
56
|
+
glob: "**/*.{json,ts,tsx}"
|
|
57
|
+
contains:
|
|
58
|
+
- 'sha256:'
|
|
59
|
+
anyMatch: true
|
|
60
|
+
script: ./strip-sha256-hash-prefixes.ts
|
|
61
|
+
- id: migration-contract-snapshots-moved-to-content-addressed-store
|
|
62
|
+
summary: |
|
|
63
|
+
Committed migration contract snapshots move from per-package sibling files
|
|
64
|
+
(`start-contract.json` / `start-contract.d.ts` / `end-contract.json` /
|
|
65
|
+
`end-contract.d.ts`) and per-space head copies
|
|
66
|
+
(`migrations/<space-id>/contract.json` / `contract.d.ts`) into a single
|
|
67
|
+
content-addressed store per migrations root, at
|
|
68
|
+
`migrations/snapshots/<hex>/contract.json` + `contract.d.ts`, where `<hex>`
|
|
69
|
+
is the contract's 64-hex storage hash (bare hex after the
|
|
70
|
+
`strip-sha256-hash-prefixes` entry above, which must run first). Every
|
|
71
|
+
distinct contract is stored once, however many migrations reference it.
|
|
72
|
+
Every emitted `migration.ts` now imports its bookend contracts from the
|
|
73
|
+
store (`../../snapshots/<hex>/contract.json`, `../../snapshots/<hex>/contract.d.ts`)
|
|
74
|
+
instead of from sibling files in its own directory.
|
|
75
|
+
This is a clean break: there is no fallback reader for the old sibling-file
|
|
76
|
+
layout, so a committed migrations tree that has not been converted fails to
|
|
77
|
+
load once you upgrade — `migration plan` / `migration new` / `migrate` /
|
|
78
|
+
`migration check` all read contract snapshots through the store only, and
|
|
79
|
+
a missing store entry fails with `MIGRATION.CONTRACT_SNAPSHOT_MISSING`
|
|
80
|
+
naming the expected hash and path. `migration.json` / `ops.json` /
|
|
81
|
+
`migrationHash` are unaffected — the contract snapshot was never part of
|
|
82
|
+
migration identity, so converting the layout changes no migration's hash.
|
|
83
|
+
To convert an existing project, run the migrator once per migrations root
|
|
84
|
+
from a checkout of the `prisma/prisma` repository at (or above) the
|
|
85
|
+
version you're upgrading to: `node scripts/migrate-migrations-layout.mjs
|
|
86
|
+
[migrationsRoot...]` (with no arguments it auto-discovers every migrations
|
|
87
|
+
root under the current directory). Per migration package, it reads
|
|
88
|
+
`migration.json`, write-if-absents the destination contract (and the
|
|
89
|
+
source contract, when present) into the store under the matching hash,
|
|
90
|
+
rewrites the committed `migration.ts` import specifiers, and deletes the
|
|
91
|
+
four sibling files. Per contract space, it store-writes any remaining
|
|
92
|
+
per-space `contract.json` / `contract.d.ts` keyed by that space's
|
|
93
|
+
`refs/head.json` hash, then deletes it. It asserts every contract's inner
|
|
94
|
+
`storage.storageHash` against the hash it's filed under before writing
|
|
95
|
+
anything (mismatch aborts the whole run, nothing is deleted), and
|
|
96
|
+
re-verifies every `migrationHash` is unchanged after conversion. Run it,
|
|
97
|
+
review the diff, then `pnpm typecheck` (or your project's equivalent) to
|
|
98
|
+
confirm every rewritten `migration.ts` import resolves.
|
|
99
|
+
detection:
|
|
100
|
+
glob: "**/migration.ts"
|
|
101
|
+
contains:
|
|
102
|
+
- "./start-contract.json"
|
|
103
|
+
- "./end-contract.json"
|
|
104
|
+
- "./start-contract'"
|
|
105
|
+
- "./end-contract'"
|
|
106
|
+
anyMatch: true
|
|
107
|
+
- id: ref-paired-snapshots-moved-to-content-addressed-store
|
|
108
|
+
summary: |
|
|
109
|
+
Ref-paired contract snapshot files (`refs/<name>.contract.json` /
|
|
110
|
+
`refs/<name>.contract.d.ts`, written by `ref set` and `--advance-ref`) are
|
|
111
|
+
no longer written or read. A ref is now only its pointer file,
|
|
112
|
+
`refs/<name>.json` (`{ hash, invariants }`); the contract it names
|
|
113
|
+
resolves through the same content-addressed store as every migration
|
|
114
|
+
graph node, `migrations/snapshots/<hex>/contract.json` + `contract.d.ts`,
|
|
115
|
+
by that hash. This is a clean break: a pointer whose store entry is
|
|
116
|
+
missing now fails with `MIGRATION.CONTRACT_SNAPSHOT_MISSING` naming the
|
|
117
|
+
expected hash and path, rather than silently falling back to the
|
|
118
|
+
migration graph. The same one-shot migrator that folds per-package and
|
|
119
|
+
per-space sibling snapshots (see the entry above) also folds any
|
|
120
|
+
existing `refs/<name>.contract.json` / `refs/<name>.contract.d.ts`
|
|
121
|
+
pairs: it write-if-absents the pair into the store under the sibling
|
|
122
|
+
pointer's `hash`, then deletes the pair — the pointer file itself is
|
|
123
|
+
read but never written, so it stays byte-identical. A `.contract.json`
|
|
124
|
+
with no sibling pointer, or whose inner `storage.storageHash` disagrees
|
|
125
|
+
with the pointer's `hash`, aborts the whole run before anything is
|
|
126
|
+
written or deleted. Run `node scripts/migrate-migrations-layout.mjs
|
|
127
|
+
[migrationsRoot...]` (same invocation as above; one run folds both
|
|
128
|
+
migration-package and ref-paired snapshots), review the diff, then
|
|
129
|
+
re-run `prisma-next ref list` to confirm your refs are unaffected.
|
|
130
|
+
detection:
|
|
131
|
+
glob: "**/refs/*.contract.json"
|
|
132
|
+
anyMatch: true
|
|
133
|
+
- id: extension-packs-config-key-renamed-to-extensions
|
|
134
|
+
summary: |
|
|
135
|
+
The `extensionPacks` key is renamed to `extensions` everywhere: the
|
|
136
|
+
low-level `defineConfig` in `prisma.config.ts`, the TS builder's
|
|
137
|
+
`defineContract` (record form), runtime/control client options, and the
|
|
138
|
+
top-level key of the emitted `contract.json` / `contract.d.ts`. The old
|
|
139
|
+
config key now fails loudly with "Config.extensionPacks is no longer
|
|
140
|
+
supported; rename it to Config.extensions" — it is never silently
|
|
141
|
+
ignored. Rename the key in `prisma.config.ts` (and `contract.ts` /
|
|
142
|
+
`db.ts` if they pass `extensionPacks` to client factories). The target
|
|
143
|
+
façades' `defineConfig` already used `extensions`; only projects on the
|
|
144
|
+
low-level config change. Because the key sits in the canonicalized bytes
|
|
145
|
+
of every contract hash, all three hashes (`storageHash`,
|
|
146
|
+
`executionHash`, `profileHash`) change for every contract: re-emit with
|
|
147
|
+
`prisma-next contract emit`, then re-anchor migrations — regenerate
|
|
148
|
+
`migrations/snapshots/<hex>/` store entries and refs for the new hashes
|
|
149
|
+
(a schema-unchanged project needs a hash-advance migration or a
|
|
150
|
+
re-baseline; the database schema itself does not change).
|
|
151
|
+
detection:
|
|
152
|
+
glob: "**/{prisma.config.ts,contract.ts,db.ts}"
|
|
153
|
+
contains:
|
|
154
|
+
- "extensionPacks"
|
|
155
|
+
- id: contract-source-format-key-renamed
|
|
156
|
+
summary: |
|
|
157
|
+
The contract source provider field `sourceFormat` is renamed to `format`
|
|
158
|
+
(`contract.source.format` in the low-level config; provider objects from
|
|
159
|
+
`prismaContract()` / `typescriptContract()` emit the new field
|
|
160
|
+
automatically once upgraded). Rename any literal `sourceFormat:` in
|
|
161
|
+
hand-written provider objects or config assertions.
|
|
162
|
+
detection:
|
|
163
|
+
glob: "**/prisma.config.ts"
|
|
164
|
+
contains:
|
|
165
|
+
- "sourceFormat"
|
|
166
|
+
- id: sugar-output-path-key-renamed-to-output
|
|
167
|
+
summary: |
|
|
168
|
+
The target façades' `defineConfig` option `outputPath` is renamed to
|
|
169
|
+
`output`. Semantics are unchanged (a directory; `contract.json` is
|
|
170
|
+
written inside it). Rename the key in `prisma.config.ts`.
|
|
171
|
+
detection:
|
|
172
|
+
glob: "**/prisma.config.ts"
|
|
173
|
+
contains:
|
|
174
|
+
- "outputPath"
|
|
175
|
+
- id: orm-count-only-mutation-terminals-renamed
|
|
176
|
+
summary: Replace `createCount(...)`, `updateCount(...)`, and `deleteCount()` with `createAndCount(...)`, `updateAndCount(...)`, and `deleteAndCount()` in ORM call sites; arguments, guards, behavior, and `Promise<number>` results are unchanged, and no compatibility aliases remain.
|
|
177
|
+
detection:
|
|
178
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
179
|
+
contains:
|
|
180
|
+
- ".createCount("
|
|
181
|
+
- ".updateCount("
|
|
182
|
+
- ".deleteCount("
|
|
183
|
+
anyMatch: true
|
|
184
|
+
- id: psl-format-error-class-removed
|
|
185
|
+
summary: |
|
|
186
|
+
The `PslFormatError` class is deleted from `@internal/psl-parser`. `format()`
|
|
187
|
+
on source with parse errors now throws a structured envelope with code
|
|
188
|
+
`PSL.PARSE_FAILED`; the diagnostics previously on `error.diagnostics` are at
|
|
189
|
+
`error.meta.diagnostics`. Replace `error instanceof PslFormatError` with
|
|
190
|
+
`isStructuredError(error) && error.code === 'PSL.PARSE_FAILED'`
|
|
191
|
+
(`isStructuredError` from `@internal/utils/structured-error`). The message
|
|
192
|
+
text is unchanged.
|
|
193
|
+
detection:
|
|
194
|
+
glob: "**/*.{ts,mts,cts}"
|
|
195
|
+
contains:
|
|
196
|
+
- "PslFormatError"
|
|
197
|
+
- id: scalar-type-descriptors-channel-removed
|
|
198
|
+
summary: |
|
|
199
|
+
The scalar-type descriptor channel is retired in favour of the unified authoring type
|
|
200
|
+
namespace. Projects with custom control-stack setups that import
|
|
201
|
+
`createPostgresScalarTypeDescriptors` / `createSqliteScalarTypeDescriptors`, or that read
|
|
202
|
+
`scalarTypeDescriptors` from a control stack or contract-source context, must migrate:
|
|
203
|
+
those exports are deleted, and scalar types are now zero-arg type-constructor
|
|
204
|
+
contributions in the component's `authoring.type` namespace — e.g.
|
|
205
|
+
`String: { kind: 'typeConstructor', output: { codecId: 'pg/text@1', nativeType: 'text' } }`.
|
|
206
|
+
Read the scalar type names via `stack.scalarTypes`, or the full name ->
|
|
207
|
+
`{ codecId, nativeType }` map via `collectScalarTypeConstructors(stack.authoringContributions.type)`
|
|
208
|
+
from `@internal/framework-components/authoring`. Standard target setups
|
|
209
|
+
(`@internal/postgres`, `@internal/sqlite`) supply the contributions themselves.
|
|
210
|
+
detection:
|
|
211
|
+
glob: "**/*.{ts,mts,cts}"
|
|
212
|
+
contains:
|
|
213
|
+
- "createPostgresScalarTypeDescriptors"
|
|
214
|
+
- "createSqliteScalarTypeDescriptors"
|
|
215
|
+
- "scalarTypeDescriptors"
|
|
216
|
+
anyMatch: true
|
|
217
|
+
- id: postgres-native-types-move-to-type-position
|
|
218
|
+
summary: |
|
|
219
|
+
PostgreSQL native storage types are authored directly in PSL type position, and the legacy `@db.*` attribute channel is removed. Rewrite `BaseType @db.Type` as `Type` and `BaseType @db.Type(args)` as `Type(args)` in both `types {}` aliases and model fields, then re-run `prisma-next contract emit`. Any remaining `@db.X(args)` fails with `@db.X(args) is no longer supported; use X(args) in type position`, preserving the constructor name and arguments in the suggested replacement. The supported translations are `@db.Char` → `Char`, `@db.VarChar` → `VarChar`, `@db.Numeric` → `Numeric`, `@db.Uuid` → `Uuid`, `@db.Inet` → `Inet`, `@db.SmallInt` → `SmallInt`, `@db.Real` → `Real`, `@db.Timestamp` → `Timestamp`, `@db.Timestamptz` → `Timestamptz`, `@db.Date` → `Date`, `@db.Time` → `Time`, and `@db.Timetz` → `Timetz`; preserve constructor arguments. Rewrite the old native-json spelling `Json @db.Json` as bare `Json`. This source migration preserves native types and supplied type parameters. It also preserves codec ids except for `@db.Date` → `Date`, which rebinds `pg/timestamptz@1` to `pg/date@1`, changes the contract storage hash, and requires re-emission plus re-signing; see the `postgres-date-rebound-to-pg-date` entry below. Separately, apply the `postgres-json-rebound-to-native-json` entry below to old bare `Json` fields that meant jsonb storage.
|
|
220
|
+
detection:
|
|
221
|
+
glob: "**/*.prisma"
|
|
222
|
+
contains:
|
|
223
|
+
- "@db."
|
|
224
|
+
anyMatch: true
|
|
225
|
+
- id: postgres-json-rebound-to-native-json
|
|
226
|
+
summary: |
|
|
227
|
+
On the postgres target the PSL `Json` scalar re-binds from `pg/jsonb@1` / `jsonb` to
|
|
228
|
+
`pg/json@1` / `json`; a new bare `Jsonb` scalar carries `pg/jsonb@1` / `jsonb`. Postgres
|
|
229
|
+
schemas that use `Json` and mean jsonb storage (which every pre-0.16 `Json` field did)
|
|
230
|
+
must switch those fields — and `types {}` aliases — to `Jsonb`, then re-run
|
|
231
|
+
`prisma-next contract emit`; with `Jsonb` the emitted `contract.json` is byte-identical
|
|
232
|
+
to the pre-0.16 output. A field left as `Json` now emits a native `json` column and a
|
|
233
|
+
new storage hash, which against an existing jsonb database is a schema change. The
|
|
234
|
+
removed `@db.Json` spelling must be rewritten from `Json @db.Json` to bare `Json`; any remaining use fails with migration guidance to use `Json` in type position. SQLite and Mongo `Json` bindings are untouched. The TS builder surface (`field.json()`, `jsonbColumn`) is unchanged and stays jsonb.
|
|
235
|
+
detection:
|
|
236
|
+
glob: "**/*.prisma"
|
|
237
|
+
contains:
|
|
238
|
+
- "Json"
|
|
239
|
+
anyMatch: true
|
|
240
|
+
- id: default-generators-no-longer-set-storage
|
|
241
|
+
summary: |
|
|
242
|
+
`@default(<generator>)` no longer influences a column's storage — the type position is
|
|
243
|
+
the only storage decider. Pre-0.16, a generator default on a bare `String` field re-picked
|
|
244
|
+
the column's storage to a sized char: `String @default(uuid())` / `@default(uuid(7))`
|
|
245
|
+
emitted `sql/char@1` / `character(36)`, `@default(cuid(2))` `character(24)`,
|
|
246
|
+
`@default(nanoid())` `character(21)` (or `character(<size>)` for `nanoid(<size>)`), and
|
|
247
|
+
`@default(ulid())` `character(26)`. From 0.16 such fields emit the target's `String`
|
|
248
|
+
storage (postgres: `pg/text@1` / `text`) with the same execution-time generator, so a
|
|
249
|
+
re-emit produces a new storage hash — against an existing database created with the char
|
|
250
|
+
storage this is a schema change. To keep the prior storage byte-identical, name it in the
|
|
251
|
+
type position: `Char(36) @default(uuid())`, `Char(24) @default(cuid(2))`,
|
|
252
|
+
`Char(21) @default(nanoid())` (or `Char(<size>)` for a sized nanoid), `Char(26)
|
|
253
|
+
@default(ulid())` — or adopt native `Uuid` for `uuid()` if a `uuid`-typed column is
|
|
254
|
+
preferred (that is a schema change too). Then re-run `prisma-next contract emit` and, if
|
|
255
|
+
you accepted a storage change, plan/apply the matching migration. Generator applicability
|
|
256
|
+
validation is unchanged (`uuid()` on `Int` still fails with
|
|
257
|
+
`PSL_INVALID_DEFAULT_APPLICABILITY`), and the TS builder presets
|
|
258
|
+
(`field.id.uuidv4String()`, `field.generated(uuidv4())`, …) are untouched — they bundle
|
|
259
|
+
their `char(N)` storage explicitly.
|
|
260
|
+
detection:
|
|
261
|
+
glob: "**/*.prisma"
|
|
262
|
+
contains:
|
|
263
|
+
- "@default(uuid("
|
|
264
|
+
- "@default(cuid("
|
|
265
|
+
- "@default(nanoid("
|
|
266
|
+
- "@default(ulid("
|
|
267
|
+
anyMatch: true
|
|
268
|
+
- id: postgres-date-rebound-to-pg-date
|
|
269
|
+
summary: |
|
|
270
|
+
On the postgres target, the bare `Date` type constructor re-binds from `pg/timestamptz@1` to the dedicated `pg/date@1` codec. Rewrite the removed `DateTime @db.Date` spelling as `Date`; leaving it unchanged now fails with migration guidance to use `Date` in type position. The stored native type is unchanged (`date`), so no schema migration is needed, but a re-emit changes the column's codec ref and therefore the contract's storage hash: run `prisma-next contract emit`, then re-sign any signed database against the regenerated contract (`prisma-next db sign`) — verify reports a hash mismatch until you do.
|
|
271
|
+
Contracts emitted before the upgrade keep working (`pg/timestamptz@1` still exists).
|
|
272
|
+
Runtime behavior changes on re-emit: date columns decode as a `Date` at UTC midnight
|
|
273
|
+
(previously the driver's local-midnight `Date` passed through, so the instant depended
|
|
274
|
+
on the process timezone — code reading local getters near midnight in negative-UTC-offset
|
|
275
|
+
zones saw the neighboring day), encode formats `YYYY-MM-DD` from UTC getters, and
|
|
276
|
+
relation `.include()` over a date column now decodes instead of failing with
|
|
277
|
+
`RUNTIME.DECODE_FAILED`. Update tests or application code that pinned the old
|
|
278
|
+
local-midnight instants to expect `new Date(Date.UTC(y, m, d))`.
|
|
279
|
+
detection:
|
|
280
|
+
glob: "**/*.prisma"
|
|
281
|
+
regex:
|
|
282
|
+
- '@db\.Date'
|
|
283
|
+
- '\sDate(\s|\?|\[|$)'
|
|
284
|
+
anyMatch: true
|
|
285
|
+
- id: sql-escape-error-class-removed
|
|
286
|
+
summary: |
|
|
287
|
+
The `SqlEscapeError` class is deleted from `@internal/target-postgres` and
|
|
288
|
+
`@internal/target-sqlite` (including its re-export from the postgres/sqlite
|
|
289
|
+
adapter `control` entrypoints). Identifier/literal escaping failures now throw a
|
|
290
|
+
structured envelope with code `CONTRACT.IDENTIFIER_INVALID`. Replace
|
|
291
|
+
`error instanceof SqlEscapeError` with
|
|
292
|
+
`isStructuredError(error) && error.code === 'CONTRACT.IDENTIFIER_INVALID'`
|
|
293
|
+
(`isStructuredError` from `@internal/utils/structured-error`). Message text is
|
|
294
|
+
unchanged.
|
|
295
|
+
detection:
|
|
296
|
+
glob: "**/*.{ts,mts,cts}"
|
|
297
|
+
contains:
|
|
298
|
+
- "SqlEscapeError"
|
|
299
|
+
- id: supabase-error-classes-removed
|
|
300
|
+
summary: |
|
|
301
|
+
The `SupabaseConfigError` and `InvalidJwtError` classes are deleted from
|
|
302
|
+
`@internal/extension-supabase/runtime`. The same failures now throw
|
|
303
|
+
structured envelopes with codes `SUPABASE.CONFIG_INVALID` and
|
|
304
|
+
`SUPABASE.JWT_INVALID`. Replace `error instanceof SupabaseConfigError` with
|
|
305
|
+
`isStructuredError(error) && error.code === 'SUPABASE.CONFIG_INVALID'` and
|
|
306
|
+
`error instanceof InvalidJwtError` with
|
|
307
|
+
`isStructuredError(error) && error.code === 'SUPABASE.JWT_INVALID'`
|
|
308
|
+
(`isStructuredError` from `@internal/utils/structured-error`). Message
|
|
309
|
+
text is unchanged.
|
|
310
|
+
detection:
|
|
311
|
+
glob: "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"
|
|
312
|
+
contains:
|
|
313
|
+
- "SupabaseConfigError"
|
|
314
|
+
- "InvalidJwtError"
|
|
315
|
+
anyMatch: true
|
|
316
|
+
|
|
317
|
+
- id: indexes-are-name-identified
|
|
318
|
+
summary: |
|
|
319
|
+
Secondary indexes are name-identified from 0.17. Every index entry in the emitted
|
|
320
|
+
`contract.json` / `contract.d.ts` now carries `name` (the full physical name) and
|
|
321
|
+
`unique`, plus `prefix` when the name is toolchain-owned; `columns` became optional
|
|
322
|
+
(an index carries either `columns` or an opaque `expression` — never both). Contracts
|
|
323
|
+
emitted by 0.16 fail validation on load (the error message contains "indexes[0].name
|
|
324
|
+
must be a string (was missing)"), and storage hashes move for every contract that
|
|
325
|
+
declares indexes — re-emit with `prisma-next contract emit`. Physical names change for wire-named indexes: an unnamed
|
|
326
|
+
PSL `@@index([a, b])` / TS `constraints.index([a, b])` and every FK-backing index now
|
|
327
|
+
CREATE as `<default-prefix>_<8hex>` content-hash wire names (e.g.
|
|
328
|
+
`user_email_idx_46df9cad`), and a TS `constraints.index([...], { name: "x" })` name is
|
|
329
|
+
now a wire prefix — the physical name becomes `x_<8hex>`. PSL
|
|
330
|
+
`@@index([...], map: "x")` is now an exact physical name whose identity is verified
|
|
331
|
+
against the live catalog. Existing databases converge without rebuilds: after
|
|
332
|
+
re-emitting, the first plan that allows the `widening` class (`db update`, or
|
|
333
|
+
`migration plan` + `migrate`) is `ALTER INDEX … RENAME TO` ops only — renames happen
|
|
334
|
+
only when a widening plan runs FIRST. Under an additive-only policy the rename pairing
|
|
335
|
+
does not run: the new wire-named index is created beside the old one, and once both
|
|
336
|
+
exist a later plan can no longer pair them — the old index is removed only by a
|
|
337
|
+
destructive-allowed plan dropping it. Update any code or tests that hard-code the old
|
|
338
|
+
physical index names.
|
|
339
|
+
detection:
|
|
340
|
+
glob: "**/*.{prisma,ts,json}"
|
|
341
|
+
contains:
|
|
342
|
+
- "@@index"
|
|
343
|
+
- "constraints.index"
|
|
344
|
+
- '"indexes":'
|
|
345
|
+
anyMatch: true
|
|
346
|
+
- id: rls-policy-migration-literal-carries-the-naming-union
|
|
347
|
+
summary: |
|
|
348
|
+
A generated migration's `this.createRlsPolicy({ policy: … })` literal spells the policy's
|
|
349
|
+
name differently from 0.17. The flat `name` / `prefix` pair is replaced by a single
|
|
350
|
+
`naming` field carrying one of two shapes: `{ kind: "exact", name: "<physical name>" }`
|
|
351
|
+
for a policy whose name the author owns, or
|
|
352
|
+
`{ kind: "wire", prefix: "<prefix>", hash: "<8hex>" }` for a toolchain-named one
|
|
353
|
+
(0.16's `name: "<prefix>_<8hex>"` plus `prefix: "<prefix>"`). Every other key is
|
|
354
|
+
unchanged. A migration file emitted by 0.16 that calls `createRlsPolicy` therefore stops
|
|
355
|
+
compiling — TypeScript reports `Property 'naming' is missing`. Regenerate the affected
|
|
356
|
+
migrations with `prisma-next migration plan`, or edit the literal by hand: the shape is
|
|
357
|
+
mechanical, and the migration's identity (`migrationHash`, the SQL it executes) does not
|
|
358
|
+
depend on the literal's spelling.
|
|
359
|
+
detection:
|
|
360
|
+
glob: "**/migrations/**/migration.ts"
|
|
361
|
+
contains:
|
|
362
|
+
- "createRlsPolicy"
|
|
363
|
+
- id: framework-error-classes-removed
|
|
364
|
+
summary: |
|
|
365
|
+
Three exported framework error classes are deleted: `ConfigFileNotFoundError`
|
|
366
|
+
(from `@internal/config-loader`), `ConfigValidationError` (from
|
|
367
|
+
`@internal/config/config-validation`), and `DomainNamespaceResolutionError`
|
|
368
|
+
(from `@internal/contract/types`). The same failures now throw structured
|
|
369
|
+
envelopes with codes `CONFIG.FILE_NOT_FOUND`, `CONFIG.VALIDATION_FAILED`, and
|
|
370
|
+
`CONTRACT.NAMESPACE_INVALID` respectively. Replace each
|
|
371
|
+
`error instanceof <Class>` with
|
|
372
|
+
`isStructuredError(error) && error.code === '<CODE>'`
|
|
373
|
+
(`isStructuredError` from `@internal/utils/structured-error`). Message
|
|
374
|
+
text is unchanged.
|
|
375
|
+
detection:
|
|
376
|
+
glob: "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"
|
|
377
|
+
contains:
|
|
378
|
+
- "ConfigFileNotFoundError"
|
|
379
|
+
- "ConfigValidationError"
|
|
380
|
+
- "DomainNamespaceResolutionError"
|
|
381
|
+
anyMatch: true
|
|
382
|
+
- id: pg-int8-application-values-are-bigint
|
|
383
|
+
summary: |
|
|
384
|
+
`pg/int8@1` carries `bigint` application values where it carried `number`. A JS `number`
|
|
385
|
+
cannot represent the whole signed 64-bit range, so any value past 2^53 was already being
|
|
386
|
+
silently rounded. Every read of an `int8` column now yields a `bigint`, and every value
|
|
387
|
+
compared against one must be a `bigint` literal. `count()` is the widest instance: it
|
|
388
|
+
resolves to `pg/int8@1`, so a counted column's row type is `bigint` and a `having`
|
|
389
|
+
comparison reads `fns.gt(fns.count(), 5n)`. Update row-type annotations, comparison
|
|
390
|
+
literals, and any arithmetic that mixes a counted value with a `number` — TypeScript will
|
|
391
|
+
not implicitly convert between the two, so `pnpm typecheck` locates every site.
|
|
392
|
+
A contract's `int8` literal defaults are also emitted as decimal strings rather than JSON
|
|
393
|
+
numbers; re-emit to pick that up.
|
|
394
|
+
detection:
|
|
395
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
396
|
+
contains:
|
|
397
|
+
- "fns.count("
|
|
398
|
+
- "pg/int8@1"
|
|
399
|
+
- "int8Column"
|
|
400
|
+
anyMatch: true
|
|
401
|
+
- id: pg-interval-values-are-structured-durations
|
|
402
|
+
summary: |
|
|
403
|
+
Reading a `pg/interval@1` column returns `{ months, days, micros }` — the three fields
|
|
404
|
+
PostgreSQL stores — where it returned a `JSON.stringify` of the driver's component object
|
|
405
|
+
such as `{"days":1}`. `months` and `days` are `number`; `micros` is `bigint`, because
|
|
406
|
+
PostgreSQL stores it as a 64-bit integer. Writing takes the same object. Replace any
|
|
407
|
+
parsing of the old string with field access, and replace interval literals with the object
|
|
408
|
+
(`{ months: 0, days: 1, micros: 0n }` for one day). The three fields stay independent
|
|
409
|
+
because a month has no fixed length: one month and thirty days are different values and
|
|
410
|
+
neither converts to the other. Serialized form is unchanged in kind but not in spelling —
|
|
411
|
+
a contract holds the ISO-8601 duration string (`P1M`, `P1Y2M3DT4H5M6S`, `PT0S`), so
|
|
412
|
+
re-emit; `micros` past microsecond resolution rounds as PostgreSQL rounds.
|
|
413
|
+
detection:
|
|
414
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
415
|
+
contains:
|
|
416
|
+
- "pg/interval@1"
|
|
417
|
+
- "intervalColumn"
|
|
418
|
+
anyMatch: true
|
|
419
|
+
- id: codec-json-forms-are-canonical
|
|
420
|
+
summary: |
|
|
421
|
+
Several codecs' JSON representation changed so a value survives the round trip through a
|
|
422
|
+
contract. `pg/numeric@1` and `sqlite/bigint@1` are decimal text where they were JSON
|
|
423
|
+
numbers — `9007199254740993` reached JSON as `…992` before, and arbitrary-precision
|
|
424
|
+
decimals lost their tail. `pg/bytea@1` is base64 where it was PostgreSQL's `\x`-prefixed
|
|
425
|
+
hex. `sqlite/blob@1` is uppercase hexadecimal where it was base64. `sqlite/bigint@1`
|
|
426
|
+
additionally accepts values it previously refused outright: half of SQLite's INTEGER range
|
|
427
|
+
had no JSON representation at all.
|
|
428
|
+
This now reaches **reads**, not only contract literals. A query that returns JSON — an
|
|
429
|
+
`.include()`'s nested rows, an aggregated child row set — projects each column through its
|
|
430
|
+
codec, so these codecs' values arrive in the forms above where they previously arrived in
|
|
431
|
+
whatever the database's own JSON conversion produced. Nine codecs project non-identically:
|
|
432
|
+
`pg/numeric@1`, `pg/int8@1`, `pg/bytea@1`, `pg/interval@1`, `pg/timestamptz@1`,
|
|
433
|
+
`pg/vector@1`, `sqlite/bigint@1`, `sqlite/blob@1` and `sqlite/json@1`. If you read such a
|
|
434
|
+
column out of an include and parse or compare its raw JSON yourself — rather than letting
|
|
435
|
+
the ORM decode it — update that code to the new form.
|
|
436
|
+
Run `prisma-next contract emit` to regenerate
|
|
437
|
+
`contract.json` / `contract.d.ts`; any literal default on one of these codecs changes
|
|
438
|
+
spelling, and with it the `storageHash`. Code that reads such a default out of a contract,
|
|
439
|
+
or that hand-writes one, must use the new form.
|
|
440
|
+
detection:
|
|
441
|
+
glob: "**/*.{ts,tsx,mts,cts,json,d.ts}"
|
|
442
|
+
contains:
|
|
443
|
+
- "pg/numeric@1"
|
|
444
|
+
- "pg/bytea@1"
|
|
445
|
+
- "pg/int8@1"
|
|
446
|
+
- "pg/interval@1"
|
|
447
|
+
- "pg/timestamptz@1"
|
|
448
|
+
- "pg/vector@1"
|
|
449
|
+
- "sqlite/bigint@1"
|
|
450
|
+
- "sqlite/blob@1"
|
|
451
|
+
- "sqlite/json@1"
|
|
452
|
+
anyMatch: true
|
|
453
|
+
- id: float-json-requires-extra-float-digits-at-least-one
|
|
454
|
+
summary: |
|
|
455
|
+
The canonical JSON of `pg/float4@1`, `pg/float8@1`, `pg/float@1` and `pg/vector@1` holds
|
|
456
|
+
only where the PostgreSQL session's `extra_float_digits` is 1 or above. That is the default
|
|
457
|
+
from PostgreSQL 12 onward, so most deployments already satisfy it — but a connection that
|
|
458
|
+
sets the GUC to 0 or below reverts to a fixed digit count and truncates: `1/3` reads back
|
|
459
|
+
as `0.333333333333333` rather than `0.3333333333333333`, and the value no longer
|
|
460
|
+
round-trips. Check any connection string, pool `options`, server config or proxy that sets
|
|
461
|
+
`extra_float_digits` and remove settings of 0 or below.
|
|
462
|
+
detection:
|
|
463
|
+
glob: "**/*.{ts,tsx,mts,cts,js,mjs,cjs,json,toml,yaml,yml,env}"
|
|
464
|
+
contains:
|
|
465
|
+
- "extra_float_digits"
|
|
466
|
+
anyMatch: true
|
|
467
|
+
- id: sqlite-real-rejects-non-finite-values
|
|
468
|
+
summary: |
|
|
469
|
+
`sqlite/real@1` rejects infinities and `NaN` on both the encode and decode sides. JSON has
|
|
470
|
+
no spelling for either, and SQLite renders an infinity as `9.0e+999`, which reads back as
|
|
471
|
+
`Infinity` rather than failing — so a non-finite value used to pass through and corrupt the
|
|
472
|
+
value silently. Guard any computation that can produce a non-finite float before writing it
|
|
473
|
+
to a `REAL` column, or store it in a column whose codec admits it.
|
|
474
|
+
detection:
|
|
475
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
476
|
+
contains:
|
|
477
|
+
- "sqlite/real@1"
|
|
478
|
+
- "realColumn"
|
|
479
|
+
anyMatch: true
|
|
480
|
+
- id: pg-timestamptz-json-is-utc-iso
|
|
481
|
+
summary: |
|
|
482
|
+
`pg/timestamptz@1`'s canonical JSON is a UTC ISO-8601 timestamp with an explicit `+00:00`
|
|
483
|
+
offset, constructed by the projection rather than inherited from the session. The form
|
|
484
|
+
previously followed the connection's `DateStyle` and `TimeZone`, so the same stored instant
|
|
485
|
+
read back differently on two connections, and under a non-ISO `DateStyle` could fail to
|
|
486
|
+
parse at all. Nothing to change if you decode through the ORM. If you read a timestamptz out
|
|
487
|
+
of database-produced JSON yourself it is now always `YYYY-MM-DDTHH:MM:SS.mmm+00:00`: drop any
|
|
488
|
+
session-dependent parsing, and drop any `SET DateStyle` / `SET TimeZone` you added to
|
|
489
|
+
stabilise it.
|
|
490
|
+
detection:
|
|
491
|
+
glob: "**/*.{ts,tsx,mts,cts,sql}"
|
|
492
|
+
contains:
|
|
493
|
+
- "pg/timestamptz@1"
|
|
494
|
+
- "timestamptzColumn"
|
|
495
|
+
- "DateStyle"
|
|
496
|
+
anyMatch: true
|
|
497
|
+
- id: sqlite-json-documents-survive-nesting
|
|
498
|
+
summary: |
|
|
499
|
+
A `sqlite/json@1` column read through a nested `.include()` arrives as a parsed document
|
|
500
|
+
where it previously arrived as a string containing JSON. SQLite carries "this text is JSON"
|
|
501
|
+
as a subtype on the value, and that subtype does not survive a derived table — which every
|
|
502
|
+
include's child row set passes through — so a document came back double-encoded. The
|
|
503
|
+
projection retags it at the boundary that consumes it. A `sqlite/text@1` column whose
|
|
504
|
+
characters happen to look like JSON is unaffected and still arrives as a string: the retag
|
|
505
|
+
follows the column's codec, not its content. Remove any `JSON.parse` you added to compensate
|
|
506
|
+
for the double encoding.
|
|
507
|
+
detection:
|
|
508
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
509
|
+
contains:
|
|
510
|
+
- "sqlite/json@1"
|
|
511
|
+
- "jsonColumn"
|
|
512
|
+
anyMatch: true
|
|
513
|
+
- id: sqlite-blob-null-is-distinct-from-empty
|
|
514
|
+
summary: |
|
|
515
|
+
A `NULL` `sqlite/blob@1` column read through database-produced JSON is `null`, where it
|
|
516
|
+
previously became an empty `Uint8Array`. SQLite's `hex(NULL)` is the empty string, which is
|
|
517
|
+
also the hex of a zero-length blob, so absence and emptiness were the same value and nothing
|
|
518
|
+
raised. If your code distinguishes "no blob" from "empty blob" — and especially if it worked
|
|
519
|
+
around the old behaviour by treating a zero-length blob as absent — that check now needs to
|
|
520
|
+
test for `null`.
|
|
521
|
+
detection:
|
|
522
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
523
|
+
contains:
|
|
524
|
+
- "sqlite/blob@1"
|
|
525
|
+
- "blobColumn"
|
|
526
|
+
anyMatch: true
|
|
527
|
+
- id: sql-float-rejects-non-finite-values
|
|
528
|
+
summary: |
|
|
529
|
+
`sql/float@1` rejects infinities and `NaN` in both JSON directions, matching
|
|
530
|
+
`sqlite/real@1`. Its `decodeJson` previously performed no check at all, and a database can
|
|
531
|
+
hold a non-finite float and spells it as a *string* in JSON — PostgreSQL emits `"NaN"` — so
|
|
532
|
+
the codec handed back a string typed as `number`, silently. Guard any computation that can
|
|
533
|
+
produce a non-finite float before writing it to a `sql/float@1` column, or use
|
|
534
|
+
`pg/numeric@1`, whose application value is text and which admits all three.
|
|
535
|
+
detection:
|
|
536
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
537
|
+
contains:
|
|
538
|
+
- "sql/float@1"
|
|
539
|
+
- "sqlFloatColumn"
|
|
540
|
+
anyMatch: true
|
|
541
|
+
- id: explicit-codec-refs-need-readable-type-params
|
|
542
|
+
summary: |
|
|
543
|
+
A codec ref supplied explicitly — `sql.value(v, { codec: { codecId: 'pg/enum@1' } })` and
|
|
544
|
+
the other surfaces that take a bare `codecId` — must carry `typeParams` the codec's schema
|
|
545
|
+
accepts when that codec is parameterized. For `pg/enum@1` that means
|
|
546
|
+
`typeParams: { typeName: '<enum type>' }`. Such a ref never passes contract validation, so
|
|
547
|
+
the omission used to surface as a static `text` native type — correct only because
|
|
548
|
+
PostgreSQL implicitly casts text to an enum, and wrong for any parameterized codec whose
|
|
549
|
+
type is not text-compatible. It now fails at lowering instead. The failure currently
|
|
550
|
+
surfaces as a params-validation error rather than a message naming the surface that produced
|
|
551
|
+
it; that diagnostic is tracked as
|
|
552
|
+
[TML-3114](https://linear.app/prisma-company/issue/TML-3114). Add the `typeParams` your
|
|
553
|
+
column declares, or drop the explicit codec and let the column's own codec resolve.
|
|
554
|
+
detection:
|
|
555
|
+
glob: "**/*.{ts,tsx,mts,cts}"
|
|
556
|
+
contains:
|
|
557
|
+
- "codec: { codecId"
|
|
558
|
+
- "pg/enum@1"
|
|
559
|
+
anyMatch: true
|
|
560
|
+
- id: sql-timestamp-json-is-utc-not-local
|
|
561
|
+
summary: |
|
|
562
|
+
**`sql/timestamp@1` now reads a zone-less timestamp as UTC where it read it in the running
|
|
563
|
+
process's local zone.** This is an interpretation change, not a formatting one, and it is the
|
|
564
|
+
dangerous half: `new Date('2026-01-02T03:04:05')` resolves in the local zone, so the same
|
|
565
|
+
stored value used to decode to a different instant on a machine in `Europe/Berlin` than on one
|
|
566
|
+
in `UTC` — shifted by the offset, silently. It now resolves as UTC on every machine.
|
|
567
|
+
If you compensated for the old shift anywhere downstream — adding the offset back, forcing
|
|
568
|
+
`TZ=UTC` on the process, normalising after decode — **remove that compensation**, or it now
|
|
569
|
+
double-corrects and the instant is wrong by twice the offset. Nothing raises: the value is
|
|
570
|
+
plausible, just wrong. If you ran with `TZ=UTC` there was no shift to compensate for and
|
|
571
|
+
nothing to change.
|
|
572
|
+
The JSON form changes with it: `encodeJson` emits `2026-01-02T03:04:05.678` where it emitted
|
|
573
|
+
`2026-01-02T03:04:05.678Z`. A `timestamp` carries no zone, so the trailing `Z` claimed one it
|
|
574
|
+
did not have; `decodeJson` now rejects an offset-bearing string outright rather than
|
|
575
|
+
reinterpreting it, since this codec cannot reproduce an offset it was handed. Update any
|
|
576
|
+
hand-written JSON, fixture or comparison that spells the old form.
|
|
577
|
+
`pg/timestamp@1` is unaffected — it already read as UTC and already emitted the zone-less
|
|
578
|
+
form.
|
|
579
|
+
detection:
|
|
580
|
+
glob: "**/*.{ts,tsx,mts,cts,json}"
|
|
581
|
+
contains:
|
|
582
|
+
- "sql/timestamp@1"
|
|
583
|
+
- "sqlTimestampColumn"
|
|
584
|
+
anyMatch: true
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
# 0.16 → 0.17 — User upgrade instructions
|
|
588
|
+
|
|
589
|
+
## `strip-sha256-hash-prefixes`
|
|
590
|
+
|
|
591
|
+
Starting at the 0.17 release, every content hash Prisma Next mints or accepts is bare lowercase hex — the `sha256:` prefix is removed across the board: emitted `contract.json` / `contract.d.ts` (including the `StorageHashBase<'…'>` / `ProfileHashBase<'…'>` branded type literals), migration manifests, refs, CLI output, and the marker/ledger bookkeeping tables in your database. The prefix carried no information (the algorithm never varied per hash), and the hash **value** — not an in-band tag — signals a format change. Loaders and validators now reject the legacy prefixed form outright.
|
|
592
|
+
|
|
593
|
+
Two distinct effects on your checked-in artefacts:
|
|
594
|
+
|
|
595
|
+
- **Contract hashes keep their value.** `storageHash` / `profileHash` are computed over contract content, which never embedded its own hash — only the textual prefix drops.
|
|
596
|
+
- **Migration hash values change.** `migrationHash` is computed over the manifest bytes, which embed the `from` / `to` contract-hash strings; with those now bare, every recomputed `migrationHash` differs from the stored one.
|
|
597
|
+
|
|
598
|
+
### Migrate checked-in `migrations/` trees — before the layout migrator
|
|
599
|
+
|
|
600
|
+
Run the colocated codemod from your project root, **before** `scripts/migrate-migrations-layout.mjs` (the snapshot-layout entries below) — the 0.17 layout migrator accepts only bare-hex trees:
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
pnpm exec tsx ./strip-sha256-hash-prefixes.ts
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
For every on-disk migration package (a `migration.json` with a sibling `ops.json`) it strips the prefix from the manifest's `from` / `to`, from hash literals inside `ops.json`, in pre-store sibling contract snapshots (`*-contract.json`, `*.d.ts`, `migration.ts`), and in content-addressed store entries (`migrations/snapshots/<hex>/contract.json` + `contract.d.ts` — the directory name is the hash's hex and does not change), recomputes `migrationHash` over the bare-hex content, and rewrites `refs/*.json` — repointing refs that held old migration hashes at the recomputed ones, and mapping the empty-tree sentinel `sha256:empty` to `empty`. The edit is format-preserving (only hash literals and the recomputed hash value change) and idempotent: re-running over an already-bare tree makes no further changes.
|
|
607
|
+
|
|
608
|
+
Use `--check` for a dry run that lists files still needing the fix and exits non-zero if any remain:
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
pnpm exec tsx ./strip-sha256-hash-prefixes.ts --check
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Re-emit live contract artefacts
|
|
615
|
+
|
|
616
|
+
Regenerate your emitted artefacts so `contract.json` / `contract.d.ts` pick up the bare-hex form:
|
|
617
|
+
|
|
618
|
+
```bash
|
|
619
|
+
pnpm emit
|
|
620
|
+
# (runs `prisma-next contract emit` under the hood)
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
The regenerated files differ only in hash representation — the hash values themselves are unchanged.
|
|
624
|
+
|
|
625
|
+
### Update hash literals your own code carries
|
|
626
|
+
|
|
627
|
+
If your application or tests hard-code hash strings (asserting a `migrationHash`, comparing a `storageHash`, matching CLI output), drop the `sha256:` prefix — and for migration hashes, read the new value from the regenerated manifest, since the value itself changed.
|
|
628
|
+
|
|
629
|
+
### Database marker/ledger
|
|
630
|
+
|
|
631
|
+
There is no compatibility shim: a database whose marker/ledger rows still hold prefixed values reports a hash mismatch on `prisma-next db verify`. Re-sign the database against your regenerated contract:
|
|
632
|
+
|
|
633
|
+
```bash
|
|
634
|
+
prisma-next db sign
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Validation
|
|
638
|
+
|
|
639
|
+
After the codemod and re-emit, run `pnpm typecheck && pnpm test` (or your application's equivalent), and exercise any command that loads your migrations (deploy or migration-status step) — the loader recomputes and verifies each manifest's `migrationHash` on read, so a stale or still-prefixed manifest fails immediately. `git grep -n "sha256:"` over your project should return no hits in committed artefacts.
|
|
640
|
+
|
|
641
|
+
Also in this release, the ORM client's internal `throw new Error(...)` sites
|
|
642
|
+
were converted to a structured-error scheme (`ORM.*` codes via `structuredError`,
|
|
643
|
+
or `InternalError` for invariants). These are internal throw sites: the errors
|
|
644
|
+
are still `Error` instances with unchanged message text, so application code
|
|
645
|
+
that catches them by message or by `instanceof Error` is unaffected. No action
|
|
646
|
+
required beyond the migration contract-snapshot layout change above.
|
|
647
|
+
|
|
648
|
+
## `indexes-are-name-identified`
|
|
649
|
+
|
|
650
|
+
Secondary indexes are **name-identified**: the contract stores every index's full physical name, and schema verification and migration planning pair indexes by that name instead of by column tuple.
|
|
651
|
+
|
|
652
|
+
### What changed in the emitted contract
|
|
653
|
+
|
|
654
|
+
Each entry in a table's `indexes` array in `contract.json` / `contract.d.ts` now always carries:
|
|
655
|
+
|
|
656
|
+
- `name` — the full physical name of the index in the database.
|
|
657
|
+
- `unique` — always present (`false` for everything authored today).
|
|
658
|
+
- `prefix` — present when the name is toolchain-owned: the physical name is then `<prefix>_<8hex>`, where the suffix is a content hash of the index definition.
|
|
659
|
+
- `columns` — now optional; an index carries either `columns` or an opaque `expression` string, never both.
|
|
660
|
+
|
|
661
|
+
Newly available in 0.17 (additive — no migration needed): `contract infer` captures the full index matrix (expression, partial `where:`, unique non-constraint, `type:`/`options:`) and the RLS surface (`@@rls`, every policy as a `policy_<operation>` block with `@@map` and verbatim reprinted bodies, `permissive = false` for RESTRICTIVE rows), so `infer → emit → db verify` is zero-issue on databases carrying those objects. Re-running `contract infer` therefore rewrites `contract.prisma` with more entries than 0.16 emitted; an index whose live name is wire-shaped (`<prefix>_<8hex>`, created by this toolchain) and whose hash recomputes now re-infers as wire-named `name:` instead of exact `map:` — both spellings verify clean, the wire-named one keeps renames first-class. `permissive` is an authorable policy-block property (default `true`; wire names for `permissive = true` policies are byte-unchanged). Contracts may also now carry two content-identical exact-named (`map:`) indexes under different names — legal twins a signed database can have. RLS policy blocks (`policy_select` etc.) accept `@@map("physical name")` to adopt an existing live policy under its exact name — no wire-name hash, drift detection byte-compares the body against Postgres's reprint (hand-authoring the text warns with `PN_EXACT_NAME_BODY_COMPARISON`), and replacing `@@map` with the plain head later converges via a single `ALTER POLICY … RENAME`. Also newly available: both authoring surfaces accept the full index parameter matrix. PSL `@@index` and TS `constraints.index` take `expression:` (instead of a fields list; requires `name:` or `map:`), `where:` (partial-index predicate), `unique:`, `type:`/`options:` (target-registered access method), and `name:` xor `map:`. Combining `map:` with a SQL body emits the `PN_EXACT_NAME_BODY_COMPARISON` warning at build time — drift detection byte-compares hand-authored text against Postgres's reprint, so prefer `name:` unless the text was captured by `contract infer`. SQLite contracts reject `expression:`/`where:` with `CONTRACT.ARGUMENT_INVALID` (the target does not support them).
|
|
662
|
+
|
|
663
|
+
A contract emitted by 0.16 fails validation when a 0.17 toolchain loads it — a `Contract structural validation failed: storage.namespaces.<ns> …` error whose message contains `indexes[0].name must be a string (was missing)` and `indexes[0].unique must be boolean (was missing)` — and the storage hash moves for every contract that declares indexes. Re-emit:
|
|
664
|
+
|
|
665
|
+
```bash
|
|
666
|
+
prisma-next contract emit
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### What changed about physical index names
|
|
670
|
+
|
|
671
|
+
| Authoring input | 0.16 physical name | 0.17 physical name |
|
|
672
|
+
| --- | --- | --- |
|
|
673
|
+
| PSL `@@index([a, b])` / TS `constraints.index([cols.a, cols.b])` (unnamed) | `<table>_<a>_<b>_idx` | `<table>_<a>_<b>_idx_<8hex>` (wire-named) |
|
|
674
|
+
| FK-backing index (derived from a relation) | `<table>_<col>_idx` | `<table>_<col>_idx_<8hex>` (wire-named) |
|
|
675
|
+
| TS `constraints.index([...], { name: "x" })` | `x` | `x_<8hex>` — the name is now a wire *prefix* |
|
|
676
|
+
| PSL `@@index([...], map: "x")` | `x` | `x` — an exact physical name, now verified against the live catalog |
|
|
677
|
+
|
|
678
|
+
The `<8hex>` suffix is a content hash over the index definition (element list, predicate, uniqueness, access method, options), so an unchanged definition always produces the same name.
|
|
679
|
+
|
|
680
|
+
### Converging an existing database
|
|
681
|
+
|
|
682
|
+
No index is rebuilt. After re-emitting the contract, the first plan that allows the `widening` operation class converges the live names with `ALTER INDEX … RENAME TO` ops only:
|
|
683
|
+
|
|
684
|
+
- `prisma-next db update` (its default policy includes widening), or
|
|
685
|
+
- `prisma-next migration plan --name converge-index-names` followed by `prisma-next migrate`.
|
|
686
|
+
|
|
687
|
+
Inspect the plan before applying — for a schema whose only drift is the index naming, it contains nothing but renames.
|
|
688
|
+
|
|
689
|
+
Under an **additive-only** policy (e.g. `db init`'s class set) the rename pairing is skipped: the plan creates the new wire-named index beside the old one. Once both indexes exist, a later widening plan has nothing left to pair — the new name is already present, and the rename op's own precheck requires its target name to be absent — so after the additive create the old index is removed **only** by a destructive-allowed plan dropping it. A rename happens only when a widening-allowed plan is the *first* convergence, before any create. This degradation is deliberate — an additive-only run never emits an op class it is not allowed to execute; if you want renames instead of create-then-drop, run the widening plan first.
|
|
690
|
+
|
|
691
|
+
### Hard-coded names
|
|
692
|
+
|
|
693
|
+
If application code, tests, or operational scripts hard-code physical index names (e.g. `user_email_idx`), read the new names from the regenerated `contract.json` — wire names now carry the hash suffix. PSL schemas that must keep a byte-exact legacy name can pin it with `@@index([...], map: "<exact name>")`.
|
|
694
|
+
|
|
695
|
+
## `rls-policy-migration-literal-carries-the-naming-union`
|
|
696
|
+
|
|
697
|
+
Generated migrations that create an RLS policy carry the policy as a literal. Where 0.16 spelled its name as two flat fields, 0.17 spells it as one `naming` field with two shapes:
|
|
698
|
+
|
|
699
|
+
```ts
|
|
700
|
+
// 0.16
|
|
701
|
+
this.createRlsPolicy({ schema: "public", table: "post", policy: {
|
|
702
|
+
name: "post_owner_a1b2c3d4",
|
|
703
|
+
prefix: "post_owner",
|
|
704
|
+
// …
|
|
705
|
+
} })
|
|
706
|
+
|
|
707
|
+
// 0.17
|
|
708
|
+
this.createRlsPolicy({ schema: "public", table: "post", policy: {
|
|
709
|
+
naming: { kind: "wire", prefix: "post_owner", hash: "a1b2c3d4" },
|
|
710
|
+
// …
|
|
711
|
+
} })
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
A policy whose name the author owns (adopted through `@@map`) carries `naming: { kind: "exact", name: "Tenant members can read" }` instead. Every other key of the literal is unchanged.
|
|
715
|
+
|
|
716
|
+
The two fields could disagree — a `prefix` that is not what `name` ends with was representable and had to be checked at runtime — while the union cannot be written wrong. A 0.16 migration file that calls `createRlsPolicy` stops compiling against 0.17 with `Property 'naming' is missing`. Either regenerate the migration (`prisma-next migration plan`) or rewrite the two fields as the one union field by hand; the migration's identity and the SQL it runs do not depend on the literal's spelling, so a hand edit needs no re-hashing.
|
|
717
|
+
|
|
718
|
+
## Incidental dependency and lint-config bumps
|
|
719
|
+
|
|
720
|
+
Routine dev-dependency bumps and biome `$schema` version alignment in `examples/` (dependabot `dev-deps` group, PR #1058) require no Prisma Next-specific upgrade action; review and test the affected examples as with any routine dependency update.
|
|
721
|
+
|
|
722
|
+
## Incidental dependency bumps in examples
|
|
723
|
+
|
|
724
|
+
Routine runtime dependency bumps in `examples/` (dependabot `runtime-deps` group, PR #1065) require no Prisma Next-specific upgrade action; review and test the affected examples as with any routine dependency update.
|
|
725
|
+
|
|
726
|
+
## Incidental runtime dependency bumps in examples (August 2026)
|
|
727
|
+
|
|
728
|
+
Routine runtime dependency bumps in `examples/` (dependabot `runtime-deps` group, PR #29872) require no Prisma Next-specific upgrade action; review and test the affected examples as with any routine dependency update.
|
|
729
|
+
|
|
730
|
+
## Incidental dev-dependency bumps in examples (August 2026)
|
|
731
|
+
|
|
732
|
+
Routine dev-dependency bumps in `examples/` (dependabot `dev-deps` group, PR #29877) require no Prisma Next-specific upgrade action; review and test the affected examples as with any routine dependency update.
|
|
733
|
+
|
|
734
|
+
## Incidental example dependency bumps (react-router 8)
|
|
735
|
+
|
|
736
|
+
The `react-router-demo` example moves its `react-router`, `@react-router/dev`, `@react-router/node`, and `@react-router/serve` dependencies from 7.x to 8.x. This is an example-local framework upgrade and requires no Prisma Next-specific upgrade action; the Prisma Next surfaces the example uses are unchanged.
|
|
737
|
+
|
|
738
|
+
## `pg-int8-application-values-are-bigint`
|
|
739
|
+
|
|
740
|
+
An `int8` is a signed 64-bit integer; a JS `number` holds integers exactly only to 2^53. The codec previously handed you a `number`, so anything larger was already wrong by the time your code saw it. It now hands you a `bigint`.
|
|
741
|
+
|
|
742
|
+
TypeScript does not implicitly convert between `number` and `bigint`, so `pnpm typecheck` finds every affected site. Three shapes recur:
|
|
743
|
+
|
|
744
|
+
- **Row-type annotations.** A counted column is `bigint`: `SqlQueryPlan<{ name: string; postCount: bigint }>`.
|
|
745
|
+
- **Comparison literals.** `fns.gt(fns.count(), 5)` becomes `fns.gt(fns.count(), 5n)`.
|
|
746
|
+
- **Values read from a driver.** A raw `pg` query returns an `int8` as a decimal *string*; convert with `BigInt(row.id)` rather than annotating it `number`.
|
|
747
|
+
|
|
748
|
+
Arithmetic mixing the two throws at runtime rather than coercing, so a site that typechecks after a cast is worth reading again.
|
|
749
|
+
|
|
750
|
+
## `pg-interval-values-are-structured-durations`
|
|
751
|
+
|
|
752
|
+
An interval is not a duration. PostgreSQL stores three independent fields — months, days and microseconds — because a month has no fixed length, so `{ months: 1 }` and `{ days: 30 }` are different intervals and neither can be converted into the other. The application value is now those three fields, so reading an interval hands you numbers to compute with rather than a string to parse.
|
|
753
|
+
|
|
754
|
+
```ts
|
|
755
|
+
// before
|
|
756
|
+
const gap: string = row.gap; // "{\"days\":1}"
|
|
757
|
+
|
|
758
|
+
// after
|
|
759
|
+
const gap = row.gap; // { months: 0, days: 1, micros: 0n }
|
|
760
|
+
const totalDays = gap.days + gap.months * 30; // your calendar rule, not ours
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
The representation is separate from the value, as it is for `pg/bytea@1` (a `Uint8Array` carried as base64) and `pg/int8@1` (a `bigint` carried as decimal text). A contract holds the ISO-8601 duration string, so re-emit to pick up the spelling — `P1M`, `P1Y2M3DT4H5M6S`, `PT0S` for zero, each component carrying its own sign.
|
|
764
|
+
|
|
765
|
+
Two details worth knowing:
|
|
766
|
+
|
|
767
|
+
- **The ISO rendering normalises where the value does not.** Thirteen months render as `P1Y1M` and read back as `{ months: 13 }`. The value keeps what you gave it.
|
|
768
|
+
- **Fractional seconds round.** PostgreSQL rounds past microsecond resolution rather than truncating — `1.1234567` seconds is `1.123457` — and both paths into the value now agree with it.
|
|
769
|
+
|
|
770
|
+
## `codec-json-forms-are-canonical`
|
|
771
|
+
|
|
772
|
+
The rule these follow is that a value written through a codec and read back must be the same value. Where a codec's JSON form could not carry its own range, the form changed rather than the range being quietly clipped.
|
|
773
|
+
|
|
774
|
+
Re-emit first (`prisma-next contract emit`), then reconcile any code that reads or writes one of these forms directly. Literal defaults are where this most often surfaces: an `int8` default of `0` is now `"0"` in `contract.json`, and the `storageHash` moves with it.
|
|
775
|
+
|
|
776
|
+
The second place it surfaces is reads. A query that returns JSON projects each column through its codec, so a column whose codec is one of the nine listed above arrives in that codec's canonical form rather than in whatever the database's own JSON conversion produced. Decoding through the ORM needs no change — the codec's `decodeJson` is the other half of the same pair, and the two moved together. What needs checking is code that bypasses the ORM's decoding: a raw query that reads an aggregated JSON column and parses it itself, a comparison against a hand-written JSON string, a snapshot of database-produced JSON.
|
|
777
|
+
|
|
778
|
+
Where a form is a strict improvement in range, nothing downstream breaks by widening. Where a form changes spelling — `bytea` from `\x`-hex to base64, `sqlite/blob@1` from base64 to uppercase hex — a hand-written comparison is the thing that breaks, and it breaks loudly rather than silently.
|
|
779
|
+
|
|
780
|
+
## `sql-timestamp-json-is-utc-not-local`
|
|
781
|
+
|
|
782
|
+
The formatting change is easy to see and easy to fix. The interpretation change is neither, so take it first.
|
|
783
|
+
|
|
784
|
+
A `timestamp` column carries no time zone. Its JSON form is therefore a zone-less string, and something has to decide which instant that string denotes. `sql/timestamp@1` used to hand the string to `new Date(...)`, which resolves a zone-less form **in the zone the process happens to be running in**. The same stored value decoded to a different instant depending on where the code ran, and it decoded silently — a `Date` is a `Date`, whichever instant it holds.
|
|
785
|
+
|
|
786
|
+
It now resolves as UTC, unconditionally, and `encodeJson` writes UTC. The pair round-trips on any machine.
|
|
787
|
+
|
|
788
|
+
The migration hazard is compensation you may already have in place:
|
|
789
|
+
|
|
790
|
+
- If you added an offset back after decoding, **remove it.** It now double-corrects, and the result is wrong by twice your offset.
|
|
791
|
+
- If you set `TZ=UTC` on the process specifically to stabilise these values, you can drop that — though leaving it costs nothing, since UTC was already the case it produced.
|
|
792
|
+
- If you normalised timestamps after reading them, check whether the normalisation is still doing anything.
|
|
793
|
+
|
|
794
|
+
None of these fail loudly. A doubled offset produces a timestamp that parses, compares and serialises perfectly well and denotes the wrong moment, which is why this entry leads with the interpretation rather than the dropped `Z`.
|
|
795
|
+
|
|
796
|
+
Two smaller consequences follow:
|
|
797
|
+
|
|
798
|
+
- `encodeJson` emits `2026-01-02T03:04:05.678` rather than `2026-01-02T03:04:05.678Z`. Update fixtures, snapshots and hand-written comparisons.
|
|
799
|
+
- `decodeJson` rejects an offset-bearing string instead of reinterpreting it. The codec cannot reproduce an offset, so accepting one would decode a value it could never encode back.
|
|
800
|
+
|
|
801
|
+
`pg/timestamp@1` needs no attention: it already read as UTC and already emitted the zone-less form, and this change brings the generic codec into line with it.
|
|
802
|
+
|
|
803
|
+
## Incidental release version bump to 0.17.0
|
|
804
|
+
|
|
805
|
+
The `chore(release): bump to 0.17.0` commit rewrites every workspace manifest's `version` field and `workspace:` pins, which touches `examples/` manifests. No user action beyond the entries above.
|