@prisma/orm-mongo 8.0.0-rc.9-dev.1 → 8.0.0-rc.9-dev.3
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/dist/contract-builder.d.mts +1 -0
- package/dist/contract-builder.d.mts.map +1 -1
- package/package.json +8 -8
- package/skills/prisma-8/SKILL.md +2 -2
- package/skills/prisma-8/references/queries.md +43 -0
- package/skills/prisma-8/upgrading/app/upgrades/8.0.0-rc.9-to-8.0.0-rc.10/instructions.md +29 -0
- package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.9-to-8.0.0-rc.10/instructions.md +33 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ContractDefinition, ContractDefinition as ContractDefinition$1, ContractFactory, ContractFactory as ContractFactory$1, ContractScaffold, ContractScaffold as ContractScaffold$1, EnumMember, EnumTypeHandle, FieldBuilder, FieldReference, ModelBuilder, MongoContractResult, MongoContractResult as MongoContractResult$1, RelationBuilder, ValueObjectBuilder, field, index, member, model, rel, valueObject } from "@prisma/orm-family-mongo/contract-ts/contract-builder";
|
|
2
2
|
import mongoFamilyPack from "@prisma/orm-family-mongo/family/pack";
|
|
3
3
|
import mongoTargetPack from "@prisma/orm-target-mongo/target/pack";
|
|
4
|
+
import "@prisma/orm-framework/contract/types";
|
|
4
5
|
import "@prisma/orm-framework/components/authoring";
|
|
5
6
|
import { ColumnTypeDescriptor } from "@prisma/orm-framework/components/codec";
|
|
6
7
|
//#region ../../../3-extensions/mongo/dist/contract-builder.d.mts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-builder.d.mts","names":[],"sources":["../../../../3-extensions/mongo/dist/contract-builder.d.mts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"contract-builder.d.mts","names":[],"sources":["../../../../3-extensions/mongo/dist/contract-builder.d.mts"],"mappings":";;;;;;;;KAOK,yBAAyB;KACzB,yBAAyB;KACzB,eAAe,WAAW,kBAAkB,sBAAsB,iCAAiC,iBAAiB;KACpH,uBAAuB,KAAK,qBAAqB,iBAAiB;WAC5D;WACA;;KAEN,qBAAqB,KAAK,mBAAmB,iBAAiB;WACxD;WACA;;iBAEM,qBAAqB,mBAAmB,sBAAsB,YAAY,aAAa,sBAAsB;WACnH,QAAQ;WACR,QAAQ;;iBAEF,qBAAqB,mBAAmB,0BAA0B;WACxE,SAAS;WACT,eAAe;WACf,QAAQ;GAChB,UAAU,YAAY,UAAU,SAAS,iBAAiB,QAAQ,sBAAsB,aAAa;WAC7F,QAAQ;WACR,QAAQ;;;;;;;;;;;;UAYT,aAAa,qBAAqB;WACjC,MAAM;WACN,OAAO;;KAEb,gBAAgB,yBAAyB,+CAA+C,WAAW,UAAU,QAAQ,WAAW,2BAA2B,KAAK;KAChK,eAAe,yBAAyB,+CAA+C,WAAW,UAAU,QAAQ,WAAW,mBAAmB,cAAc;KAChK,mBAAmB,yBAAyB,+CAA+C,KAAK,mBAAmB,YAAY;cACtH;;UAEJ,iBAAiB,8BAA8B,wDAAwD,qDAAqD,mBAAmB,0BAA0B;;YAEvM;;WAED,UAAU;;WAEV;;WAEA;;WAEA;aACE;aACA,OAAO;;;WAGT,QAAQ;;WAER,OAAO;;;;;WAKP,SAAS;;EAElB,IAAI,GAAG;;EAEP,OAAO,GAAG;;EAEV,UAAU,GAAG;;;;;;;;KAQV,eAAe;WACT;;;;;;;;KAQN,WAAW,mBAAmB,cAAc;WACtC;KACN,+BAA+B,aAAa,WAAW;WACjD,aAAa;IACpB;;;;;;;;KAQC,cAAc,mBAAmB,uBAAuB,2BAA2B,cAAc,KAAK,uDAAuD,0BAA0B,qBAAqB,WAAW,YAAY,YAAY,qBAAqB,WAAW,YAAY,YAAY,MAAM,MAAM,OAAO,UAAU,SAAS,YAAY,iBAAiB,MAAM,oBAAoB,WAAW,mBAAmB,WAAW,uBAAuB;;;;;;;;;;;;cAY3b,UAAU,2DAA2D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma/orm-mongo",
|
|
3
|
-
"version": "8.0.0-rc.9-dev.
|
|
3
|
+
"version": "8.0.0-rc.9-dev.3",
|
|
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.9-dev.
|
|
14
|
-
"@prisma/orm-framework": "8.0.0-rc.9-dev.
|
|
15
|
-
"@prisma/orm-target-mongo": "8.0.0-rc.9-dev.
|
|
16
|
-
"@prisma/orm-toolchain": "8.0.0-rc.9-dev.
|
|
13
|
+
"@prisma/orm-family-mongo": "8.0.0-rc.9-dev.3",
|
|
14
|
+
"@prisma/orm-framework": "8.0.0-rc.9-dev.3",
|
|
15
|
+
"@prisma/orm-target-mongo": "8.0.0-rc.9-dev.3",
|
|
16
|
+
"@prisma/orm-toolchain": "8.0.0-rc.9-dev.3",
|
|
17
17
|
"pathe": "^2.0.3"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@internal/mongo": "8.0.0-rc.9-dev.
|
|
21
|
-
"@repo/tsconfig": "8.0.0-rc.9-dev.
|
|
22
|
-
"@repo/tsdown": "8.0.0-rc.9-dev.
|
|
20
|
+
"@internal/mongo": "8.0.0-rc.9-dev.3",
|
|
21
|
+
"@repo/tsconfig": "8.0.0-rc.9-dev.3",
|
|
22
|
+
"@repo/tsdown": "8.0.0-rc.9-dev.3",
|
|
23
23
|
"tsdown": "0.22.14",
|
|
24
24
|
"typescript": "5.9.3"
|
|
25
25
|
},
|
package/skills/prisma-8/SKILL.md
CHANGED
|
@@ -17,7 +17,7 @@ description: >-
|
|
|
17
17
|
(schema.prisma + @prisma/client projects).
|
|
18
18
|
metadata:
|
|
19
19
|
library: '@prisma/orm-mongo'
|
|
20
|
-
library_version: '8.0.0-rc.9-dev.
|
|
20
|
+
library_version: '8.0.0-rc.9-dev.3'
|
|
21
21
|
---
|
|
22
22
|
|
|
23
23
|
# Prisma Next (Prisma 8)
|
|
@@ -53,7 +53,7 @@ Open the reference whose triggers match the task. If more than one matches, open
|
|
|
53
53
|
| Author migrations | [`references/migrations.md`](references/migrations.md) | `db update` vs `migration plan`, `db migrate`, `migration new`, `migration show`, `db update --dry-run`, `db verify`, `db sign`, data migration, `dataTransform`, placeholder sentinels in framework-rendered `migration.ts`, `MIGRATION.HASH_MISMATCH`, PN-MIG-2001 unfilled placeholder, schema drift |
|
|
54
54
|
| Migration graph, refs, plan origin | [`references/migration-model.md`](references/migration-model.md) | migration graph, refs, `migration ref set` / `list` / `delete`, the `db` ref, `--advance-ref`, `migration plan --from`, `from: (baseline)` in plan output, greenfield / from-scratch plan, baseline, first migration before deploy (Composer / CD-managed databases), chaining migrations, retrofitting migrations onto an existing database, `MIGRATION.HASH_NOT_IN_GRAPH`, `MIGRATION.PATH_UNREACHABLE` at plan/chain time |
|
|
55
55
|
| Review migrations on deploy | [`references/migration-review.md`](references/migration-review.md) | "what migrations are going to run", "what runs on deploy / merge", merge conflict, diamond convergence, concurrent migrations, migration status, ref management for CI, staging / production environment refs, `MIGRATION.DIVERGED`, `MIGRATION.NO_MARKER`, `MIGRATION.MARKER_NOT_IN_HISTORY`, `db migrate status`, `db migrate diff`, `db migrate resolve` |
|
|
56
|
-
| Write queries | [`references/queries.md`](references/queries.md) | query, where, select, project, orderBy, limit, offset, take, skip, include, lookup, first, all, count, aggregate, groupBy, create, update, delete, upsert, returning, transaction, `db.orm`, `db.sql`, `db.query.from(...)` (Mongo pipeline), namespace-aware accessors, `.all()` Thenable, single-use iterators (`RUNTIME.ITERATOR_CONSUMED`), target-declared aggregate types (`count`, integer `sum`, and integer `avg` are `number`; `count` and integer `sum` throw outside ±(2^53 − 1) rather than round, while `avg` is a fraction already and carries no guard; `countBigInt` / `sumBigInt` / `avgDecimal` are the lossless forms, `avgDecimal` on PostgreSQL only), drizzle-style, kysely-style. Postgres/SQLite specifics: [`references/queries-postgres.md`](references/queries-postgres.md); Mongo specifics: [`references/queries-mongo.md`](references/queries-mongo.md) |
|
|
56
|
+
| Write queries | [`references/queries.md`](references/queries.md) | query, where, select, project, orderBy, limit, offset, take, skip, include, lookup, first, all, count, aggregate, groupBy, create, update, delete, upsert, returning, transaction, `db.orm`, `db.sql`, `db.query.from(...)` (Mongo pipeline), namespace-aware accessors, `.all()` Thenable, single-use iterators (`RUNTIME.ITERATOR_CONSUMED`), target-declared aggregate types (`count`, integer `sum`, and integer `avg` are `number`; `count` and integer `sum` throw outside ±(2^53 − 1) rather than round, while `avg` is a fraction already and carries no guard; `countBigInt` / `sumBigInt` / `avgDecimal` are the lossless forms, `avgDecimal` on PostgreSQL only), drizzle-style, kysely-style. Naming types (§ *Naming model and result types*): "type of my model", "return type of a query", `ResultType`, `Scalars`, `Shape`, `models` / `Models` namespace in `contract.d.ts`, `Models.public_User`, Prisma 7 `Prisma.User` / `GetPayload` / `UserGetPayload` equivalents. Postgres/SQLite specifics: [`references/queries-postgres.md`](references/queries-postgres.md); Mongo specifics: [`references/queries-mongo.md`](references/queries-mongo.md) |
|
|
57
57
|
| Wire the runtime | [`references/runtime.md`](references/runtime.md) | `db.ts`, `postgres<Contract>(...)` / `sqlite<Contract>(...)` / `mongo<Contract>(...)` façades, middleware composition (telemetry, lints, budgets), `DATABASE_URL`, `.env`, connection pool / `poolOptions`, dev vs prod config, transactions, read replicas, multi-database, script won't exit / hangs, `db.close` / `pool.end`, `await using` / `[Symbol.asyncDispose]` |
|
|
58
58
|
| Build-tool integration | [`references/build.md`](references/build.md) | Vite plugin (`@internal/vite-plugin-contract-emit`, Vite 7/8), `vite.config.ts`, contract emit on save, HMR / dev server, Next.js / Webpack / esbuild / Rollup / Turbopack (named gaps, not fabricated) |
|
|
59
59
|
| Supabase | [`references/supabase.md`](references/supabase.md) | `@internal/extension-supabase`, RLS, row level security, policies (`policy_select` / `policy_update` / `@@rls`, `auth.uid()`), role binding (`asUser(jwt)` / `asAnon()` / `asServiceRole()`), `auth.users`, cross-space FKs to `supabase:auth.AuthUser`, JWT / JWKS (`SUPABASE_JWKS_URL`, `SUPABASE_JWT_SECRET`), `SUPABASE.JWT_INVALID`, `SUPABASE.CONFIG_INVALID`, `RoleBoundDb`, session pooler |
|
|
@@ -126,6 +126,49 @@ console.log('Seeded.');
|
|
|
126
126
|
await db.close();
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
## Naming model and result types
|
|
130
|
+
|
|
131
|
+
The model is the whole row plus its relations, and each related model carries its own relations in turn, so no query returns a value of the model type. A query returns the fields it fetched. The default fetch returns `Scalars<Model>`, the model without relations: `db.orm.public.User.first()` returns `Scalars<Model> | null`, and `db.orm.public.User.all()` returns `Scalars<Model>[]` (or its async iterable). Four types cover every case, and none needs a client in scope:
|
|
132
|
+
|
|
133
|
+
- `Models.<ns>_<Model>` (from `contract.d.ts`) — every scalar field and every relation. On SQLite, which has no schemas, the name is bare: `Models.User` and `typeof models.User`. On Postgres the schema is part of the name: `Models.public_User`, or `typeof models.public.User` by dotted access. A model declared without a schema is in the unbound namespace: `Models.unbound_User` and `typeof models.__unbound__.User`. Mongo names its models the same way. A polymorphic base also emits one member per variant and an `Any<Base>` union (`Models.public_AnyTask`).
|
|
134
|
+
- `Scalars<M>` — the model without relations; what a default fetch returns. Distributes over unions, so `Scalars<Models.public_AnyTask>` is the union of variant rows.
|
|
135
|
+
- `Shape<M, Spec>` — a data structure derived from the model, for declaring an endpoint's response type once and having the compiler check the body at the `return`. At every level of `Spec`: `'+'` is a union of scalar and relation names to keep (a relation named there comes with all of its scalars and none of its relations; the scalars are narrowed only when `'+'` names a scalar, so `'+': 'posts'` alone is every scalar plus posts); `'-'` is a union of scalar names to drop; `'+'` naming a scalar beside `'-'` is a compile error, while `{ '-': 'passwordHash'; '+': 'posts' }` is every scalar but the hash plus posts; any other key is a relation whose value is a nested spec that narrows the related model. Relations are absent unless asked for; `X[]`, `X | null`, or `X` comes from the model. Wrong names, a relation in `'-'`, a non-object relation value, and a relation both in `'+'` and as a key are compile errors. No `where`/`orderBy`/`limit`; compose extras with TypeScript (`Shape<M> & { postCount: number }`).
|
|
136
|
+
- `ResultType<typeof query>` — the row of any ORM collection value (plain, `.include()`, `.select()`, `.variant()`), and of SQL lane plans. Bind the query to a name first; `typeof` needs a value.
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import type { models, Models } from './prisma/contract';
|
|
140
|
+
import type { Scalars, Shape } from '@prisma/orm-postgres/family-contract/types';
|
|
141
|
+
import type { ResultType } from '@prisma/orm-postgres/components/runtime';
|
|
142
|
+
|
|
143
|
+
type User = typeof models.public.User; // same type as Models.public_User
|
|
144
|
+
type UserRow = ResultType<typeof db.orm.public.User>; // Scalars<Models.public_User>
|
|
145
|
+
|
|
146
|
+
const usersWithTasks = () => db.orm.public.User.include('tasks');
|
|
147
|
+
type UserWithTasks = ResultType<ReturnType<typeof usersWithTasks>>; // Shape<Models.public_User, { '+': 'tasks' }>
|
|
148
|
+
|
|
149
|
+
const projected = db.orm.public.User.select('id');
|
|
150
|
+
type UserId = ResultType<typeof projected>; // { id: number }
|
|
151
|
+
|
|
152
|
+
// An endpoint declares its response from the model; the query behind it is an implementation detail.
|
|
153
|
+
type UserResponse = Shape<Models.public_User, { '-': 'email'; posts: { '+': 'id' | 'title' | 'tags' } }>;
|
|
154
|
+
|
|
155
|
+
async function getUserWithPosts(userId: Models.public_User['id']): Promise<UserResponse | null> {
|
|
156
|
+
const user = await db.orm.public.User.where({ id: userId })
|
|
157
|
+
.include('posts', (posts) => posts.include('tags'))
|
|
158
|
+
.first();
|
|
159
|
+
if (user === null) return null;
|
|
160
|
+
const { email: _email, ...rest } = user;
|
|
161
|
+
return { ...rest, posts: user.posts.map(({ id, title, tags }) => ({ id, title, tags })) };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// @ts-expect-error 'nope' is not a relation of User
|
|
165
|
+
type Bad = Shape<Models.public_User, { nope: {} }>;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
On Mongo the imports are `@prisma/orm-mongo/family-contract/types` and `@prisma/orm-mongo/components/runtime`; embedded documents are fields, so they stay in `Scalars`. To name a model plus some of its relations, write `Shape<Models.public_User, { '+': 'id' | 'posts' }>`, not `Pick<Models.public_User, 'id' | 'posts'>`: `Pick` demands fully loaded nested posts that no query returns. Input types (`CreateInput<Contract, 'User'>`, `MutationUpdateInput<Contract, 'User'>`, `ShorthandWhereFilter<Contract, 'public', 'User'>`) come from `@prisma/orm-postgres/orm-client`.
|
|
169
|
+
|
|
170
|
+
Coming from Prisma 7: `Prisma.User` → `Models.public_User` (note: now carries relations; the scalars-only row is `Scalars<Models.public_User>`); `Prisma.UserGetPayload<{ include: { posts: true } }>` → `Shape<Models.public_User, { '+': 'posts' }>`; `Prisma.UserGetPayload<{ select: { id: true; posts: { select: { title: true } } } }>` → `Shape<Models.public_User, { '+': 'id'; posts: { '+': 'title' } }>`; `Prisma.UserCreateInput` → `CreateInput<Contract, 'User'>`; `Awaited<ReturnType<typeof fn>>` → `ResultType<typeof query>`.
|
|
171
|
+
|
|
129
172
|
## Common Pitfalls (cross-target)
|
|
130
173
|
|
|
131
174
|
1. **Using Postgres examples on a Mongo project (or vice versa).** Check `db.ts` and load the correct target guide ([`queries-postgres.md`](./queries-postgres.md) or [`queries-mongo.md`](./queries-mongo.md)).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
from: "8.0.0-rc.9"
|
|
3
|
+
to: "8.0.0-rc.10"
|
|
4
|
+
changes:
|
|
5
|
+
- id: to-one-relations-record-nullable
|
|
6
|
+
summary: |
|
|
7
|
+
Every `1:1` and `N:1` relation in `contract.json` now carries a `nullable` boolean. A
|
|
8
|
+
contract without it still loads, with the flag derived from the foreign-key columns (or
|
|
9
|
+
fields), but its `contract.d.ts` lacks the `Models` namespace. Re-run
|
|
10
|
+
`prisma contract emit` so the emitted `contract.json` / `contract.d.ts` match the installed
|
|
11
|
+
toolchain.
|
|
12
|
+
detection:
|
|
13
|
+
glob: "**/contract.json"
|
|
14
|
+
matches:
|
|
15
|
+
- '"cardinality":\s*"(?:N:1|1:1)",\s*"on":'
|
|
16
|
+
- id: contract-dts-exports-models
|
|
17
|
+
summary: |
|
|
18
|
+
`contract.d.ts` now exports a `Models` namespace and a `models` constant that name every
|
|
19
|
+
model with its fields and relations. The re-emit above produces them; use them with
|
|
20
|
+
`Scalars` and `Shape` to name row types without a client in scope.
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## `to-one-relations-record-nullable`
|
|
24
|
+
|
|
25
|
+
For every `contract.json` matched by `detection`, run the project's emit command (`prisma contract emit`, or the project's `contract:emit` script) once after upgrading. The emit reads the `?` on each to-one relation field in the schema and writes `"nullable": true` or `"nullable": false` next to that relation's `"cardinality"`. The emit also fails, rather than emitting, when a required relation field sits over a nullable foreign key or the reverse; fix the schema so the field's `?` matches the key's `?`, then emit again. Until the re-emit, the client and the migration tools still load the old `contract.json`: a to-one relation without the flag is treated as nullable when any of its foreign-key columns (Mongo: fields) is nullable, and as required otherwise. Migration contract snapshots written by earlier versions are never rewritten and load the same way.
|
|
26
|
+
|
|
27
|
+
## `contract-dts-exports-models`
|
|
28
|
+
|
|
29
|
+
After the emit, `import type { Models, models } from './prisma/contract'` (the project's contract path) gives `Models.<namespace>_<Model>` for every model, and `Scalars<M>` / `Shape<M, { '+': 'relation' }>` from `@prisma/orm-postgres/family-contract/types` (or the Mongo family package) derive the default row and a data structure with relations from it. Replace hand-written row types that duplicate a model's fields with these when convenient.
|
package/skills/prisma-8/upgrading/extension/upgrades/8.0.0-rc.9-to-8.0.0-rc.10/instructions.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
from: "8.0.0-rc.9"
|
|
3
|
+
to: "8.0.0-rc.10"
|
|
4
|
+
# sql-orm-client doc-comment sweep: reviewed, no entry required
|
|
5
|
+
changes:
|
|
6
|
+
- id: to-one-relations-record-nullable
|
|
7
|
+
summary: |
|
|
8
|
+
`ContractNonJunctionRelation`'s `'1:1'` and `'N:1'` members now require `nullable: boolean`,
|
|
9
|
+
and contract validation rejects a `contract.json` whose to-one relations lack it. Set
|
|
10
|
+
`nullable` on every to-one relation the extension constructs, and rebuild the extension's
|
|
11
|
+
contract space so its emitted `contract.json` / `contract.d.ts` carry the flag.
|
|
12
|
+
detection:
|
|
13
|
+
glob: "**/*.ts"
|
|
14
|
+
matches:
|
|
15
|
+
- '(?<!\bnullable\b(?:[^{}]|\{[^{}]*\})*)(?:(?<=\bon\s*:(?:[^{}]|\{[^{}]*\})*)|(?=(?:[^{}]|\{[^{}]*\})*\bon\s*:))\bcardinality:\s*[''"](?:N:1|1:1)[''"](?!(?:[^{}]|\{[^{}]*\})*\bnullable\b)'
|
|
16
|
+
- id: contract-space-re-emit-nullable
|
|
17
|
+
summary: |
|
|
18
|
+
The extension's emitted `contract.json` must carry `nullable` on every `1:1` and `N:1`
|
|
19
|
+
relation. Rebuild the contract space (the package's `build:contract-space` script) once
|
|
20
|
+
after upgrading.
|
|
21
|
+
detection:
|
|
22
|
+
glob: "**/contract.json"
|
|
23
|
+
matches:
|
|
24
|
+
- '"cardinality":\s*"(?:N:1|1:1)",\s*"on":'
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## `to-one-relations-record-nullable`
|
|
28
|
+
|
|
29
|
+
For every TypeScript file matched by `detection`, find each object literal that builds a to-one contract relation (`cardinality: 'N:1'` or `'1:1'` together with an `on` join) and add `nullable: <boolean>` to it: `true` when the relation field is optional (the local foreign-key columns are nullable), `false` when it is required. The side of a one-to-one relation that does not own the foreign key is always `nullable: true`. Contract builders and PSL authoring set the flag from the field's `?`, so only code that assembles `ContractRelation` values by hand needs the edit.
|
|
30
|
+
|
|
31
|
+
## `contract-space-re-emit-nullable`
|
|
32
|
+
|
|
33
|
+
For every `contract.json` matched by `detection`, run the extension package's `build:contract-space` script (or its emit command) once after upgrading. The expected diff is one `"nullable"` boolean per to-one relation in `contract.json`, plus the `Models` namespace, `models` constant, and `RelationKeys` import in `contract.d.ts`.
|