@prisma/orm-mongo 8.0.0-rc.5-dev.2 → 8.0.0-rc.5-dev.4
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma/orm-mongo",
|
|
3
|
-
"version": "8.0.0-rc.5-dev.
|
|
3
|
+
"version": "8.0.0-rc.5-dev.4",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
"skills"
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@prisma/orm-family-mongo": "8.0.0-rc.5-dev.
|
|
14
|
-
"@prisma/orm-framework": "8.0.0-rc.5-dev.
|
|
15
|
-
"@prisma/orm-target-mongo": "8.0.0-rc.5-dev.
|
|
16
|
-
"@prisma/orm-toolchain": "8.0.0-rc.5-dev.
|
|
13
|
+
"@prisma/orm-family-mongo": "8.0.0-rc.5-dev.4",
|
|
14
|
+
"@prisma/orm-framework": "8.0.0-rc.5-dev.4",
|
|
15
|
+
"@prisma/orm-target-mongo": "8.0.0-rc.5-dev.4",
|
|
16
|
+
"@prisma/orm-toolchain": "8.0.0-rc.5-dev.4",
|
|
17
17
|
"pathe": "^2.0.3"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@internal/mongo": "8.0.0-rc.5-dev.
|
|
21
|
-
"@repo/tsconfig": "8.0.0-rc.5-dev.
|
|
22
|
-
"@repo/tsdown": "8.0.0-rc.5-dev.
|
|
20
|
+
"@internal/mongo": "8.0.0-rc.5-dev.4",
|
|
21
|
+
"@repo/tsconfig": "8.0.0-rc.5-dev.4",
|
|
22
|
+
"@repo/tsdown": "8.0.0-rc.5-dev.4",
|
|
23
23
|
"tsdown": "0.22.14",
|
|
24
24
|
"typescript": "5.9.3"
|
|
25
25
|
},
|
package/skills/prisma-8/SKILL.md
CHANGED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
from: "8.0.0-rc.5"
|
|
3
|
+
to: "8.0.0-rc.6"
|
|
4
|
+
changes:
|
|
5
|
+
- id: postgres-temporal-representations
|
|
6
|
+
summary: |
|
|
7
|
+
PostgreSQL temporal columns no longer read as JavaScript `Date`. Each of `date`,
|
|
8
|
+
`timestamp(p)`, `timestamptz(p)` and `time(p)` now offers two explicit representations,
|
|
9
|
+
and five codecs were removed with no compatibility aliases:
|
|
10
|
+
|
|
11
|
+
| Retired | Replace with (Temporal) | Replace with (text) |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `pg/date@1` | `Date` → `pg/date-temporal@1` (`Temporal.PlainDate`) | `DateString` |
|
|
14
|
+
| `pg/timestamp@1` | `Timestamp(p)` → `pg/timestamp-temporal@1` (`Temporal.PlainDateTime`) | `TimestampString(p)` |
|
|
15
|
+
| `pg/timestamptz@1` | `Timestamptz(p)` → `pg/timestamptz-temporal@1` (`Temporal.Instant`) | `TimestamptzString(p)` |
|
|
16
|
+
| `pg/time@1` | `Time(p)` → `pg/time-temporal@1` (`Temporal.PlainTime`) | `TimeString(p)` |
|
|
17
|
+
| `sql/timestamp@1` (`field.timestamp()`) | `Timestamptz(p)` or `field.temporal.timestamptz(p)` | `TimestamptzString(p)` |
|
|
18
|
+
|
|
19
|
+
1. **Decide a representation per column.** The bare PSL spellings (`Date`, `Timestamp`,
|
|
20
|
+
`Timestamptz`, `Time`) keep working and now select the Temporal-backed codec. If a
|
|
21
|
+
column's values should stay text — because your code treats them as strings, or
|
|
22
|
+
because they include values Temporal cannot denote (`infinity`, non-ISO `DateStyle`
|
|
23
|
+
output, years beyond ±271821) — rename the type to its `*String` spelling. Note
|
|
24
|
+
`pg/time@1` already handed back a `string`: a `time` column whose value you treat as
|
|
25
|
+
text needs `TimeString(p)`, not `Time(p)`.
|
|
26
|
+
2. **Replace `field.timestamp()`.** The generic cross-target helper and its
|
|
27
|
+
`sql/timestamp@1` codec are gone. On PostgreSQL use `field.temporal.timestamptz(p)`
|
|
28
|
+
(or `field.temporal.timestamptzString(p)`), or the bare `Timestamptz(p)` type.
|
|
29
|
+
3. **Repoint any codec id you wrote by hand.** Ids appear in raw-lane return
|
|
30
|
+
declarations (``db.raw.sql`now()`.returns('pg/timestamptz@1')``), in
|
|
31
|
+
`prepare({ id })`, and in hand-built contracts. A retired id no longer resolves and
|
|
32
|
+
fails contract validation rather than degrading silently.
|
|
33
|
+
4. **Re-emit every contract.** `prisma contract emit` rewrites `contract.json` and
|
|
34
|
+
`contract.d.ts` together; the generated application types are where the new
|
|
35
|
+
representation becomes visible. Commit the regenerated artifacts. A contract emitted
|
|
36
|
+
before this release references a codec the registry cannot resolve and is rejected at
|
|
37
|
+
runtime.
|
|
38
|
+
5. **Provide a Temporal implementation if you kept any Temporal-backed column.** Prisma
|
|
39
|
+
bundles no polyfill. Where the runtime has no native `Temporal`, install a global one
|
|
40
|
+
in your entry point before any query runs — `import 'temporal-polyfill/full/global';`.
|
|
41
|
+
Take `full/global`, not `global`: the default build omits non-ISO calendars and its
|
|
42
|
+
published types resolve to `export {}`, so TypeScript will not see the namespace.
|
|
43
|
+
**Every read** of a Temporal-backed column needs it, and so does any insert into a
|
|
44
|
+
table carrying `temporal.updatedAt()`, whose generated value is a `Temporal.Instant`.
|
|
45
|
+
Without it the operation fails with `RUNTIME.TEMPORAL_UNAVAILABLE`, which names the
|
|
46
|
+
codec and recommends the matching `*String` type. A contract whose temporal columns
|
|
47
|
+
are all `*String` needs no Temporal anywhere.
|
|
48
|
+
6. **Update application code that consumed a `Date`.** `Temporal.Instant.from()` parses
|
|
49
|
+
**only an ISO string carrying an offset** — it throws on a `Date`, on an
|
|
50
|
+
epoch-millisecond number, and on a date-time string with no offset. Convert by source:
|
|
51
|
+
|
|
52
|
+
| You have | Use |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| a `Date` | `instant = date.toTemporalInstant()`, or `Temporal.Instant.fromEpochMilliseconds(date.getTime())` |
|
|
55
|
+
| epoch milliseconds | `Temporal.Instant.fromEpochMilliseconds(ms)` |
|
|
56
|
+
| an ISO string **with** an offset (`…Z`, `…+02:00`) | `Temporal.Instant.from(text)` |
|
|
57
|
+
| an ISO string **without** an offset | pick the zone it meant: `Temporal.PlainDateTime.from(text).toZonedDateTime('UTC').toInstant()` |
|
|
58
|
+
| "now" | `Temporal.Now.instant()` |
|
|
59
|
+
|
|
60
|
+
Match the column, not just the type name: a `date` column takes a
|
|
61
|
+
`Temporal.PlainDate` (`Temporal.PlainDate.from('2024-01-01')`), a `timestamp` column a
|
|
62
|
+
`Temporal.PlainDateTime`, a `time` column a `Temporal.PlainTime`. Only `timestamptz`
|
|
63
|
+
takes an `Instant`.
|
|
64
|
+
|
|
65
|
+
`Temporal.Instant.compare(a, b)` replaces `a.getTime() - b.getTime()`, but **both
|
|
66
|
+
operands must already be `Instant`s** — it throws on a `Date`. Values read back from
|
|
67
|
+
the ORM already are; convert anything you brought from elsewhere first.
|
|
68
|
+
|
|
69
|
+
In tests, be careful with **`toMatchObject`**. A Temporal value has no own enumerable
|
|
70
|
+
properties — every accessor lives on the prototype — so a subset matcher finds nothing
|
|
71
|
+
to compare and passes for *any* value of the same type. `toEqual` is not affected
|
|
72
|
+
(Vitest compares these correctly), but `toMatchObject` will silently stop checking the
|
|
73
|
+
timestamp. Where you need a subset match, compare `toString()` or use the type's own
|
|
74
|
+
`equals` / `compare`.
|
|
75
|
+
detection:
|
|
76
|
+
glob: "**/*.{ts,mts,cts,prisma,json}"
|
|
77
|
+
regex:
|
|
78
|
+
- "pg/(date|timestamp|timestamptz|time)@1"
|
|
79
|
+
- "sql/timestamp@1"
|
|
80
|
+
- "field\\.timestamp\\("
|
|
81
|
+
anyMatch: true
|
|
82
|
+
- id: literal-default-needs-the-string-spelling
|
|
83
|
+
summary: |
|
|
84
|
+
A literal `@default(...)` on a Temporal-backed temporal column cannot be emitted today.
|
|
85
|
+
The default value is encoded through the column's codec while the contract is being
|
|
86
|
+
emitted, inside the CLI's own process, and stock Node ships no `Temporal`. So
|
|
87
|
+
|
|
88
|
+
```prisma
|
|
89
|
+
occurredAt Timestamptz @default("2024-01-01T00:00:00Z")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
fails `prisma contract emit` with `CONTRACT.SOURCE_LOAD_FAILED` and "this runtime has no
|
|
93
|
+
global Temporal implementation".
|
|
94
|
+
|
|
95
|
+
Use the string spelling for a column that needs a literal default:
|
|
96
|
+
|
|
97
|
+
```prisma
|
|
98
|
+
occurredAt TimestamptzString @default("2024-01-01T00:00:00Z")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Function defaults are unaffected — `@default(now())` lowers to a PostgreSQL `now()`
|
|
102
|
+
storage default, never passes through a codec, and works on either representation.
|
|
103
|
+
detection:
|
|
104
|
+
glob: "**/*.prisma"
|
|
105
|
+
regex:
|
|
106
|
+
- "(Date|Timestamp|Timestamptz|Time)(\\([0-9]+\\))?\\s+@default\\(\""
|
|
107
|
+
anyMatch: true
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
# 8.0.0-rc.4 → 8.0.0-rc.5 — User upgrade instructions
|
|
112
|
+
|
|
113
|
+
## `attach-pg-client-error-listener`
|
|
114
|
+
|
|
115
|
+
Walk every file matched by `detection.glob`. For each `pg.Client` or `pg.Pool` your code constructs and uses directly (not one handed to `postgres({ pg: ... })` / `supabase({ pg: ... })` — the runtime covers those since rc.5), attach an `'error'` listener right after construction, before `connect()`:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const client = new pg.Client({ connectionString });
|
|
119
|
+
client.on('error', () => {});
|
|
120
|
+
await client.connect();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
A no-op listener is enough: connect and query failures still reject their own promises, so nothing real is masked — the listener only stops a dropped idle connection from becoming an uncaught exception. If the handle is long-lived and you have a logging channel, log the error instead of discarding it.
|
|
124
|
+
|
|
125
|
+
Note that a surrounding `try/catch` does **not** cover this case — the `'error'` event is emitted on the client object asynchronously, outside any promise chain the `catch` can see.
|
|
126
|
+
|
|
127
|
+
# 8.0.0-rc.4 → 8.0.0-rc.5 — User upgrade instructions
|
|
128
|
+
|
|
129
|
+
## `attach-pg-client-error-listener`
|
|
130
|
+
|
|
131
|
+
Walk every file matched by `detection.glob`. For each `pg.Client` or `pg.Pool` your code constructs and uses directly (not one handed to `postgres({ pg: ... })` / `supabase({ pg: ... })` — the runtime covers those since rc.5), attach an `'error'` listener right after construction, before `connect()`:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const client = new pg.Client({ connectionString });
|
|
135
|
+
client.on('error', () => {});
|
|
136
|
+
await client.connect();
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
A no-op listener is enough: connect and query failures still reject their own promises, so nothing real is masked — the listener only stops a dropped idle connection from becoming an uncaught exception. If the handle is long-lived and you have a logging channel, log the error instead of discarding it.
|
|
140
|
+
|
|
141
|
+
Note that a surrounding `try/catch` does **not** cover this case — the `'error'` event is emitted on the client object asynchronously, outside any promise chain the `catch` can see.
|
|
142
|
+
|
|
143
|
+
# PostgreSQL temporal representations
|
|
144
|
+
|
|
145
|
+
Both entries are schema-and-code renames; there is no codemod, because the choice between the
|
|
146
|
+
Temporal and the string representation is per column and only you know which values a column
|
|
147
|
+
holds. Work through the schema first, re-emit, then let the generated `contract.d.ts` types drive
|
|
148
|
+
the application-code changes — the compiler will point at every site whose value type moved.
|
|
149
|
+
|
|
150
|
+
Two behaviours are worth knowing before you choose. A Temporal codec rejects what `Temporal` cannot
|
|
151
|
+
denote — `infinity`, years beyond roughly ±271821, and output rendered under a non-ISO `DateStyle` —
|
|
152
|
+
naming the `*String` type that reads the same column losslessly. And a nested read returns the same
|
|
153
|
+
text a flat read does, because temporal expressions are cast to `text` before PostgreSQL builds the
|
|
154
|
+
JSON, which means both reflect the session `TimeZone`.
|
|
155
|
+
|
|
156
|
+
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
from: "8.0.0-rc.5"
|
|
3
|
+
to: "8.0.0-rc.6"
|
|
4
|
+
changes:
|
|
5
|
+
- id: postgres-temporal-codec-ids-retired
|
|
6
|
+
summary: |
|
|
7
|
+
Five PostgreSQL temporal codec ids were removed with no compatibility aliases. Each
|
|
8
|
+
native type now has two representation-explicit codecs — one whose application value is a
|
|
9
|
+
`Temporal.*`, one that passes PostgreSQL's own text through unchanged:
|
|
10
|
+
|
|
11
|
+
| Retired | Temporal replacement | Text replacement |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `pg/date@1` | `pg/date-temporal@1` (`Temporal.PlainDate`) | `pg/date-string@1` |
|
|
14
|
+
| `pg/timestamp@1` | `pg/timestamp-temporal@1` (`Temporal.PlainDateTime`) | `pg/timestamp-string@1` |
|
|
15
|
+
| `pg/timestamptz@1` | `pg/timestamptz-temporal@1` (`Temporal.Instant`) | `pg/timestamptz-string@1` |
|
|
16
|
+
| `pg/time@1` | `pg/time-temporal@1` (`Temporal.PlainTime`) | `pg/time-string@1` |
|
|
17
|
+
| `sql/timestamp@1` | `pg/timestamptz-temporal@1` | `pg/timestamptz-string@1` |
|
|
18
|
+
|
|
19
|
+
An extension names these ids in more places than a user does. Sweep all of them:
|
|
20
|
+
|
|
21
|
+
1. **Column descriptors in an extension contract.** A pack that declares its own tables
|
|
22
|
+
(`extensionModel(...)` with `{ codecId, nativeType }` column literals) picks the
|
|
23
|
+
representation on its consumers' behalf. Choose the Temporal id where application code
|
|
24
|
+
reads the column as a value, and the `*String` id where it should stay text — the
|
|
25
|
+
Supabase pack's `auth` tables took the Temporal id for exactly that reason.
|
|
26
|
+
2. **`descriptor-meta` registrations, control-plane hooks and aggregate matrices.** Any
|
|
27
|
+
table keyed by codec id gains two entries where it had one, or moves its single entry.
|
|
28
|
+
A parity or coverage table that enumerates ids needs the four new `*-string@1` ids as
|
|
29
|
+
well as the four `*-temporal@1` ones.
|
|
30
|
+
3. **Hand-built contracts and test doubles.** Deserializing a contract literal that names
|
|
31
|
+
a retired id now fails validation rather than resolving to something plausible.
|
|
32
|
+
4. **Introspection maps.** `date`, `timestamp`, `timestamptz` and `time` map to the bare
|
|
33
|
+
PSL names (`Date`, `Timestamp(p)`, `Timestamptz(p)`, `Time(p)`), which resolve to the
|
|
34
|
+
Temporal codecs. The `*String` names are authoring-only and must claim no
|
|
35
|
+
`targetTypes`, or they compete for introspection ownership.
|
|
36
|
+
5. **Re-emit any contract your package commits.** `build:contract-space` (or
|
|
37
|
+
`prisma contract emit`) rewrites `contract.json` and `contract.d.ts`; commit both.
|
|
38
|
+
detection:
|
|
39
|
+
glob: "**/*.{ts,mts,cts,json}"
|
|
40
|
+
regex:
|
|
41
|
+
- "pg/(date|timestamp|timestamptz|time)@1"
|
|
42
|
+
- "sql/timestamp@1"
|
|
43
|
+
anyMatch: true
|
|
44
|
+
- id: temporal-codecs-require-a-global-and-refuse-a-date
|
|
45
|
+
summary: |
|
|
46
|
+
A Temporal-backed codec reads the application's global `Temporal` implementation. Prisma
|
|
47
|
+
neither bundles nor imports a polyfill, and the check is lazy: registering a pack,
|
|
48
|
+
validating a contract, resolving a descriptor and constructing a codec instance all
|
|
49
|
+
succeed with no `Temporal` in scope. Only invoking one fails, with
|
|
50
|
+
`RUNTIME.TEMPORAL_UNAVAILABLE`.
|
|
51
|
+
|
|
52
|
+
Two consequences for an extension:
|
|
53
|
+
|
|
54
|
+
1. **Your test suites need the global.** If your package exercises a Temporal-backed
|
|
55
|
+
column, install a polyfill in a vitest `setupFiles` entry
|
|
56
|
+
(`import 'temporal-polyfill/full/global';`) and add `temporal-polyfill` as a
|
|
57
|
+
devDependency. For TypeScript to see the same global, add a `.d.ts` under your test
|
|
58
|
+
directory containing `/// <reference types="temporal-polyfill/types/global" />` — the
|
|
59
|
+
package's own `temporal-polyfill/global` types resolve to `export {}` and declare
|
|
60
|
+
nothing.
|
|
61
|
+
2. **Encode is nominally typed now.** These codecs check `Symbol.toStringTag` and refuse
|
|
62
|
+
anything that is not their own Temporal type, including a `Date`, with
|
|
63
|
+
`RUNTIME.ENCODE_FAILED` naming the codec. If your pack contributes a mutation-default
|
|
64
|
+
generator or any other value that lands in a Temporal-backed column, it must produce
|
|
65
|
+
the matching `Temporal.*` value — a `Date` no longer slips through to be serialized as
|
|
66
|
+
`Date.prototype.toString()`.
|
|
67
|
+
detection:
|
|
68
|
+
glob: "**/*.{ts,mts,cts}"
|
|
69
|
+
regex:
|
|
70
|
+
- "pg/(date|timestamp|timestamptz|time)-temporal@1"
|
|
71
|
+
anyMatch: true
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
# 8.0.0-rc.4 → 8.0.0-rc.5 — Extension author upgrade instructions
|
|
76
|
+
|
|
77
|
+
## `wrap-pg-constructions-with-suppress-idle-connection-errors`
|
|
78
|
+
|
|
79
|
+
Walk every file matched by `detection.glob`. For each pg `Pool` or `Client` the extension constructs, wrap the construction:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { suppressIdleConnectionErrors } from '@internal/driver-postgres/runtime';
|
|
83
|
+
|
|
84
|
+
const pool = suppressIdleConnectionErrors(
|
|
85
|
+
new Pool({ connectionString: options.url }),
|
|
86
|
+
);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This is the same translation applied to the in-repo `@internal/postgres` and `@internal/extension-supabase` runtimes in this transition. The helper only attaches a no-op `'error'` listener (connect/query failures still reject their own promises), so behavior is otherwise unchanged; without it, a dropped idle connection crashes the process that hosts the extension.
|
|
90
|
+
|
|
91
|
+
If your extension's test suite fakes the `pg` module, the fakes need an `on` method (`on = vi.fn().mockReturnThis()` on a class fake, or `on: vi.fn()` on an object literal) — the runtime now calls `.on('error', ...)` on every pool, client, and checked-out pool client.
|
|
92
|
+
## `distinct-on-requires-postgres-capability`
|
|
93
|
+
|
|
94
|
+
`Collection#distinctOn(...)` used to compile and run on any target, but only Postgres ever
|
|
95
|
+
rendered its `DISTINCT ON` clause — a call on any other target (SQLite) compiled clean and
|
|
96
|
+
silently returned undeduped rows at runtime. The method now carries the same capability gate the
|
|
97
|
+
sql-builder lane already enforces: its parameter type narrows to `never` unless the contract
|
|
98
|
+
declares `postgres.distinctOn`, so the same call is a compile error on a contract that lacks it,
|
|
99
|
+
and a runtime error carrying `ORM.CAPABILITY_MISSING` if reached dynamically (e.g. through a
|
|
100
|
+
hand-built `CollectionState`).
|
|
101
|
+
|
|
102
|
+
Find every `.distinctOn(...)` call your code makes on a `Collection` and check whether the
|
|
103
|
+
contract it runs against declares `postgres.distinctOn`. If it does, nothing changes — the call
|
|
104
|
+
already worked correctly and keeps compiling. If it does not, the call was already producing the
|
|
105
|
+
wrong result set; either move the collection onto a Postgres-capable contract, or remove the
|
|
106
|
+
`.distinctOn(...)` call and accept the undeduped rows it was silently returning before.
|
|
107
|
+
|
|
108
|
+
`Collection#distinct(...)` is unaffected — it lowers to a portable `ROW_NUMBER` dedup and needs
|
|
109
|
+
no capability, on any target.
|
|
110
|
+
|
|
111
|
+
## `groupby-pre-group-pagination-now-scopes-rows`
|
|
112
|
+
|
|
113
|
+
Any `.take(...)`, `.skip(...)`, `.cursor(...)`, `.distinct(...)`, `.distinctOn(...)`, or
|
|
114
|
+
`.orderBy(...)` your extension calls *before* `.groupBy(...)` on a `Collection` used to be
|
|
115
|
+
silently dropped once `.groupBy(...)` joined the chain — the aggregate reduced over every
|
|
116
|
+
matching row, ignoring the pagination clause entirely. It now scopes the rows that get grouped,
|
|
117
|
+
the same way root `.aggregate()` scopes its rows (see the sibling entry for that fix, already
|
|
118
|
+
shipped in `8.0.0-rc.4` → `8.0.0-rc.5`'s predecessor window).
|
|
119
|
+
|
|
120
|
+
There is no detection regex for this one worth writing: the call sites that need re-checking
|
|
121
|
+
look identical, in source, to the call sites that already worked correctly (a chain built with
|
|
122
|
+
this scoping in mind, versus one that assumed the pagination clause was a no-op). Grep for
|
|
123
|
+
`.groupBy(` and read every match with a pre-group pagination clause; if the test asserting its
|
|
124
|
+
result seeds fewer distinct groups than pagination scope allows, or asserts totals computed over
|
|
125
|
+
every row rather than the paginated window, the expected values need updating to match the now-
|
|
126
|
+
correct behavior.
|
|
127
|
+
|
|
128
|
+
## `groupby-post-group-pagination-requires-order-by`
|
|
129
|
+
|
|
130
|
+
`GroupedCollection` (what `.groupBy(...)` returns) gained `take()`, `skip()`, and `orderBy()`,
|
|
131
|
+
which page the *grouped* rows when written after `.groupBy(...)` — previously `.groupBy(...)` had
|
|
132
|
+
no chain of its own past `.having(...)`. Calling post-group `take()` or `skip()` without a prior
|
|
133
|
+
post-group `orderBy()` is a compile error: the parameter type narrows to `never`, because a
|
|
134
|
+
database may return groups in any order and "the first n groups" has no defined meaning without
|
|
135
|
+
one.
|
|
136
|
+
|
|
137
|
+
If your extension's own code (or its test suite) calls `.groupBy(...).take(...)` or
|
|
138
|
+
`.groupBy(...).skip(...)` with no `.orderBy(...)` between them, it will fail to compile after this
|
|
139
|
+
upgrade. There is no default ordering to insert automatically — add an `.orderBy(...)` naming one
|
|
140
|
+
of the fields you passed to `groupBy(...)` (ascending or descending is your call; whichever
|
|
141
|
+
matches what "the first n groups" should mean for that query) before the `take()` / `skip()` call.
|
|
142
|
+
|
|
143
|
+
# 8.0.0-rc.4 → 8.0.0-rc.5 — Extension author upgrade instructions
|
|
144
|
+
|
|
145
|
+
## `wrap-pg-constructions-with-suppress-idle-connection-errors`
|
|
146
|
+
|
|
147
|
+
Walk every file matched by `detection.glob`. For each pg `Pool` or `Client` the extension constructs, wrap the construction:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { suppressIdleConnectionErrors } from '@internal/driver-postgres/runtime';
|
|
151
|
+
|
|
152
|
+
const pool = suppressIdleConnectionErrors(
|
|
153
|
+
new Pool({ connectionString: options.url }),
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
This is the same translation applied to the in-repo `@internal/postgres` and `@internal/extension-supabase` runtimes in this transition. The helper only attaches a no-op `'error'` listener (connect/query failures still reject their own promises), so behavior is otherwise unchanged; without it, a dropped idle connection crashes the process that hosts the extension.
|
|
158
|
+
|
|
159
|
+
If your extension's test suite fakes the `pg` module, the fakes need an `on` method (`on = vi.fn().mockReturnThis()` on a class fake, or `on: vi.fn()` on an object literal) — the runtime now calls `.on('error', ...)` on every pool, client, and checked-out pool client.
|
|
160
|
+
## `distinct-on-requires-postgres-capability`
|
|
161
|
+
|
|
162
|
+
`Collection#distinctOn(...)` used to compile and run on any target, but only Postgres ever
|
|
163
|
+
rendered its `DISTINCT ON` clause — a call on any other target (SQLite) compiled clean and
|
|
164
|
+
silently returned undeduped rows at runtime. The method now carries the same capability gate the
|
|
165
|
+
sql-builder lane already enforces: its parameter type narrows to `never` unless the contract
|
|
166
|
+
declares `postgres.distinctOn`, so the same call is a compile error on a contract that lacks it,
|
|
167
|
+
and a runtime error carrying `ORM.CAPABILITY_MISSING` if reached dynamically (e.g. through a
|
|
168
|
+
hand-built `CollectionState`).
|
|
169
|
+
|
|
170
|
+
Find every `.distinctOn(...)` call your code makes on a `Collection` and check whether the
|
|
171
|
+
contract it runs against declares `postgres.distinctOn`. If it does, nothing changes — the call
|
|
172
|
+
already worked correctly and keeps compiling. If it does not, the call was already producing the
|
|
173
|
+
wrong result set; either move the collection onto a Postgres-capable contract, or remove the
|
|
174
|
+
`.distinctOn(...)` call and accept the undeduped rows it was silently returning before.
|
|
175
|
+
|
|
176
|
+
`Collection#distinct(...)` is unaffected — it lowers to a portable `ROW_NUMBER` dedup and needs
|
|
177
|
+
no capability, on any target.
|
|
178
|
+
|
|
179
|
+
# PostgreSQL temporal representations, for extension authors
|
|
180
|
+
|
|
181
|
+
There is no codemod: the retired ids map to *two* replacements each, and which one an extension
|
|
182
|
+
should name is a judgement about what its consumers do with the column. Sweep by id, decide per
|
|
183
|
+
site, then re-emit any committed contract artifact.
|
|
184
|
+
|
|
185
|
+
Four behaviours bear on an extension's own codecs. Writes serialize at full precision and let
|
|
186
|
+
PostgreSQL round to the column's declared precision, carries included. A Temporal codec rejects
|
|
187
|
+
`infinity`, years beyond roughly ±271821, and non-ISO `DateStyle` output, naming the `*String` type
|
|
188
|
+
that reads them losslessly. The driver hands temporal OIDs through as server text rather than
|
|
189
|
+
building a `Date`. And temporal expressions are cast to `text` before PostgreSQL builds JSON, so a
|
|
190
|
+
nested read returns the same text a flat one does.
|
|
191
|
+
|
|
192
|
+
|